From fb15e9fd744ee5c04d451fd2e2c7389f91d3a02f Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Mon, 15 Aug 2016 13:23:14 +0200 Subject: [PATCH 1/2] Write only modified chunks --- .../cryptofs/AbstractFileChannel.java | 2 +- .../org/cryptomator/cryptofs/ChunkData.java | 31 ++++++++++++++ .../cryptofs/CryptoFileChannel.java | 40 +++++++++++++------ .../CryptoFileSystemProviderTest.java | 3 +- 4 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/ChunkData.java diff --git a/src/main/java/org/cryptomator/cryptofs/AbstractFileChannel.java b/src/main/java/org/cryptomator/cryptofs/AbstractFileChannel.java index 69bb9d77..bb1b36d1 100644 --- a/src/main/java/org/cryptomator/cryptofs/AbstractFileChannel.java +++ b/src/main/java/org/cryptomator/cryptofs/AbstractFileChannel.java @@ -15,7 +15,7 @@ import java.nio.channels.WritableByteChannel; /** - * Not thread-safe. + * TODO Not thread-safe. */ abstract class AbstractFileChannel extends FileChannel { diff --git a/src/main/java/org/cryptomator/cryptofs/ChunkData.java b/src/main/java/org/cryptomator/cryptofs/ChunkData.java new file mode 100644 index 00000000..7cdc226d --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/ChunkData.java @@ -0,0 +1,31 @@ +package org.cryptomator.cryptofs; + +import java.nio.ByteBuffer; + +class ChunkData { + + public static ChunkData writtenChunkData(ByteBuffer bytes) { + return new ChunkData(bytes, true); + } + + public static ChunkData readChunkData(ByteBuffer bytes) { + return new ChunkData(bytes, false); + } + + private final ByteBuffer bytes; + private final boolean written; + + private ChunkData(ByteBuffer bytes, boolean written) { + this.bytes = bytes; + this.written = written; + } + + public ByteBuffer bytes() { + return bytes; + } + + public boolean wasWritten() { + return written; + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java index 64e12f9b..6c890ec1 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java @@ -8,6 +8,9 @@ *******************************************************************************/ package org.cryptomator.cryptofs; +import static org.cryptomator.cryptofs.ChunkData.readChunkData; +import static org.cryptomator.cryptofs.ChunkData.writtenChunkData; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; @@ -36,7 +39,7 @@ import com.google.common.cache.RemovalNotification; /** - * Not thread-safe. + * TODO Not thread-safe. */ class CryptoFileChannel extends AbstractFileChannel { @@ -46,7 +49,7 @@ class CryptoFileChannel extends AbstractFileChannel { private final Cryptor cryptor; private final FileChannel ch; private final FileHeader header; - private final LoadingCache cleartextChunks; + private final LoadingCache cleartextChunks; private final Set openOptions; private final Collection ioExceptionsDuringWrite = new ArrayList<>(); @@ -104,7 +107,7 @@ public int write(ByteBuffer src, long position) throws IOException { } if (len == payloadSize) { // complete chunk, no need to load and decrypt from file: - cleartextChunks.put(chunkIndex, ByteBuffer.allocate(payloadSize)); + cleartextChunks.put(chunkIndex, writtenChunkData(ByteBuffer.allocate(payloadSize))); } final ByteBuffer chunkBuf = loadCleartextChunk(chunkIndex); chunkBuf.position(offset).limit(Math.max(chunkBuf.limit(), len)); @@ -178,7 +181,7 @@ protected void implCloseChannel() throws IOException { private ByteBuffer loadCleartextChunk(long chunkIndex) { try { - return cleartextChunks.get(chunkIndex); + return cleartextChunks.get(chunkIndex).bytes(); } catch (ExecutionException e) { if (e.getCause() instanceof AuthenticationFailedException) { // TODO @@ -189,10 +192,10 @@ private ByteBuffer loadCleartextChunk(long chunkIndex) { } } - private class CleartextChunkLoader extends CacheLoader { + private class CleartextChunkLoader extends CacheLoader { @Override - public ByteBuffer load(Long chunkIndex) throws Exception { + public ChunkData load(Long chunkIndex) throws Exception { LOG.debug("load chunk" + chunkIndex); int payloadSize = cryptor.fileContentCryptor().cleartextChunkSize(); int chunkSize = cryptor.fileContentCryptor().ciphertextChunkSize(); @@ -201,24 +204,27 @@ public ByteBuffer load(Long chunkIndex) throws Exception { int read = ch.read(ciphertextBuf, ciphertextPos); if (read == -1) { // append - return ByteBuffer.allocate(payloadSize); + return writtenChunkData(ByteBuffer.allocate(payloadSize)); } else { ciphertextBuf.flip(); - return cryptor.fileContentCryptor().decryptChunk(ciphertextBuf, chunkIndex, header, true); + return readChunkData(cryptor.fileContentCryptor().decryptChunk(ciphertextBuf, chunkIndex, header, true)); } } } - private class CleartextChunkSaver implements RemovalListener { + private class CleartextChunkSaver implements RemovalListener { @Override - public void onRemoval(RemovalNotification notification) { - long chunkIndex = notification.getKey(); - if (openOptions.contains(StandardOpenOption.WRITE) && chunkIndex * cryptor.fileContentCryptor().cleartextChunkSize() < size()) { + public void onRemoval(RemovalNotification notification) { + onRemoval(notification.getKey(), notification.getValue()); + } + + private void onRemoval(long chunkIndex, ChunkData chunkData) { + if (channelIsWritable() && chunkLiesInFile(chunkIndex) && chunkData.wasWritten()) { LOG.debug("save chunk" + chunkIndex); long ciphertextPos = chunkIndex * cryptor.fileContentCryptor().ciphertextChunkSize() + cryptor.fileHeaderCryptor().headerSize(); - ByteBuffer cleartextBuf = notification.getValue().asReadOnlyBuffer(); + ByteBuffer cleartextBuf = chunkData.bytes().asReadOnlyBuffer(); cleartextBuf.flip(); ByteBuffer ciphertextBuf = cryptor.fileContentCryptor().encryptChunk(cleartextBuf, chunkIndex, header); try { @@ -229,6 +235,14 @@ public void onRemoval(RemovalNotification notification) { } } + private boolean chunkLiesInFile(long chunkIndex) { + return chunkIndex * cryptor.fileContentCryptor().cleartextChunkSize() < size(); + } + + private boolean channelIsWritable() { + return openOptions.contains(StandardOpenOption.WRITE); + } + } } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index b98c1795..296bdcac 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.net.URI; +import java.net.URLEncoder; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.FileSystem; @@ -74,7 +75,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx @Test public void testGetFsViaNioApi() throws IOException { - URI fsUri = URI.create("cryptomator://" + tmpPath.toString()); + URI fsUri = URI.create("cryptomator://" + URLEncoder.encode(tmpPath.toString(), "UTF-8")); FileSystem fs = FileSystems.newFileSystem(fsUri, ImmutableMap.of(CryptoFileSystemProvider.FS_ENV_PW, "asd")); Assert.assertTrue(fs instanceof CryptoFileSystem); FileSystem fs2 = FileSystems.getFileSystem(fsUri); From a75fa3e53a3b37ff83430154dad9c02a817488ff Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Mon, 15 Aug 2016 15:02:59 +0200 Subject: [PATCH 2/2] Fixed codacy issue regarding field order --- .../java/org/cryptomator/cryptofs/ChunkData.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/ChunkData.java b/src/main/java/org/cryptomator/cryptofs/ChunkData.java index 7cdc226d..9b6eb699 100644 --- a/src/main/java/org/cryptomator/cryptofs/ChunkData.java +++ b/src/main/java/org/cryptomator/cryptofs/ChunkData.java @@ -3,14 +3,6 @@ import java.nio.ByteBuffer; class ChunkData { - - public static ChunkData writtenChunkData(ByteBuffer bytes) { - return new ChunkData(bytes, true); - } - - public static ChunkData readChunkData(ByteBuffer bytes) { - return new ChunkData(bytes, false); - } private final ByteBuffer bytes; private final boolean written; @@ -19,6 +11,14 @@ private ChunkData(ByteBuffer bytes, boolean written) { this.bytes = bytes; this.written = written; } + + public static ChunkData writtenChunkData(ByteBuffer bytes) { + return new ChunkData(bytes, true); + } + + public static ChunkData readChunkData(ByteBuffer bytes) { + return new ChunkData(bytes, false); + } public ByteBuffer bytes() { return bytes;