Implement --nocache

Disable block cache (in EncFS) and stat cache (in kernel).
This is needed if the backing files may be modified
behind the back of EncFS (for example, when you mount
an encrypted filesystem exported by encfs --reverse).

The reverse grow tests fail when this option is not passed to the
decrypting mount.
This commit is contained in:
Jakob Unterwurzacher 2014-11-17 21:57:06 +01:00
parent 9f9e30a73f
commit dee3f628e3
5 changed files with 41 additions and 19 deletions

View File

@ -27,6 +27,8 @@
#include "i18n.h" #include "i18n.h"
#include "FileUtils.h"
template <typename Type> template <typename Type>
inline Type min(Type A, Type B) { inline Type min(Type A, Type B) {
return (B < A) ? B : A; return (B < A) ? B : A;
@ -41,6 +43,7 @@ BlockFileIO::BlockFileIO(int blockSize, const FSConfigPtr &cfg)
: _blockSize(blockSize), _allowHoles(cfg->config->allowHoles) { : _blockSize(blockSize), _allowHoles(cfg->config->allowHoles) {
rAssert(_blockSize > 1); rAssert(_blockSize > 1);
_cache.data = new unsigned char[_blockSize]; _cache.data = new unsigned char[_blockSize];
_noCache = cfg->opts->noCache;
} }
BlockFileIO::~BlockFileIO() { BlockFileIO::~BlockFileIO() {
@ -61,8 +64,11 @@ ssize_t BlockFileIO::cacheReadOneBlock(const IORequest &req) const {
/* we can satisfy the request even if _cache.dataLen is too short, because /* we can satisfy the request even if _cache.dataLen is too short, because
* we always request a full block during reads. This just means we are * we always request a full block during reads. This just means we are
* in the last block of a file, which may be smaller than the blocksize. */ * in the last block of a file, which may be smaller than the blocksize.
if ((req.offset == _cache.offset) && (_cache.dataLen != 0)) { * For reverse encryption, the cache must not be used at all, because
* the lower file may have changed behind our back. */
if ( (_noCache == false) && (req.offset == _cache.offset) &&
(_cache.dataLen != 0)) {
// satisfy request from cache // satisfy request from cache
int len = req.dataLen; int len = req.dataLen;
if (_cache.dataLen < len) len = _cache.dataLen; // Don't read past EOF if (_cache.dataLen < len) len = _cache.dataLen; // Don't read past EOF

View File

@ -57,6 +57,7 @@ class BlockFileIO : public FileIO {
int _blockSize; int _blockSize;
bool _allowHoles; bool _allowHoles;
bool _noCache;
// cache last block for speed... // cache last block for speed...
mutable IORequest _cache; mutable IORequest _cache;

View File

@ -76,6 +76,11 @@ struct EncFS_Opts {
bool reverseEncryption; // Reverse encryption bool reverseEncryption; // Reverse encryption
bool noCache; /* Disable block cache (in EncFS) and stat cache (in kernel).
* This is needed if the backing files may be modified
* behind the back of EncFS (for example, in reverse mode).
* See main.cpp for a longer explaination. */
ConfigMode configMode; ConfigMode configMode;
EncFS_Opts() { EncFS_Opts() {
@ -90,6 +95,7 @@ struct EncFS_Opts {
ownerCreate = false; ownerCreate = false;
reverseEncryption = false; reverseEncryption = false;
configMode = Config_Prompt; configMode = Config_Prompt;
noCache = false;
} }
}; };

View File

@ -212,6 +212,7 @@ static bool processArgs(int argc, char *argv[],
// {"single-thread", 0, 0, 's'}, // single-threaded mode // {"single-thread", 0, 0, 's'}, // single-threaded mode
{"stdinpass", 0, 0, 'S'}, // read password from stdin {"stdinpass", 0, 0, 'S'}, // read password from stdin
{"annotate", 0, 0, 513}, // Print annotation lines to stderr {"annotate", 0, 0, 513}, // Print annotation lines to stderr
{"nocache", 0, 0, 514}, // disable caching
{"verbose", 0, 0, 'v'}, // verbose mode {"verbose", 0, 0, 'v'}, // verbose mode
{"version", 0, 0, 'V'}, // version {"version", 0, 0, 'V'}, // version
{"reverse", 0, 0, 'r'}, // reverse encryption {"reverse", 0, 0, 'r'}, // reverse encryption
@ -272,24 +273,32 @@ static bool processArgs(int argc, char *argv[],
case 'D': case 'D':
out->opts->forceDecode = true; out->opts->forceDecode = true;
break; break;
/* By default, the kernel caches file metadata for one second.
* This is fine for EncFS' normal mode, but for --reverse, this
* means that the encrypted view will be up to one second out of
* date.
* Quoting Goswin von Brederlow:
* "Caching only works correctly if you implement a disk based
* filesystem, one where only the fuse process can alter
* metadata and all access goes only through fuse. Any overlay
* filesystem where something can change the underlying
* filesystem without going through fuse can run into
* inconsistencies."
* Enabling reverse automatically enables noCache. */
case 'r': case 'r':
out->opts->reverseEncryption = true; out->opts->reverseEncryption = true;
/* By default, the kernel caches file metadata for one second. case 514:
* This is fine for EncFS' normal mode, but for --reverse, this /* Disable EncFS block cache
* means that the encrypted view will be up to one second out of * Causes reverse grow tests to fail because short reads
* date. * are returned */
* Quoting Goswin von Brederlow: out->opts->noCache = true;
* "Caching only works correctly if you implement a disk based /* Disable kernel stat() cache
* filesystem, one where only the fuse process can alter * Causes reverse grow tests to fail because stale stat() data
* metadata and all access goes only through fuse. Any overlay * is returned */
* filesystem where something can change the underlying PUSHARG("-oattr_timeout=0");
* filesystem without going through fuse can run into /* Disable kernel dentry cache
* inconsistencies." * Fallout unknown, disabling for safety */
* Disable caching so the encrypted view stays consistent with PUSHARG("-oentry_timeout=0");
* the backing files. */
PUSHARG("-oattr_timeout=0"); // Causes reverse grow tests to fail
// because stale stat() data is returned
PUSHARG("-oentry_timeout=0"); // Fallout unknown, disabling for safety
break; break;
case 'm': case 'm':
out->opts->mountOnDemand = true; out->opts->mountOnDemand = true;

View File

@ -48,7 +48,7 @@ sub mount
ok(waitForFile("$plain/.encfs6.xml"), "plain .encfs6.xml exists") or BAIL_OUT("'$plain/.encfs6.xml'"); ok(waitForFile("$plain/.encfs6.xml"), "plain .encfs6.xml exists") or BAIL_OUT("'$plain/.encfs6.xml'");
my $e = encName(".encfs6.xml"); my $e = encName(".encfs6.xml");
ok(waitForFile("$ciphertext/$e"), "encrypted .encfs6.xml exists") or BAIL_OUT("'$ciphertext/$e'"); ok(waitForFile("$ciphertext/$e"), "encrypted .encfs6.xml exists") or BAIL_OUT("'$ciphertext/$e'");
system("ENCFS6_CONFIG=$plain/.encfs6.xml ./encfs/encfs -o attr_timeout=0 --extpass=\"echo test\" $ciphertext $decrypted"); system("ENCFS6_CONFIG=$plain/.encfs6.xml ./encfs/encfs --nocache --extpass=\"echo test\" $ciphertext $decrypted");
ok(waitForFile("$decrypted/.encfs6.xml"), "decrypted .encfs6.xml exists") or BAIL_OUT("'$decrypted/.encfs6.xml'"); ok(waitForFile("$decrypted/.encfs6.xml"), "decrypted .encfs6.xml exists") or BAIL_OUT("'$decrypted/.encfs6.xml'");
} }