From 58cb91102d809fae14ef2887c012b594c267028e Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Sat, 19 Jan 2019 13:02:41 -0800 Subject: [PATCH] Implement cryfs-unmount for unmounting filesystems --- .circleci/config.yml | 2 +- ChangeLog.txt | 1 + src/CMakeLists.txt | 1 + src/cpp-utils/system/path.h | 26 ++++ src/cryfs-cli/CallAfterTimeout.h | 6 +- src/cryfs-cli/Cli.cpp | 9 +- src/cryfs-cli/Cli.h | 22 +-- src/cryfs-cli/Environment.cpp | 2 +- src/cryfs-cli/Environment.h | 6 +- src/cryfs-cli/VersionChecker.cpp | 2 +- src/cryfs-cli/VersionChecker.h | 6 +- src/cryfs-cli/main.cpp | 6 +- src/cryfs-cli/program_options/Parser.cpp | 2 +- src/cryfs-cli/program_options/Parser.h | 8 +- .../program_options/ProgramOptions.cpp | 9 +- .../program_options/ProgramOptions.h | 6 +- src/cryfs-cli/program_options/utils.cpp | 2 +- src/cryfs-cli/program_options/utils.h | 6 +- src/cryfs-unmount/CMakeLists.txt | 24 ++++ src/cryfs-unmount/Cli.cpp | 36 +++++ src/cryfs-unmount/Cli.h | 14 ++ src/cryfs-unmount/main_unmount.cpp | 38 ++++++ src/cryfs-unmount/program_options/Parser.cpp | 128 ++++++++++++++++++ src/cryfs-unmount/program_options/Parser.h | 37 +++++ .../program_options/ProgramOptions.cpp | 25 ++++ .../program_options/ProgramOptions.h | 30 ++++ src/fspp/fuse/Fuse.cpp | 26 +++- src/fspp/fuse/Fuse.h | 2 + test/cpp-utils/CMakeLists.txt | 1 + test/cpp-utils/system/PathTest.cpp | 32 +++++ test/cryfs-cli/CMakeLists.txt | 4 +- test/cryfs-cli/CallAfterTimeoutTest.cpp | 2 +- test/cryfs-cli/CryfsUnmountTest.cpp | 25 ++++ test/cryfs-cli/EnvironmentTest.cpp | 2 +- test/cryfs-cli/VersionCheckerTest.cpp | 2 +- test/cryfs-cli/program_options/ParserTest.cpp | 2 +- .../program_options/ProgramOptionsTest.cpp | 2 +- test/cryfs-cli/program_options/UtilsTest.cpp | 2 +- test/cryfs-cli/testutils/CliTest.h | 18 +-- 39 files changed, 497 insertions(+), 77 deletions(-) create mode 100644 src/cpp-utils/system/path.h create mode 100644 src/cryfs-unmount/CMakeLists.txt create mode 100644 src/cryfs-unmount/Cli.cpp create mode 100644 src/cryfs-unmount/Cli.h create mode 100644 src/cryfs-unmount/main_unmount.cpp create mode 100644 src/cryfs-unmount/program_options/Parser.cpp create mode 100644 src/cryfs-unmount/program_options/Parser.h create mode 100644 src/cryfs-unmount/program_options/ProgramOptions.cpp create mode 100644 src/cryfs-unmount/program_options/ProgramOptions.h create mode 100644 test/cpp-utils/system/PathTest.cpp create mode 100644 test/cryfs-cli/CryfsUnmountTest.cpp diff --git a/.circleci/config.yml b/.circleci/config.yml index 302eaf7e..d3723416 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -479,7 +479,7 @@ jobs: OMP_NUM_THREADS: "1" CXXFLAGS: "-O2 -fsanitize=thread -fno-omit-frame-pointer" BUILD_TYPE: "Debug" - GTEST_ARGS: "--gtest_filter=-LoggingTest.LoggingAlsoWorksAfterFork:AssertTest_DebugBuild.*:CliTest_Setup.*:CliTest_IntegrityCheck.*:*/CliTest_WrongEnvironment.*" + GTEST_ARGS: "--gtest_filter=-LoggingTest.LoggingAlsoWorksAfterFork:AssertTest_DebugBuild.*:CliTest_Setup.*:CliTest_IntegrityCheck.*:*/CliTest_WrongEnvironment.*:CliTest_Unmount.*" CMAKE_FLAGS: "" RUN_TESTS: true clang_tidy: diff --git a/ChangeLog.txt b/ChangeLog.txt index 0d3bab5d..5655f461 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -12,6 +12,7 @@ New Features & Improvements: * New block size options: 4KB and 16KB * New default block size: 16KB. This should decrease the size of the ciphertext directory for most users. * Increased scrypt hardness to (N=1048576, r=4, p=8) to make it harder to crack the key while allowing cryfs to take advantage of multicore machines. +* cryfs-unmount tool to unmount filesystems Fixed bugs: * `du` shows correct file system size on Mac OS X. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 42b57a14..a803b1c8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,4 +8,5 @@ add_subdirectory(blockstore) add_subdirectory(blobstore) add_subdirectory(cryfs) add_subdirectory(cryfs-cli) +add_subdirectory(cryfs-unmount) add_subdirectory(stats) diff --git a/src/cpp-utils/system/path.h b/src/cpp-utils/system/path.h new file mode 100644 index 00000000..bac7692b --- /dev/null +++ b/src/cpp-utils/system/path.h @@ -0,0 +1,26 @@ +#pragma once +#ifndef MESSMER_CPPUTILS_SYSTEM_PATH_H +#define MESSMER_CPPUTILS_SYSTEM_PATH_H + +#include +#include + +namespace cpputils { + +#if defined(_MSC_VER) + +inline bool path_is_just_drive_letter(const boost::filesystem::path& path) { + return path.has_root_path() && !path.has_root_directory() && !path.has_parent_path(); +} + +#else + +inline constexpr bool path_is_just_drive_letter(const boost::filesystem::path& /*path*/) { + return false; +} + +#endif + +} + +#endif diff --git a/src/cryfs-cli/CallAfterTimeout.h b/src/cryfs-cli/CallAfterTimeout.h index afab392e..16cfe48c 100644 --- a/src/cryfs-cli/CallAfterTimeout.h +++ b/src/cryfs-cli/CallAfterTimeout.h @@ -1,12 +1,12 @@ #pragma once -#ifndef MESSMER_CRYFS_SRC_CLI_CALLAFTERTIMEOUT_H -#define MESSMER_CRYFS_SRC_CLI_CALLAFTERTIMEOUT_H +#ifndef MESSMER_CRYFSCLI_CALLAFTERTIMEOUT_H +#define MESSMER_CRYFSCLI_CALLAFTERTIMEOUT_H #include #include #include -namespace cryfs { +namespace cryfs_cli { class CallAfterTimeout final { public: CallAfterTimeout(boost::chrono::milliseconds timeout, std::function callback); diff --git a/src/cryfs-cli/Cli.cpp b/src/cryfs-cli/Cli.cpp index c7fe0bad..8f3dd291 100644 --- a/src/cryfs-cli/Cli.cpp +++ b/src/cryfs-cli/Cli.cpp @@ -30,6 +30,7 @@ //TODO Many functions accessing the ProgramOptions object. Factor out into class that stores it as a member. //TODO Factor out class handling askPassword +using namespace cryfs_cli; using namespace cryfs; namespace bf = boost::filesystem; using namespace cpputils::logging; @@ -66,7 +67,7 @@ using gitversion::VersionCompare; //TODO Replace ASSERTs with other error handling when it is not a programming error but an environment influence (e.g. a block is missing) //TODO Can we improve performance by setting compiler parameter -maes for scrypt? -namespace cryfs { +namespace cryfs_cli { Cli::Cli(RandomGenerator &keyGenerator, const SCryptSettings &scryptSettings, shared_ptr console): _keyGenerator(keyGenerator), _scryptSettings(scryptSettings), _console(), _noninteractive(false), _idleUnmounter(none), _device(none) { @@ -262,12 +263,8 @@ namespace cryfs { _initLogfile(options); -#ifdef __APPLE__ - std::cout << "\nMounting filesystem. To unmount, call:\n$ umount " << options.mountDir() << "\n" << std::endl; -#else - std::cout << "\nMounting filesystem. To unmount, call:\n$ fusermount -u " << options.mountDir() << "\n" + std::cout << "\nMounting filesystem. To unmount, call:\n$ cryfs-unmount " << options.mountDir() << "\n" << std::endl; -#endif fuse->run(options.mountDir(), options.fuseOptions()); if (stoppedBecauseOfIntegrityViolation) { diff --git a/src/cryfs-cli/Cli.h b/src/cryfs-cli/Cli.h index 0195a967..bdffaa54 100644 --- a/src/cryfs-cli/Cli.h +++ b/src/cryfs-cli/Cli.h @@ -1,6 +1,6 @@ #pragma once -#ifndef MESSMER_CRYFS_CLI_H -#define MESSMER_CRYFS_CLI_H +#ifndef MESSMER_CRYFSCLI_CLI_H +#define MESSMER_CRYFSCLI_CLI_H #include "program_options/ProgramOptions.h" #include @@ -14,7 +14,7 @@ #include #include -namespace cryfs { +namespace cryfs_cli { class Cli final { public: Cli(cpputils::RandomGenerator &keyGenerator, const cpputils::SCryptSettings& scryptSettings, std::shared_ptr console); @@ -23,9 +23,9 @@ namespace cryfs { private: void _checkForUpdates(cpputils::unique_ref httpClient); void _runFilesystem(const program_options::ProgramOptions &options, std::function 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 _loadOrCreateConfigFile(boost::filesystem::path configFilePath, LocalStateDir localStateDir, const boost::optional &cipher, const boost::optional &blocksizeBytes, bool allowFilesystemUpgrade, const boost::optional &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem); + cryfs::CryConfigLoader::ConfigLoadResult _loadOrCreateConfig(const program_options::ProgramOptions &options, const cryfs::LocalStateDir& localStateDir); + void _checkConfigIntegrity(const boost::filesystem::path& basedir, const cryfs::LocalStateDir& localStateDir, const cryfs::CryConfigFile& config, bool allowReplacedFilesystem); + boost::optional _loadOrCreateConfigFile(boost::filesystem::path configFilePath, cryfs::LocalStateDir localStateDir, const boost::optional &cipher, const boost::optional &blocksizeBytes, bool allowFilesystemUpgrade, const boost::optional &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem); boost::filesystem::path _determineConfigFile(const program_options::ProgramOptions &options); static std::function _askPasswordForExistingFilesystem(std::shared_ptr console); static std::function _askPasswordForNewFilesystem(std::shared_ptr console); @@ -37,11 +37,11 @@ namespace cryfs { void _sanityChecks(const program_options::ProgramOptions &options); void _checkMountdirDoesntContainBasedir(const program_options::ProgramOptions &options); bool _pathContains(const boost::filesystem::path &parent, const boost::filesystem::path &child); - void _checkDirAccessible(const boost::filesystem::path &dir, const std::string &name, ErrorCode errorCode); - std::shared_ptr _checkDirWriteable(const boost::filesystem::path &dir, const std::string &name, ErrorCode errorCode); - void _checkDirReadable(const boost::filesystem::path &dir, std::shared_ptr tempfile, const std::string &name, ErrorCode errorCode); + void _checkDirAccessible(const boost::filesystem::path &dir, const std::string &name, cryfs::ErrorCode errorCode); + std::shared_ptr _checkDirWriteable(const boost::filesystem::path &dir, const std::string &name, cryfs::ErrorCode errorCode); + void _checkDirReadable(const boost::filesystem::path &dir, std::shared_ptr tempfile, const std::string &name, cryfs::ErrorCode errorCode); boost::optional> _createIdleCallback(boost::optional minutes, std::function callback); - void _sanityCheckFilesystem(CryDevice *device); + void _sanityCheckFilesystem(cryfs::CryDevice *device); cpputils::RandomGenerator &_keyGenerator; @@ -49,7 +49,7 @@ namespace cryfs { std::shared_ptr _console; bool _noninteractive; boost::optional> _idleUnmounter; - boost::optional> _device; + boost::optional> _device; DISALLOW_COPY_AND_ASSIGN(Cli); }; diff --git a/src/cryfs-cli/Environment.cpp b/src/cryfs-cli/Environment.cpp index 7a41079b..e3d3d33a 100644 --- a/src/cryfs-cli/Environment.cpp +++ b/src/cryfs-cli/Environment.cpp @@ -6,7 +6,7 @@ using std::string; namespace bf = boost::filesystem; -namespace cryfs { +namespace cryfs_cli { const string Environment::FRONTEND_KEY = "CRYFS_FRONTEND"; const string Environment::FRONTEND_NONINTERACTIVE = "noninteractive"; const string Environment::NOUPDATECHECK_KEY = "CRYFS_NO_UPDATE_CHECK"; diff --git a/src/cryfs-cli/Environment.h b/src/cryfs-cli/Environment.h index 506d9877..47ec79e8 100644 --- a/src/cryfs-cli/Environment.h +++ b/src/cryfs-cli/Environment.h @@ -1,11 +1,11 @@ #pragma once -#ifndef MESSMER_CRYFS_CLI_ENVIRONMENT_H -#define MESSMER_CRYFS_CLI_ENVIRONMENT_H +#ifndef MESSMER_CRYFSCLI_ENVIRONMENT_H +#define MESSMER_CRYFSCLI_ENVIRONMENT_H #include #include -namespace cryfs { +namespace cryfs_cli { class Environment { public: diff --git a/src/cryfs-cli/VersionChecker.cpp b/src/cryfs-cli/VersionChecker.cpp index f51d1101..ddfbc2d8 100644 --- a/src/cryfs-cli/VersionChecker.cpp +++ b/src/cryfs-cli/VersionChecker.cpp @@ -13,7 +13,7 @@ using boost::property_tree::ptree; using boost::property_tree::json_parser_error; using namespace cpputils::logging; -namespace cryfs { +namespace cryfs_cli { VersionChecker::VersionChecker(HttpClient* httpClient) : _versionInfo(_getVersionInfo(httpClient)) {} diff --git a/src/cryfs-cli/VersionChecker.h b/src/cryfs-cli/VersionChecker.h index bc1e54a9..32950714 100644 --- a/src/cryfs-cli/VersionChecker.h +++ b/src/cryfs-cli/VersionChecker.h @@ -1,5 +1,5 @@ -#ifndef MESSMER_CRYFS_SRC_CLI_VERSIONCHECKER_H -#define MESSMER_CRYFS_SRC_CLI_VERSIONCHECKER_H +#ifndef MESSMER_CRYFSCLI_VERSIONCHECKER_H +#define MESSMER_CRYFSCLI_VERSIONCHECKER_H #include #include @@ -8,7 +8,7 @@ #include #include -namespace cryfs { +namespace cryfs_cli { class VersionChecker final { public: //TODO Write a cpputils::shared_ref and use it diff --git a/src/cryfs-cli/main.cpp b/src/cryfs-cli/main.cpp index d5831aea..6c22adfa 100644 --- a/src/cryfs-cli/main.cpp +++ b/src/cryfs-cli/main.cpp @@ -10,7 +10,7 @@ #include #endif -using namespace cryfs; +using namespace cryfs_cli; using cpputils::Random; using cpputils::SCrypt; using cpputils::IOStreamConsole; @@ -35,13 +35,13 @@ int main(int argc, const char *argv[]) { #endif return Cli(keyGenerator, SCrypt::DefaultSettings, make_shared()) .main(argc, argv, std::move(httpClient), []{}); - } catch (const CryfsException &e) { + } catch (const cryfs::CryfsException &e) { if (e.what() != string()) { std::cerr << "Error: " << e.what() << std::endl; } return exitCode(e.errorCode()); } catch (const std::exception &e) { cerr << "Error: " << e.what(); - return exitCode(ErrorCode::UnspecifiedError); + return exitCode(cryfs::ErrorCode::UnspecifiedError); } } diff --git a/src/cryfs-cli/program_options/Parser.cpp b/src/cryfs-cli/program_options/Parser.cpp index 54ebf1b4..5310e364 100644 --- a/src/cryfs-cli/program_options/Parser.cpp +++ b/src/cryfs-cli/program_options/Parser.cpp @@ -8,7 +8,7 @@ namespace po = boost::program_options; namespace bf = boost::filesystem; -using namespace cryfs::program_options; +using namespace cryfs_cli::program_options; using cryfs::CryConfigConsole; using cryfs::CryfsException; using cryfs::ErrorCode; diff --git a/src/cryfs-cli/program_options/Parser.h b/src/cryfs-cli/program_options/Parser.h index 4ecec05c..735c2b8d 100644 --- a/src/cryfs-cli/program_options/Parser.h +++ b/src/cryfs-cli/program_options/Parser.h @@ -1,12 +1,12 @@ #pragma once -#ifndef MESSMER_CRYFS_PROGRAMOPTIONS_PARSER_H -#define MESSMER_CRYFS_PROGRAMOPTIONS_PARSER_H +#ifndef MESSMER_CRYFSCLI_PROGRAMOPTIONS_PARSER_H +#define MESSMER_CRYFSCLI_PROGRAMOPTIONS_PARSER_H #include "ProgramOptions.h" #include #include -namespace cryfs { +namespace cryfs_cli { namespace program_options { class Parser final { public: @@ -21,7 +21,7 @@ namespace cryfs { static void _addPositionalOptionForBaseDir(boost::program_options::options_description *desc, boost::program_options::positional_options_description *positional); static void _showHelp(); - [[noreturn]] static void _showHelpAndExit(const std::string& message, ErrorCode errorCode); + [[noreturn]] static void _showHelpAndExit(const std::string& message, cryfs::ErrorCode errorCode); [[noreturn]] static void _showCiphersAndExit(const std::vector &supportedCiphers); [[noreturn]] static void _showVersionAndExit(); static boost::program_options::variables_map _parseOptionsOrShowHelp(const std::vector &options, const std::vector &supportedCiphers); diff --git a/src/cryfs-cli/program_options/ProgramOptions.cpp b/src/cryfs-cli/program_options/ProgramOptions.cpp index ccf25b87..fe0d3a80 100644 --- a/src/cryfs-cli/program_options/ProgramOptions.cpp +++ b/src/cryfs-cli/program_options/ProgramOptions.cpp @@ -1,8 +1,9 @@ #include "ProgramOptions.h" #include #include +#include -using namespace cryfs::program_options; +using namespace cryfs_cli::program_options; using std::string; using std::vector; using boost::optional; @@ -16,11 +17,7 @@ ProgramOptions::ProgramOptions(bf::path baseDir, bf::path mountDir, optional missingBlockIsIntegrityViolation, vector fuseOptions) : _configFile(std::move(configFile)), _baseDir(bf::absolute(std::move(baseDir))), _mountDir(std::move(mountDir)), -#if defined(_MSC_VER) - _mountDirIsDriveLetter(_mountDir.has_root_path() && !_mountDir.has_root_directory() && !_mountDir.has_parent_path()), -#else - _mountDirIsDriveLetter(false), -#endif + _mountDirIsDriveLetter(cpputils::path_is_just_drive_letter(_mountDir)), _foreground(foreground), _allowFilesystemUpgrade(allowFilesystemUpgrade), _allowReplacedFilesystem(allowReplacedFilesystem), _allowIntegrityViolations(allowIntegrityViolations), _cipher(std::move(cipher)), _blocksizeBytes(std::move(blocksizeBytes)), _unmountAfterIdleMinutes(std::move(unmountAfterIdleMinutes)), diff --git a/src/cryfs-cli/program_options/ProgramOptions.h b/src/cryfs-cli/program_options/ProgramOptions.h index 545fea0c..60ee0200 100644 --- a/src/cryfs-cli/program_options/ProgramOptions.h +++ b/src/cryfs-cli/program_options/ProgramOptions.h @@ -1,6 +1,6 @@ #pragma once -#ifndef MESSMER_CRYFS_PROGRAMOPTIONS_PROGRAMOPTIONS_H -#define MESSMER_CRYFS_PROGRAMOPTIONS_PROGRAMOPTIONS_H +#ifndef MESSMER_CRYFSCLI_PROGRAMOPTIONS_PROGRAMOPTIONS_H +#define MESSMER_CRYFSCLI_PROGRAMOPTIONS_PROGRAMOPTIONS_H #include #include @@ -8,7 +8,7 @@ #include #include -namespace cryfs { +namespace cryfs_cli { namespace program_options { class ProgramOptions final { public: diff --git a/src/cryfs-cli/program_options/utils.cpp b/src/cryfs-cli/program_options/utils.cpp index f3f65a9a..f207370c 100644 --- a/src/cryfs-cli/program_options/utils.cpp +++ b/src/cryfs-cli/program_options/utils.cpp @@ -7,7 +7,7 @@ using std::make_pair; using std::vector; using std::string; -namespace cryfs { +namespace cryfs_cli { namespace program_options { pair, vector> splitAtDoubleDash(const vector &options) { auto doubleDashIterator = std::find(options.begin(), options.end(), string("--")); diff --git a/src/cryfs-cli/program_options/utils.h b/src/cryfs-cli/program_options/utils.h index b78369b9..2b4a7955 100644 --- a/src/cryfs-cli/program_options/utils.h +++ b/src/cryfs-cli/program_options/utils.h @@ -1,12 +1,12 @@ #pragma once -#ifndef MESSMER_CRYFS_PROGRAMOPTIONS_UTILS_H -#define MESSMER_CRYFS_PROGRAMOPTIONS_UTILS_H +#ifndef MESSMER_CRYFSCLI_PROGRAMOPTIONS_UTILS_H +#define MESSMER_CRYFSCLI_PROGRAMOPTIONS_UTILS_H #include #include #include -namespace cryfs { +namespace cryfs_cli { namespace program_options { /** * Splits an array of program options into two arrays of program options, split at a double dash '--' option. diff --git a/src/cryfs-unmount/CMakeLists.txt b/src/cryfs-unmount/CMakeLists.txt new file mode 100644 index 00000000..0a246a7f --- /dev/null +++ b/src/cryfs-unmount/CMakeLists.txt @@ -0,0 +1,24 @@ +project (cryfs-unmount) +INCLUDE(GNUInstallDirs) + +set(SOURCES + program_options/ProgramOptions.cpp + program_options/Parser.cpp + Cli.cpp +) + +add_library(${PROJECT_NAME} ${SOURCES}) +target_link_libraries(${PROJECT_NAME} PUBLIC cpp-utils cryfs fspp-fuse) +target_enable_style_warnings(${PROJECT_NAME}) +target_activate_cpp14(${PROJECT_NAME}) + +add_executable(${PROJECT_NAME}_bin main_unmount.cpp) +set_target_properties(${PROJECT_NAME}_bin PROPERTIES OUTPUT_NAME cryfs-unmount) +target_link_libraries(${PROJECT_NAME}_bin PUBLIC ${PROJECT_NAME}) +target_enable_style_warnings(${PROJECT_NAME}_bin) +target_activate_cpp14(${PROJECT_NAME}_bin) + +install(TARGETS ${PROJECT_NAME}_bin + CONFIGURATIONS Debug Release RelWithDebInfo + DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/src/cryfs-unmount/Cli.cpp b/src/cryfs-unmount/Cli.cpp new file mode 100644 index 00000000..aa5c06f2 --- /dev/null +++ b/src/cryfs-unmount/Cli.cpp @@ -0,0 +1,36 @@ +#include "Cli.h" +#include +#include +#include +#include + +#include + +using fspp::fuse::Fuse; +using cryfs_unmount::program_options::Parser; +using cryfs_unmount::program_options::ProgramOptions; + +namespace cryfs_unmount { + +namespace { +void _showVersion() { + std::cout << "CryFS Version " << gitversion::VersionString() << std::endl; +} +} + +void Cli::main(int argc, const char* argv[]) { + _showVersion(); + ProgramOptions options = Parser(argc, argv).parse(); + + if (!boost::filesystem::exists(options.mountDir())) { + throw cryfs::CryfsException("Given mountdir doesn't exist", cryfs::ErrorCode::InaccessibleMountDir); + } + // TODO This doesn't seem to work with relative paths + std::cout << "Unmounting CryFS filesystem at " << options.mountDir() << "." << std::endl; + Fuse::unmount(options.mountDir()); + + // TODO Wait until it is actually unmounted and then show a better success message? + std::cout << "Filesystem is unmounting now." << std::endl; +} + +} diff --git a/src/cryfs-unmount/Cli.h b/src/cryfs-unmount/Cli.h new file mode 100644 index 00000000..d69b6f3e --- /dev/null +++ b/src/cryfs-unmount/Cli.h @@ -0,0 +1,14 @@ +#pragma once +#ifndef MESSMER_CRYFSUNMOUNT_CLI_H +#define MESSMER_CRYFSUNMOUNT_CLI_H + +namespace cryfs_unmount { + +class Cli final { +public: + void main(int argc, const char* argv[]); +}; + +} + +#endif diff --git a/src/cryfs-unmount/main_unmount.cpp b/src/cryfs-unmount/main_unmount.cpp new file mode 100644 index 00000000..b3e2637c --- /dev/null +++ b/src/cryfs-unmount/main_unmount.cpp @@ -0,0 +1,38 @@ +#if defined(_MSC_VER) +#include +#include +#endif + +#include +#include +#include +#include "Cli.h" + +using std::cerr; +using cryfs::ErrorCode; + +int main(int argc, const char *argv[]) { +#if defined(_MSC_VER) + if (!IsWindows7SP1OrGreater()) { + std::cerr << "CryFS is currently only supported on Windows 7 SP1 (or later)." << std::endl; + exit(1); + } +#endif + + cpputils::showBacktraceOnCrash(); + + try { + cryfs_unmount::Cli().main(argc, argv); + } + catch (const cryfs::CryfsException &e) { + if (e.what() != std::string()) { + std::cerr << "Error " << static_cast(e.errorCode()) << ": " << e.what() << std::endl; + } + return exitCode(e.errorCode()); + } + catch (const std::runtime_error &e) { + std::cerr << "Error: " << e.what() << std::endl; + return exitCode(ErrorCode::UnspecifiedError); + } + return exitCode(ErrorCode::Success); +} diff --git a/src/cryfs-unmount/program_options/Parser.cpp b/src/cryfs-unmount/program_options/Parser.cpp new file mode 100644 index 00000000..25e27951 --- /dev/null +++ b/src/cryfs-unmount/program_options/Parser.cpp @@ -0,0 +1,128 @@ +#include "Parser.h" +#include +#include +#include +#include +#include + +namespace po = boost::program_options; +namespace bf = boost::filesystem; +using namespace cryfs_unmount::program_options; +using cryfs::CryConfigConsole; +using cryfs::CryfsException; +using cryfs::ErrorCode; +using std::vector; +using std::cerr; +using std::endl; +using std::string; +using namespace cpputils::logging; + +Parser::Parser(int argc, const char *argv[]) + :_options(_argsToVector(argc, argv)) { +} + +vector Parser::_argsToVector(int argc, const char *argv[]) { + vector result; + for (int i = 0; i < argc; ++i) { + result.push_back(argv[i]); + } + return result; +} + +ProgramOptions Parser::parse() const { + po::variables_map vm = _parseOptionsOrShowHelp(_options); + + if (!vm.count("mount-dir")) { + _showHelpAndExit("Please specify a mount directory.", ErrorCode::InvalidArguments); + } + bf::path mountDir = vm["mount-dir"].as(); + + return ProgramOptions(std::move(mountDir)); +} + +po::variables_map Parser::_parseOptionsOrShowHelp(const vector &options) { + try { + return _parseOptions(options); + } + catch (const CryfsException& e) { + // If CryfsException is thrown, we already know what's wrong. + // Show usage information and pass through the exception, don't catch it. + if (e.errorCode() != ErrorCode::Success) { + _showHelp(); + } + throw; + } + catch (const std::exception &e) { + std::cerr << e.what() << std::endl; + _showHelpAndExit("Invalid arguments", ErrorCode::InvalidArguments); + } +} + +po::variables_map Parser::_parseOptions(const vector &options) { + po::options_description desc; + po::positional_options_description positional_desc; + _addAllowedOptions(&desc); + _addPositionalOptionForBaseDir(&desc, &positional_desc); + + po::variables_map vm; + vector _options = _to_const_char_vector(options); + po::store(po::command_line_parser(_options.size(), _options.data()) + .options(desc).positional(positional_desc).run(), vm); + if (vm.count("help")) { + _showHelpAndExit("", ErrorCode::Success); + } + if (vm.count("version")) { + _showVersionAndExit(); + } + po::notify(vm); + + return vm; +} + +vector Parser::_to_const_char_vector(const vector &options) { + vector result; + result.reserve(options.size()); + for (const string &option : options) { + result.push_back(option.c_str()); + } + return result; +} + +void Parser::_addAllowedOptions(po::options_description *desc) { + po::options_description options("Allowed options"); + string cipher_description = "Cipher to use for encryption. See possible values by calling cryfs with --show-ciphers. Default: "; + cipher_description += CryConfigConsole::DEFAULT_CIPHER; + string blocksize_description = "The block size used when storing ciphertext blocks (in bytes). Default: "; + blocksize_description += std::to_string(CryConfigConsole::DEFAULT_BLOCKSIZE_BYTES); + options.add_options() + ("help,h", "show help message") + ("version", "Show CryFS version number") + ; + desc->add(options); +} + +void Parser::_addPositionalOptionForBaseDir(po::options_description *desc, po::positional_options_description *positional) { + positional->add("mount-dir", 1); + po::options_description hidden("Hidden options"); + hidden.add_options() + ("mount-dir", po::value(), "Mount directory") + ; + desc->add(hidden); +} + +void Parser::_showHelp() { + cerr << "Usage: cryfs-unmount [mountPoint]\n"; + po::options_description desc; + _addAllowedOptions(&desc); + cerr << desc << endl; +} + +[[noreturn]] void Parser::_showHelpAndExit(const std::string& message, ErrorCode errorCode) { + _showHelp(); + throw CryfsException(message, errorCode); +} + +[[noreturn]] void Parser::_showVersionAndExit() { + // no need to show version because it was already shown in the CryFS header before parsing program options + throw CryfsException("", ErrorCode::Success); +} diff --git a/src/cryfs-unmount/program_options/Parser.h b/src/cryfs-unmount/program_options/Parser.h new file mode 100644 index 00000000..6f98eb1f --- /dev/null +++ b/src/cryfs-unmount/program_options/Parser.h @@ -0,0 +1,37 @@ +#pragma once +#ifndef MESSMER_CRYFSUNMOUNT_PROGRAMOPTIONS_PARSER_H +#define MESSMER_CRYFSUNMOUNT_PROGRAMOPTIONS_PARSER_H + +#include "ProgramOptions.h" +#include +#include + +namespace cryfs_unmount { + namespace program_options { + class Parser final { + public: + Parser(int argc, const char *argv[]); + + ProgramOptions parse() const; + + private: + static std::vector _argsToVector(int argc, const char *argv[]); + static std::vector _to_const_char_vector(const std::vector &options); + static void _addAllowedOptions(boost::program_options::options_description *desc); + static void _addPositionalOptionForBaseDir(boost::program_options::options_description *desc, + boost::program_options::positional_options_description *positional); + static void _showHelp(); + [[noreturn]] static void _showHelpAndExit(const std::string& message, cryfs::ErrorCode errorCode); + [[noreturn]] static void _showCiphersAndExit(const std::vector &supportedCiphers); + [[noreturn]] static void _showVersionAndExit(); + static boost::program_options::variables_map _parseOptionsOrShowHelp(const std::vector &options); + static boost::program_options::variables_map _parseOptions(const std::vector &options); + + std::vector _options; + + DISALLOW_COPY_AND_ASSIGN(Parser); + }; + } +} + +#endif diff --git a/src/cryfs-unmount/program_options/ProgramOptions.cpp b/src/cryfs-unmount/program_options/ProgramOptions.cpp new file mode 100644 index 00000000..4fa22740 --- /dev/null +++ b/src/cryfs-unmount/program_options/ProgramOptions.cpp @@ -0,0 +1,25 @@ +#include "ProgramOptions.h" +#include +#include +#include + +using namespace cryfs_unmount::program_options; +using std::string; +namespace bf = boost::filesystem; + +ProgramOptions::ProgramOptions(bf::path mountDir) + :_mountDir(std::move(mountDir)), + _mountDirIsDriveLetter(cpputils::path_is_just_drive_letter(_mountDir)) +{ + if (!_mountDirIsDriveLetter) { + _mountDir = bf::absolute(std::move(_mountDir)); + } +} + +const bf::path &ProgramOptions::mountDir() const { + return _mountDir; +} + +bool ProgramOptions::mountDirIsDriveLetter() const { + return _mountDirIsDriveLetter; +} diff --git a/src/cryfs-unmount/program_options/ProgramOptions.h b/src/cryfs-unmount/program_options/ProgramOptions.h new file mode 100644 index 00000000..08f83ab2 --- /dev/null +++ b/src/cryfs-unmount/program_options/ProgramOptions.h @@ -0,0 +1,30 @@ +#pragma once +#ifndef MESSMER_CRYFSUNMOUNT_PROGRAMOPTIONS_PROGRAMOPTIONS_H +#define MESSMER_CRYFSUNMOUNT_PROGRAMOPTIONS_PROGRAMOPTIONS_H + +#include +#include +#include +#include +#include + +namespace cryfs_unmount { + namespace program_options { + class ProgramOptions final { + public: + ProgramOptions(boost::filesystem::path mountDir); + ProgramOptions(ProgramOptions &&rhs) = default; + + const boost::filesystem::path &mountDir() const; + bool mountDirIsDriveLetter() const; + + private: + boost::filesystem::path _mountDir; + bool _mountDirIsDriveLetter; + + DISALLOW_COPY_AND_ASSIGN(ProgramOptions); + }; + } +} + +#endif diff --git a/src/fspp/fuse/Fuse.cpp b/src/fspp/fuse/Fuse.cpp index 9db3cb19..68489964 100644 --- a/src/fspp/fuse/Fuse.cpp +++ b/src/fspp/fuse/Fuse.cpp @@ -7,9 +7,15 @@ #include #include #include +#include #include #include "InvalidFilesystem.h" +#if defined(_MSC_VER) +#include +#include +#endif + using std::vector; using std::string; @@ -307,14 +313,24 @@ bool Fuse::running() const { } void Fuse::stop() { + unmount(_mountdir, false); +} + +void Fuse::unmount(const bf::path& mountdir, bool force) { //TODO Find better way to unmount (i.e. don't use external fusermount). Unmounting by kill(getpid(), SIGINT) worked, but left the mount directory transport endpoint as not connected. -#ifdef __APPLE__ - int ret = system(("umount " + _mountdir.string()).c_str()); +#if defined(__APPLE__) + int returncode = cpputils::Subprocess::call(std::string("umount ") + mountdir.string()).exitcode; +#elif defined(_MSC_VER) + std::wstring mountdir_ = std::wstring_convert>().from_bytes(mountdir.string()); + BOOL success = DokanRemoveMountPoint(mountdir_.c_str()); + int returncode = success ? 0 : -1; #else - int ret = system(("fusermount -z -u " + _mountdir.string()).c_str()); // "-z" takes care that if the filesystem can't be unmounted right now because something is opened, it will be unmounted as soon as it can be. + std::string command = force ? "fusermount -u" : "fusermount -z -u"; // "-z" takes care that if the filesystem can't be unmounted right now because something is opened, it will be unmounted as soon as it can be. + int returncode = cpputils::Subprocess::call( + command + " " + mountdir.string()).exitcode; #endif - if (ret != 0) { - LOG(ERR, "Could not unmount filesystem"); + if (returncode != 0) { + throw std::runtime_error("Could not unmount filesystem"); } } diff --git a/src/fspp/fuse/Fuse.h b/src/fspp/fuse/Fuse.h index 7d173654..2da56c44 100644 --- a/src/fspp/fuse/Fuse.h +++ b/src/fspp/fuse/Fuse.h @@ -28,6 +28,8 @@ public: bool running() const; void stop(); + static void unmount(const boost::filesystem::path &mountdir, bool force = false); + int getattr(const boost::filesystem::path &path, fspp::fuse::STAT *stbuf); int fgetattr(const boost::filesystem::path &path, fspp::fuse::STAT *stbuf, fuse_file_info *fileinfo); int readlink(const boost::filesystem::path &path, char *buf, size_t size); diff --git a/test/cpp-utils/CMakeLists.txt b/test/cpp-utils/CMakeLists.txt index 4afd0818..52873bc6 100644 --- a/test/cpp-utils/CMakeLists.txt +++ b/test/cpp-utils/CMakeLists.txt @@ -49,6 +49,7 @@ set(SOURCES assert/assert_debug_test.cpp system/GetTotalMemoryTest.cpp system/TimeTest.cpp + system/PathTest.cpp system/FiletimeTest.cpp system/MemoryTest.cpp system/HomedirTest.cpp diff --git a/test/cpp-utils/system/PathTest.cpp b/test/cpp-utils/system/PathTest.cpp new file mode 100644 index 00000000..75275b5e --- /dev/null +++ b/test/cpp-utils/system/PathTest.cpp @@ -0,0 +1,32 @@ +#include +#include + +using cpputils::path_is_just_drive_letter; + +#if defined(_MSC_VER) + +TEST(PathTest, pathIsJustDriveLetter) { + EXPECT_FALSE(path_is_just_drive_letter("C")); + EXPECT_TRUE(path_is_just_drive_letter("C:")); + EXPECT_FALSE(path_is_just_drive_letter("C:\\")); + EXPECT_FALSE(path_is_just_drive_letter("C:/")); + EXPECT_FALSE(path_is_just_drive_letter("C:\\test")); + EXPECT_FALSE(path_is_just_drive_letter("C:\\test\\")); + EXPECT_FALSE(path_is_just_drive_letter("/")); + EXPECT_FALSE(path_is_just_drive_letter("")); +} + +#else + +TEST(PathTest, onNonWindowsWeDontHaveDriveLetterPaths) { + EXPECT_FALSE(path_is_just_drive_letter("C")); + EXPECT_FALSE(path_is_just_drive_letter("C:")); + EXPECT_FALSE(path_is_just_drive_letter("C:\\")); + EXPECT_FALSE(path_is_just_drive_letter("C:/")); + EXPECT_FALSE(path_is_just_drive_letter("C:\\test")); + EXPECT_FALSE(path_is_just_drive_letter("C:\\test\\")); + EXPECT_FALSE(path_is_just_drive_letter("/")); + EXPECT_FALSE(path_is_just_drive_letter("")); +} + +#endif diff --git a/test/cryfs-cli/CMakeLists.txt b/test/cryfs-cli/CMakeLists.txt index a122ae97..b120419a 100644 --- a/test/cryfs-cli/CMakeLists.txt +++ b/test/cryfs-cli/CMakeLists.txt @@ -12,12 +12,12 @@ set(SOURCES EnvironmentTest.cpp VersionCheckerTest.cpp CliTest_IntegrityCheck.cpp + CryfsUnmountTest.cpp ) add_executable(${PROJECT_NAME} ${SOURCES}) -target_link_libraries(${PROJECT_NAME} googletest cryfs-cli) +target_link_libraries(${PROJECT_NAME} googletest cryfs-cli cryfs-unmount fspp-fuse) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) - diff --git a/test/cryfs-cli/CallAfterTimeoutTest.cpp b/test/cryfs-cli/CallAfterTimeoutTest.cpp index 0b65d05e..2ba7eec1 100644 --- a/test/cryfs-cli/CallAfterTimeoutTest.cpp +++ b/test/cryfs-cli/CallAfterTimeoutTest.cpp @@ -8,7 +8,7 @@ using cpputils::make_unique_ref; using boost::chrono::milliseconds; using boost::chrono::minutes; using boost::this_thread::sleep_for; -using namespace cryfs; +using namespace cryfs_cli; class CallAfterTimeoutTest : public ::testing::Test { public: diff --git a/test/cryfs-cli/CryfsUnmountTest.cpp b/test/cryfs-cli/CryfsUnmountTest.cpp new file mode 100644 index 00000000..0857ec85 --- /dev/null +++ b/test/cryfs-cli/CryfsUnmountTest.cpp @@ -0,0 +1,25 @@ +#include "testutils/CliTest.h" +#include +#include + +using CliTest_Unmount = CliTest; +namespace bf = boost::filesystem; + +namespace { +void unmount(const bf::path& mountdir) { + std::vector _args = {"cryfs-unmount", mountdir.string().c_str()}; + cryfs_unmount::Cli().main(2, _args.data()); +} + +TEST_F(CliTest_Unmount, givenMountedFilesystem_whenUnmounting_thenSucceeds) { + // we're passing in boost::none as mountdir so EXPECT_RUN_SUCCESS doesn't unmount itself. + // if the unmount we're calling here in the onMounted callback wouldn't work, EXPECT_RUN_SUCCESS + // would never return and this would be a deadlock. + EXPECT_RUN_SUCCESS({basedir.string().c_str(), mountdir.string().c_str(), "-f"}, boost::none, [this] () { + unmount(mountdir); + }); +} + +// TODO Test calling with invalid args, valid '--version' or '--help' args, with a non-mounted mountdir and a nonexisting mountdir. + +} diff --git a/test/cryfs-cli/EnvironmentTest.cpp b/test/cryfs-cli/EnvironmentTest.cpp index 0a3485c5..017ce21e 100644 --- a/test/cryfs-cli/EnvironmentTest.cpp +++ b/test/cryfs-cli/EnvironmentTest.cpp @@ -4,7 +4,7 @@ #include #include -using namespace cryfs; +using namespace cryfs_cli; using std::string; using boost::optional; using boost::none; diff --git a/test/cryfs-cli/VersionCheckerTest.cpp b/test/cryfs-cli/VersionCheckerTest.cpp index 9008510d..90bb9190 100644 --- a/test/cryfs-cli/VersionCheckerTest.cpp +++ b/test/cryfs-cli/VersionCheckerTest.cpp @@ -8,7 +8,7 @@ using cpputils::FakeHttpClient; using cpputils::unique_ref; using cpputils::make_unique_ref; using boost::none; -using namespace cryfs; +using namespace cryfs_cli; class VersionCheckerTest: public ::testing::Test { public: diff --git a/test/cryfs-cli/program_options/ParserTest.cpp b/test/cryfs-cli/program_options/ParserTest.cpp index 7e129a0e..dd03d359 100644 --- a/test/cryfs-cli/program_options/ParserTest.cpp +++ b/test/cryfs-cli/program_options/ParserTest.cpp @@ -7,7 +7,7 @@ #include using namespace cryfs; -using namespace cryfs::program_options; +using namespace cryfs_cli::program_options; using std::vector; using std::string; using boost::none; diff --git a/test/cryfs-cli/program_options/ProgramOptionsTest.cpp b/test/cryfs-cli/program_options/ProgramOptionsTest.cpp index a6eec70c..d556aa00 100644 --- a/test/cryfs-cli/program_options/ProgramOptionsTest.cpp +++ b/test/cryfs-cli/program_options/ProgramOptionsTest.cpp @@ -2,7 +2,7 @@ #include #include -using namespace cryfs::program_options; +using namespace cryfs_cli::program_options; using boost::none; using boost::optional; using std::ostream; diff --git a/test/cryfs-cli/program_options/UtilsTest.cpp b/test/cryfs-cli/program_options/UtilsTest.cpp index 38e6cbb3..b6fe53ed 100644 --- a/test/cryfs-cli/program_options/UtilsTest.cpp +++ b/test/cryfs-cli/program_options/UtilsTest.cpp @@ -1,7 +1,7 @@ #include "testutils/ProgramOptionsTestBase.h" #include -using namespace cryfs::program_options; +using namespace cryfs_cli::program_options; using std::pair; using std::vector; using std::string; diff --git a/test/cryfs-cli/testutils/CliTest.h b/test/cryfs-cli/testutils/CliTest.h index dcfb1cff..872953a8 100644 --- a/test/cryfs-cli/testutils/CliTest.h +++ b/test/cryfs-cli/testutils/CliTest.h @@ -19,6 +19,7 @@ #include #include "../../cryfs/testutils/MockConsole.h" #include "../../cryfs/testutils/TestWithFakeHomeDirectory.h" +#include #include #include #include @@ -45,7 +46,7 @@ public: int run(const std::vector& args, std::function onMounted) { std::vector _args; _args.reserve(args.size() + 1); - _args.emplace_back("cryfs"); + _args.emplace_back("cryfs"); for (const std::string& arg : args) { _args.emplace_back(arg.c_str()); } @@ -53,7 +54,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(), std::move(onMounted)); + return cryfs_cli::Cli(keyGenerator, cpputils::SCrypt::TestSettings, console).main(_args.size(), _args.data(), _httpClient(), std::move(onMounted)); } void EXPECT_EXIT_WITH_HELP_MESSAGE(const std::vector& args, const std::string &message, cryfs::ErrorCode errorCode) { @@ -90,18 +91,7 @@ public: }; static void _unmount(const boost::filesystem::path &mountDir) { - int returncode = -1; -#if defined(__APPLE__) - returncode = cpputils::Subprocess::call(std::string("umount ") + mountDir.string().c_str() + " 2>/dev/null").exitcode; -#elif defined(_MSC_VER) - std::wstring mountDir_ = std::wstring_convert>().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 - EXPECT_EQ(0, returncode); + fspp::fuse::Fuse::unmount(mountDir, true); } FilesystemOutput run_filesystem(const std::vector& args, boost::optional mountDirForUnmounting, std::function onMounted) {