--unmount-idle x automatically unmounts the filesystem after x minutes without a filesystem operation.

This commit is contained in:
Sebastian Messmer 2015-11-12 15:06:53 -08:00
parent 4dab8c7426
commit 978e7dbc46
16 changed files with 90 additions and 14 deletions

View File

@ -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.

View File

@ -21,6 +21,8 @@ namespace cryfs {
boost::chrono::time_point<boost::chrono::steady_clock> _start;
cpputils::LoopThread _checkTimeoutThread;
std::mutex _mutex;
DISALLOW_COPY_AND_ASSIGN(CallAfterTimeout);
};
inline CallAfterTimeout::CallAfterTimeout(boost::chrono::milliseconds timeout, std::function<void()> callback)

View File

@ -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<char *> fuseOptions = options.fuseOptions();
@ -159,6 +174,14 @@ namespace cryfs {
}
}
optional<unique_ref<CallAfterTimeout>> Cli::_createIdleCallback(optional<double> minutes, function<void()> callback) {
if (minutes == none) {
return none;
}
uint64_t millis = std::round(60000 * (*minutes));
return make_unique_ref<CallAfterTimeout>(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.

View File

@ -7,6 +7,7 @@
#include <boost/filesystem/path.hpp>
#include <messmer/cpp-utils/tempfile/TempFile.h>
#include <messmer/cpp-utils/random/RandomGenerator.h>
#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<cpputils::TempFile> _checkDirWriteable(const boost::filesystem::path &dir, const std::string &name);
void _checkDirReadable(const boost::filesystem::path &dir, std::shared_ptr<cpputils::TempFile> tempfile, const std::string &name);
boost::optional<cpputils::unique_ref<CallAfterTimeout>> _createIdleCallback(boost::optional<double> minutes, std::function<void()> callback);
cpputils::RandomGenerator &_keyGenerator;
cpputils::SCryptSettings _scryptSettings;

View File

@ -39,9 +39,9 @@ ProgramOptions Parser::parse(const vector<string> &supportedCiphers) const {
if (foreground) {
options.second.push_back(const_cast<char*>("-f"));
}
optional<unsigned int> unmountAfterIdleMinutes = none;
optional<double> unmountAfterIdleMinutes = none;
if (vm.count("unmount-idle")) {
unmountAfterIdleMinutes = vm["unmount-idle"].as<unsigned int>();
unmountAfterIdleMinutes = vm["unmount-idle"].as<double>();
}
optional<string> 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<string>(), "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<unsigned int>(), "Automatically unmount after specified number of idle minutes.")
("unmount-idle", po::value<double>(), "Automatically unmount after specified number of idle minutes.")
("extpass", po::value<string>(), "External program to use for password input")
("logfile", po::value<string>(), "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.")
;

View File

@ -8,7 +8,7 @@ using std::vector;
using boost::optional;
ProgramOptions::ProgramOptions(const string &baseDir, const string &mountDir, const optional<string> &configFile,
bool foreground, const optional<unsigned int> &unmountAfterIdleMinutes,
bool foreground, const optional<double> &unmountAfterIdleMinutes,
const optional<string> &logFile, const optional<string> &cipher,
const optional<string> &extPass, const vector<char*> &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<unsigned int> &ProgramOptions::unmountAfterIdleMinutes() const {
const optional<double> &ProgramOptions::unmountAfterIdleMinutes() const {
return _unmountAfterIdleMinutes;
}

View File

@ -12,7 +12,7 @@ namespace cryfs {
class ProgramOptions final {
public:
ProgramOptions(const std::string &baseDir, const std::string &mountDir, const boost::optional<std::string> &configFile,
bool foreground, const boost::optional<unsigned int> &unmountAfterIdleMinutes,
bool foreground, const boost::optional<double> &unmountAfterIdleMinutes,
const boost::optional<std::string> &logFile, const boost::optional<std::string> &cipher,
const boost::optional<std::string> &extPass, const std::vector<char *> &fuseOptions);
ProgramOptions(ProgramOptions &&rhs);
@ -23,7 +23,7 @@ namespace cryfs {
const boost::optional<std::string> &configFile() const;
bool foreground() const;
const boost::optional<std::string> &cipher() const;
const boost::optional<unsigned int> &unmountAfterIdleMinutes() const;
const boost::optional<double> &unmountAfterIdleMinutes() const;
const boost::optional<std::string> &logFile() const;
const boost::optional<std::string> &extPass() const;
const std::vector<char *> &fuseOptions() const;
@ -34,7 +34,7 @@ namespace cryfs {
boost::optional<std::string> _configFile;
bool _foreground;
boost::optional<std::string> _cipher;
boost::optional<unsigned int> _unmountAfterIdleMinutes;
boost::optional<double> _unmountAfterIdleMinutes;
boost::optional<std::string> _logFile;
boost::optional<std::string> _extPass;
std::vector<char *> _fuseOptions;

View File

@ -57,7 +57,8 @@ CryDevice::CryDevice(CryConfigFile configFile, unique_ref<BlockStore> blockStore
), BLOCKSIZE_BYTES)))
)
),
_rootKey(GetOrCreateRootKey(&configFile)) {
_rootKey(GetOrCreateRootKey(&configFile)),
_onFsAction() {
}
Key CryDevice::CreateRootBlobAndReturnKey() {
@ -69,6 +70,8 @@ Key CryDevice::CreateRootBlobAndReturnKey() {
optional<unique_ref<fspp::Node>> 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<unique_ref<fspp::Node>>(make_unique_ref<CryDir>(this, none, _rootKey));
@ -121,6 +124,7 @@ unique_ref<FsBlobRef> 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<blockstore::BlockStore> CryDevice::CreateEncryptedBlockStor
return CryCiphers::find(config.Cipher()).createEncryptedBlockstore(std::move(baseBlockStore), config.EncryptionKey());
}
void CryDevice::onFsAction(std::function<void()> callback) {
_onFsAction.push_back(callback);
}
void CryDevice::callFsActionCallbacks() const {
for (const auto &callback : _onFsAction) {
callback();
}
}
}

View File

@ -31,14 +31,18 @@ public:
cpputils::unique_ref<parallelaccessfsblobstore::DirBlobRef> LoadDirBlob(const boost::filesystem::path &path);
void RemoveBlob(const blockstore::Key &key);
void onFsAction(std::function<void()> callback);
boost::optional<cpputils::unique_ref<fspp::Node>> Load(const boost::filesystem::path &path) override;
void callFsActionCallbacks() const;
private:
cpputils::unique_ref<parallelaccessfsblobstore::ParallelAccessFsBlobStore> _fsBlobStore;
blockstore::Key _rootKey;
std::vector<std::function<void()>> _onFsAction;
blockstore::Key GetOrCreateRootKey(CryConfigFile *config);
blockstore::Key CreateRootBlobAndReturnKey();

View File

@ -37,12 +37,14 @@ CryDir::~CryDir() {
}
unique_ref<fspp::OpenFile> 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<CryOpenFile>(std::move(child));
return make_unique_ref<CryOpenFile>(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<DirBlobRef> CryDir::LoadBlob() const {
}
unique_ref<vector<fspp::Dir::Entry>> CryDir::children() const {
device()->callFsActionCallbacks();
auto children = make_unique_ref<vector<fspp::Dir::Entry>>();
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<vector<fspp::Dir::Entry>> 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);

View File

@ -35,16 +35,19 @@ unique_ref<parallelaccessfsblobstore::FileBlobRef> CryFile::LoadBlob() const {
}
unique_ref<fspp::OpenFile> CryFile::open(int flags) const {
device()->callFsActionCallbacks();
auto blob = LoadBlob();
return make_unique_ref<CryOpenFile>(std::move(blob));
return make_unique_ref<CryOpenFile>(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;
}

View File

@ -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<FsBlobRef> 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?

View File

@ -17,8 +17,8 @@ using fspp::fuse::FuseErrnoException;
namespace cryfs {
CryOpenFile::CryOpenFile(unique_ref<FileBlobRef> fileBlob)
: _fileBlob(std::move(fileBlob)) {
CryOpenFile::CryOpenFile(const CryDevice *device, unique_ref<FileBlobRef> 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);
}

View File

@ -10,7 +10,7 @@ class CryDevice;
class CryOpenFile: public fspp::OpenFile {
public:
explicit CryOpenFile(cpputils::unique_ref<parallelaccessfsblobstore::FileBlobRef> fileBlob);
explicit CryOpenFile(const CryDevice *device, cpputils::unique_ref<parallelaccessfsblobstore::FileBlobRef> 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<parallelaccessfsblobstore::FileBlobRef> _fileBlob;
DISALLOW_COPY_AND_ASSIGN(CryOpenFile);

View File

@ -40,10 +40,12 @@ unique_ref<SymlinkBlobRef> 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();
}

View File

@ -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"}),