22
33import com .google .common .annotations .VisibleForTesting ;
44import com .google .common .base .Preconditions ;
5+ import jakarta .inject .Inject ;
56import org .cryptomator .cryptofs .CryptoFileSystemStats ;
7+ import org .cryptomator .cryptofs .CryptoPath ;
68import org .cryptomator .cryptofs .EffectiveOpenOptions ;
9+ import org .cryptomator .cryptofs .event .FileIsInUseEvent ;
10+ import org .cryptomator .cryptofs .event .FilesystemEvent ;
711import org .cryptomator .cryptofs .fh .BufferPool ;
812import org .cryptomator .cryptofs .fh .Chunk ;
913import org .cryptomator .cryptofs .fh .ChunkCache ;
14+ import org .cryptomator .cryptofs .fh .CurrentOpenFileCleartextPath ;
1015import org .cryptomator .cryptofs .fh .CurrentOpenFilePath ;
1116import org .cryptomator .cryptofs .fh .ExceptionsDuringWrite ;
1217import org .cryptomator .cryptofs .fh .FileHeaderHolder ;
1318import org .cryptomator .cryptofs .fh .OpenFileModifiedDate ;
1419import org .cryptomator .cryptofs .fh .OpenFileSize ;
20+ import org .cryptomator .cryptofs .inuse .FileAlreadyInUseException ;
21+ import org .cryptomator .cryptofs .inuse .InUseManager ;
22+ import org .cryptomator .cryptofs .inuse .StubInUseManager ;
23+ import org .cryptomator .cryptofs .inuse .UseInfo ;
1524import org .cryptomator .cryptolib .api .Cryptor ;
1625import org .slf4j .Logger ;
1726import org .slf4j .LoggerFactory ;
1827
19- import jakarta .inject .Inject ;
2028import java .io .IOException ;
2129import java .nio .ByteBuffer ;
2230import java .nio .MappedByteBuffer ;
@@ -55,10 +63,28 @@ public class CleartextFileChannel extends AbstractFileChannel {
5563 private final AtomicReference <Instant > lastModified ;
5664 private final ExceptionsDuringWrite exceptionsDuringWrite ;
5765 private final Consumer <FileChannel > closeListener ;
66+ private final Consumer <FilesystemEvent > eventConsumer ;
5867 private final CryptoFileSystemStats stats ;
68+ private final InUseManager inUseManager ;
69+ private final AtomicReference <CryptoPath > currentCleartextPath ;
5970
6071 @ Inject
61- public CleartextFileChannel (FileChannel ciphertextFileChannel , FileHeaderHolder fileHeaderHolder , ReadWriteLock readWriteLock , Cryptor cryptor , ChunkCache chunkCache , BufferPool bufferPool , EffectiveOpenOptions options , @ OpenFileSize AtomicLong fileSize , @ OpenFileModifiedDate AtomicReference <Instant > lastModified , @ CurrentOpenFilePath AtomicReference <Path > currentPath , ExceptionsDuringWrite exceptionsDuringWrite , Consumer <FileChannel > closeListener , CryptoFileSystemStats stats ) {
72+ public CleartextFileChannel (FileChannel ciphertextFileChannel , //
73+ FileHeaderHolder fileHeaderHolder , //
74+ ReadWriteLock readWriteLock , //
75+ Cryptor cryptor , //
76+ ChunkCache chunkCache , //
77+ BufferPool bufferPool , //
78+ EffectiveOpenOptions options , //
79+ @ OpenFileSize AtomicLong fileSize , //
80+ @ OpenFileModifiedDate AtomicReference <Instant > lastModified , //
81+ @ CurrentOpenFilePath AtomicReference <Path > currentPath , //
82+ @ CurrentOpenFileCleartextPath AtomicReference <CryptoPath > currentCleartextPath , //
83+ ExceptionsDuringWrite exceptionsDuringWrite , //
84+ Consumer <FileChannel > closeListener , //
85+ Consumer <FilesystemEvent > eventConsumer , //
86+ CryptoFileSystemStats stats , //
87+ InUseManager inUseManager ) {
6288 super (readWriteLock );
6389 this .ciphertextFileChannel = ciphertextFileChannel ;
6490 this .fileHeaderHolder = fileHeaderHolder ;
@@ -67,11 +93,14 @@ public CleartextFileChannel(FileChannel ciphertextFileChannel, FileHeaderHolder
6793 this .bufferPool = bufferPool ;
6894 this .options = options ;
6995 this .currentFilePath = currentPath ;
96+ this .currentCleartextPath = currentCleartextPath ;
7097 this .fileSize = fileSize ;
7198 this .lastModified = lastModified ;
7299 this .exceptionsDuringWrite = exceptionsDuringWrite ;
73100 this .closeListener = closeListener ;
101+ this .eventConsumer = eventConsumer ;
74102 this .stats = stats ;
103+ this .inUseManager = inUseManager ;
75104 if (options .append ()) {
76105 position = fileSize .get ();
77106 }
@@ -80,6 +109,23 @@ public CleartextFileChannel(FileChannel ciphertextFileChannel, FileHeaderHolder
80109 }
81110 }
82111
112+ @ VisibleForTesting
113+ CleartextFileChannel (FileChannel ciphertextFileChannel , //
114+ FileHeaderHolder fileHeaderHolder , //
115+ ReadWriteLock readWriteLock , //
116+ Cryptor cryptor , //
117+ ChunkCache chunkCache , //
118+ BufferPool bufferPool , //
119+ EffectiveOpenOptions options , //
120+ AtomicLong fileSize , //
121+ AtomicReference <Instant > lastModified , //
122+ AtomicReference <Path > currentPath , //
123+ ExceptionsDuringWrite exceptionsDuringWrite , //
124+ Consumer <FileChannel > closeListener , //
125+ CryptoFileSystemStats stats ) {
126+ this (ciphertextFileChannel , fileHeaderHolder , readWriteLock , cryptor , chunkCache , bufferPool , options , fileSize , lastModified , currentPath , new AtomicReference <>(null ), exceptionsDuringWrite , closeListener , ignored -> {}, stats , new StubInUseManager ());
127+ }
128+
83129 @ Override
84130 public long size () throws IOException {
85131 assertOpen ();
@@ -124,6 +170,17 @@ protected int readLocked(ByteBuffer dst, long position) throws IOException {
124170
125171 @ Override
126172 protected int writeLocked (ByteBuffer src , long position ) throws IOException {
173+ var path = currentFilePath .get ();
174+ if (path != null && inUseManager .isInUseByOthers (path )) {
175+ var useInfo = inUseManager .getUseInfo (path ).orElse (new UseInfo ("UNKNOWN" , Instant .now ()));
176+ var cleartextPath = currentCleartextPath .get ();
177+ if (cleartextPath != null ) {
178+ eventConsumer .accept (new FileIsInUseEvent (cleartextPath , path , useInfo .owner (), useInfo .lastUpdated (), () -> inUseManager .ignoreInUse (path )));
179+ } else {
180+ LOG .warn ("Unable to emit FileIsInUseEvent: Cleartext path is null. Ciphertext path is {}." , path );
181+ }
182+ throw new FileAlreadyInUseException (path );
183+ }
127184 long oldFileSize = fileSize .get ();
128185 long written ;
129186 if (position > oldFileSize ) {
@@ -256,8 +313,8 @@ void persistLastModified() throws IOException {
256313 FileTime lastAccessTime = FileTime .from (Instant .now ());
257314 var p = currentFilePath .get ();
258315 if (p != null ) {
259- p .getFileSystem ().provider ()//
260- .getFileAttributeView (p , BasicFileAttributeView .class )
316+ p .getFileSystem ().provider () //
317+ .getFileAttributeView (p , BasicFileAttributeView .class ) //
261318 .setTimes (lastModifiedTime , lastAccessTime , null );
262319 }
263320
@@ -328,7 +385,7 @@ long beginOfChunk(long cleartextPos) {
328385 protected void implCloseChannel () throws IOException {
329386 var closeActions = List .<CloseAction >of (this ::flush , //
330387 super ::implCloseChannel , //
331- () -> closeListener .accept (ciphertextFileChannel ),
388+ () -> closeListener .accept (ciphertextFileChannel ), //
332389 ciphertextFileChannel ::close , //
333390 this ::tryPersistLastModified );
334391 tryAll (closeActions .iterator ());
0 commit comments