Fuse offers an onMounted callback that is called when everything is ready.
This commit is contained in:
parent
449133e3da
commit
64150f294e
@ -51,8 +51,8 @@ using std::string;
|
||||
using std::endl;
|
||||
using std::shared_ptr;
|
||||
using std::make_shared;
|
||||
using std::make_unique;
|
||||
using std::function;
|
||||
using std::make_shared;
|
||||
using boost::optional;
|
||||
using boost::none;
|
||||
using boost::chrono::minutes;
|
||||
@ -219,7 +219,7 @@ namespace cryfs {
|
||||
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 {
|
||||
LocalStateDir localStateDir(Environment::localStateDir());
|
||||
auto blockStore = make_unique_ref<OnDiskBlockStore2>(options.baseDir());
|
||||
@ -229,11 +229,11 @@ namespace cryfs {
|
||||
options.allowIntegrityViolations(), missingBlockIsIntegrityViolation));
|
||||
_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?");
|
||||
|
||||
//TODO Test auto unmounting after idle timeout
|
||||
_idleUnmounter = _createIdleCallback(options.unmountAfterIdleMinutes(), [fuse] {fuse->stop();});
|
||||
_idleUnmounter = _createIdleCallback(options.unmountAfterIdleMinutes(), [fs] {fs->stop();});
|
||||
if (_idleUnmounter != none) {
|
||||
(*_device)->onFsAction(std::bind(&CallAfterTimeout::resetTimer, _idleUnmounter->get()));
|
||||
}
|
||||
@ -241,7 +241,7 @@ namespace cryfs {
|
||||
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);
|
||||
|
||||
@ -369,14 +369,14 @@ namespace cryfs {
|
||||
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();
|
||||
|
||||
try {
|
||||
_showVersion(std::move(httpClient));
|
||||
ProgramOptions options = program_options::Parser(argc, argv).parse(CryCiphers::supportedCipherNames());
|
||||
_sanityChecks(options);
|
||||
_runFilesystem(options);
|
||||
_runFilesystem(options, std::move(onMounted));
|
||||
} catch (const CryfsException &e) {
|
||||
if (e.errorCode() != ErrorCode::Success) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
|
@ -18,11 +18,11 @@ namespace cryfs {
|
||||
class Cli final {
|
||||
public:
|
||||
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:
|
||||
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);
|
||||
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);
|
||||
|
@ -26,7 +26,7 @@ int main(int argc, const char *argv[]) {
|
||||
auto httpClient = make_unique_ref<cpputils::CurlHttpClient>();
|
||||
#endif
|
||||
return Cli(keyGenerator, SCrypt::DefaultSettings, make_shared<IOStreamConsole>())
|
||||
.main(argc, argv, std::move(httpClient));
|
||||
.main(argc, argv, std::move(httpClient), []{});
|
||||
} catch (const CryfsException &e) {
|
||||
if (e.errorCode() != ErrorCode::Success) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
|
@ -229,8 +229,10 @@ Fuse::~Fuse() {
|
||||
_argv.clear();
|
||||
}
|
||||
|
||||
Fuse::Fuse(std::function<shared_ptr<Filesystem> (Fuse *fuse)> init, 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)) {
|
||||
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)), _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) {
|
||||
@ -866,6 +868,7 @@ void Fuse::init(fuse_conn_info *conn) {
|
||||
LOG(INFO, "Filesystem started.");
|
||||
|
||||
_running = true;
|
||||
_onMounted();
|
||||
|
||||
#ifdef FSPP_LOG
|
||||
cpputils::logging::setLevel(DEBUG);
|
||||
|
@ -21,7 +21,7 @@ class Filesystem;
|
||||
|
||||
class Fuse final {
|
||||
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();
|
||||
|
||||
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);
|
||||
|
||||
std::function<std::shared_ptr<Filesystem> (Fuse *fuse)> _init;
|
||||
std::function<void()> _onMounted;
|
||||
std::shared_ptr<Filesystem> _fs;
|
||||
boost::filesystem::path _mountdir;
|
||||
std::vector<char*> _argv;
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <cpp-utils/logging/logging.h>
|
||||
#include <cpp-utils/process/subprocess.h>
|
||||
#include <cpp-utils/network/FakeHttpClient.h>
|
||||
#include <cpp-utils/lock/ConditionBarrier.h>
|
||||
#include "../../cryfs/testutils/MockConsole.h"
|
||||
#include "../../cryfs/testutils/TestWithFakeHomeDirectory.h"
|
||||
#include <cryfs/ErrorCodes.h>
|
||||
@ -41,7 +42,7 @@ public:
|
||||
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;
|
||||
_args.reserve(args.size() + 1);
|
||||
_args.emplace_back("cryfs");
|
||||
@ -52,7 +53,7 @@ public:
|
||||
ON_CALL(*console, askPassword(testing::StrEq("Password: "))).WillByDefault(testing::Return("pass"));
|
||||
ON_CALL(*console, askPassword(testing::StrEq("Confirm Password: "))).WillByDefault(testing::Return("pass"));
|
||||
// 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) {
|
||||
@ -60,7 +61,7 @@ public:
|
||||
}
|
||||
|
||||
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_TRUE(std::regex_search(filesystem_output.stderr_, std::regex(message)));
|
||||
@ -70,7 +71,7 @@ public:
|
||||
//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");
|
||||
|
||||
FilesystemOutput filesystem_output = _run_filesystem(args, mountDir);
|
||||
FilesystemOutput filesystem_output = _run_filesystem(args, mountDir, []{});
|
||||
|
||||
EXPECT_EQ(0, filesystem_output.exit_code);
|
||||
EXPECT_TRUE(std::regex_search(filesystem_output.stdout_, std::regex("Mounting filesystem")));
|
||||
@ -82,44 +83,64 @@ public:
|
||||
std::string stderr_;
|
||||
};
|
||||
|
||||
FilesystemOutput _run_filesystem(const std::vector<std::string>& args, const boost::optional<boost::filesystem::path>& mountDirForUnmounting) {
|
||||
testing::internal::CaptureStdout();
|
||||
testing::internal::CaptureStderr();
|
||||
std::future<int> exit_code = std::async(std::launch::async, [this, &args] {
|
||||
return run(args);
|
||||
});
|
||||
|
||||
if (mountDirForUnmounting.is_initialized()) {
|
||||
boost::filesystem::path mountDir = *mountDirForUnmounting;
|
||||
std::future<bool> unmount_success = std::async(std::launch::async, [&mountDir] {
|
||||
static void _unmount(const boost::filesystem::path &mountDir) {
|
||||
int returncode = -1;
|
||||
while (returncode != 0) {
|
||||
#if defined(__APPLE__)
|
||||
returncode = cpputils::Subprocess::call(std::string("umount ") + mountDir.string().c_str() + " 2>/dev/null").exitcode;
|
||||
#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;
|
||||
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;
|
||||
});
|
||||
|
||||
std::future<bool> on_mounted_success = std::async(std::launch::async, [&] {
|
||||
isMountedOrFailedBarrier.wait();
|
||||
if (exited) {
|
||||
// file system already exited on its own, this indicates an error. It should have stayed mounted.
|
||||
// while the exit_code from run() will signal an error in this case, we didn't encounter another
|
||||
// error in the onMounted future, so return true here.
|
||||
return true;
|
||||
}
|
||||
// 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 != unmount_success.wait_for(std::chrono::seconds(10))) {
|
||||
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 << "Unmount thread didn't finish";
|
||||
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(unmount_success.get()); // this also re-throws any potential exceptions
|
||||
}
|
||||
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))) {
|
||||
testing::internal::GetCapturedStdout(); // stop capturing stdout
|
||||
|
@ -57,7 +57,7 @@ unique_ref<FuseTest::TempTestFS> FuseTest::TestFS() {
|
||||
|
||||
FuseTest::TempTestFS::TempTestFS(shared_ptr<MockFilesystem> fsimpl)
|
||||
:_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"});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user