Skip to content

Commit 8b9d605

Browse files
Merge branch 'release/1.8.5'
2 parents e2edee0 + b1c723d commit 8b9d605

23 files changed

+272
-161
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
target/
1313
test-output/
1414

15+
# Maven #
16+
target/
17+
pom.xml.versionsBackup
18+
1519
# IntelliJ Settings Files #
1620
.idea/
1721
out/

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<modelVersion>4.0.0</modelVersion>
33
<groupId>org.cryptomator</groupId>
44
<artifactId>cryptofs</artifactId>
5-
<version>1.8.4</version>
5+
<version>1.8.5</version>
66
<name>Cryptomator Crypto Filesystem</name>
77
<description>This library provides the Java filesystem provider used by Cryptomator.</description>
88
<url>https://github.com/cryptomator/cryptofs</url>

src/main/java/org/cryptomator/cryptofs/ConflictResolver.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ private Path renameConflictingFile(Path canonicalPath, Path conflictingPath, Str
121121
alternativePath = canonicalPath.resolveSibling(alternativeCiphertextFileName);
122122
}
123123
LOG.info("Moving conflicting file {} to {}", conflictingPath, alternativePath);
124-
return Files.move(conflictingPath, alternativePath, StandardCopyOption.ATOMIC_MOVE);
124+
Path resolved = Files.move(conflictingPath, alternativePath, StandardCopyOption.ATOMIC_MOVE);
125+
longFileNameProvider.persistCachedIfDeflated(resolved);
126+
return resolved;
125127
} catch (AuthenticationFailedException e) {
126128
// not decryptable, no need to resolve any kind of conflict
127129
LOG.info("Found valid Base32 string, which is an unauthentic ciphertext: {}", conflictingPath);

src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class CryptoFileSystemImpl extends CryptoFileSystem {
7575
private final CryptoFileStore fileStore;
7676
private final CryptoFileSystemStats stats;
7777
private final CryptoPathMapper cryptoPathMapper;
78+
private final LongFileNameProvider longFileNameProvider;
7879
private final CryptoPathFactory cryptoPathFactory;
7980
private final PathMatcherFactory pathMatcherFactory;
8081
private final DirectoryStreamFactory directoryStreamFactory;
@@ -95,7 +96,7 @@ class CryptoFileSystemImpl extends CryptoFileSystem {
9596

9697
@Inject
9798
public CryptoFileSystemImpl(CryptoFileSystemProvider provider, CryptoFileSystems cryptoFileSystems, @PathToVault Path pathToVault, Cryptor cryptor,
98-
CryptoFileStore fileStore, CryptoFileSystemStats stats, CryptoPathMapper cryptoPathMapper, CryptoPathFactory cryptoPathFactory,
99+
CryptoFileStore fileStore, CryptoFileSystemStats stats, CryptoPathMapper cryptoPathMapper, LongFileNameProvider longFileNameProvider, CryptoPathFactory cryptoPathFactory,
99100
PathMatcherFactory pathMatcherFactory, DirectoryStreamFactory directoryStreamFactory, DirectoryIdProvider dirIdProvider,
100101
AttributeProvider fileAttributeProvider, AttributeByNameProvider fileAttributeByNameProvider, AttributeViewProvider fileAttributeViewProvider,
101102
OpenCryptoFiles openCryptoFiles, Symlinks symlinks, FinallyUtil finallyUtil, CiphertextDirectoryDeleter ciphertextDirDeleter, ReadonlyFlag readonlyFlag, RootDirectoryInitializer rootDirectoryInitializer) {
@@ -106,6 +107,7 @@ public CryptoFileSystemImpl(CryptoFileSystemProvider provider, CryptoFileSystems
106107
this.fileStore = fileStore;
107108
this.stats = stats;
108109
this.cryptoPathMapper = cryptoPathMapper;
110+
this.longFileNameProvider = longFileNameProvider;
109111
this.cryptoPathFactory = cryptoPathFactory;
110112
this.pathMatcherFactory = pathMatcherFactory;
111113
this.directoryStreamFactory = directoryStreamFactory;
@@ -302,6 +304,7 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute<?>... attrs) throws
302304
// create dir if and only if the dirFile has been created right now (not if it has been created before):
303305
try {
304306
Files.createDirectories(ciphertextDir.path);
307+
longFileNameProvider.persistCachedIfDeflated(ciphertextDirFile);
305308
} catch (IOException e) {
306309
// make sure there is no orphan dir file:
307310
Files.delete(ciphertextDirFile);
@@ -351,7 +354,9 @@ private FileChannel newFileChannel(CryptoPath cleartextFilePath, EffectiveOpenOp
351354
throw new FileAlreadyExistsException(cleartextFilePath.toString());
352355
} else {
353356
// might also throw FileAlreadyExists:
354-
return openCryptoFiles.getOrCreate(ciphertextPath).newFileChannel(options);
357+
FileChannel ch = openCryptoFiles.getOrCreate(ciphertextPath).newFileChannel(options);
358+
longFileNameProvider.persistCachedIfDeflated(ciphertextPath);
359+
return ch;
355360
}
356361
}
357362

@@ -419,6 +424,7 @@ private void copySymlink(CryptoPath cleartextSource, CryptoPath cleartextTarget,
419424
Path ciphertextTargetFile = cryptoPathMapper.getCiphertextFilePath(cleartextTarget, CiphertextFileType.SYMLINK);
420425
CopyOption[] resolvedOptions = ArrayUtils.without(options, LinkOption.NOFOLLOW_LINKS).toArray(CopyOption[]::new);
421426
Files.copy(ciphertextSourceFile, ciphertextTargetFile, resolvedOptions);
427+
longFileNameProvider.persistCachedIfDeflated(ciphertextTargetFile);
422428
} else {
423429
CryptoPath resolvedSource = symlinks.resolveRecursively(cleartextSource);
424430
CryptoPath resolvedTarget = symlinks.resolveRecursively(cleartextTarget);
@@ -431,6 +437,7 @@ private void copyFile(CryptoPath cleartextSource, CryptoPath cleartextTarget, Co
431437
Path ciphertextSourceFile = cryptoPathMapper.getCiphertextFilePath(cleartextSource, CiphertextFileType.FILE);
432438
Path ciphertextTargetFile = cryptoPathMapper.getCiphertextFilePath(cleartextTarget, CiphertextFileType.FILE);
433439
Files.copy(ciphertextSourceFile, ciphertextTargetFile, options);
440+
longFileNameProvider.persistCachedIfDeflated(ciphertextTargetFile);
434441
}
435442

436443
private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarget, CopyOption[] options) throws IOException {
@@ -439,6 +446,7 @@ private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge
439446
if (Files.notExists(ciphertextTargetDirFile)) {
440447
// create new:
441448
createDirectory(cleartextTarget);
449+
longFileNameProvider.persistCachedIfDeflated(ciphertextTargetDirFile);
442450
} else if (ArrayUtils.contains(options, StandardCopyOption.REPLACE_EXISTING)) {
443451
// keep existing (if empty):
444452
Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path;
@@ -517,6 +525,7 @@ private void moveSymlink(CryptoPath cleartextSource, CryptoPath cleartextTarget,
517525
Path ciphertextTargetFile = cryptoPathMapper.getCiphertextFilePath(cleartextTarget, CiphertextFileType.SYMLINK);
518526
try (OpenCryptoFiles.TwoPhaseMove twoPhaseMove = openCryptoFiles.prepareMove(ciphertextSourceFile, ciphertextTargetFile)) {
519527
Files.move(ciphertextSourceFile, ciphertextTargetFile, options);
528+
longFileNameProvider.persistCachedIfDeflated(ciphertextTargetFile);
520529
twoPhaseMove.commit();
521530
}
522531
}
@@ -528,6 +537,7 @@ private void moveFile(CryptoPath cleartextSource, CryptoPath cleartextTarget, Co
528537
Path ciphertextTargetFile = cryptoPathMapper.getCiphertextFilePath(cleartextTarget, CiphertextFileType.FILE);
529538
try (OpenCryptoFiles.TwoPhaseMove twoPhaseMove = openCryptoFiles.prepareMove(ciphertextSourceFile, ciphertextTargetFile)) {
530539
Files.move(ciphertextSourceFile, ciphertextTargetFile, options);
540+
longFileNameProvider.persistCachedIfDeflated(ciphertextTargetFile);
531541
twoPhaseMove.commit();
532542
}
533543
}
@@ -540,6 +550,7 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge
540550
if (!ArrayUtils.contains(options, StandardCopyOption.REPLACE_EXISTING)) {
541551
// try to move, don't replace:
542552
Files.move(ciphertextSourceDirFile, ciphertextTargetDirFile, options);
553+
longFileNameProvider.persistCachedIfDeflated(ciphertextTargetDirFile);
543554
} else if (ArrayUtils.contains(options, StandardCopyOption.ATOMIC_MOVE)) {
544555
// replace atomically (impossible):
545556
assert ArrayUtils.contains(options, StandardCopyOption.REPLACE_EXISTING);
@@ -558,6 +569,7 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge
558569
Files.delete(ciphertextTargetDir);
559570
}
560571
Files.move(ciphertextSourceDirFile, ciphertextTargetDirFile, options);
572+
longFileNameProvider.persistCachedIfDeflated(ciphertextTargetDirFile);
561573
}
562574
dirIdProvider.move(ciphertextSourceDirFile, ciphertextTargetDirFile);
563575
cryptoPathMapper.invalidatePathMapping(cleartextSource);

src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*******************************************************************************/
99
package org.cryptomator.cryptofs;
1010

11+
import com.google.common.base.Throwables;
1112
import com.google.common.cache.CacheBuilder;
1213
import com.google.common.cache.CacheLoader;
1314
import com.google.common.cache.LoadingCache;
@@ -24,6 +25,7 @@
2425
import java.nio.file.Files;
2526
import java.nio.file.Path;
2627
import java.nio.file.StandardOpenOption;
28+
import java.time.Duration;
2729
import java.util.Arrays;
2830
import java.util.concurrent.ExecutionException;
2931

@@ -34,16 +36,18 @@
3436
class LongFileNameProvider {
3537

3638
private static final BaseEncoding BASE32 = BaseEncoding.base32();
37-
private static final int MAX_CACHE_SIZE = 5000;
39+
private static final Duration MAX_CACHE_AGE = Duration.ofMinutes(1);
3840
public static final String LONG_NAME_FILE_EXT = ".lng";
3941

4042
private final Path metadataRoot;
43+
private final ReadonlyFlag readonlyFlag;
4144
private final LoadingCache<String, String> longNames;
4245

4346
@Inject
44-
public LongFileNameProvider(@PathToVault Path pathToVault) {
47+
public LongFileNameProvider(@PathToVault Path pathToVault, ReadonlyFlag readonlyFlag) {
4548
this.metadataRoot = pathToVault.resolve(METADATA_DIR_NAME);
46-
this.longNames = CacheBuilder.newBuilder().maximumSize(MAX_CACHE_SIZE).build(new Loader());
49+
this.readonlyFlag = readonlyFlag;
50+
this.longNames = CacheBuilder.newBuilder().expireAfterAccess(MAX_CACHE_AGE).build(new Loader());
4751
}
4852

4953
private class Loader extends CacheLoader<String, String> {
@@ -64,36 +68,50 @@ public String inflate(String shortFileName) throws IOException {
6468
try {
6569
return longNames.get(shortFileName);
6670
} catch (ExecutionException e) {
67-
if (e.getCause() instanceof IOException || e.getCause() instanceof UncheckedIOException) {
68-
throw new IOException(e);
69-
} else {
70-
throw new UncheckedExecutionException("Unexpected exception", e);
71-
}
71+
Throwables.throwIfInstanceOf(e.getCause(), IOException.class);
72+
throw new IllegalStateException("Unexpected exception", e);
7273
}
7374
}
7475

75-
public String deflate(String longFileName) throws IOException {
76+
public String deflate(String longFileName) {
7677
byte[] longFileNameBytes = longFileName.getBytes(UTF_8);
7778
byte[] hash = MessageDigestSupplier.SHA1.get().digest(longFileNameBytes);
7879
String shortName = BASE32.encode(hash) + LONG_NAME_FILE_EXT;
79-
if (longNames.getIfPresent(shortName) == null) {
80+
String cachedLongName = longNames.getIfPresent(shortName);
81+
if (cachedLongName == null) {
8082
longNames.put(shortName, longFileName);
81-
// TODO markuskreusch, overheadhunter: do we really want to persist this at this point?...
82-
// ...maybe the caller only wanted to know if a file exists without creating anything.
83-
Path file = resolveMetadataFile(shortName);
84-
Path fileDir = file.getParent();
85-
assert fileDir != null : "resolveMetadataFile returned path to a file";
86-
Files.createDirectories(fileDir);
87-
try (WritableByteChannel ch = Files.newByteChannel(file, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) {
88-
ch.write(ByteBuffer.wrap(longFileNameBytes));
89-
} catch (FileAlreadyExistsException e) {
90-
// no-op: if the file already exists, we assume its content to be what we want (or we found a SHA1 collision ;-))
91-
assert Arrays.equals(Files.readAllBytes(file), longFileNameBytes);
92-
}
83+
} else {
84+
assert cachedLongName.equals(longFileName);
9385
}
9486
return shortName;
9587
}
9688

89+
public void persistCachedIfDeflated(Path ciphertextFile) throws IOException {
90+
String filename = ciphertextFile.getFileName().toString();
91+
if (isDeflated(filename)) {
92+
persistCached(filename);
93+
}
94+
}
95+
96+
// visible for testing
97+
void persistCached(String shortName) throws IOException {
98+
readonlyFlag.assertWritable();
99+
String longName = longNames.getIfPresent(shortName);
100+
if (longName == null) {
101+
throw new IllegalStateException("Long name for " + shortName + " has not been shortened within the last " + MAX_CACHE_AGE);
102+
}
103+
Path file = resolveMetadataFile(shortName);
104+
Path fileDir = file.getParent();
105+
assert fileDir != null : "resolveMetadataFile returned path to a file";
106+
Files.createDirectories(fileDir);
107+
try (WritableByteChannel ch = Files.newByteChannel(file, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) {
108+
ch.write(UTF_8.encode(longName));
109+
} catch (FileAlreadyExistsException e) {
110+
// no-op: if the file already exists, we assume its content to be what we want (or we found a SHA1 collision ;-))
111+
assert Arrays.equals(Files.readAllBytes(file), longName.getBytes(UTF_8));
112+
}
113+
}
114+
97115
private Path resolveMetadataFile(String shortName) {
98116
return metadataRoot.resolve(shortName.substring(0, 2)).resolve(shortName.substring(2, 4)).resolve(shortName);
99117
}

src/main/java/org/cryptomator/cryptofs/Symlinks.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@
2222
public class Symlinks {
2323

2424
private final CryptoPathMapper cryptoPathMapper;
25+
private final LongFileNameProvider longFileNameProvider;
2526
private final OpenCryptoFiles openCryptoFiles;
2627
private final ReadonlyFlag readonlyFlag;
2728

2829
@Inject
29-
Symlinks(CryptoPathMapper cryptoPathMapper, OpenCryptoFiles openCryptoFiles, ReadonlyFlag readonlyFlag) {
30+
Symlinks(CryptoPathMapper cryptoPathMapper, LongFileNameProvider longFileNameProvider, OpenCryptoFiles openCryptoFiles, ReadonlyFlag readonlyFlag) {
3031
this.cryptoPathMapper = cryptoPathMapper;
32+
this.longFileNameProvider = longFileNameProvider;
3133
this.openCryptoFiles = openCryptoFiles;
3234
this.readonlyFlag = readonlyFlag;
3335
}
@@ -41,6 +43,7 @@ public void createSymbolicLink(CryptoPath cleartextPath, Path target, FileAttrib
4143
EffectiveOpenOptions openOptions = EffectiveOpenOptions.from(EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW), readonlyFlag);
4244
ByteBuffer content = UTF_8.encode(target.toString());
4345
openCryptoFiles.writeCiphertextFile(ciphertextSymlinkFile, openOptions, content);
46+
longFileNameProvider.persistCachedIfDeflated(ciphertextSymlinkFile);
4447
}
4548

4649
public CryptoPath readSymbolicLink(CryptoPath cleartextPath) throws IOException {

src/main/java/org/cryptomator/cryptofs/ch/ChannelComponent.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import dagger.BindsInstance;
44
import dagger.Subcomponent;
55
import org.cryptomator.cryptofs.EffectiveOpenOptions;
6+
import org.cryptomator.cryptolib.api.FileHeader;
67

78
import java.nio.channels.FileChannel;
89

@@ -24,6 +25,12 @@ interface Builder {
2425
@BindsInstance
2526
Builder ciphertextChannel(FileChannel ciphertextChannel);
2627

28+
@BindsInstance
29+
Builder mustWriteHeader(@MustWriteHeader boolean mustWriteHeader);
30+
31+
@BindsInstance
32+
Builder fileHeader(FileHeader fileHeader);
33+
2734
ChannelComponent build();
2835
}
2936

src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
import org.cryptomator.cryptofs.fh.ChunkCache;
88
import org.cryptomator.cryptofs.fh.ChunkData;
99
import org.cryptomator.cryptofs.fh.ExceptionsDuringWrite;
10-
import org.cryptomator.cryptofs.fh.FileHeaderLoader;
1110
import org.cryptomator.cryptofs.fh.OpenFileModifiedDate;
1211
import org.cryptomator.cryptofs.fh.OpenFileSize;
1312
import org.cryptomator.cryptolib.api.Cryptor;
13+
import org.cryptomator.cryptolib.api.FileHeader;
1414
import org.slf4j.Logger;
1515
import org.slf4j.LoggerFactory;
1616

@@ -40,7 +40,7 @@ public class CleartextFileChannel extends AbstractFileChannel {
4040
private static final Logger LOG = LoggerFactory.getLogger(CleartextFileChannel.class);
4141

4242
private final FileChannel ciphertextFileChannel;
43-
private final FileHeaderLoader fileHeaderLoader;
43+
private final FileHeader fileHeader;
4444
private final Cryptor cryptor;
4545
private final ChunkCache chunkCache;
4646
private final EffectiveOpenOptions options;
@@ -50,13 +50,13 @@ public class CleartextFileChannel extends AbstractFileChannel {
5050
private final ExceptionsDuringWrite exceptionsDuringWrite;
5151
private final ChannelCloseListener closeListener;
5252
private final CryptoFileSystemStats stats;
53-
private boolean headerWritten;
53+
private boolean mustWriteHeader;
5454

5555
@Inject
56-
public CleartextFileChannel(FileChannel ciphertextFileChannel, FileHeaderLoader fileHeaderLoader, ReadWriteLock readWriteLock, Cryptor cryptor, ChunkCache chunkCache, EffectiveOpenOptions options, @OpenFileSize AtomicLong fileSize, @OpenFileModifiedDate AtomicReference<Instant> lastModified, Supplier<BasicFileAttributeView> attrViewProvider, ExceptionsDuringWrite exceptionsDuringWrite, ChannelCloseListener closeListener, CryptoFileSystemStats stats) {
56+
public CleartextFileChannel(FileChannel ciphertextFileChannel, FileHeader fileHeader, @MustWriteHeader boolean mustWriteHeader, ReadWriteLock readWriteLock, Cryptor cryptor, ChunkCache chunkCache, EffectiveOpenOptions options, @OpenFileSize AtomicLong fileSize, @OpenFileModifiedDate AtomicReference<Instant> lastModified, Supplier<BasicFileAttributeView> attrViewProvider, ExceptionsDuringWrite exceptionsDuringWrite, ChannelCloseListener closeListener, CryptoFileSystemStats stats) {
5757
super(readWriteLock);
5858
this.ciphertextFileChannel = ciphertextFileChannel;
59-
this.fileHeaderLoader = fileHeaderLoader;
59+
this.fileHeader = fileHeader;
6060
this.cryptor = cryptor;
6161
this.chunkCache = chunkCache;
6262
this.options = options;
@@ -69,7 +69,7 @@ public CleartextFileChannel(FileChannel ciphertextFileChannel, FileHeaderLoader
6969
if (options.append()) {
7070
position = fileSize.get();
7171
}
72-
this.headerWritten = !options.writable();
72+
this.mustWriteHeader = mustWriteHeader;
7373
if (options.createNew() || options.create()) {
7474
lastModified.compareAndSet(Instant.EPOCH, Instant.now());
7575
}
@@ -174,10 +174,11 @@ private long writeLockedInternal(ByteSource src, long position) throws IOExcepti
174174
}
175175

176176
private void writeHeaderIfNeeded() throws IOException {
177-
if (!headerWritten) {
177+
if (mustWriteHeader) {
178178
LOG.trace("{} - Writing file header.", this);
179-
ciphertextFileChannel.write(cryptor.fileHeaderCryptor().encryptHeader(fileHeaderLoader.get()), 0);
180-
headerWritten = true;
179+
ByteBuffer encryptedHeader = cryptor.fileHeaderCryptor().encryptHeader(fileHeader);
180+
ciphertextFileChannel.write(encryptedHeader, 0);
181+
mustWriteHeader = false; // write the header only once!
181182
}
182183
}
183184

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.cryptomator.cryptofs.ch;
2+
3+
import javax.inject.Qualifier;
4+
import java.lang.annotation.Documented;
5+
import java.lang.annotation.Retention;
6+
7+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
8+
9+
@Qualifier
10+
@Documented
11+
@Retention(RUNTIME)
12+
@interface MustWriteHeader {
13+
}

src/main/java/org/cryptomator/cryptofs/fh/ChunkIO.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ public void unregisterChannel(FileChannel channel) {
4343
writableChannels.remove(channel);
4444
}
4545

46-
long size() throws IOException {
47-
return getReadableChannel().size();
48-
}
49-
5046
int read(ByteBuffer dst, long position) throws IOException {
5147
return getReadableChannel().read(dst, position);
5248
}

0 commit comments

Comments
 (0)