88 *******************************************************************************/
99package org .cryptomator .cryptofs ;
1010
11+ import jakarta .inject .Inject ;
1112import org .cryptomator .cryptofs .attr .AttributeByNameProvider ;
1213import org .cryptomator .cryptofs .attr .AttributeProvider ;
1314import org .cryptomator .cryptofs .attr .AttributeViewProvider ;
1920import org .cryptomator .cryptofs .dir .CiphertextDirectoryDeleter ;
2021import org .cryptomator .cryptofs .dir .DirectoryStreamFactory ;
2122import org .cryptomator .cryptofs .dir .DirectoryStreamFilters ;
23+ import org .cryptomator .cryptofs .event .FileIsInUseEvent ;
24+ import org .cryptomator .cryptofs .event .FilesystemEvent ;
2225import org .cryptomator .cryptofs .fh .OpenCryptoFiles ;
26+ import org .cryptomator .cryptofs .inuse .FileAlreadyInUseException ;
27+ import org .cryptomator .cryptofs .inuse .InUseManager ;
28+ import org .cryptomator .cryptofs .inuse .UseInfo ;
2329import org .cryptomator .cryptolib .api .Cryptor ;
2430
25- import jakarta .inject .Inject ;
2631import java .io .IOException ;
2732import java .nio .channels .FileChannel ;
2833import java .nio .file .AccessDeniedException ;
5661import java .nio .file .attribute .PosixFileAttributes ;
5762import java .nio .file .attribute .PosixFilePermission ;
5863import java .nio .file .attribute .UserPrincipalLookupService ;
64+ import java .time .Instant ;
5965import java .util .Arrays ;
6066import java .util .Collections ;
6167import java .util .EnumSet ;
6268import java .util .Map ;
6369import java .util .Optional ;
6470import java .util .Set ;
71+ import java .util .function .Consumer ;
6572import java .util .stream .Collectors ;
6673
6774import static java .lang .String .format ;
@@ -92,10 +99,11 @@ class CryptoFileSystemImpl extends CryptoFileSystem {
9299 private final CiphertextDirectoryDeleter ciphertextDirDeleter ;
93100 private final ReadonlyFlag readonlyFlag ;
94101 private final CryptoFileSystemProperties fileSystemProperties ;
95-
102+ private final InUseManager inUseManager ;
96103 private final CryptoPath rootPath ;
97104 private final CryptoPath emptyPath ;
98105 private final FileNameDecryptor fileNameDecryptor ;
106+ private final Consumer <FilesystemEvent > eventConsumer ;
99107
100108 private volatile boolean open = true ;
101109
@@ -105,7 +113,7 @@ public CryptoFileSystemImpl(CryptoFileSystemProvider provider, CryptoFileSystems
105113 PathMatcherFactory pathMatcherFactory , DirectoryStreamFactory directoryStreamFactory , DirectoryIdProvider dirIdProvider , DirectoryIdBackup dirIdBackup , //
106114 AttributeProvider fileAttributeProvider , AttributeByNameProvider fileAttributeByNameProvider , AttributeViewProvider fileAttributeViewProvider , //
107115 OpenCryptoFiles openCryptoFiles , Symlinks symlinks , FinallyUtil finallyUtil , CiphertextDirectoryDeleter ciphertextDirDeleter , ReadonlyFlag readonlyFlag , //
108- CryptoFileSystemProperties fileSystemProperties , FileNameDecryptor fileNameDecryptor ) {
116+ CryptoFileSystemProperties fileSystemProperties , InUseManager inUseManager , FileNameDecryptor fileNameDecryptor , Consumer < FilesystemEvent > eventConsumer ) {
109117 this .provider = provider ;
110118 this .cryptoFileSystems = cryptoFileSystems ;
111119 this .pathToVault = pathToVault ;
@@ -130,7 +138,9 @@ public CryptoFileSystemImpl(CryptoFileSystemProvider provider, CryptoFileSystems
130138
131139 this .rootPath = cryptoPathFactory .rootFor (this );
132140 this .emptyPath = cryptoPathFactory .emptyFor (this );
141+ this .inUseManager = inUseManager ;
133142 this .fileNameDecryptor = fileNameDecryptor ;
143+ this .eventConsumer = eventConsumer ;
134144 }
135145
136146 @ Override
@@ -203,9 +213,11 @@ public void close() throws IOException {
203213 open = false ;
204214 finallyUtil .guaranteeInvocationOf ( //
205215 () -> cryptoFileSystems .remove (this ), //
206- () -> openCryptoFiles .close (), //
207- () -> directoryStreamFactory .close (), //
208- () -> cryptor .destroy ());
216+ openCryptoFiles ::close , //
217+ directoryStreamFactory ::close , //
218+ inUseManager ::close , //
219+ cryptor ::destroy //
220+ );
209221 }
210222 }
211223
@@ -402,8 +414,9 @@ private FileChannel newFileChannelFromFile(CryptoPath cleartextFilePath, Effecti
402414 Files .createDirectories (ciphertextPath .getRawPath ()); // suppresses FileAlreadyExists
403415 }
404416
405- FileChannel ch = openCryptoFiles . getOrCreate ( ciphertextFilePath ). newFileChannel ( options , attrs ); // might throw FileAlreadyExists
417+ FileChannel ch = null ;
406418 try {
419+ ch = openCryptoFiles .getOrCreate (ciphertextFilePath ).newFileChannel (options , attrs ); // might throw FileAlreadyExists
407420 if (options .writable ()) {
408421 ciphertextPath .persistLongFileName ();
409422 stats .incrementAccessesWritten ();
@@ -414,7 +427,17 @@ private FileChannel newFileChannelFromFile(CryptoPath cleartextFilePath, Effecti
414427 stats .incrementAccesses ();
415428 return ch ;
416429 } catch (Exception e ) {
417- ch .close ();
430+ if (e instanceof FileAlreadyInUseException ) {
431+ var useInfo = inUseManager .getUseInfo (ciphertextFilePath ).orElse (new UseInfo ("UNKNOWN" , Instant .now ()));
432+ eventConsumer .accept (new FileIsInUseEvent (cleartextFilePath , ciphertextFilePath , useInfo .owner (), useInfo .lastUpdated (), () -> inUseManager .ignoreInUse (ciphertextFilePath )));
433+ }
434+ if (ch != null ) {
435+ try {
436+ ch .close ();
437+ } catch (IOException closeEx ) {
438+ e .addSuppressed (closeEx );
439+ }
440+ }
418441 throw e ;
419442 }
420443 }
@@ -428,11 +451,18 @@ void delete(CryptoPath cleartextPath) throws IOException {
428451 CiphertextFilePath ciphertextPath = cryptoPathMapper .getCiphertextFilePath (cleartextPath );
429452 switch (ciphertextFileType ) {
430453 case DIRECTORY -> deleteDirectory (cleartextPath , ciphertextPath );
431- case FILE , SYMLINK -> deleteFileOrSymlink (ciphertextPath );
454+ case FILE -> deleteFile (cleartextPath , ciphertextPath );
455+ case SYMLINK -> deleteSymlink (ciphertextPath );
432456 }
433457 }
434458
435- private void deleteFileOrSymlink (CiphertextFilePath ciphertextPath ) throws IOException {
459+ private void deleteFile (CryptoPath cleartextPath , CiphertextFilePath ciphertextPath ) throws IOException {
460+ checkUsage (cleartextPath , ciphertextPath );
461+ openCryptoFiles .delete (ciphertextPath .getFilePath ());
462+ Files .walkFileTree (ciphertextPath .getRawPath (), DeletingFileVisitor .INSTANCE );
463+ }
464+
465+ private void deleteSymlink (CiphertextFilePath ciphertextPath ) throws IOException {
436466 openCryptoFiles .delete (ciphertextPath .getFilePath ());
437467 Files .walkFileTree (ciphertextPath .getRawPath (), DeletingFileVisitor .INSTANCE );
438468 }
@@ -605,6 +635,8 @@ private void moveFile(CryptoPath cleartextSource, CryptoPath cleartextTarget, Co
605635 CiphertextFilePath ciphertextSource = cryptoPathMapper .getCiphertextFilePath (cleartextSource );
606636 CiphertextFilePath ciphertextTarget = cryptoPathMapper .getCiphertextFilePath (cleartextTarget );
607637 try (OpenCryptoFiles .TwoPhaseMove twoPhaseMove = openCryptoFiles .prepareMove (ciphertextSource .getRawPath (), ciphertextTarget .getRawPath ())) {
638+ checkUsage (cleartextSource , ciphertextSource );
639+ checkUsage (cleartextTarget , ciphertextTarget );
608640 if (ciphertextTarget .isShortened ()) {
609641 Files .createDirectories (ciphertextTarget .getRawPath ());
610642 ciphertextTarget .persistLongFileName ();
@@ -700,4 +732,14 @@ public String toString() {
700732 return format ("%sCryptoFileSystem(%s)" , open ? "" : "closed " , pathToVault );
701733 }
702734
735+ //visible for testing
736+ void checkUsage (CryptoPath cleartextPath , CiphertextFilePath ciphertextPath ) throws FileAlreadyInUseException {
737+ var path = ciphertextPath .getFilePath ();
738+ if (inUseManager .isInUseByOthers (path )) {
739+ var useInfo = inUseManager .getUseInfo (path ).orElse (new UseInfo ("UNKNOWN" , Instant .now ()));
740+ eventConsumer .accept (new FileIsInUseEvent (cleartextPath , ciphertextPath .getRawPath (), useInfo .owner (), useInfo .lastUpdated (), () -> inUseManager .ignoreInUse (path )));
741+ throw new FileAlreadyInUseException (ciphertextPath .getRawPath ());
742+ }
743+ }
744+
703745}
0 commit comments