From 978e7dbc467a22ab8b66dee4ff9fbabf019b0401 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Thu, 12 Nov 2015 15:06:53 -0800 Subject: [PATCH] --unmount-idle x automatically unmounts the filesystem after x minutes without a filesystem operation. --- ChangeLog.txt | 1 + src/cli/CallAfterTimeout.h | 2 ++ src/cli/Cli.cpp | 23 ++++++++++++++++++++++ src/cli/Cli.h | 2 ++ src/cli/program_options/Parser.cpp | 6 +++--- src/cli/program_options/ProgramOptions.cpp | 4 ++-- src/cli/program_options/ProgramOptions.h | 6 +++--- src/filesystem/CryDevice.cpp | 16 ++++++++++++++- src/filesystem/CryDevice.h | 4 ++++ src/filesystem/CryDir.cpp | 7 ++++++- src/filesystem/CryFile.cpp | 5 ++++- src/filesystem/CryNode.cpp | 7 +++++++ src/filesystem/CryOpenFile.cpp | 11 +++++++++-- src/filesystem/CryOpenFile.h | 3 ++- src/filesystem/CrySymlink.cpp | 2 ++ test/cli/program_options/ParserTest.cpp | 5 +++++ 16 files changed, 90 insertions(+), 14 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 1fc04afa..2916081b 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -11,3 +11,4 @@ Version 0.8.1 * Allow --cipher=xxx to specify cipher on command line. If cryfs is creating a new filesystem, it will use this cipher. If it is opening an existing filesystem, it will check whether this is the cipher used by it. * --show-ciphers shows a list of all supported ciphers * --extpass allows using an external program for password input +* --unmount-idle x automatically unmounts the filesystem after x minutes without a filesystem operation. diff --git a/src/cli/CallAfterTimeout.h b/src/cli/CallAfterTimeout.h index 30a70a70..97c6e93d 100644 --- a/src/cli/CallAfterTimeout.h +++ b/src/cli/CallAfterTimeout.h @@ -21,6 +21,8 @@ namespace cryfs { boost::chrono::time_point _start; cpputils::LoopThread _checkTimeoutThread; std::mutex _mutex; + + DISALLOW_COPY_AND_ASSIGN(CallAfterTimeout); }; inline CallAfterTimeout::CallAfterTimeout(boost::chrono::milliseconds timeout, std::function callback) diff --git a/src/cli/Cli.cpp b/src/cli/Cli.cpp index 1ddfe039..cff37164 100644 --- a/src/cli/Cli.cpp +++ b/src/cli/Cli.cpp @@ -48,7 +48,15 @@ using std::endl; using std::vector; using std::shared_ptr; using std::make_shared; +using std::unique_ptr; +using std::make_unique; +using std::function; +using boost::optional; using boost::none; +using boost::chrono::duration; +using boost::chrono::duration_cast; +using boost::chrono::minutes; +using boost::chrono::milliseconds; //TODO Support files > 4GB //TODO Improve parallelity. @@ -148,6 +156,13 @@ namespace cryfs { _initLogfile(options); + //TODO Test auto unmounting after idle timeout + //TODO This can fail due to a race condition if the filesystem isn't started yet (e.g. passing --unmount-idle 0"). + auto idleUnmounter = _createIdleCallback(options.unmountAfterIdleMinutes(), [&fuse] {fuse.stop();}); + if (idleUnmounter != none) { + device.onFsAction(std::bind(&CallAfterTimeout::resetTimer, idleUnmounter->get())); + } + std::cout << "\nMounting filesystem. To unmount, call:\n$ fusermount -u " << options.mountDir() << "\n" << std::endl; vector fuseOptions = options.fuseOptions(); @@ -159,6 +174,14 @@ namespace cryfs { } } + optional> Cli::_createIdleCallback(optional minutes, function callback) { + if (minutes == none) { + return none; + } + uint64_t millis = std::round(60000 * (*minutes)); + return make_unique_ref(milliseconds(millis), callback); + } + void Cli::_initLogfile(const ProgramOptions &options) { spdlog::drop("cryfs"); //TODO Test that --logfile parameter works. Should be: file if specified, otherwise stderr if foreground, else syslog. diff --git a/src/cli/Cli.h b/src/cli/Cli.h index 340066f8..81a78ca3 100644 --- a/src/cli/Cli.h +++ b/src/cli/Cli.h @@ -7,6 +7,7 @@ #include #include #include +#include "CallAfterTimeout.h" namespace cryfs { class Cli final { @@ -29,6 +30,7 @@ namespace cryfs { void _checkDirAccessible(const boost::filesystem::path &dir, const std::string &name); std::shared_ptr _checkDirWriteable(const boost::filesystem::path &dir, const std::string &name); void _checkDirReadable(const boost::filesystem::path &dir, std::shared_ptr tempfile, const std::string &name); + boost::optional> _createIdleCallback(boost::optional minutes, std::function callback); cpputils::RandomGenerator &_keyGenerator; cpputils::SCryptSettings _scryptSettings; diff --git a/src/cli/program_options/Parser.cpp b/src/cli/program_options/Parser.cpp index aa64d966..36775c96 100644 --- a/src/cli/program_options/Parser.cpp +++ b/src/cli/program_options/Parser.cpp @@ -39,9 +39,9 @@ ProgramOptions Parser::parse(const vector &supportedCiphers) const { if (foreground) { options.second.push_back(const_cast("-f")); } - optional unmountAfterIdleMinutes = none; + optional unmountAfterIdleMinutes = none; if (vm.count("unmount-idle")) { - unmountAfterIdleMinutes = vm["unmount-idle"].as(); + unmountAfterIdleMinutes = vm["unmount-idle"].as(); } optional logfile = none; if (vm.count("logfile")) { @@ -104,7 +104,7 @@ void Parser::_addAllowedOptions(po::options_description *desc) { ("foreground,f", "Run CryFS in foreground.") ("cipher", po::value(), "Cipher to use for encryption. See possible values by calling cryfs with --show-ciphers") ("show-ciphers", "Show list of supported ciphers.") - ("unmount-idle", po::value(), "Automatically unmount after specified number of idle minutes.") + ("unmount-idle", po::value(), "Automatically unmount after specified number of idle minutes.") ("extpass", po::value(), "External program to use for password input") ("logfile", po::value(), "Specify the file to write log messages to. If this is not specified, log messages will go to stdout, or syslog if CryFS is running in the background.") ; diff --git a/src/cli/program_options/ProgramOptions.cpp b/src/cli/program_options/ProgramOptions.cpp index 183f34e1..e77372fd 100644 --- a/src/cli/program_options/ProgramOptions.cpp +++ b/src/cli/program_options/ProgramOptions.cpp @@ -8,7 +8,7 @@ using std::vector; using boost::optional; ProgramOptions::ProgramOptions(const string &baseDir, const string &mountDir, const optional &configFile, - bool foreground, const optional &unmountAfterIdleMinutes, + bool foreground, const optional &unmountAfterIdleMinutes, const optional &logFile, const optional &cipher, const optional &extPass, const vector &fuseOptions) :_baseDir(baseDir), _mountDir(new char[mountDir.size()+1]), _configFile(configFile), _foreground(foreground), @@ -50,7 +50,7 @@ bool ProgramOptions::foreground() const { return _foreground; } -const optional &ProgramOptions::unmountAfterIdleMinutes() const { +const optional &ProgramOptions::unmountAfterIdleMinutes() const { return _unmountAfterIdleMinutes; } diff --git a/src/cli/program_options/ProgramOptions.h b/src/cli/program_options/ProgramOptions.h index dbec3159..1bca5756 100644 --- a/src/cli/program_options/ProgramOptions.h +++ b/src/cli/program_options/ProgramOptions.h @@ -12,7 +12,7 @@ namespace cryfs { class ProgramOptions final { public: ProgramOptions(const std::string &baseDir, const std::string &mountDir, const boost::optional &configFile, - bool foreground, const boost::optional &unmountAfterIdleMinutes, + bool foreground, const boost::optional &unmountAfterIdleMinutes, const boost::optional &logFile, const boost::optional &cipher, const boost::optional &extPass, const std::vector &fuseOptions); ProgramOptions(ProgramOptions &&rhs); @@ -23,7 +23,7 @@ namespace cryfs { const boost::optional &configFile() const; bool foreground() const; const boost::optional &cipher() const; - const boost::optional &unmountAfterIdleMinutes() const; + const boost::optional &unmountAfterIdleMinutes() const; const boost::optional &logFile() const; const boost::optional &extPass() const; const std::vector &fuseOptions() const; @@ -34,7 +34,7 @@ namespace cryfs { boost::optional _configFile; bool _foreground; boost::optional _cipher; - boost::optional _unmountAfterIdleMinutes; + boost::optional _unmountAfterIdleMinutes; boost::optional _logFile; boost::optional _extPass; std::vector _fuseOptions; diff --git a/src/filesystem/CryDevice.cpp b/src/filesystem/CryDevice.cpp index 3e2cd3cd..f7c08ce3 100644 --- a/src/filesystem/CryDevice.cpp +++ b/src/filesystem/CryDevice.cpp @@ -57,7 +57,8 @@ CryDevice::CryDevice(CryConfigFile configFile, unique_ref blockStore ), BLOCKSIZE_BYTES))) ) ), - _rootKey(GetOrCreateRootKey(&configFile)) { + _rootKey(GetOrCreateRootKey(&configFile)), + _onFsAction() { } Key CryDevice::CreateRootBlobAndReturnKey() { @@ -69,6 +70,8 @@ Key CryDevice::CreateRootBlobAndReturnKey() { optional> CryDevice::Load(const bf::path &path) { ASSERT(path.is_absolute(), "Non absolute path given"); + callFsActionCallbacks(); + if (path.parent_path().empty()) { //We are asked to load the root directory '/'. return optional>(make_unique_ref(this, none, _rootKey)); @@ -121,6 +124,7 @@ unique_ref CryDevice::LoadBlob(const bf::path &path) { } void CryDevice::statfs(const bf::path &path, struct statvfs *fsstat) { + callFsActionCallbacks(); throw FuseErrnoException(ENOTSUP); } @@ -165,4 +169,14 @@ cpputils::unique_ref CryDevice::CreateEncryptedBlockStor return CryCiphers::find(config.Cipher()).createEncryptedBlockstore(std::move(baseBlockStore), config.EncryptionKey()); } +void CryDevice::onFsAction(std::function callback) { + _onFsAction.push_back(callback); +} + +void CryDevice::callFsActionCallbacks() const { + for (const auto &callback : _onFsAction) { + callback(); + } +} + } diff --git a/src/filesystem/CryDevice.h b/src/filesystem/CryDevice.h index d38ddd55..4730f36a 100644 --- a/src/filesystem/CryDevice.h +++ b/src/filesystem/CryDevice.h @@ -31,14 +31,18 @@ public: cpputils::unique_ref LoadDirBlob(const boost::filesystem::path &path); void RemoveBlob(const blockstore::Key &key); + void onFsAction(std::function callback); + boost::optional> Load(const boost::filesystem::path &path) override; + void callFsActionCallbacks() const; private: cpputils::unique_ref _fsBlobStore; blockstore::Key _rootKey; + std::vector> _onFsAction; blockstore::Key GetOrCreateRootKey(CryConfigFile *config); blockstore::Key CreateRootBlobAndReturnKey(); diff --git a/src/filesystem/CryDir.cpp b/src/filesystem/CryDir.cpp index a26fab7b..0308d8c5 100644 --- a/src/filesystem/CryDir.cpp +++ b/src/filesystem/CryDir.cpp @@ -37,12 +37,14 @@ CryDir::~CryDir() { } unique_ref CryDir::createAndOpenFile(const string &name, mode_t mode, uid_t uid, gid_t gid) { + device()->callFsActionCallbacks(); auto child = device()->CreateFileBlob(); LoadBlob()->AddChildFile(name, child->key(), mode, uid, gid); - return make_unique_ref(std::move(child)); + return make_unique_ref(device(), std::move(child)); } void CryDir::createDir(const string &name, mode_t mode, uid_t uid, gid_t gid) { + device()->callFsActionCallbacks(); auto blob = LoadBlob(); auto child = device()->CreateDirBlob(); blob->AddChildDir(name, child->key(), mode, uid, gid); @@ -56,6 +58,7 @@ unique_ref CryDir::LoadBlob() const { } unique_ref> CryDir::children() const { + device()->callFsActionCallbacks(); auto children = make_unique_ref>(); children->push_back(fspp::Dir::Entry(fspp::Dir::EntryType::DIR, ".")); children->push_back(fspp::Dir::Entry(fspp::Dir::EntryType::DIR, "..")); @@ -65,10 +68,12 @@ unique_ref> CryDir::children() const { } fspp::Dir::EntryType CryDir::getType() const { + device()->callFsActionCallbacks(); return fspp::Dir::EntryType::DIR; } void CryDir::createSymlink(const string &name, const bf::path &target, uid_t uid, gid_t gid) { + device()->callFsActionCallbacks(); auto blob = LoadBlob(); auto child = device()->CreateSymlinkBlob(target); blob->AddChildSymlink(name, child->key(), uid, gid); diff --git a/src/filesystem/CryFile.cpp b/src/filesystem/CryFile.cpp index 9e902d39..48636dc1 100644 --- a/src/filesystem/CryFile.cpp +++ b/src/filesystem/CryFile.cpp @@ -35,16 +35,19 @@ unique_ref CryFile::LoadBlob() const { } unique_ref CryFile::open(int flags) const { + device()->callFsActionCallbacks(); auto blob = LoadBlob(); - return make_unique_ref(std::move(blob)); + return make_unique_ref(device(), std::move(blob)); } void CryFile::truncate(off_t size) const { + device()->callFsActionCallbacks(); auto blob = LoadBlob(); blob->resize(size); } fspp::Dir::EntryType CryFile::getType() const { + device()->callFsActionCallbacks(); return fspp::Dir::EntryType::FILE; } diff --git a/src/filesystem/CryNode.cpp b/src/filesystem/CryNode.cpp index bbe39773..cc30a622 100644 --- a/src/filesystem/CryNode.cpp +++ b/src/filesystem/CryNode.cpp @@ -35,12 +35,14 @@ CryNode::~CryNode() { } void CryNode::access(int mask) const { + device()->callFsActionCallbacks(); //TODO return; throw FuseErrnoException(ENOTSUP); } void CryNode::rename(const bf::path &to) { + device()->callFsActionCallbacks(); if (_parent == none) { //We are the root direcory. //TODO What should we do? @@ -59,11 +61,13 @@ void CryNode::rename(const bf::path &to) { } void CryNode::utimens(const timespec times[2]) { + device()->callFsActionCallbacks(); //TODO throw FuseErrnoException(ENOTSUP); } void CryNode::remove() { + device()->callFsActionCallbacks(); //TODO Instead of all these if-else and having _parent being an optional, we could also introduce a CryRootDir which inherits from fspp::Dir. if (_parent == none) { //We are the root direcory. @@ -87,6 +91,7 @@ unique_ref CryNode::LoadBlob() const { } void CryNode::stat(struct ::stat *result) const { + device()->callFsActionCallbacks(); if(_parent == none) { //We are the root directory. //TODO What should we do? @@ -97,6 +102,7 @@ void CryNode::stat(struct ::stat *result) const { } void CryNode::chmod(mode_t mode) { + device()->callFsActionCallbacks(); if (_parent == none) { //We are the root direcory. //TODO What should we do? @@ -106,6 +112,7 @@ void CryNode::chmod(mode_t mode) { } void CryNode::chown(uid_t uid, gid_t gid) { + device()->callFsActionCallbacks(); if (_parent == none) { //We are the root direcory. //TODO What should we do? diff --git a/src/filesystem/CryOpenFile.cpp b/src/filesystem/CryOpenFile.cpp index 72c60fe9..bdbcca67 100644 --- a/src/filesystem/CryOpenFile.cpp +++ b/src/filesystem/CryOpenFile.cpp @@ -17,8 +17,8 @@ using fspp::fuse::FuseErrnoException; namespace cryfs { -CryOpenFile::CryOpenFile(unique_ref fileBlob) -: _fileBlob(std::move(fileBlob)) { +CryOpenFile::CryOpenFile(const CryDevice *device, unique_ref fileBlob) +: _device(device), _fileBlob(std::move(fileBlob)) { } CryOpenFile::~CryOpenFile() { @@ -26,32 +26,39 @@ CryOpenFile::~CryOpenFile() { } void CryOpenFile::flush() { + _device->callFsActionCallbacks(); _fileBlob->flush(); } void CryOpenFile::stat(struct ::stat *result) const { + _device->callFsActionCallbacks(); result->st_mode = S_IFREG | S_IRUSR | S_IXUSR | S_IWUSR; result->st_size = _fileBlob->size(); return; } void CryOpenFile::truncate(off_t size) const { + _device->callFsActionCallbacks(); _fileBlob->resize(size); } ssize_t CryOpenFile::read(void *buf, size_t count, off_t offset) const { + _device->callFsActionCallbacks(); return _fileBlob->read(buf, offset, count); } void CryOpenFile::write(const void *buf, size_t count, off_t offset) { + _device->callFsActionCallbacks(); _fileBlob->write(buf, offset, count); } void CryOpenFile::fsync() { + _device->callFsActionCallbacks(); //throw FuseErrnoException(ENOTSUP); } void CryOpenFile::fdatasync() { + _device->callFsActionCallbacks(); //throw FuseErrnoException(ENOTSUP); } diff --git a/src/filesystem/CryOpenFile.h b/src/filesystem/CryOpenFile.h index 4170ce5e..af09a91c 100644 --- a/src/filesystem/CryOpenFile.h +++ b/src/filesystem/CryOpenFile.h @@ -10,7 +10,7 @@ class CryDevice; class CryOpenFile: public fspp::OpenFile { public: - explicit CryOpenFile(cpputils::unique_ref fileBlob); + explicit CryOpenFile(const CryDevice *device, cpputils::unique_ref fileBlob); virtual ~CryOpenFile(); void stat(struct ::stat *result) const override; @@ -22,6 +22,7 @@ public: void fdatasync() override; private: + const CryDevice *_device; cpputils::unique_ref _fileBlob; DISALLOW_COPY_AND_ASSIGN(CryOpenFile); diff --git a/src/filesystem/CrySymlink.cpp b/src/filesystem/CrySymlink.cpp index a1b8c9be..f40cfd4b 100644 --- a/src/filesystem/CrySymlink.cpp +++ b/src/filesystem/CrySymlink.cpp @@ -40,10 +40,12 @@ unique_ref CrySymlink::LoadBlob() const { } fspp::Dir::EntryType CrySymlink::getType() const { + device()->callFsActionCallbacks(); return fspp::Dir::EntryType::SYMLINK; } bf::path CrySymlink::target() const { + device()->callFsActionCallbacks(); auto blob = LoadBlob(); return blob->target(); } diff --git a/test/cli/program_options/ParserTest.cpp b/test/cli/program_options/ParserTest.cpp index 5e601df9..83c84356 100644 --- a/test/cli/program_options/ParserTest.cpp +++ b/test/cli/program_options/ParserTest.cpp @@ -81,6 +81,11 @@ TEST_F(ProgramOptionsParserTest, UnmountAfterIdleMinutesGiven) { EXPECT_EQ(10, options.unmountAfterIdleMinutes().value()); } +TEST_F(ProgramOptionsParserTest, UnmountAfterIdleMinutesGiven_Float) { + ProgramOptions options = parse({"./myExecutable", "/home/user/baseDir", "--unmount-idle", "0.5", "/home/user/mountDir"}); + EXPECT_EQ(0.5, options.unmountAfterIdleMinutes().value()); +} + TEST_F(ProgramOptionsParserTest, InvalidCipher) { EXPECT_DEATH( parse({"./myExecutable", "/home/user/baseDir", "--cipher", "invalid-cipher", "/home/user/mountDir"}),