diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b257aaa --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "monthly" + day: "monday" + time: "06:00" + timezone: "UTC" + groups: + maven-dependencies: + patterns: + - "*" + + - package-ecosystem: "github-actions" + directory: "/" # even for `.github/workflows` + schedule: + interval: "monthly" + groups: + github-actions: + patterns: + - "*" \ No newline at end of file diff --git a/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java b/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java index 5fc4dd9..3bba864 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java +++ b/src/main/java/org/cryptomator/frontend/fuse/OpenFile.java @@ -19,11 +19,19 @@ public class OpenFile implements Closeable { private final Path path; private final FileChannel channel; + /** + * Whether any data has been changed on this file. + * + * This value only changes while holding a write lock, see {@link org.cryptomator.frontend.fuse.locks.LockManager}. + */ + private volatile boolean dirty; + private OpenFile(Path path, FileChannel channel) { this.path = path; this.channel = channel; + this.dirty = false; } - + static OpenFile create(Path path, Set options, FileAttribute... attrs) throws IOException { FileChannel ch = FileChannel.open(path, options, attrs); return new OpenFile(path, ch); @@ -69,6 +77,7 @@ public int read(ByteBuffer buf, long num, long offset) throws IOException { * @throws IOException If an exception occurs during write. */ public int write(ByteBuffer buf, long num, long offset) throws IOException { + dirty = true; if (num > Integer.MAX_VALUE) { throw new IOException("Requested too many bytes"); } @@ -80,6 +89,16 @@ public int write(ByteBuffer buf, long num, long offset) throws IOException { return written; } + /** + * Tests, if this OpenFile is dirty. + * An OpenFile is dirty, if its write method is called at least once. + * + * @return {@code true} if {@link OpenFile#write(ByteBuffer, long, long)} was called on this object, otherwise {@code false} + */ + boolean isDirty() { + return dirty; + } + @Override public void close() throws IOException { channel.close(); @@ -87,6 +106,7 @@ public void close() throws IOException { public void fsync(boolean metaData) throws IOException { channel.force(metaData); + dirty = false; } public void truncate(long size) throws IOException { diff --git a/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java b/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java index f1471cb..04ab047 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java +++ b/src/main/java/org/cryptomator/frontend/fuse/OpenFileFactory.java @@ -67,8 +67,15 @@ public void close(long fileHandle) throws ClosedChannelException, IOException { } } - public int getOpenFileCount(){ - return openFiles.size(); + /** + * Tests, if any {@link OpenFile} is considered dirty, e.g. might have pending changes. + * This method is neither atomic regarding the set of OpenFiles nor regarding each OpenFile individually. Therefore, external synchronization is required. + * + * @return {@code true} if and only if at least one dirty {@link OpenFile} exists. Otherwise {@code false}. + * @see OpenFile#isDirty() + */ + boolean hasDirtyFiles() { + return openFiles.entrySet().stream().anyMatch(entry -> entry.getValue().isDirty()); } /** diff --git a/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java b/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java index 9c2d833..0efc945 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/ReadOnlyAdapter.java @@ -35,7 +35,6 @@ import java.util.Collections; import java.util.EnumSet; import java.util.Set; -import java.util.function.BooleanSupplier; public sealed class ReadOnlyAdapter implements FuseNioAdapter permits ReadWriteAdapter { @@ -52,7 +51,6 @@ public sealed class ReadOnlyAdapter implements FuseNioAdapter permits ReadWriteA private final ReadOnlyDirectoryHandler dirHandler; private final ReadOnlyFileHandler fileHandler; private final ReadOnlyLinkHandler linkHandler; - private final BooleanSupplier hasOpenFiles; protected ReadOnlyAdapter(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, FileStore fileStore, OpenFileFactory openFiles, ReadOnlyDirectoryHandler dirHandler, ReadOnlyFileHandler fileHandler, boolean enableXattr) { this.errno = errno; @@ -66,7 +64,6 @@ protected ReadOnlyAdapter(Errno errno, Path root, int maxFileNameLength, FileNam this.dirHandler = dirHandler; this.fileHandler = fileHandler; this.linkHandler = new ReadOnlyLinkHandler(fileNameTranscoder); - this.hasOpenFiles = () -> openFiles.getOpenFileCount() != 0; } public static ReadOnlyAdapter create(Errno errno, Path root, int maxFileNameLength, FileNameTranscoder fileNameTranscoder, boolean enableXattr) { @@ -377,7 +374,7 @@ public void destroy() { @Override public boolean isInUse() { try (PathLock pLock = lockManager.tryLockForWriting("/")) { - return hasOpenFiles.getAsBoolean(); + return openFiles.hasDirtyFiles(); } catch (AlreadyLockedException e) { return true; }