mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 01:19:28 +01:00
JBR-9531 Prevent unexpected recursive usage of java.io over nio wrappers
Shortly said, we don't need to handle recursive invocations of the `java.io` to nio adapters, while this recursion leads to problems. # Problem Since we're not allowed to modify public API of Java classes, an unreliable workaround `IoOverNio.PARENT_FOR_FILE_CHANNEL_IMPL` had been introduced. This trick lets us pass some object into a called function without adding a new function argument. The trick backfired at the following code: ```java // at java.base/java.io.IoOverNioFileSystem.initializeStreamUsingNio (simplified code) IoOverNio.PARENT_FOR_FILE_CHANNEL_IMPL.set(owner); return initializeStreamsUsingNio0(owner, nioFs, file, nioPath, optionsForChannel, channelCleanable); // at java.base/java.io.IoOverNioFileSystem.initializeStreamsUsingNio0 channel = nioFs.provider().newFileChannel(nioPath, optionsForChannel); ``` The intention of setting `PARENT_FOR_FILE_CHANNEL_IMPL` is to path `owner` inside a constructor of `sun.nio.ch.FileChannelImpl` which may be called by `newFileChannel(nioPath, optionsForChannel)`. The implementation of `newFileChannel` in IntelliJ triggered class loading. The classloader of IntelliJ triggered the call `java.nio.file.Files.readAllBytes`. The latter code also invoked the constructor of `FileChannelImpl`, and the constructor got from the thread-local variable the value for the field `parent`. That value was supposed to be passed to a different invocation of the constructor. The observable result of this bug was a `NullPointerExceptions` from `java.io.FileOutputStream.close`. Hypothetically, this bug could also lead to closing file descriptors earlier as expected in other unpredictable places. # Rejected approaches * The cleanest solution could be passing specific function arguments through function arguments. However, we may neither create a new public function in the module `java.base`, nor add an argument to an existing one. Thus, we have to keep dealing with thread-local variables. * To hold several values in the thread-local variable. In this case, the constructor of `FileChannelImpl` should somehow choose the right value from the list. The only possible way for filtering values is by the provided path. It doesn't look like a performant and reliable solution. # Chosen solution This commit disables recursive invocations of `java.io` from `java.nio` adapters. The chosen solution is tied to specifics of IntelliJ. The reason for introducing java.io over java.nio adapter was to be able to access files from remote machines without rewriting old code. It is not expected that an implementation of `java.nio.file.spi.FileSystemProvider` accesses the file system using `java.io`. The only imaginable way to get a `java.io` call from `java.nio` is class loading. In case of IntelliJ, it's assumed that the class loader never accesses classes and jars from a remote machine.
This commit is contained in:
@@ -159,5 +159,84 @@ public class IoOverNio {
|
||||
* <p>
|
||||
* The problem was found with the test {@code jtreg:test/jdk/java/io/FileDescriptor/Sharing.java}.
|
||||
*/
|
||||
public static final ThreadLocal<Closeable> PARENT_FOR_FILE_CHANNEL_IMPL = new ThreadLocal<>();
|
||||
public static class ParentForFileChannelImplHolder {
|
||||
private static final ThreadLocal<Closeable> holder = new ThreadLocal<>();
|
||||
|
||||
private ParentForFileChannelImplHolder() {}
|
||||
|
||||
public static Closeable get() {
|
||||
return holder.get();
|
||||
}
|
||||
|
||||
public static void set(Closeable parent) {
|
||||
RecursionGuard.ensureActive();
|
||||
holder.set(parent);
|
||||
}
|
||||
|
||||
public static void remove() {
|
||||
RecursionGuard.ensureActive();
|
||||
holder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>With java.io over java.nio backend, it's possible that some code invokes file system operations while
|
||||
* already executing a similar operation. An example is when a classloader uses {@link FileSystems#getDefault()}
|
||||
* during class loading. Such cases break usage of {@link ParentForFileChannelImplHolder}.</p>
|
||||
*
|
||||
* <p>This class is to be used around places that can hypothetically access {@link ParentForFileChannelImplHolder}
|
||||
* recursively.</p>
|
||||
*/
|
||||
public static class RecursionGuard implements Closeable {
|
||||
private static final ThreadLocal<RecursionGuard> HEAD = new ThreadLocal<>();
|
||||
|
||||
private final RecursionGuard parent;
|
||||
private final Object label;
|
||||
private final ThreadLocalCloseable additionalClosable;
|
||||
|
||||
/**
|
||||
* @param label A unique object for a specific method. The object is used for reference equality.
|
||||
* A static string or a reference to a class is a good candidate.
|
||||
*/
|
||||
public static RecursionGuard create(Object label) {
|
||||
ThreadLocalCloseable additionalClosable = null;
|
||||
for (var guard = HEAD.get(); guard != null; guard = guard.parent) {
|
||||
if (guard.label == label) {
|
||||
additionalClosable = disableInThisThread();
|
||||
break;
|
||||
}
|
||||
}
|
||||
var result = new RecursionGuard(HEAD.get(), label, additionalClosable);
|
||||
HEAD.set(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private RecursionGuard(RecursionGuard parent, Object label, ThreadLocalCloseable additionalClosable) {
|
||||
this.parent = parent;
|
||||
this.label = label;
|
||||
this.additionalClosable = additionalClosable;
|
||||
}
|
||||
|
||||
public static void ensureActive() {
|
||||
if (HEAD.get() == null) {
|
||||
throw new Error("RecursionGuard is not installed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
HEAD.set(parent);
|
||||
if (additionalClosable != null) {
|
||||
additionalClosable.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intended only for suppressing warnings about unused variables.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void blackhole(Object any) {
|
||||
// Nothing here.
|
||||
}
|
||||
}
|
||||
@@ -156,37 +156,40 @@ public class FileInputStream extends InputStream
|
||||
}
|
||||
path = file.getPath();
|
||||
|
||||
java.nio.file.FileSystem nioFs = IoOverNioFileSystem.acquireNioFs(path);
|
||||
Path nioPath = null;
|
||||
if (nioFs != null && path != null) {
|
||||
try {
|
||||
nioPath = nioFs.getPath(path);
|
||||
isRegularFile = Files.isRegularFile(nioPath);
|
||||
} catch (InvalidPathException _) {
|
||||
// Nothing.
|
||||
try (var guard = IoOverNio.RecursionGuard.create(FileInputStream.class)) {
|
||||
IoOverNio.blackhole(guard);
|
||||
java.nio.file.FileSystem nioFs = IoOverNioFileSystem.acquireNioFs(path);
|
||||
Path nioPath = null;
|
||||
if (nioFs != null && path != null) {
|
||||
try {
|
||||
nioPath = nioFs.getPath(path);
|
||||
isRegularFile = Files.isRegularFile(nioPath);
|
||||
} catch (InvalidPathException _) {
|
||||
// Nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Two significant differences between the legacy java.io and java.nio.files:
|
||||
// * java.nio.file allows to open directories as streams, java.io.FileInputStream doesn't.
|
||||
// * java.nio.file doesn't work well with pseudo devices, i.e., `seek()` fails, while java.io works well.
|
||||
useNio = nioPath != null && isRegularFile == Boolean.TRUE;
|
||||
// Two significant differences between the legacy java.io and java.nio.files:
|
||||
// * java.nio.file allows to open directories as streams, java.io.FileInputStream doesn't.
|
||||
// * java.nio.file doesn't work well with pseudo devices, i.e., `seek()` fails, while java.io works well.
|
||||
useNio = nioPath != null && isRegularFile == Boolean.TRUE;
|
||||
|
||||
if (useNio) {
|
||||
var bundle = IoOverNioFileSystem.initializeStreamUsingNio(
|
||||
this, nioFs, file, nioPath, Set.of(StandardOpenOption.READ), channelCleanable);
|
||||
channel = bundle.channel();
|
||||
fd = bundle.fd();
|
||||
externalChannelHolder = bundle.externalChannelHolder();
|
||||
} else {
|
||||
fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
open(path);
|
||||
FileCleanable.register(fd); // open set the fd, register the cleanup
|
||||
externalChannelHolder = null;
|
||||
}
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("Created a FileInputStream for %s%n", file);
|
||||
if (useNio) {
|
||||
var bundle = IoOverNioFileSystem.initializeStreamUsingNio(
|
||||
this, nioFs, file, nioPath, Set.of(StandardOpenOption.READ), channelCleanable);
|
||||
channel = bundle.channel();
|
||||
fd = bundle.fd();
|
||||
externalChannelHolder = bundle.externalChannelHolder();
|
||||
} else {
|
||||
fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
open(path);
|
||||
FileCleanable.register(fd); // open set the fd, register the cleanup
|
||||
externalChannelHolder = null;
|
||||
}
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("Created a FileInputStream for %s%n", file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -225,46 +225,49 @@ public class FileOutputStream extends OutputStream
|
||||
}
|
||||
this.path = file.getPath();
|
||||
|
||||
java.nio.file.FileSystem nioFs = IoOverNioFileSystem.acquireNioFs(path);
|
||||
useNio = path != null && nioFs != null;
|
||||
if (useNio) {
|
||||
Path nioPath = nioFs.getPath(path);
|
||||
try (var guard = IoOverNio.RecursionGuard.create(FileOutputStream.class)) {
|
||||
IoOverNio.blackhole(guard);
|
||||
java.nio.file.FileSystem nioFs = IoOverNioFileSystem.acquireNioFs(path);
|
||||
useNio = path != null && nioFs != null;
|
||||
if (useNio) {
|
||||
Path nioPath = nioFs.getPath(path);
|
||||
|
||||
// java.io backend doesn't open DOS hidden files for writing, but java.nio.file opens.
|
||||
// This code mimics the old behavior.
|
||||
if (nioFs.getSeparator().equals("\\")) {
|
||||
DosFileAttributes attrs = null;
|
||||
try {
|
||||
var view = Files.getFileAttributeView(nioPath, DosFileAttributeView.class);
|
||||
if (view != null) {
|
||||
attrs = view.readAttributes();
|
||||
// java.io backend doesn't open DOS hidden files for writing, but java.nio.file opens.
|
||||
// This code mimics the old behavior.
|
||||
if (nioFs.getSeparator().equals("\\")) {
|
||||
DosFileAttributes attrs = null;
|
||||
try {
|
||||
var view = Files.getFileAttributeView(nioPath, DosFileAttributeView.class);
|
||||
if (view != null) {
|
||||
attrs = view.readAttributes();
|
||||
}
|
||||
} catch (IOException | UnsupportedOperationException _) {
|
||||
// Windows paths without DOS attributes? Not a problem in this case.
|
||||
}
|
||||
if (attrs != null && (attrs.isHidden() || attrs.isDirectory())) {
|
||||
throw new FileNotFoundException(file.getPath() + " (Access is denied)");
|
||||
}
|
||||
} catch (IOException | UnsupportedOperationException _) {
|
||||
// Windows paths without DOS attributes? Not a problem in this case.
|
||||
}
|
||||
if (attrs != null && (attrs.isHidden() || attrs.isDirectory())) {
|
||||
throw new FileNotFoundException(file.getPath() + " (Access is denied)");
|
||||
}
|
||||
|
||||
Set<OpenOption> options = append
|
||||
? Set.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND)
|
||||
: Set.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
var bundle = IoOverNioFileSystem.initializeStreamUsingNio(
|
||||
this, nioFs, file, nioPath, options, channelCleanable);
|
||||
channel = bundle.channel();
|
||||
fd = bundle.fd();
|
||||
externalChannelHolder = bundle.externalChannelHolder();
|
||||
} else {
|
||||
this.fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
|
||||
open(this.path, append);
|
||||
FileCleanable.register(fd); // open sets the fd, register the cleanup
|
||||
externalChannelHolder = null;
|
||||
}
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("Created a FileOutputStream for %s%n", file);
|
||||
}
|
||||
|
||||
Set<OpenOption> options = append
|
||||
? Set.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND)
|
||||
: Set.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
var bundle = IoOverNioFileSystem.initializeStreamUsingNio(
|
||||
this, nioFs, file, nioPath, options, channelCleanable);
|
||||
channel = bundle.channel();
|
||||
fd = bundle.fd();
|
||||
externalChannelHolder = bundle.externalChannelHolder();
|
||||
} else {
|
||||
this.fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
|
||||
open(this.path, append);
|
||||
FileCleanable.register(fd); // open sets the fd, register the cleanup
|
||||
externalChannelHolder = null;
|
||||
}
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("Created a FileOutputStream for %s%n", file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -273,10 +273,10 @@ class IoOverNioFileSystem extends FileSystem {
|
||||
try {
|
||||
// This tricky thread local variable allows specifying an argument for sun.nio.ch.FileChannelImpl.<init>
|
||||
// which is not present in the NIO public API and which is not easy to specify another way.
|
||||
IoOverNio.PARENT_FOR_FILE_CHANNEL_IMPL.set(owner);
|
||||
IoOverNio.ParentForFileChannelImplHolder.set(owner);
|
||||
return initializeStreamsUsingNio0(owner, nioFs, file, nioPath, optionsForChannel, channelCleanable);
|
||||
} finally {
|
||||
IoOverNio.PARENT_FOR_FILE_CHANNEL_IMPL.remove();
|
||||
IoOverNio.ParentForFileChannelImplHolder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,7 +876,8 @@ class IoOverNioFileSystem extends FileSystem {
|
||||
|
||||
@Override
|
||||
public boolean delete(File f) {
|
||||
try {
|
||||
try (var guard = IoOverNio.RecursionGuard.create("IoOverNioFileSystem.delete")) {
|
||||
IoOverNio.blackhole(guard);
|
||||
boolean result = delete0(f, true);
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("IoOverNioFileSystem.delete(%s) = %b%n", f, result);
|
||||
|
||||
@@ -279,47 +279,50 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
|
||||
}
|
||||
path = name;
|
||||
|
||||
FileSystem nioFs = IoOverNioFileSystem.acquireNioFs(path);
|
||||
Path nioPath = null;
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
nioPath = nioFs.getPath(path);
|
||||
} catch (InvalidPathException _) {
|
||||
// Nothing.
|
||||
try (var guard = IoOverNio.RecursionGuard.create(RandomAccessFile.class)) {
|
||||
IoOverNio.blackhole(guard);
|
||||
FileSystem nioFs = IoOverNioFileSystem.acquireNioFs(path);
|
||||
Path nioPath = null;
|
||||
if (nioFs != null) {
|
||||
try {
|
||||
nioPath = nioFs.getPath(path);
|
||||
} catch (InvalidPathException _) {
|
||||
// Nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Two significant differences between the legacy java.io and java.nio.files:
|
||||
// * java.nio.file allows to open directories as streams, java.io.FileInputStream doesn't.
|
||||
// * java.nio.file doesn't work well with pseudo devices, i.e., `seek()` fails, while java.io works well.
|
||||
boolean isRegularFile;
|
||||
try {
|
||||
isRegularFile = nioPath != null &&
|
||||
Files.readAttributes(nioPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS).isRegularFile();
|
||||
}
|
||||
catch (NoSuchFileException _) {
|
||||
isRegularFile = true;
|
||||
}
|
||||
catch (IOException _) {
|
||||
isRegularFile = false;
|
||||
}
|
||||
// Two significant differences between the legacy java.io and java.nio.files:
|
||||
// * java.nio.file allows to open directories as streams, java.io.FileInputStream doesn't.
|
||||
// * java.nio.file doesn't work well with pseudo devices, i.e., `seek()` fails, while java.io works well.
|
||||
boolean isRegularFile;
|
||||
try {
|
||||
isRegularFile = nioPath != null &&
|
||||
Files.readAttributes(nioPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS).isRegularFile();
|
||||
}
|
||||
catch (NoSuchFileException _) {
|
||||
isRegularFile = true;
|
||||
}
|
||||
catch (IOException _) {
|
||||
isRegularFile = false;
|
||||
}
|
||||
|
||||
useNio = nioPath != null && isRegularFile;
|
||||
if (useNio) {
|
||||
var bundle = IoOverNioFileSystem.initializeStreamUsingNio(
|
||||
this, nioFs, file, nioPath, optionsForChannel(imode), channelCleanable);
|
||||
channel = bundle.channel();
|
||||
fd = bundle.fd();
|
||||
externalChannelHolder = bundle.externalChannelHolder();
|
||||
} else {
|
||||
fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
open(name, imode);
|
||||
FileCleanable.register(fd); // open sets the fd, register the cleanup
|
||||
externalChannelHolder = null;
|
||||
}
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("Created a RandomAccessFile for %s%n", file);
|
||||
useNio = nioPath != null && isRegularFile;
|
||||
if (useNio) {
|
||||
var bundle = IoOverNioFileSystem.initializeStreamUsingNio(
|
||||
this, nioFs, file, nioPath, optionsForChannel(imode), channelCleanable);
|
||||
channel = bundle.channel();
|
||||
fd = bundle.fd();
|
||||
externalChannelHolder = bundle.externalChannelHolder();
|
||||
} else {
|
||||
fd = new FileDescriptor();
|
||||
fd.attach(this);
|
||||
open(name, imode);
|
||||
FileCleanable.register(fd); // open sets the fd, register the cleanup
|
||||
externalChannelHolder = null;
|
||||
}
|
||||
if (DEBUG.writeTraces()) {
|
||||
System.err.printf("Created a RandomAccessFile for %s%n", file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ public class FileChannelImpl
|
||||
this.sync = sync;
|
||||
this.direct = direct;
|
||||
if (parent == null) {
|
||||
parent = IoOverNio.PARENT_FOR_FILE_CHANNEL_IMPL.get();
|
||||
parent = IoOverNio.ParentForFileChannelImplHolder.get();
|
||||
}
|
||||
this.parent = parent;
|
||||
if (direct) {
|
||||
|
||||
@@ -49,16 +49,19 @@ public class ManglingFileSystem extends FileSystem {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
ManglingFileSystemProvider.triggerSporadicFileAccess();
|
||||
provider.defaultFs.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
ManglingFileSystemProvider.triggerSporadicFileAccess();
|
||||
return provider.defaultFs.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
ManglingFileSystemProvider.triggerSporadicFileAccess();
|
||||
return provider.defaultFs.isReadOnly();
|
||||
}
|
||||
|
||||
@@ -69,6 +72,7 @@ public class ManglingFileSystem extends FileSystem {
|
||||
|
||||
@Override
|
||||
public Iterable<Path> getRootDirectories() {
|
||||
ManglingFileSystemProvider.triggerSporadicFileAccess();
|
||||
Iterable<Path> delegateRoots = provider.defaultFs.getRootDirectories();
|
||||
List<Path> result = new ArrayList<>();
|
||||
for (Path delegateRoot : delegateRoots) {
|
||||
@@ -86,11 +90,13 @@ public class ManglingFileSystem extends FileSystem {
|
||||
|
||||
@Override
|
||||
public Iterable<FileStore> getFileStores() {
|
||||
ManglingFileSystemProvider.triggerSporadicFileAccess();
|
||||
return provider.defaultFs.getFileStores();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> supportedFileAttributeViews() {
|
||||
ManglingFileSystemProvider.triggerSporadicFileAccess();
|
||||
return provider.defaultFs.supportedFileAttributeViews();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,19 +23,12 @@
|
||||
|
||||
package testNio;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.AccessMode;
|
||||
import java.nio.file.CopyOption;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.FileStore;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributeView;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.DosFileAttributeView;
|
||||
@@ -48,6 +41,7 @@ import java.nio.file.spi.FileSystemProvider;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
public static final String extraContent = "3x7r4";
|
||||
@@ -168,6 +162,7 @@ public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
|
||||
@Override
|
||||
public FileChannel newFileChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
return new ManglingFileChannel(defaultProvider.newFileChannel(unwrap(path), options, attrs));
|
||||
}
|
||||
|
||||
@@ -178,11 +173,13 @@ public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
|
||||
@Override
|
||||
public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
return new ManglingDirectoryStream(manglingFs, (ManglingPath) dir, defaultProvider.newDirectoryStream(unwrap(dir), filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
if (prohibitFileTreeModifications) {
|
||||
throw new AccessDeniedException(dir.toString(), null, "Test: All file tree modifications are prohibited");
|
||||
}
|
||||
@@ -191,6 +188,7 @@ public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
|
||||
@Override
|
||||
public void delete(Path path) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
if (prohibitFileTreeModifications) {
|
||||
throw new AccessDeniedException(path.toString(), null, "Test: All file tree modifications are prohibited");
|
||||
}
|
||||
@@ -199,6 +197,7 @@ public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
|
||||
@Override
|
||||
public void copy(Path source, Path target, CopyOption... options) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
if (prohibitFileTreeModifications) {
|
||||
throw new AccessDeniedException(source.toString(), null, "Test: All file tree modifications are prohibited");
|
||||
}
|
||||
@@ -209,6 +208,7 @@ public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
|
||||
@Override
|
||||
public void move(Path source, Path target, CopyOption... options) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
if (prohibitFileTreeModifications) {
|
||||
throw new AccessDeniedException(source.toString(), null, "Test: All file tree modifications are prohibited");
|
||||
}
|
||||
@@ -219,6 +219,7 @@ public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
|
||||
@Override
|
||||
public boolean isSameFile(Path path, Path path2) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
Path source2 = unwrap(path);
|
||||
Path target2 = unwrap(path2);
|
||||
return defaultProvider.isSameFile(source2, target2);
|
||||
@@ -226,16 +227,19 @@ public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
|
||||
@Override
|
||||
public boolean isHidden(Path path) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
return defaultProvider.isHidden(unwrap(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileStore getFileStore(Path path) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
return defaultProvider.getFileStore(unwrap(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkAccess(Path path, AccessMode... modes) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
defaultProvider.checkAccess(unwrap(path), modes);
|
||||
if (denyAccessToEverything) {
|
||||
throw new AccessDeniedException(path.toString(), null, "Test: No access rules to anything");
|
||||
@@ -244,11 +248,13 @@ public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
|
||||
@Override
|
||||
public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
|
||||
triggerSporadicFileAccess();
|
||||
return wrap(defaultProvider.getFileAttributeView(unwrap(path), type, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
return wrap(defaultProvider.readAttributes(unwrap(path), type, options));
|
||||
}
|
||||
|
||||
@@ -259,6 +265,7 @@ public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
|
||||
@Override
|
||||
public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
if (prohibitFileTreeModifications) {
|
||||
throw new AccessDeniedException(path.toString(), null, "Test: All file tree modifications are prohibited");
|
||||
}
|
||||
@@ -267,11 +274,13 @@ public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
|
||||
@Override
|
||||
public boolean exists(Path path, LinkOption... options) {
|
||||
triggerSporadicFileAccess();
|
||||
return defaultProvider.exists(unwrap(path), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs) throws IOException {
|
||||
triggerSporadicFileAccess();
|
||||
if (prohibitFileTreeModifications) {
|
||||
throw new AccessDeniedException(link.toString(), null, "Test: All file tree modifications are prohibited");
|
||||
}
|
||||
@@ -303,4 +312,26 @@ public class ManglingFileSystemProvider extends FileSystemProvider {
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
private static final AtomicLong counter = new AtomicLong();
|
||||
|
||||
static void triggerSporadicFileAccess() {
|
||||
if (FileSystems.getDefault() != null) {
|
||||
try {
|
||||
var tempFile = new File(System.getProperty("java.io.tmpdir"), "test.tmp." + counter.incrementAndGet());
|
||||
try {
|
||||
try (var out = new java.io.FileOutputStream(tempFile)) {
|
||||
out.write(1);
|
||||
}
|
||||
try (var in = new java.io.FileInputStream(tempFile)) {
|
||||
in.read();
|
||||
}
|
||||
} finally {
|
||||
tempFile.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@ public class ManglingPath implements Path {
|
||||
|
||||
@Override
|
||||
public Path toRealPath(LinkOption... options) throws IOException {
|
||||
ManglingFileSystemProvider.triggerSporadicFileAccess();
|
||||
return new ManglingPath(fileSystem, delegate.toRealPath(options));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user