Skip to content

Commit d4d0670

Browse files
committed
[GR-60022] Consider newDelegatingFileSystem API.
PullRequest: graal/19469
2 parents 6e04ddc + 667e74b commit d4d0670

File tree

10 files changed

+602
-192
lines changed

10 files changed

+602
-192
lines changed

sdk/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ This changelog summarizes major changes between GraalVM SDK versions. The main f
88
* GR-57817 Starting with JDK 24 users now need to configure native access privileges to the java executable in order to avoid warnings from being printed by the JDK.
99
For usages of the module-path pass the `--enable-native-access=org.graalvm.truffle` option and for class-path usages pass the `--enable-native-access=ALL-UNNAMED` option to resolve the new warning. Note that Truffle automatically forwards the native access capability to all loaded languages and tools, therefore no further configuration is required. If the native access is denied by the user with `--illegal-native-access=deny` then loading the optimizing runtime will fail and the fallback runtime will be used. More information can be found in the integrity-by-default [JEP-472](https://openjdk.org/jeps/472).
1010
* GR-54300 `Context` and `Engine` is now automatically closed when no longer strongly referenced. A reachable `Value` or `PolyglotException` will keep the associated `Context` reachable. Additionally, the `Context` remains reachable when explicitly entered or if there is an active polyglot thread within it. The `Engine` remains reachable when there is a strongly reachable `Language`, `Instrument`, or `Context` instance. However, it is still recommended not to rely on garbage collection for closing. Instead, use the try-with-resources pattern for explicit context and engine management. For more information, refer to the [Automatic Close on GC documentation](https://github.com/oracle/graal/blob/master/truffle/docs/CloseOnGc.md).
11+
* GR-60022 Introduced the [FileSystem.newCompositeFileSystem](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/io/FileSystem.html#newCompositeFileSystem(org.graalvm.polyglot.io.FileSystem,org.graalvm.polyglot.io.FileSystem.Selector...)) method, which creates a composite `FileSystem` delegating operations to the specified delegate FileSystem instances based on the provided selectors.
12+
* GR-60022 Introduced the [FileSystem.newDenyIOFileSystem](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/io/FileSystem.html#newDenyIOFileSystem()) method, which creates a `FileSystem` that denies all file operations except for path parsing.
1113

1214
## Version 24.1.0
1315
* GR-51177 Enable random offsets of runtime compiled function entry points for the UNTRUSTED polyglot sandbox policy.

sdk/src/org.graalvm.polyglot/snapshot.sigtest

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ meth public abstract java.lang.annotation.ElementType[] value()
122122
CLSS public abstract interface java.lang.constant.Constable
123123
meth public abstract java.util.Optional<? extends java.lang.constant.ConstantDesc> describeConstable()
124124

125+
CLSS public abstract interface java.util.function.Predicate<%0 extends java.lang.Object>
126+
anno 0 java.lang.FunctionalInterface()
127+
meth public abstract boolean test({java.util.function.Predicate%0})
128+
meth public java.util.function.Predicate<{java.util.function.Predicate%0}> and(java.util.function.Predicate<? super {java.util.function.Predicate%0}>)
129+
meth public java.util.function.Predicate<{java.util.function.Predicate%0}> negate()
130+
meth public java.util.function.Predicate<{java.util.function.Predicate%0}> or(java.util.function.Predicate<? super {java.util.function.Predicate%0}>)
131+
meth public static <%0 extends java.lang.Object> java.util.function.Predicate<{%%0}> isEqual(java.lang.Object)
132+
meth public static <%0 extends java.lang.Object> java.util.function.Predicate<{%%0}> not(java.util.function.Predicate<? super {%%0}>)
133+
125134
CLSS public final org.graalvm.polyglot.Context
126135
innr public final Builder
127136
intf java.lang.AutoCloseable
@@ -676,7 +685,9 @@ meth public org.graalvm.polyglot.io.ByteSequence subSequence(int,int)
676685
meth public static org.graalvm.polyglot.io.ByteSequence create(byte[])
677686

678687
CLSS public abstract interface org.graalvm.polyglot.io.FileSystem
688+
innr public abstract static Selector
679689
meth public !varargs boolean isSameFile(java.nio.file.Path,java.nio.file.Path,java.nio.file.LinkOption[]) throws java.io.IOException
690+
meth public !varargs static org.graalvm.polyglot.io.FileSystem newCompositeFileSystem(org.graalvm.polyglot.io.FileSystem,org.graalvm.polyglot.io.FileSystem$Selector[])
680691
meth public !varargs void copy(java.nio.file.Path,java.nio.file.Path,java.nio.file.CopyOption[]) throws java.io.IOException
681692
meth public !varargs void createSymbolicLink(java.nio.file.Path,java.nio.file.Path,java.nio.file.attribute.FileAttribute<?>[]) throws java.io.IOException
682693
meth public !varargs void move(java.nio.file.Path,java.nio.file.Path,java.nio.file.CopyOption[]) throws java.io.IOException
@@ -701,11 +712,22 @@ meth public static org.graalvm.polyglot.io.FileSystem allowInternalResourceAcces
701712
meth public static org.graalvm.polyglot.io.FileSystem allowLanguageHomeAccess(org.graalvm.polyglot.io.FileSystem)
702713
anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="")
703714
meth public static org.graalvm.polyglot.io.FileSystem newDefaultFileSystem()
715+
meth public static org.graalvm.polyglot.io.FileSystem newDenyIOFileSystem()
704716
meth public static org.graalvm.polyglot.io.FileSystem newFileSystem(java.nio.file.FileSystem)
705717
meth public static org.graalvm.polyglot.io.FileSystem newReadOnlyFileSystem(org.graalvm.polyglot.io.FileSystem)
706718
meth public void createLink(java.nio.file.Path,java.nio.file.Path) throws java.io.IOException
707719
meth public void setCurrentWorkingDirectory(java.nio.file.Path)
708720

721+
CLSS public abstract static org.graalvm.polyglot.io.FileSystem$Selector
722+
outer org.graalvm.polyglot.io.FileSystem
723+
cons protected init(org.graalvm.polyglot.io.FileSystem)
724+
intf java.util.function.Predicate<java.nio.file.Path>
725+
meth public abstract boolean test(java.nio.file.Path)
726+
meth public final org.graalvm.polyglot.io.FileSystem getFileSystem()
727+
meth public static org.graalvm.polyglot.io.FileSystem$Selector of(org.graalvm.polyglot.io.FileSystem,java.util.function.Predicate<java.nio.file.Path>)
728+
supr java.lang.Object
729+
hfds fileSystem
730+
709731
CLSS public final org.graalvm.polyglot.io.IOAccess
710732
fld public final static org.graalvm.polyglot.io.IOAccess ALL
711733
fld public final static org.graalvm.polyglot.io.IOAccess NONE

sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Engine.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1960,6 +1960,16 @@ public FileSystem newNIOFileSystem(java.nio.file.FileSystem fileSystem) {
19601960
throw noPolyglotImplementationFound();
19611961
}
19621962

1963+
@Override
1964+
public FileSystem newCompositeFileSystem(FileSystem fallbackFileSystem, FileSystem.Selector... delegates) {
1965+
throw noPolyglotImplementationFound();
1966+
}
1967+
1968+
@Override
1969+
public FileSystem newDenyIOFileSystem() {
1970+
throw noPolyglotImplementationFound();
1971+
}
1972+
19631973
@Override
19641974
public ByteSequence asByteSequence(Object object) {
19651975
throw noPolyglotImplementationFound();

sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/AbstractPolyglotImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,6 +1450,14 @@ public FileSystem newNIOFileSystem(java.nio.file.FileSystem fileSystem) {
14501450
return getNext().newNIOFileSystem(fileSystem);
14511451
}
14521452

1453+
public FileSystem newCompositeFileSystem(FileSystem fallbackFileSystem, FileSystem.Selector... delegates) {
1454+
return getNext().newCompositeFileSystem(fallbackFileSystem, delegates);
1455+
}
1456+
1457+
public FileSystem newDenyIOFileSystem() {
1458+
return getNext().newDenyIOFileSystem();
1459+
}
1460+
14531461
public ByteSequence asByteSequence(Object object) {
14541462
return getNext().asByteSequence(object);
14551463
}

sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/io/FileSystem.java

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import java.util.Map;
6767
import java.util.Objects;
6868
import java.util.Set;
69+
import java.util.function.Predicate;
6970

7071
import org.graalvm.polyglot.Context;
7172
import org.graalvm.polyglot.Engine;
@@ -578,4 +579,119 @@ static FileSystem newReadOnlyFileSystem(FileSystem fileSystem) {
578579
static FileSystem newFileSystem(java.nio.file.FileSystem fileSystem) {
579580
return IOHelper.ImplHolder.IMPL.newNIOFileSystem(fileSystem);
580581
}
582+
583+
/**
584+
* Creates a {@link FileSystem} that denies all file operations except for path parsing. Any
585+
* attempt to perform file operations such as reading, writing, or deletion will result in a
586+
* {@link SecurityException} being thrown.
587+
* <p>
588+
* Typically, this file system does not need to be explicitly installed to restrict access to
589+
* host file systems. Instead, use {@code Context.newBuilder().allowIO(IOAccess.NONE)}. This
590+
* method is intended primarily for use as a fallback file system in a
591+
* {@link #newCompositeFileSystem(FileSystem, Selector...) composite file system}.
592+
*
593+
* @since 24.2
594+
*/
595+
static FileSystem newDenyIOFileSystem() {
596+
return IOHelper.ImplHolder.IMPL.newDenyIOFileSystem();
597+
}
598+
599+
/**
600+
* Creates a composite {@link FileSystem} that delegates operations to the provided
601+
* {@code delegates}. The {@link FileSystem} of the first {@code delegate} whose
602+
* {@link Selector#test(Path)} method accepts the path is used for the file system operation. If
603+
* no {@code delegate} accepts the path, the {@code fallbackFileSystem} is used.
604+
* <p>
605+
* The {@code fallbackFileSystem} is responsible for parsing {@link Path} objects. All provided
606+
* file systems must use the same {@link Path} type, {@link #getSeparator() separator}, and
607+
* {@link #getPathSeparator() path separator}. If any file system does not meet this
608+
* requirement, an {@link IllegalArgumentException} is thrown.
609+
* <p>
610+
* The composite file system maintains its own notion of the current working directory and
611+
* ensures that the {@link #setCurrentWorkingDirectory(Path)} method is not invoked on any of
612+
* the delegates. When a request to set the current working directory is received, the composite
613+
* file system verifies that the specified path corresponds to an existing directory by
614+
* consulting either the appropriate delegate or the {@code fallbackFileSystem}. If an explicit
615+
* current working directory has been set, the composite file system normalizes and resolves all
616+
* relative paths to absolute paths prior to delegating operations. Conversely, if no explicit
617+
* current working directory is set, the composite file system directly forwards the incoming
618+
* path, whether relative or absolute, to the appropriate delegate. Furthermore, when an
619+
* explicit current working directory is set, the composite file system does not delegate
620+
* {@code toAbsolutePath} operations, as delegates do not maintain an independent notion of the
621+
* current working directory. If the current working directory is unset, {@code toAbsolutePath}
622+
* operations are delegated to the {@code fallbackFileSystem}.
623+
* <p>
624+
* Operations that are independent of path context, including {@code getTempDirectory},
625+
* {@code getSeparator}, and {@code getPathSeparator}, are handled exclusively by the
626+
* {@code fallbackFileSystem}.
627+
*
628+
* @throws IllegalArgumentException if the file systems do not use the same {@link Path} type,
629+
* {@link #getSeparator() separator}, or {@link #getPathSeparator() path separator}
630+
* @since 24.2
631+
*/
632+
static FileSystem newCompositeFileSystem(FileSystem fallbackFileSystem, Selector... delegates) {
633+
return IOHelper.ImplHolder.IMPL.newCompositeFileSystem(fallbackFileSystem, delegates);
634+
}
635+
636+
/**
637+
* A selector for determining which {@link FileSystem} should handle operations on a given
638+
* {@link Path}. This class encapsulates a {@link FileSystem} and defines a condition for
639+
* selecting it.
640+
*
641+
* @since 24.2
642+
*/
643+
abstract class Selector implements Predicate<Path> {
644+
645+
private final FileSystem fileSystem;
646+
647+
/**
648+
* Creates a {@link Selector} for the specified {@link FileSystem}.
649+
*
650+
* @since 24.2
651+
*/
652+
protected Selector(FileSystem fileSystem) {
653+
this.fileSystem = Objects.requireNonNull(fileSystem, "FileSystem must be non-null");
654+
}
655+
656+
/**
657+
* Returns the {@link FileSystem} associated with this selector.
658+
*
659+
* @since 24.2
660+
*/
661+
public final FileSystem getFileSystem() {
662+
return fileSystem;
663+
}
664+
665+
/**
666+
* Tests whether the {@link FileSystem} associated with this selector can handle operations
667+
* on the specified {@link Path}.
668+
*
669+
* @param path the path to test, provided as a normalized absolute path. The given
670+
* {@code path} has no path components equal to {@code "."} or {@code ".."}.
671+
* @return {@code true} if the associated {@link FileSystem} can handle the {@code path};
672+
* {@code false} otherwise
673+
* @since 24.2
674+
*/
675+
public abstract boolean test(Path path);
676+
677+
/**
678+
* Creates a {@link Selector} for the specified {@link FileSystem} using the provided
679+
* {@link Predicate}.
680+
*
681+
* @param fileSystem the {@link FileSystem} to associate with the selector
682+
* @param predicate the condition to determine if the {@link FileSystem} can handle a given
683+
* path
684+
* @return a new {@link Selector} that delegates path testing to the {@code predicate}
685+
* @since 24.2
686+
*/
687+
public static Selector of(FileSystem fileSystem, Predicate<Path> predicate) {
688+
Objects.requireNonNull(predicate, "Predicate must be non-null");
689+
return new Selector(fileSystem) {
690+
@Override
691+
public boolean test(Path path) {
692+
return predicate.test(path);
693+
}
694+
};
695+
}
696+
}
581697
}

truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/FileSystemsTest.java

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
import org.graalvm.polyglot.Context;
105105
import org.graalvm.polyglot.Engine;
106106
import org.graalvm.polyglot.io.FileSystem;
107+
import org.graalvm.polyglot.io.FileSystem.Selector;
107108
import org.graalvm.polyglot.io.IOAccess;
108109
import org.junit.AfterClass;
109110
import org.junit.Assert;
@@ -145,7 +146,7 @@ public class FileSystemsTest {
145146
private static final String FILE_TMP_DIR = "tmpfolder";
146147
private static final String FULL_IO_DEPRECATED = "Full IO - Deprecated";
147148
private static final String NO_IO_DEPRECATED = "No IO - Deprecated";
148-
private static final String CUSTOM_FS_DEPRECATED = "Custom File System - Deprecated";
149+
private static final String CUSTOM_FS_DEPRECATED = "Custom file system - Deprecated";
149150
private static final String FULL_IO = "Full IO";
150151
private static final String NO_IO = "No IO";
151152
private static final String NO_IO_UNDER_LANGUAGE_HOME_PUBLIC_FILE = "No IO under language home - public file";
@@ -154,11 +155,14 @@ public class FileSystemsTest {
154155
private static final String CONDITIONAL_IO_READ_WRITE_PART = "Conditional IO - read/write part";
155156
private static final String CONDITIONAL_IO_READ_ONLY_PART = "Conditional IO - read only part";
156157
private static final String CONDITIONAL_IO_PRIVATE_PART = "Conditional IO - private part";
157-
private static final String MEMORY_FILE_SYSTEM = "Memory FileSystem";
158-
private static final String MEMORY_FILE_SYSTEM_WITH_LANGUAGE_HOMES = "Memory FileSystem With Language Homes";
159-
private static final String MEMORY_FILE_SYSTEM_WITH_LANGUAGE_HOMES_INTERNAL_FILE = "Memory FileSystem With Language Homes - internal file";
160-
private static final String CONTEXT_PRE_INITIALIZATION_FILESYSTEM_BUILD_TIME = "Context pre-initialization filesystem build time";
161-
private static final String CONTEXT_PRE_INITIALIZATION_FILESYSTEM_EXECUTION_TIME = "Context pre-initialization filesystem execution time";
158+
private static final String MEMORY_FILE_SYSTEM = "Memory file system";
159+
private static final String MEMORY_FILE_SYSTEM_WITH_LANGUAGE_HOMES = "Memory file system with language homes";
160+
private static final String MEMORY_FILE_SYSTEM_WITH_LANGUAGE_HOMES_INTERNAL_FILE = "Memory file system with language homes - internal file";
161+
private static final String CONTEXT_PRE_INITIALIZATION_FILESYSTEM_BUILD_TIME = "Context pre-initialization file system build time";
162+
private static final String CONTEXT_PRE_INITIALIZATION_FILESYSTEM_EXECUTION_TIME = "Context pre-initialization files ystem execution time";
163+
private static final String COMPOSITE_FILE_SYSTEM_DELEGATE = "Composite file system read write delegate";
164+
private static final String COMPOSITE_FILE_SYSTEM_FALLBACK = "Composite file system read only fallback";
165+
private static final String DENY_IO = "Deny IO file system";
162166

163167
private static final Map<String, Configuration> cfgs = new HashMap<>();
164168

@@ -183,7 +187,10 @@ public static Collection<String> createParameters() {
183187
MEMORY_FILE_SYSTEM_WITH_LANGUAGE_HOMES,
184188
MEMORY_FILE_SYSTEM_WITH_LANGUAGE_HOMES_INTERNAL_FILE,
185189
CONTEXT_PRE_INITIALIZATION_FILESYSTEM_BUILD_TIME,
186-
CONTEXT_PRE_INITIALIZATION_FILESYSTEM_EXECUTION_TIME);
190+
CONTEXT_PRE_INITIALIZATION_FILESYSTEM_EXECUTION_TIME,
191+
COMPOSITE_FILE_SYSTEM_DELEGATE,
192+
COMPOSITE_FILE_SYSTEM_FALLBACK,
193+
DENY_IO);
187194
}
188195

189196
@BeforeClass
@@ -318,6 +325,35 @@ public static void createConfigurations() throws IOException, ReflectiveOperatio
318325
ctx = Context.newBuilder().allowIO(ioAccess).build();
319326
cfgs.put(CONTEXT_PRE_INITIALIZATION_FILESYSTEM_EXECUTION_TIME, new Configuration(CONTEXT_PRE_INITIALIZATION_FILESYSTEM_EXECUTION_TIME, ctx, workDir, fileSystem, true, true, true, true));
320327

328+
// Composite file system with read-only fallback and read write delegate.
329+
FileSystem readWriteFs = FileSystem.newDefaultFileSystem();
330+
Path tmp = Files.createTempDirectory(FileSystemsTest.class.getSimpleName()).toRealPath();
331+
Path readOnlyFolder = Files.createDirectories(tmp.resolve("readOnly"));
332+
Path readWriteFolder = Files.createDirectories(tmp.resolve("readWrite"));
333+
createContent(readOnlyFolder, readWriteFs);
334+
createContent(readWriteFolder, readWriteFs);
335+
FileSystem readOnlyFallBack = FileSystem.newReadOnlyFileSystem(readWriteFs);
336+
Selector readWriteDelegate = Selector.of(readWriteFs, (p) -> p.startsWith(readWriteFolder));
337+
338+
// Read write delegate
339+
fileSystem = FileSystem.newCompositeFileSystem(readOnlyFallBack, readWriteDelegate);
340+
fileSystem.setCurrentWorkingDirectory(readWriteFolder);
341+
ioAccess = IOAccess.newBuilder().fileSystem(fileSystem).build();
342+
ctx = Context.newBuilder().allowIO(ioAccess).build();
343+
cfgs.put(COMPOSITE_FILE_SYSTEM_DELEGATE, new Configuration(COMPOSITE_FILE_SYSTEM_DELEGATE, ctx, readWriteFolder, readWriteFs, true, true, true, true));
344+
// Read only fallback
345+
fileSystem = FileSystem.newCompositeFileSystem(readOnlyFallBack, readWriteDelegate);
346+
fileSystem.setCurrentWorkingDirectory(readOnlyFolder);
347+
ioAccess = IOAccess.newBuilder().fileSystem(fileSystem).build();
348+
ctx = Context.newBuilder().allowIO(ioAccess).build();
349+
cfgs.put(COMPOSITE_FILE_SYSTEM_FALLBACK, new Configuration(COMPOSITE_FILE_SYSTEM_FALLBACK, ctx, readOnlyFolder, readWriteFs, true, true, false, true));
350+
351+
// Deny IO file system
352+
fileSystem = FileSystem.newDenyIOFileSystem();
353+
ioAccess = IOAccess.newBuilder().fileSystem(fileSystem).build();
354+
ctx = Context.newBuilder().allowIO(ioAccess).build();
355+
privateDir = createContent(Files.createTempDirectory(FileSystemsTest.class.getSimpleName()), fullIO);
356+
cfgs.put(DENY_IO, new Configuration(DENY_IO, ctx, privateDir, Paths.get("").toAbsolutePath(), fullIO, true, false, false, false));
321357
}
322358

323359
@SuppressWarnings("deprecation")

0 commit comments

Comments
 (0)