Fuse offers an onMounted callback that is called when everything is ready.

This commit is contained in:
Sebastian Messmer 2018-12-09 12:27:53 -05:00
parent 449133e3da
commit 64150f294e
7 changed files with 97 additions and 72 deletions

View File

@ -51,8 +51,8 @@ using std::string;
using std::endl; using std::endl;
using std::shared_ptr; using std::shared_ptr;
using std::make_shared; using std::make_shared;
using std::make_unique;
using std::function; using std::function;
using std::make_shared;
using boost::optional; using boost::optional;
using boost::none; using boost::none;
using boost::chrono::minutes; using boost::chrono::minutes;
@ -219,7 +219,7 @@ namespace cryfs {
cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem); cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem);
} }
void Cli::_runFilesystem(const ProgramOptions &options) { void Cli::_runFilesystem(const ProgramOptions &options, std::function<void()> onMounted) {
try { try {
LocalStateDir localStateDir(Environment::localStateDir()); LocalStateDir localStateDir(Environment::localStateDir());
auto blockStore = make_unique_ref<OnDiskBlockStore2>(options.baseDir()); auto blockStore = make_unique_ref<OnDiskBlockStore2>(options.baseDir());
@ -229,11 +229,11 @@ namespace cryfs {
options.allowIntegrityViolations(), missingBlockIsIntegrityViolation)); options.allowIntegrityViolations(), missingBlockIsIntegrityViolation));
_sanityCheckFilesystem(_device->get()); _sanityCheckFilesystem(_device->get());
auto initFilesystem = [this, &options] (fspp::fuse::Fuse *fuse){ auto initFilesystem = [&] (fspp::fuse::Fuse *fs){
ASSERT(_device != none, "File system not ready to be initialized. Was it already initialized before?"); ASSERT(_device != none, "File system not ready to be initialized. Was it already initialized before?");
//TODO Test auto unmounting after idle timeout //TODO Test auto unmounting after idle timeout
_idleUnmounter = _createIdleCallback(options.unmountAfterIdleMinutes(), [fuse] {fuse->stop();}); _idleUnmounter = _createIdleCallback(options.unmountAfterIdleMinutes(), [fs] {fs->stop();});
if (_idleUnmounter != none) { if (_idleUnmounter != none) {
(*_device)->onFsAction(std::bind(&CallAfterTimeout::resetTimer, _idleUnmounter->get())); (*_device)->onFsAction(std::bind(&CallAfterTimeout::resetTimer, _idleUnmounter->get()));
} }
@ -241,7 +241,7 @@ namespace cryfs {
return make_shared<fspp::FilesystemImpl>(std::move(*_device)); return make_shared<fspp::FilesystemImpl>(std::move(*_device));
}; };
auto fuse = make_unique_ref<fspp::fuse::Fuse>(initFilesystem, "cryfs", "cryfs@" + options.baseDir().string()); auto fuse = make_unique<fspp::fuse::Fuse>(initFilesystem, std::move(onMounted), "cryfs", "cryfs@" + options.baseDir().string());
_initLogfile(options); _initLogfile(options);
@ -369,14 +369,14 @@ namespace cryfs {
return false; return false;
} }
int Cli::main(int argc, const char *argv[], unique_ref<HttpClient> httpClient) { int Cli::main(int argc, const char *argv[], unique_ref<HttpClient> httpClient, std::function<void()> onMounted) {
cpputils::showBacktraceOnCrash(); cpputils::showBacktraceOnCrash();
try { try {
_showVersion(std::move(httpClient)); _showVersion(std::move(httpClient));
ProgramOptions options = program_options::Parser(argc, argv).parse(CryCiphers::supportedCipherNames()); ProgramOptions options = program_options::Parser(argc, argv).parse(CryCiphers::supportedCipherNames());
_sanityChecks(options); _sanityChecks(options);
_runFilesystem(options); _runFilesystem(options, std::move(onMounted));
} catch (const CryfsException &e) { } catch (const CryfsException &e) {
if (e.errorCode() != ErrorCode::Success) { if (e.errorCode() != ErrorCode::Success) {
std::cerr << "Error: " << e.what() << std::endl; std::cerr << "Error: " << e.what() << std::endl;

View File

@ -18,11 +18,11 @@ namespace cryfs {
class Cli final { class Cli final {
public: public:
Cli(cpputils::RandomGenerator &keyGenerator, const cpputils::SCryptSettings& scryptSettings, std::shared_ptr<cpputils::Console> console); Cli(cpputils::RandomGenerator &keyGenerator, const cpputils::SCryptSettings& scryptSettings, std::shared_ptr<cpputils::Console> console);
int main(int argc, const char *argv[], cpputils::unique_ref<cpputils::HttpClient> httpClient); int main(int argc, const char *argv[], cpputils::unique_ref<cpputils::HttpClient> httpClient, std::function<void()> onMounted);
private: private:
void _checkForUpdates(cpputils::unique_ref<cpputils::HttpClient> httpClient); void _checkForUpdates(cpputils::unique_ref<cpputils::HttpClient> httpClient);
void _runFilesystem(const program_options::ProgramOptions &options); void _runFilesystem(const program_options::ProgramOptions &options, std::function<void()> onMounted);
CryConfigLoader::ConfigLoadResult _loadOrCreateConfig(const program_options::ProgramOptions &options, const LocalStateDir& localStateDir); CryConfigLoader::ConfigLoadResult _loadOrCreateConfig(const program_options::ProgramOptions &options, const LocalStateDir& localStateDir);
void _checkConfigIntegrity(const boost::filesystem::path& basedir, const LocalStateDir& localStateDir, const CryConfigFile& config, bool allowReplacedFilesystem); void _checkConfigIntegrity(const boost::filesystem::path& basedir, const LocalStateDir& localStateDir, const CryConfigFile& config, bool allowReplacedFilesystem);
boost::optional<CryConfigLoader::ConfigLoadResult> _loadOrCreateConfigFile(boost::filesystem::path configFilePath, LocalStateDir localStateDir, const boost::optional<std::string> &cipher, const boost::optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade, const boost::optional<bool> &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem); boost::optional<CryConfigLoader::ConfigLoadResult> _loadOrCreateConfigFile(boost::filesystem::path configFilePath, LocalStateDir localStateDir, const boost::optional<std::string> &cipher, const boost::optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade, const boost::optional<bool> &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem);

View File

@ -26,7 +26,7 @@ int main(int argc, const char *argv[]) {
auto httpClient = make_unique_ref<cpputils::CurlHttpClient>(); auto httpClient = make_unique_ref<cpputils::CurlHttpClient>();
#endif #endif
return Cli(keyGenerator, SCrypt::DefaultSettings, make_shared<IOStreamConsole>()) return Cli(keyGenerator, SCrypt::DefaultSettings, make_shared<IOStreamConsole>())
.main(argc, argv, std::move(httpClient)); .main(argc, argv, std::move(httpClient), []{});
} catch (const CryfsException &e) { } catch (const CryfsException &e) {
if (e.errorCode() != ErrorCode::Success) { if (e.errorCode() != ErrorCode::Success) {
std::cerr << "Error: " << e.what() << std::endl; std::cerr << "Error: " << e.what() << std::endl;

View File

@ -229,8 +229,10 @@ Fuse::~Fuse() {
_argv.clear(); _argv.clear();
} }
Fuse::Fuse(std::function<shared_ptr<Filesystem> (Fuse *fuse)> init, std::string fstype, boost::optional<std::string> fsname) Fuse::Fuse(std::function<shared_ptr<Filesystem> (Fuse *fuse)> init, std::function<void()> onMounted, std::string fstype, boost::optional<std::string> fsname)
:_init(std::move(init)), _fs(make_shared<InvalidFilesystem>()), _mountdir(), _running(false), _fstype(std::move(fstype)), _fsname(std::move(fsname)) { :_init(std::move(init)), _onMounted(std::move(onMounted)), _fs(make_shared<InvalidFilesystem>()), _mountdir(), _running(false), _fstype(std::move(fstype)), _fsname(std::move(fsname)) {
ASSERT(static_cast<bool>(_init), "Invalid init given");
ASSERT(static_cast<bool>(_onMounted), "Invalid onMounted given");
} }
void Fuse::_logException(const std::exception &e) { void Fuse::_logException(const std::exception &e) {
@ -866,6 +868,7 @@ void Fuse::init(fuse_conn_info *conn) {
LOG(INFO, "Filesystem started."); LOG(INFO, "Filesystem started.");
_running = true; _running = true;
_onMounted();
#ifdef FSPP_LOG #ifdef FSPP_LOG
cpputils::logging::setLevel(DEBUG); cpputils::logging::setLevel(DEBUG);

View File

@ -21,7 +21,7 @@ class Filesystem;
class Fuse final { class Fuse final {
public: public:
explicit Fuse(std::function<std::shared_ptr<Filesystem> (Fuse *fuse)> init, std::string fstype, boost::optional<std::string> fsname); explicit Fuse(std::function<std::shared_ptr<Filesystem> (Fuse *fuse)> init, std::function<void()> onMounted, std::string fstype, boost::optional<std::string> fsname);
~Fuse(); ~Fuse();
void run(const boost::filesystem::path &mountdir, const std::vector<std::string> &fuseOptions); void run(const boost::filesystem::path &mountdir, const std::vector<std::string> &fuseOptions);
@ -69,6 +69,7 @@ private:
void _add_fuse_option_if_not_exists(std::vector<char *> *argv, const std::string &key, const std::string &value); void _add_fuse_option_if_not_exists(std::vector<char *> *argv, const std::string &key, const std::string &value);
std::function<std::shared_ptr<Filesystem> (Fuse *fuse)> _init; std::function<std::shared_ptr<Filesystem> (Fuse *fuse)> _init;
std::function<void()> _onMounted;
std::shared_ptr<Filesystem> _fs; std::shared_ptr<Filesystem> _fs;
boost::filesystem::path _mountdir; boost::filesystem::path _mountdir;
std::vector<char*> _argv; std::vector<char*> _argv;

View File

@ -16,6 +16,7 @@
#include <cpp-utils/logging/logging.h> #include <cpp-utils/logging/logging.h>
#include <cpp-utils/process/subprocess.h> #include <cpp-utils/process/subprocess.h>
#include <cpp-utils/network/FakeHttpClient.h> #include <cpp-utils/network/FakeHttpClient.h>
#include <cpp-utils/lock/ConditionBarrier.h>
#include "../../cryfs/testutils/MockConsole.h" #include "../../cryfs/testutils/MockConsole.h"
#include "../../cryfs/testutils/TestWithFakeHomeDirectory.h" #include "../../cryfs/testutils/TestWithFakeHomeDirectory.h"
#include <cryfs/ErrorCodes.h> #include <cryfs/ErrorCodes.h>
@ -41,18 +42,18 @@ public:
return std::move(httpClient); return std::move(httpClient);
} }
int run(const std::vector<std::string>& args) { int run(const std::vector<std::string>& args, std::function<void()> onMounted) {
std::vector<const char*> _args; std::vector<const char*> _args;
_args.reserve(args.size() + 1); _args.reserve(args.size() + 1);
_args.emplace_back("cryfs"); _args.emplace_back("cryfs");
for (const std::string& arg : args) { for (const std::string& arg : args) {
_args.emplace_back(arg.c_str()); _args.emplace_back(arg.c_str());
} }
auto &keyGenerator = cpputils::Random::PseudoRandom(); auto &keyGenerator = cpputils::Random::PseudoRandom();
ON_CALL(*console, askPassword(testing::StrEq("Password: "))).WillByDefault(testing::Return("pass")); ON_CALL(*console, askPassword(testing::StrEq("Password: "))).WillByDefault(testing::Return("pass"));
ON_CALL(*console, askPassword(testing::StrEq("Confirm Password: "))).WillByDefault(testing::Return("pass")); ON_CALL(*console, askPassword(testing::StrEq("Confirm Password: "))).WillByDefault(testing::Return("pass"));
// Run Cryfs // Run Cryfs
return cryfs::Cli(keyGenerator, cpputils::SCrypt::TestSettings, console).main(_args.size(), _args.data(), _httpClient()); return cryfs::Cli(keyGenerator, cpputils::SCrypt::TestSettings, console).main(_args.size(), _args.data(), _httpClient(), std::move(onMounted));
} }
void EXPECT_EXIT_WITH_HELP_MESSAGE(const std::vector<std::string>& args, const std::string &message, cryfs::ErrorCode errorCode) { void EXPECT_EXIT_WITH_HELP_MESSAGE(const std::vector<std::string>& args, const std::string &message, cryfs::ErrorCode errorCode) {
@ -60,7 +61,7 @@ public:
} }
void EXPECT_RUN_ERROR(const std::vector<std::string>& args, const std::string& message, cryfs::ErrorCode errorCode) { void EXPECT_RUN_ERROR(const std::vector<std::string>& args, const std::string& message, cryfs::ErrorCode errorCode) {
FilesystemOutput filesystem_output = _run_filesystem(args, boost::none); FilesystemOutput filesystem_output = _run_filesystem(args, boost::none, []{});
EXPECT_EQ(exitCode(errorCode), filesystem_output.exit_code); EXPECT_EQ(exitCode(errorCode), filesystem_output.exit_code);
EXPECT_TRUE(std::regex_search(filesystem_output.stderr_, std::regex(message))); EXPECT_TRUE(std::regex_search(filesystem_output.stderr_, std::regex(message)));
@ -70,10 +71,10 @@ public:
//TODO Make this work when run in background //TODO Make this work when run in background
ASSERT(std::find(args.begin(), args.end(), string("-f")) != args.end(), "Currently only works if run in foreground"); ASSERT(std::find(args.begin(), args.end(), string("-f")) != args.end(), "Currently only works if run in foreground");
FilesystemOutput filesystem_output = _run_filesystem(args, mountDir); FilesystemOutput filesystem_output = _run_filesystem(args, mountDir, []{});
EXPECT_EQ(0, filesystem_output.exit_code); EXPECT_EQ(0, filesystem_output.exit_code);
EXPECT_TRUE(std::regex_search(filesystem_output.stdout_, std::regex("Mounting filesystem"))); EXPECT_TRUE(std::regex_search(filesystem_output.stdout_, std::regex("Mounting filesystem")));
} }
struct FilesystemOutput final { struct FilesystemOutput final {
@ -82,60 +83,80 @@ public:
std::string stderr_; std::string stderr_;
}; };
FilesystemOutput _run_filesystem(const std::vector<std::string>& args, const boost::optional<boost::filesystem::path>& mountDirForUnmounting) { static void _unmount(const boost::filesystem::path &mountDir) {
testing::internal::CaptureStdout(); int returncode = -1;
testing::internal::CaptureStderr(); #if defined(__APPLE__)
std::future<int> exit_code = std::async(std::launch::async, [this, &args] { returncode = cpputils::Subprocess::call(std::string("umount ") + mountDir.string().c_str() + " 2>/dev/null").exitcode;
return run(args); #elif defined(_MSC_VER)
std::wstring mountDir_ = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(mountDir.string());
BOOL success = DokanRemoveMountPoint(mountDir_.c_str());
returncode = success ? 0 : -1;
#else
returncode = cpputils::Subprocess::call(
std::string("fusermount -u ") + mountDir.string().c_str() + " 2>/dev/null").exitcode;
#endif
ASSERT(returncode == 0, "Unmount failed");
}
FilesystemOutput _run_filesystem(const std::vector<std::string>& args, boost::optional<boost::filesystem::path> mountDirForUnmounting, std::function<void()> onMounted) {
testing::internal::CaptureStdout();
testing::internal::CaptureStderr();
bool exited = false;
cpputils::ConditionBarrier isMountedOrFailedBarrier;
std::future<int> exit_code = std::async(std::launch::async, [&] {
int exit_code = run(args, [&] { isMountedOrFailedBarrier.release(); });
// just in case it fails, we also want to release the barrier.
// if it succeeds, this will release it a second time, which doesn't hurt.
exited = true;
isMountedOrFailedBarrier.release();
return exit_code;
}); });
if (mountDirForUnmounting.is_initialized()) { std::future<bool> on_mounted_success = std::async(std::launch::async, [&] {
boost::filesystem::path mountDir = *mountDirForUnmounting; isMountedOrFailedBarrier.wait();
std::future<bool> unmount_success = std::async(std::launch::async, [&mountDir] { if (exited) {
int returncode = -1; // file system already exited on its own, this indicates an error. It should have stayed mounted.
while (returncode != 0) { // while the exit_code from run() will signal an error in this case, we didn't encounter another
#if defined(__APPLE__) // error in the onMounted future, so return true here.
returncode = cpputils::Subprocess::call(std::string("umount ") + mountDir.string().c_str() + " 2>/dev/null").exitcode; return true;
#elif defined(_MSC_VER)
// Somehow this sleeping is needed to not deadlock. Race condition in mounting/unmounting?
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::wstring mountDir_ = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(mountDir.string());
BOOL success = DokanRemoveMountPoint(mountDir_.c_str());
returncode = success ? 0 : -1;
#else
returncode = cpputils::Subprocess::call(std::string("fusermount -u ") + mountDir.string().c_str() + " 2>/dev/null").exitcode;
#endif
}
return true;
});
if(std::future_status::ready != unmount_success.wait_for(std::chrono::seconds(10))) {
testing::internal::GetCapturedStdout(); // stop capturing stdout
testing::internal::GetCapturedStderr(); // stop capturing stderr
std::cerr << "Unmount thread didn't finish";
// The std::future destructor of a future created with std::async blocks until the future is ready.
// so, instead of causing a deadlock, rather abort
exit(EXIT_FAILURE);
} }
EXPECT_TRUE(unmount_success.get()); // this also re-throws any potential exceptions // now we know the filesystem stayed online, so we can call the onMounted callback
onMounted();
// and unmount it afterwards
if (mountDirForUnmounting.is_initialized()) {
_unmount(*mountDirForUnmounting);
}
return true;
});
if(std::future_status::ready != on_mounted_success.wait_for(std::chrono::seconds(10))) {
testing::internal::GetCapturedStdout(); // stop capturing stdout
testing::internal::GetCapturedStderr(); // stop capturing stderr
std::cerr << "onMounted thread (e.g. used for unmount) didn't finish";
// The std::future destructor of a future created with std::async blocks until the future is ready.
// so, instead of causing a deadlock, rather abort
exit(EXIT_FAILURE);
} }
EXPECT_TRUE(on_mounted_success.get()); // this also re-throws any potential exceptions
if(std::future_status::ready != exit_code.wait_for(std::chrono::seconds(10))) { if(std::future_status::ready != exit_code.wait_for(std::chrono::seconds(10))) {
testing::internal::GetCapturedStdout(); // stop capturing stdout testing::internal::GetCapturedStdout(); // stop capturing stdout
testing::internal::GetCapturedStderr(); // stop capturing stderr testing::internal::GetCapturedStderr(); // stop capturing stderr
std::cerr << "Filesystem thread didn't finish"; std::cerr << "Filesystem thread didn't finish";
// The std::future destructor of a future created with std::async blocks until the future is ready. // The std::future destructor of a future created with std::async blocks until the future is ready.
// so, instead of causing a deadlock, rather abort // so, instead of causing a deadlock, rather abort
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
return { return {
exit_code.get(), exit_code.get(),
testing::internal::GetCapturedStdout(), testing::internal::GetCapturedStdout(),
testing::internal::GetCapturedStderr() testing::internal::GetCapturedStderr()
}; };
} }
}; };

View File

@ -57,7 +57,7 @@ unique_ref<FuseTest::TempTestFS> FuseTest::TestFS() {
FuseTest::TempTestFS::TempTestFS(shared_ptr<MockFilesystem> fsimpl) FuseTest::TempTestFS::TempTestFS(shared_ptr<MockFilesystem> fsimpl)
:_mountDir(), :_mountDir(),
_fuse([fsimpl] (Fuse*) {return fsimpl;}, "fusetest", boost::none), _fuse_thread(&_fuse) { _fuse([fsimpl] (Fuse*) {return fsimpl;}, []{}, "fusetest", boost::none), _fuse_thread(&_fuse) {
_fuse_thread.start(_mountDir.path(), {"-f"}); _fuse_thread.start(_mountDir.path(), {"-f"});
} }