When CryFS fails to load a file system, the process stops with a helpful error code, which can be used by GUI tools to show detailed messages.

This commit is contained in:
Sebastian Messmer 2018-02-02 00:08:01 +00:00
parent 5463c14872
commit 5948f63fc8
16 changed files with 299 additions and 128 deletions

View File

@ -3,6 +3,7 @@ Version 0.9.9 (unreleased)
Improvements: Improvements:
* Add --allow-filesystem-upgrade option which will upgrade old file systems without asking the user. This will be especially helpful for GUI tools. * Add --allow-filesystem-upgrade option which will upgrade old file systems without asking the user. This will be especially helpful for GUI tools.
* Add --version option that shows the CryFS version and exits. * Add --version option that shows the CryFS version and exits.
* When CryFS fails to load a file system, the process stops with a helpful error code, which can be used by GUI tools to show detailed messages.
Version 0.9.8 Version 0.9.8
-------------- --------------

View File

@ -0,0 +1,42 @@
#pragma once
#ifndef MESSMER_CPPUTILS_CAPTURESTDERRRAII_H
#define MESSMER_CPPUTILS_CAPTURESTDERRRAII_H
#include <cpp-utils/macros.h>
#include <iostream>
#include <gmock/gmock.h>
namespace cpputils {
class CaptureStderrRAII final {
public:
CaptureStderrRAII() {
_oldBuffer = std::cerr.rdbuf();
// Capture stderr to _buffer
std::cerr.rdbuf(_buffer.rdbuf());
}
~CaptureStderrRAII() {
// reset
std::cerr.rdbuf(_oldBuffer);
}
std::string stderr() const {
return _buffer.str();
}
void EXPECT_MATCHES(const std::string &regex) {
EXPECT_THAT(stderr(), testing::MatchesRegex(".*" + regex + ".*"));
}
private:
std::stringstream _buffer;
std::streambuf *_oldBuffer;
DISALLOW_COPY_AND_ASSIGN(CaptureStderrRAII);
};
}
#endif

View File

@ -24,6 +24,7 @@
#include <gitversion/VersionCompare.h> #include <gitversion/VersionCompare.h>
#include <cpp-utils/io/NoninteractiveConsole.h> #include <cpp-utils/io/NoninteractiveConsole.h>
#include "Environment.h" #include "Environment.h"
#include <cryfs/CryfsException.h>
//TODO Many functions accessing the ProgramOptions object. Factor out into class that stores it as a member. //TODO Many functions accessing the ProgramOptions object. Factor out into class that stores it as a member.
//TODO Factor out class handling askPassword //TODO Factor out class handling askPassword
@ -169,7 +170,7 @@ namespace cryfs {
//TODO Test //TODO Test
string password = _askPasswordFromStdin("Password: "); string password = _askPasswordFromStdin("Password: ");
if (!_checkPassword(password)) { if (!_checkPassword(password)) {
throw std::runtime_error("Invalid password. Password cannot be empty."); throw CryfsException("Invalid password. Password cannot be empty.", ErrorCode::EmptyPassword);
} }
return password; return password;
} }
@ -199,18 +200,13 @@ namespace cryfs {
} }
CryConfigFile Cli::_loadOrCreateConfig(const ProgramOptions &options) { CryConfigFile Cli::_loadOrCreateConfig(const ProgramOptions &options) {
try { auto configFile = _determineConfigFile(options);
auto configFile = _determineConfigFile(options); auto config = _loadOrCreateConfigFile(configFile, options.cipher(), options.blocksizeBytes(),
auto config = _loadOrCreateConfigFile(configFile, options.cipher(), options.blocksizeBytes(), options.allowFilesystemUpgrade()); options.allowFilesystemUpgrade());
if (config == none) { if (config == none) {
std::cerr << "Could not load config file. Did you enter the correct password?" << std::endl; throw CryfsException("Could not load config file. Did you enter the correct password?", ErrorCode::WrongPassword);
exit(1);
}
return std::move(*config);
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
exit(1);
} }
return std::move(*config);
} }
optional<CryConfigFile> Cli::_loadOrCreateConfigFile(const bf::path &configFilePath, const optional<string> &cipher, const optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade) { optional<CryConfigFile> Cli::_loadOrCreateConfigFile(const bf::path &configFilePath, const optional<string> &cipher, const optional<uint32_t> &blocksizeBytes, bool allowFilesystemUpgrade) {
@ -262,11 +258,11 @@ namespace cryfs {
//Try to list contents of base directory //Try to list contents of base directory
auto _rootDir = device->Load("/"); // this might throw an exception if the root blob doesn't exist auto _rootDir = device->Load("/"); // this might throw an exception if the root blob doesn't exist
if (_rootDir == none) { if (_rootDir == none) {
throw std::runtime_error("Couldn't find root blob"); throw CryfsException("Couldn't find root blob", ErrorCode::InvalidFilesystem);
} }
auto rootDir = dynamic_pointer_move<CryDir>(*_rootDir); auto rootDir = dynamic_pointer_move<CryDir>(*_rootDir);
if (rootDir == none) { if (rootDir == none) {
throw std::runtime_error("Base directory blob doesn't contain a directory"); throw CryfsException("Base directory blob doesn't contain a directory", ErrorCode::InvalidFilesystem);
} }
(*rootDir)->children(); // Load children (*rootDir)->children(); // Load children
} }
@ -293,39 +289,40 @@ namespace cryfs {
} }
void Cli::_sanityChecks(const ProgramOptions &options) { void Cli::_sanityChecks(const ProgramOptions &options) {
_checkDirAccessible(options.baseDir(), "base directory"); _checkDirAccessible(options.baseDir(), "base directory", ErrorCode::InaccessibleBaseDir);
_checkDirAccessible(options.mountDir(), "mount directory"); _checkDirAccessible(options.mountDir(), "mount directory", ErrorCode::InaccessibleMountDir);
_checkMountdirDoesntContainBasedir(options); _checkMountdirDoesntContainBasedir(options);
} }
void Cli::_checkDirAccessible(const bf::path &dir, const std::string &name) { void Cli::_checkDirAccessible(const bf::path &dir, const std::string &name, ErrorCode errorCode) {
if (!bf::exists(dir)) { if (!bf::exists(dir)) {
bool create = _console->askYesNo("Could not find " + name + ". Do you want to create it?", false); bool create = _console->askYesNo("Could not find " + name + ". Do you want to create it?", false);
if (create) { if (create) {
if (!bf::create_directory(dir)) { if (!bf::create_directory(dir)) {
throw std::runtime_error("Error creating "+name); throw CryfsException("Error creating "+name, errorCode);
} }
} else { } else {
throw std::runtime_error(name + " not found."); //std::cerr << "Exit code: " << exitCode(errorCode) << std::endl;
throw CryfsException(name + " not found.", errorCode);
} }
} }
if (!bf::is_directory(dir)) { if (!bf::is_directory(dir)) {
throw std::runtime_error(name+" is not a directory."); throw CryfsException(name+" is not a directory.", errorCode);
} }
auto file = _checkDirWriteable(dir, name); auto file = _checkDirWriteable(dir, name, errorCode);
_checkDirReadable(dir, file, name); _checkDirReadable(dir, file, name, errorCode);
} }
shared_ptr<TempFile> Cli::_checkDirWriteable(const bf::path &dir, const std::string &name) { shared_ptr<TempFile> Cli::_checkDirWriteable(const bf::path &dir, const std::string &name, ErrorCode errorCode) {
auto path = dir / "tempfile"; auto path = dir / "tempfile";
try { try {
return make_shared<TempFile>(path); return make_shared<TempFile>(path);
} catch (const std::runtime_error &e) { } catch (const std::runtime_error &e) {
throw std::runtime_error("Could not write to "+name+"."); throw CryfsException("Could not write to "+name+".", errorCode);
} }
} }
void Cli::_checkDirReadable(const bf::path &dir, shared_ptr<TempFile> tempfile, const std::string &name) { void Cli::_checkDirReadable(const bf::path &dir, shared_ptr<TempFile> tempfile, const std::string &name, ErrorCode errorCode) {
ASSERT(bf::equivalent(dir, tempfile->path().parent_path()), "This function should be called with a file inside the directory"); ASSERT(bf::equivalent(dir, tempfile->path().parent_path()), "This function should be called with a file inside the directory");
try { try {
bool found = false; bool found = false;
@ -340,13 +337,13 @@ namespace cryfs {
throw std::runtime_error("Error accessing "+name+"."); throw std::runtime_error("Error accessing "+name+".");
} }
} catch (const boost::filesystem::filesystem_error &e) { } catch (const boost::filesystem::filesystem_error &e) {
throw std::runtime_error("Could not read from "+name+"."); throw CryfsException("Could not read from "+name+".", errorCode);
} }
} }
void Cli::_checkMountdirDoesntContainBasedir(const ProgramOptions &options) { void Cli::_checkMountdirDoesntContainBasedir(const ProgramOptions &options) {
if (_pathContains(options.mountDir(), options.baseDir())) { if (_pathContains(options.mountDir(), options.baseDir())) {
throw std::runtime_error("base directory can't be inside the mount directory."); throw CryfsException("base directory can't be inside the mount directory.", ErrorCode::BaseDirInsideMountDir);
} }
} }
@ -367,17 +364,21 @@ namespace cryfs {
int Cli::main(int argc, const char *argv[]) { int Cli::main(int argc, const char *argv[]) {
cpputils::showBacktraceOnSigSegv(); cpputils::showBacktraceOnSigSegv();
_showVersion();
ProgramOptions options = program_options::Parser(argc, argv).parse(CryCiphers::supportedCipherNames());
try { try {
_showVersion();
ProgramOptions options = program_options::Parser(argc, argv).parse(CryCiphers::supportedCipherNames());
_sanityChecks(options); _sanityChecks(options);
_runFilesystem(options); _runFilesystem(options);
} catch (const CryfsException &e) {
if (e.errorCode() != ErrorCode::Success) {
std::cerr << "Error: " << e.what() << std::endl;
}
return exitCode(e.errorCode());
} catch (const std::runtime_error &e) { } catch (const std::runtime_error &e) {
std::cerr << "Error: " << e.what() << std::endl; std::cerr << "Error: " << e.what() << std::endl;
exit(1); return exitCode(ErrorCode::UnspecifiedError);
} }
return 0; return exitCode(ErrorCode::Success);
} }
} }

View File

@ -11,6 +11,7 @@
#include <cpp-utils/network/HttpClient.h> #include <cpp-utils/network/HttpClient.h>
#include <cryfs/filesystem/CryDevice.h> #include <cryfs/filesystem/CryDevice.h>
#include "CallAfterTimeout.h" #include "CallAfterTimeout.h"
#include <cryfs/ErrorCodes.h>
namespace cryfs { namespace cryfs {
class Cli final { class Cli final {
@ -35,9 +36,9 @@ namespace cryfs {
void _sanityChecks(const program_options::ProgramOptions &options); void _sanityChecks(const program_options::ProgramOptions &options);
void _checkMountdirDoesntContainBasedir(const program_options::ProgramOptions &options); void _checkMountdirDoesntContainBasedir(const program_options::ProgramOptions &options);
bool _pathContains(const boost::filesystem::path &parent, const boost::filesystem::path &child); bool _pathContains(const boost::filesystem::path &parent, const boost::filesystem::path &child);
void _checkDirAccessible(const boost::filesystem::path &dir, const std::string &name); void _checkDirAccessible(const boost::filesystem::path &dir, const std::string &name, ErrorCode errorCode);
std::shared_ptr<cpputils::TempFile> _checkDirWriteable(const boost::filesystem::path &dir, const std::string &name); std::shared_ptr<cpputils::TempFile> _checkDirWriteable(const boost::filesystem::path &dir, const std::string &name, ErrorCode errorCode);
void _checkDirReadable(const boost::filesystem::path &dir, std::shared_ptr<cpputils::TempFile> tempfile, const std::string &name); void _checkDirReadable(const boost::filesystem::path &dir, std::shared_ptr<cpputils::TempFile> tempfile, const std::string &name, ErrorCode errorCode);
boost::optional<cpputils::unique_ref<CallAfterTimeout>> _createIdleCallback(boost::optional<double> minutes, std::function<void()> callback); boost::optional<cpputils::unique_ref<CallAfterTimeout>> _createIdleCallback(boost::optional<double> minutes, std::function<void()> callback);
void _sanityCheckFilesystem(CryDevice *device); void _sanityCheckFilesystem(CryDevice *device);

View File

@ -3,6 +3,7 @@
#include <cpp-utils/crypto/kdf/Scrypt.h> #include <cpp-utils/crypto/kdf/Scrypt.h>
#include <cpp-utils/network/CurlHttpClient.h> #include <cpp-utils/network/CurlHttpClient.h>
#include <cpp-utils/io/IOStreamConsole.h> #include <cpp-utils/io/IOStreamConsole.h>
#include <cryfs/CryfsException.h>
using namespace cryfs; using namespace cryfs;
using cpputils::Random; using cpputils::Random;
@ -17,8 +18,13 @@ int main(int argc, const char *argv[]) {
auto &keyGenerator = Random::OSRandom(); auto &keyGenerator = Random::OSRandom();
return Cli(keyGenerator, SCrypt::DefaultSettings, make_shared<IOStreamConsole>(), return Cli(keyGenerator, SCrypt::DefaultSettings, make_shared<IOStreamConsole>(),
make_shared<CurlHttpClient>()).main(argc, argv); make_shared<CurlHttpClient>()).main(argc, argv);
} catch (const CryfsException &e) {
if (e.errorCode() != ErrorCode::Success) {
std::cerr << "Error: " << e.what() << std::endl;
}
return exitCode(e.errorCode());
} catch (const std::exception &e) { } catch (const std::exception &e) {
cerr << "Error: " << e.what(); cerr << "Error: " << e.what();
return EXIT_FAILURE; return exitCode(ErrorCode::UnspecifiedError);
} }
} }

View File

@ -3,12 +3,14 @@
#include <iostream> #include <iostream>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <cryfs/config/CryConfigConsole.h> #include <cryfs/config/CryConfigConsole.h>
#include <cryfs/CryfsException.h>
#include <cryfs-cli/Environment.h> #include <cryfs-cli/Environment.h>
namespace po = boost::program_options; namespace po = boost::program_options;
namespace bf = boost::filesystem; namespace bf = boost::filesystem;
using namespace cryfs::program_options; using namespace cryfs::program_options;
using cryfs::CryConfigConsole; using cryfs::CryConfigConsole;
using cryfs::CryfsException;
using std::pair; using std::pair;
using std::vector; using std::vector;
using std::cerr; using std::cerr;
@ -35,12 +37,10 @@ ProgramOptions Parser::parse(const vector<string> &supportedCiphers) const {
po::variables_map vm = _parseOptionsOrShowHelp(options.first, supportedCiphers); po::variables_map vm = _parseOptionsOrShowHelp(options.first, supportedCiphers);
if (!vm.count("base-dir")) { if (!vm.count("base-dir")) {
std::cerr << "Please specify a base directory.\n"; _showHelpAndExit("Please specify a base directory.", ErrorCode::InvalidArguments);
_showHelpAndExit();
} }
if (!vm.count("mount-dir")) { if (!vm.count("mount-dir")) {
std::cerr << "Please specify a mount directory.\n"; _showHelpAndExit("Please specify a mount directory.", ErrorCode::InvalidArguments);
_showHelpAndExit();
} }
bf::path baseDir = bf::absolute(vm["base-dir"].as<string>()); bf::path baseDir = bf::absolute(vm["base-dir"].as<string>());
bf::path mountDir = bf::absolute(vm["mount-dir"].as<string>()); bf::path mountDir = bf::absolute(vm["mount-dir"].as<string>());
@ -76,17 +76,23 @@ ProgramOptions Parser::parse(const vector<string> &supportedCiphers) const {
void Parser::_checkValidCipher(const string &cipher, const vector<string> &supportedCiphers) { void Parser::_checkValidCipher(const string &cipher, const vector<string> &supportedCiphers) {
if (std::find(supportedCiphers.begin(), supportedCiphers.end(), cipher) == supportedCiphers.end()) { if (std::find(supportedCiphers.begin(), supportedCiphers.end(), cipher) == supportedCiphers.end()) {
std::cerr << "Invalid cipher: " << cipher << std::endl; throw CryfsException("Invalid cipher: " + cipher, ErrorCode::InvalidArguments);
exit(1);
} }
} }
po::variables_map Parser::_parseOptionsOrShowHelp(const vector<string> &options, const vector<string> &supportedCiphers) { po::variables_map Parser::_parseOptionsOrShowHelp(const vector<string> &options, const vector<string> &supportedCiphers) {
try { try {
return _parseOptions(options, supportedCiphers); return _parseOptions(options, supportedCiphers);
} 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) { } catch(const std::exception &e) {
std::cerr << e.what() << std::endl; std::cerr << e.what() << std::endl;
_showHelpAndExit(); _showHelpAndExit("Invalid arguments", ErrorCode::InvalidArguments);
} }
} }
@ -101,7 +107,7 @@ po::variables_map Parser::_parseOptions(const vector<string> &options, const vec
po::store(po::command_line_parser(_options.size(), _options.data()) po::store(po::command_line_parser(_options.size(), _options.data())
.options(desc).positional(positional_desc).run(), vm); .options(desc).positional(positional_desc).run(), vm);
if (vm.count("help")) { if (vm.count("help")) {
_showHelpAndExit(); _showHelpAndExit("", ErrorCode::Success);
} }
if (vm.count("show-ciphers")) { if (vm.count("show-ciphers")) {
_showCiphersAndExit(supportedCiphers); _showCiphersAndExit(supportedCiphers);
@ -159,28 +165,32 @@ void Parser::_addPositionalOptionForBaseDir(po::options_description *desc, po::p
for (const auto &cipher : supportedCiphers) { for (const auto &cipher : supportedCiphers) {
std::cerr << cipher << "\n"; std::cerr << cipher << "\n";
} }
exit(0); throw CryfsException("", ErrorCode::Success);
} }
[[noreturn]] void Parser::_showHelpAndExit() { void Parser::_showHelp() {
cerr << "Usage: cryfs [options] baseDir mountPoint [-- [FUSE Mount Options]]\n"; cerr << "Usage: cryfs [options] baseDir mountPoint [-- [FUSE Mount Options]]\n";
po::options_description desc; po::options_description desc;
_addAllowedOptions(&desc); _addAllowedOptions(&desc);
cerr << desc << endl; cerr << desc << endl;
cerr << "Environment variables:\n" cerr << "Environment variables:\n"
<< " " << Environment::FRONTEND_KEY << "=" << Environment::FRONTEND_NONINTERACTIVE << "\n" << " " << Environment::FRONTEND_KEY << "=" << Environment::FRONTEND_NONINTERACTIVE << "\n"
<< "\tWork better together with tools.\n" << "\tWork better together with tools.\n"
<< "\tWith this option set, CryFS won't ask anything, but use default values\n" << "\tWith this option set, CryFS won't ask anything, but use default values\n"
<< "\tfor options you didn't specify on command line. Furthermore, it won't\n" << "\tfor options you didn't specify on command line. Furthermore, it won't\n"
<< "\task you to enter a new password a second time (password confirmation).\n" << "\task you to enter a new password a second time (password confirmation).\n"
<< " " << Environment::NOUPDATECHECK_KEY << "=true\n" << " " << Environment::NOUPDATECHECK_KEY << "=true\n"
<< "\tBy default, CryFS connects to the internet to check for known\n" << "\tBy default, CryFS connects to the internet to check for known\n"
<< "\tsecurity vulnerabilities and new versions. This option disables this.\n" << "\tsecurity vulnerabilities and new versions. This option disables this.\n"
<< endl; << endl;
exit(1); }
[[noreturn]] void Parser::_showHelpAndExit(const std::string& message, ErrorCode errorCode) {
_showHelp();
throw CryfsException(message, errorCode);
} }
[[noreturn]] void Parser::_showVersionAndExit() { [[noreturn]] void Parser::_showVersionAndExit() {
// no need to show version because it was already shown in the CryFS header before parsing program options // no need to show version because it was already shown in the CryFS header before parsing program options
exit(0); throw CryfsException("", ErrorCode::Success);
} }

View File

@ -4,6 +4,7 @@
#include "ProgramOptions.h" #include "ProgramOptions.h"
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <cryfs/ErrorCodes.h>
namespace cryfs { namespace cryfs {
namespace program_options { namespace program_options {
@ -19,7 +20,8 @@ namespace cryfs {
static void _addAllowedOptions(boost::program_options::options_description *desc); static void _addAllowedOptions(boost::program_options::options_description *desc);
static void _addPositionalOptionForBaseDir(boost::program_options::options_description *desc, static void _addPositionalOptionForBaseDir(boost::program_options::options_description *desc,
boost::program_options::positional_options_description *positional); boost::program_options::positional_options_description *positional);
[[noreturn]] static void _showHelpAndExit(); static void _showHelp();
[[noreturn]] static void _showHelpAndExit(const std::string& message, ErrorCode errorCode);
[[noreturn]] static void _showCiphersAndExit(const std::vector<std::string> &supportedCiphers); [[noreturn]] static void _showCiphersAndExit(const std::vector<std::string> &supportedCiphers);
[[noreturn]] static void _showVersionAndExit(); [[noreturn]] static void _showVersionAndExit();
static boost::program_options::variables_map _parseOptionsOrShowHelp(const std::vector<std::string> &options, const std::vector<std::string> &supportedCiphers); static boost::program_options::variables_map _parseOptionsOrShowHelp(const std::vector<std::string> &options, const std::vector<std::string> &supportedCiphers);

View File

@ -2,6 +2,7 @@ project (cryfs)
set(LIB_SOURCES set(LIB_SOURCES
# cryfs.cpp # cryfs.cpp
CryfsException.cpp
config/crypto/outer/OuterConfig.cpp config/crypto/outer/OuterConfig.cpp
config/crypto/outer/OuterEncryptor.cpp config/crypto/outer/OuterEncryptor.cpp
config/crypto/CryConfigEncryptorFactory.cpp config/crypto/CryConfigEncryptorFactory.cpp

View File

@ -0,0 +1 @@
#include "CryfsException.h"

View File

@ -0,0 +1,25 @@
#pragma once
#ifndef MESSMER_CRYFS_CRYFSEXCEPTION_H
#define MESSMER_CRYFS_CRYFSEXCEPTION_H
#include "ErrorCodes.h"
#include <stdexcept>
namespace cryfs {
class CryfsException final : public std::runtime_error {
public:
CryfsException(std::string message, ErrorCode errorCode)
: std::runtime_error(std::move(message)), _errorCode(errorCode) {}
ErrorCode errorCode() const {
return _errorCode;
}
private:
ErrorCode _errorCode;
};
}
#endif

50
src/cryfs/ErrorCodes.h Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#ifndef MESSMER_CRYFSCLI_EXITCODES_H
#define MESSMER_CRYFSCLI_EXITCODES_H
namespace cryfs {
enum class ErrorCode : int {
Success = 0,
// An error happened that doesn't have an error code associated with it
UnspecifiedError = 1,
// The command line arguments are invalid.
InvalidArguments = 10,
// Couldn't load config file. Probably the password is wrong
WrongPassword = 11,
// Password cannot be empty
EmptyPassword = 12,
// The file system format is too new for this CryFS version. Please update your CryFS version.
TooNewFilesystemFormat = 13,
// The file system format is too old for this CryFS version. Run with --allow-filesystem-upgrade to upgrade it.
TooOldFilesystemFormat = 14,
// The file system uses a different cipher than the one specified on the command line using the --cipher argument.
WrongCipher = 15,
// Base directory doesn't exist or is inaccessible (i.e. not read or writable or not a directory)
InaccessibleBaseDir = 16,
// Mount directory doesn't exist or is inaccessible (i.e. not read or writable or not a directory)
InaccessibleMountDir = 17,
// Base directory can't be a subdirectory of the mount directory
BaseDirInsideMountDir = 18,
// Something's wrong with the file system.
InvalidFilesystem = 19,
};
inline int exitCode(ErrorCode code) {
return static_cast<int>(code);
}
}
#endif

View File

@ -6,6 +6,7 @@
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <gitversion/gitversion.h> #include <gitversion/gitversion.h>
#include <gitversion/VersionCompare.h> #include <gitversion/VersionCompare.h>
#include "../CryfsException.h"
namespace bf = boost::filesystem; namespace bf = boost::filesystem;
using cpputils::unique_ref; using cpputils::unique_ref;
@ -58,19 +59,19 @@ optional<CryConfigFile> CryConfigLoader::_loadConfig(const bf::path &filename, b
void CryConfigLoader::_checkVersion(const CryConfig &config, bool allowFilesystemUpgrade) { void CryConfigLoader::_checkVersion(const CryConfig &config, bool allowFilesystemUpgrade) {
if (gitversion::VersionCompare::isOlderThan(gitversion::VersionString(), config.Version())) { if (gitversion::VersionCompare::isOlderThan(gitversion::VersionString(), config.Version())) {
if (!_console->askYesNo("This filesystem is for CryFS " + config.Version() + " and should not be opened with older versions. It is strongly recommended to update your CryFS version. However, if you have backed up your base directory and know what you're doing, you can continue trying to load it. Do you want to continue?", false)) { if (!_console->askYesNo("This filesystem is for CryFS " + config.Version() + " and should not be opened with older versions. It is strongly recommended to update your CryFS version. However, if you have backed up your base directory and know what you're doing, you can continue trying to load it. Do you want to continue?", false)) {
throw std::runtime_error("This filesystem is for CryFS " + config.Version() + ". Please update your CryFS version."); throw CryfsException("This filesystem is for CryFS " + config.Version() + ". Please update your CryFS version.", ErrorCode::TooNewFilesystemFormat);
} }
} }
if (!allowFilesystemUpgrade && gitversion::VersionCompare::isOlderThan(config.Version(), gitversion::VersionString())) { if (!allowFilesystemUpgrade && gitversion::VersionCompare::isOlderThan(config.Version(), gitversion::VersionString())) {
if (!_console->askYesNo("This filesystem is for CryFS " + config.Version() + ". It can be migrated to CryFS " + gitversion::VersionString() + ", but afterwards couldn't be opened anymore with older versions. Do you want to migrate it?", false)) { if (!_console->askYesNo("This filesystem is for CryFS " + config.Version() + ". It can be migrated to CryFS " + gitversion::VersionString() + ", but afterwards couldn't be opened anymore with older versions. Do you want to migrate it?", false)) {
throw std::runtime_error("This filesystem is for CryFS " + config.Version() + ". It has to be migrated."); throw CryfsException("This filesystem is for CryFS " + config.Version() + ". It has to be migrated.", ErrorCode::TooOldFilesystemFormat);
} }
} }
} }
void CryConfigLoader::_checkCipher(const CryConfig &config) const { void CryConfigLoader::_checkCipher(const CryConfig &config) const {
if (_cipherFromCommandLine != none && config.Cipher() != *_cipherFromCommandLine) { if (_cipherFromCommandLine != none && config.Cipher() != *_cipherFromCommandLine) {
throw std::runtime_error(string() + "Filesystem uses " + config.Cipher() + " cipher and not " + *_cipherFromCommandLine + " as specified."); throw CryfsException(string() + "Filesystem uses " + config.Cipher() + " cipher and not " + *_cipherFromCommandLine + " as specified.", ErrorCode::WrongCipher);
} }
} }

View File

@ -2,26 +2,28 @@
using CliTest_ShowingHelp = CliTest; using CliTest_ShowingHelp = CliTest;
using cryfs::ErrorCode;
TEST_F(CliTest_ShowingHelp, HelpLongOption) { TEST_F(CliTest_ShowingHelp, HelpLongOption) {
EXPECT_EXIT_WITH_HELP_MESSAGE({"--help"}); EXPECT_EXIT_WITH_HELP_MESSAGE({"--help"}, "", ErrorCode::Success);
} }
TEST_F(CliTest_ShowingHelp, HelpLongOptionTogetherWithOtherOptions) { TEST_F(CliTest_ShowingHelp, HelpLongOptionTogetherWithOtherOptions) {
EXPECT_EXIT_WITH_HELP_MESSAGE({basedir.c_str(), mountdir.c_str(), "--help"}); EXPECT_EXIT_WITH_HELP_MESSAGE({basedir.c_str(), mountdir.c_str(), "--help"}, "", ErrorCode::Success);
} }
TEST_F(CliTest_ShowingHelp, HelpShortOption) { TEST_F(CliTest_ShowingHelp, HelpShortOption) {
EXPECT_EXIT_WITH_HELP_MESSAGE({"-h"}); EXPECT_EXIT_WITH_HELP_MESSAGE({"-h"}, "", ErrorCode::Success);
} }
TEST_F(CliTest_ShowingHelp, HelpShortOptionTogetherWithOtherOptions) { TEST_F(CliTest_ShowingHelp, HelpShortOptionTogetherWithOtherOptions) {
EXPECT_EXIT_WITH_HELP_MESSAGE({basedir.c_str(), mountdir.c_str(), "-h"}); EXPECT_EXIT_WITH_HELP_MESSAGE({basedir.c_str(), mountdir.c_str(), "-h"}, "", ErrorCode::Success);
} }
TEST_F(CliTest_ShowingHelp, MissingAllOptions) { TEST_F(CliTest_ShowingHelp, MissingAllOptions) {
EXPECT_EXIT_WITH_HELP_MESSAGE({}, "Please specify a base directory"); EXPECT_EXIT_WITH_HELP_MESSAGE({}, "Please specify a base directory", ErrorCode::InvalidArguments);
} }
TEST_F(CliTest_ShowingHelp, MissingDir) { TEST_F(CliTest_ShowingHelp, MissingDir) {
EXPECT_EXIT_WITH_HELP_MESSAGE({basedir.c_str()}, "Please specify a mount directory"); EXPECT_EXIT_WITH_HELP_MESSAGE({basedir.c_str()}, "Please specify a mount directory", ErrorCode::InvalidArguments);
} }

View File

@ -7,6 +7,7 @@ using ::testing::Return;
using ::testing::_; using ::testing::_;
using std::vector; using std::vector;
using cpputils::TempFile; using cpputils::TempFile;
using cryfs::ErrorCode;
struct TestConfig { struct TestConfig {
bool externalConfigfile; bool externalConfigfile;
@ -41,10 +42,11 @@ public:
EXPECT_RUN_SUCCESS(args(), mountdir); EXPECT_RUN_SUCCESS(args(), mountdir);
} }
void Test_Run_Error(const char *expectedError) { void Test_Run_Error(const char *expectedError, cryfs::ErrorCode errorCode) {
EXPECT_RUN_ERROR( EXPECT_RUN_ERROR(
args(), args(),
expectedError expectedError,
errorCode
); );
} }
@ -85,7 +87,7 @@ TEST_P(CliTest_WrongEnvironment, NoErrorCondition) {
TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir) { TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir) {
mountdir = basedir; mountdir = basedir;
Test_Run_Error("Error: base directory can't be inside the mount directory"); Test_Run_Error("Error: base directory can't be inside the mount directory", ErrorCode::BaseDirInsideMountDir);
} }
bf::path make_relative(const bf::path &path) { bf::path make_relative(const bf::path &path) {
@ -100,26 +102,26 @@ bf::path make_relative(const bf::path &path) {
TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir_MountDirRelative) { TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir_MountDirRelative) {
mountdir = make_relative(basedir); mountdir = make_relative(basedir);
Test_Run_Error("Error: base directory can't be inside the mount directory"); Test_Run_Error("Error: base directory can't be inside the mount directory", ErrorCode::BaseDirInsideMountDir);
} }
TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir_BaseDirRelative) { TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir_BaseDirRelative) {
mountdir = basedir; mountdir = basedir;
basedir = make_relative(basedir); basedir = make_relative(basedir);
Test_Run_Error("Error: base directory can't be inside the mount directory"); Test_Run_Error("Error: base directory can't be inside the mount directory", ErrorCode::BaseDirInsideMountDir);
} }
TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir_BothRelative) { TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir_BothRelative) {
basedir = make_relative(basedir); basedir = make_relative(basedir);
mountdir = basedir; mountdir = basedir;
Test_Run_Error("Error: base directory can't be inside the mount directory"); Test_Run_Error("Error: base directory can't be inside the mount directory", ErrorCode::BaseDirInsideMountDir);
} }
TEST_P(CliTest_WrongEnvironment, BaseDir_DoesntExist) { TEST_P(CliTest_WrongEnvironment, BaseDir_DoesntExist) {
_basedir.remove(); _basedir.remove();
// ON_CALL and not EXPECT_CALL, because this is a death test (i.e. it is forked) and gmock EXPECT_CALL in fork children don't report to parents. // ON_CALL and not EXPECT_CALL, because this is a death test (i.e. it is forked) and gmock EXPECT_CALL in fork children don't report to parents.
ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(false)); ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(false));
Test_Run_Error("Error: base directory not found"); Test_Run_Error("Error: base directory not found", ErrorCode::InaccessibleBaseDir);
} }
TEST_P(CliTest_WrongEnvironment, BaseDir_DoesntExist_Noninteractive) { TEST_P(CliTest_WrongEnvironment, BaseDir_DoesntExist_Noninteractive) {
@ -128,7 +130,7 @@ TEST_P(CliTest_WrongEnvironment, BaseDir_DoesntExist_Noninteractive) {
// So we set a default answer that shouldn't crash and check it's not called by checking that it crashes. // So we set a default answer that shouldn't crash and check it's not called by checking that it crashes.
ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(true)); ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(true));
::setenv("CRYFS_FRONTEND", "noninteractive", 1); ::setenv("CRYFS_FRONTEND", "noninteractive", 1);
Test_Run_Error("Error: base directory not found"); Test_Run_Error("Error: base directory not found", ErrorCode::InaccessibleBaseDir);
::unsetenv("CRYFS_FRONTEND"); ::unsetenv("CRYFS_FRONTEND");
} }
@ -143,7 +145,7 @@ TEST_P(CliTest_WrongEnvironment, BaseDir_DoesntExist_Create) {
TEST_P(CliTest_WrongEnvironment, BaseDir_IsNotDirectory) { TEST_P(CliTest_WrongEnvironment, BaseDir_IsNotDirectory) {
TempFile basedirfile; TempFile basedirfile;
basedir = basedirfile.path(); basedir = basedirfile.path();
Test_Run_Error("Error: base directory is not a directory"); Test_Run_Error("Error: base directory is not a directory", ErrorCode::InaccessibleBaseDir);
} }
TEST_P(CliTest_WrongEnvironment, BaseDir_AllPermissions) { TEST_P(CliTest_WrongEnvironment, BaseDir_AllPermissions) {
@ -155,29 +157,29 @@ TEST_P(CliTest_WrongEnvironment, BaseDir_AllPermissions) {
TEST_P(CliTest_WrongEnvironment, BaseDir_NoReadPermission) { TEST_P(CliTest_WrongEnvironment, BaseDir_NoReadPermission) {
SetNoReadPermission(basedir); SetNoReadPermission(basedir);
Test_Run_Error("Error: Could not read from base directory"); Test_Run_Error("Error: Could not read from base directory", ErrorCode::InaccessibleBaseDir);
} }
TEST_P(CliTest_WrongEnvironment, BaseDir_NoWritePermission) { TEST_P(CliTest_WrongEnvironment, BaseDir_NoWritePermission) {
SetNoWritePermission(basedir); SetNoWritePermission(basedir);
Test_Run_Error("Error: Could not write to base directory"); Test_Run_Error("Error: Could not write to base directory", ErrorCode::InaccessibleBaseDir);
} }
TEST_P(CliTest_WrongEnvironment, BaseDir_NoExePermission) { TEST_P(CliTest_WrongEnvironment, BaseDir_NoExePermission) {
SetNoExePermission(basedir); SetNoExePermission(basedir);
Test_Run_Error("Error: Could not write to base directory"); Test_Run_Error("Error: Could not write to base directory", ErrorCode::InaccessibleBaseDir);
} }
TEST_P(CliTest_WrongEnvironment, BaseDir_NoPermission) { TEST_P(CliTest_WrongEnvironment, BaseDir_NoPermission) {
SetNoPermission(basedir); SetNoPermission(basedir);
Test_Run_Error("Error: Could not write to base directory"); Test_Run_Error("Error: Could not write to base directory", ErrorCode::InaccessibleBaseDir);
} }
TEST_P(CliTest_WrongEnvironment, MountDir_DoesntExist) { TEST_P(CliTest_WrongEnvironment, MountDir_DoesntExist) {
_mountdir.remove(); _mountdir.remove();
// ON_CALL and not EXPECT_CALL, because this is a death test (i.e. it is forked) and gmock EXPECT_CALL in fork children don't report to parents. // ON_CALL and not EXPECT_CALL, because this is a death test (i.e. it is forked) and gmock EXPECT_CALL in fork children don't report to parents.
ON_CALL(*console, askYesNo("Could not find mount directory. Do you want to create it?", _)).WillByDefault(Return(false)); ON_CALL(*console, askYesNo("Could not find mount directory. Do you want to create it?", _)).WillByDefault(Return(false));
Test_Run_Error("Error: mount directory not found"); Test_Run_Error("mount directory not found", ErrorCode::InaccessibleMountDir);
} }
TEST_P(CliTest_WrongEnvironment, MountDir_DoesntExist_Noninteractive) { TEST_P(CliTest_WrongEnvironment, MountDir_DoesntExist_Noninteractive) {
@ -186,7 +188,7 @@ TEST_P(CliTest_WrongEnvironment, MountDir_DoesntExist_Noninteractive) {
// So we set a default answer that shouldn't crash and check it's not called by checking that it crashes. // So we set a default answer that shouldn't crash and check it's not called by checking that it crashes.
ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(true)); ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(true));
::setenv("CRYFS_FRONTEND", "noninteractive", 1); ::setenv("CRYFS_FRONTEND", "noninteractive", 1);
Test_Run_Error("Error: mount directory not found"); Test_Run_Error("mount directory not found", ErrorCode::InaccessibleMountDir);
::unsetenv("CRYFS_FRONTEND"); ::unsetenv("CRYFS_FRONTEND");
} }
@ -201,7 +203,7 @@ TEST_P(CliTest_WrongEnvironment, MountDir_DoesntExist_Create) {
TEST_P(CliTest_WrongEnvironment, MountDir_IsNotDirectory) { TEST_P(CliTest_WrongEnvironment, MountDir_IsNotDirectory) {
TempFile mountdirfile; TempFile mountdirfile;
mountdir = mountdirfile.path(); mountdir = mountdirfile.path();
Test_Run_Error("Error: mount directory is not a directory"); Test_Run_Error("Error: mount directory is not a directory", ErrorCode::InaccessibleMountDir);
} }
TEST_P(CliTest_WrongEnvironment, MountDir_AllPermissions) { TEST_P(CliTest_WrongEnvironment, MountDir_AllPermissions) {
@ -213,20 +215,20 @@ TEST_P(CliTest_WrongEnvironment, MountDir_AllPermissions) {
TEST_P(CliTest_WrongEnvironment, MountDir_NoReadPermission) { TEST_P(CliTest_WrongEnvironment, MountDir_NoReadPermission) {
SetNoReadPermission(mountdir); SetNoReadPermission(mountdir);
Test_Run_Error("Error: Could not read from mount directory"); Test_Run_Error("Error: Could not read from mount directory", ErrorCode::InaccessibleMountDir);
} }
TEST_P(CliTest_WrongEnvironment, MountDir_NoWritePermission) { TEST_P(CliTest_WrongEnvironment, MountDir_NoWritePermission) {
SetNoWritePermission(mountdir); SetNoWritePermission(mountdir);
Test_Run_Error("Error: Could not write to mount directory"); Test_Run_Error("Error: Could not write to mount directory", ErrorCode::InaccessibleMountDir);
} }
TEST_P(CliTest_WrongEnvironment, MountDir_NoExePermission) { TEST_P(CliTest_WrongEnvironment, MountDir_NoExePermission) {
SetNoExePermission(mountdir); SetNoExePermission(mountdir);
Test_Run_Error("Error: Could not write to mount directory"); Test_Run_Error("Error: Could not write to mount directory", ErrorCode::InaccessibleMountDir);
} }
TEST_P(CliTest_WrongEnvironment, MountDir_NoPermission) { TEST_P(CliTest_WrongEnvironment, MountDir_NoPermission) {
SetNoPermission(mountdir); SetNoPermission(mountdir);
Test_Run_Error("Error: Could not write to mount directory"); Test_Run_Error("Error: Could not write to mount directory", ErrorCode::InaccessibleMountDir);
} }

View File

@ -3,6 +3,8 @@
#include <cryfs/config/CryCipher.h> #include <cryfs/config/CryCipher.h>
#include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h> #include <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
#include <gitversion/gitversion.h> #include <gitversion/gitversion.h>
#include <cryfs/CryfsException.h>
#include <cpp-utils/testutils/CaptureStderrRAII.h>
using namespace cryfs; using namespace cryfs;
using namespace cryfs::program_options; using namespace cryfs::program_options;
@ -10,6 +12,7 @@ using std::vector;
using std::string; using std::string;
using boost::none; using boost::none;
namespace bf = boost::filesystem; namespace bf = boost::filesystem;
using cpputils::CaptureStderrRAII;
class ProgramOptionsParserTest: public ProgramOptionsTestBase { class ProgramOptionsParserTest: public ProgramOptionsTestBase {
public: public:
@ -20,39 +23,58 @@ public:
}; };
TEST_F(ProgramOptionsParserTest, MissingAllOptions) { TEST_F(ProgramOptionsParserTest, MissingAllOptions) {
EXPECT_DEATH( CaptureStderrRAII captureStderr;
parse({"./myExecutable"}), try {
"Usage:" parse({"./myExecutable"});
); EXPECT_TRUE(false); // expect throws
} catch (const CryfsException& e) {
EXPECT_EQ(ErrorCode::InvalidArguments, e.errorCode());
captureStderr.EXPECT_MATCHES("Usage:"); // expect show usage information
}
} }
TEST_F(ProgramOptionsParserTest, MissingDir) { TEST_F(ProgramOptionsParserTest, MissingDir) {
EXPECT_DEATH( CaptureStderrRAII captureStderr;
parse({"./myExecutable", "/home/user/baseDir"}), try {
"Usage:" parse({"./myExecutable", "/home/user/baseDir"});
); EXPECT_TRUE(false); // expect throw
} catch (const CryfsException& e) {
EXPECT_EQ(ErrorCode::InvalidArguments, e.errorCode());
captureStderr.EXPECT_MATCHES("Usage:"); // expect show usage information
}
} }
TEST_F(ProgramOptionsParserTest, HelpLongOption) { TEST_F(ProgramOptionsParserTest, HelpLongOption) {
EXPECT_DEATH( CaptureStderrRAII captureStderr;
parse({"./myExecutable", "--help"}), try {
"Usage:" parse({"./myExecutable", "--help"});
); EXPECT_TRUE(false); // expect throw
} catch (const CryfsException& e) {
EXPECT_EQ(ErrorCode::Success, e.errorCode());
captureStderr.EXPECT_MATCHES("Usage:"); // expect show usage information
}
} }
TEST_F(ProgramOptionsParserTest, HelpShortOption) { TEST_F(ProgramOptionsParserTest, HelpShortOption) {
EXPECT_DEATH( CaptureStderrRAII captureStderr;
parse({"./myExecutable", "-h"}), try {
"Usage:" parse({"./myExecutable", "-h"});
); EXPECT_TRUE(false); // expect throw
} catch (const CryfsException& e) {
EXPECT_EQ(ErrorCode::Success, e.errorCode());
captureStderr.EXPECT_MATCHES("Usage:"); // expect show usage information
}
} }
TEST_F(ProgramOptionsParserTest, ShowCiphers) { TEST_F(ProgramOptionsParserTest, ShowCiphers) {
EXPECT_EXIT( CaptureStderrRAII captureStderr;
parse({"./myExecutable", "--show-ciphers"}), try {
::testing::ExitedWithCode(0), parse({"./myExecutable", "--show-ciphers"});
"aes-256-gcm" EXPECT_TRUE(false); // expect throw
); } catch (const CryfsException& e) {
EXPECT_EQ(ErrorCode::Success, e.errorCode());
captureStderr.EXPECT_MATCHES("aes-256-gcm"); // expect show ciphers
}
} }
TEST_F(ProgramOptionsParserTest, BaseDir_Absolute) { TEST_F(ProgramOptionsParserTest, BaseDir_Absolute) {
@ -136,10 +158,13 @@ TEST_F(ProgramOptionsParserTest, CipherNotGiven) {
} }
TEST_F(ProgramOptionsParserTest, InvalidCipher) { TEST_F(ProgramOptionsParserTest, InvalidCipher) {
EXPECT_DEATH( try {
parse({"./myExecutable", "/home/user/baseDir", "--cipher", "invalid-cipher", "/home/user/mountDir"}), parse({"./myExecutable", "/home/user/baseDir", "--cipher", "invalid-cipher", "/home/user/mountDir"});
"Invalid cipher: invalid-cipher" EXPECT_TRUE(false); // expect throw
); } catch (const CryfsException& e) {
EXPECT_EQ(ErrorCode::InvalidArguments, e.errorCode());
EXPECT_THAT(e.what(), testing::MatchesRegex(".*Invalid cipher: invalid-cipher.*"));
}
} }
TEST_F(ProgramOptionsParserTest, UnmountAfterIdleMinutesGiven) { TEST_F(ProgramOptionsParserTest, UnmountAfterIdleMinutesGiven) {

View File

@ -12,6 +12,8 @@
#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 "../../cryfs/testutils/MockConsole.h" #include "../../cryfs/testutils/MockConsole.h"
#include <cryfs/ErrorCodes.h>
#include <cpp-utils/testutils/CaptureStderrRAII.h>
class CliTest : public ::testing::Test { class CliTest : public ::testing::Test {
public: public:
@ -31,7 +33,7 @@ public:
return httpClient; return httpClient;
} }
void run(std::vector<const char*> args) { int run(std::vector<const char*> args) {
std::vector<const char*> _args; std::vector<const char*> _args;
_args.reserve(args.size()+1); _args.reserve(args.size()+1);
_args.push_back("cryfs"); _args.push_back("cryfs");
@ -43,19 +45,18 @@ public:
std::cin.putback('\n'); std::cin.putback('s'); std::cin.putback('s'); std::cin.putback('a'); std::cin.putback('p'); std::cin.putback('\n'); std::cin.putback('s'); std::cin.putback('s'); std::cin.putback('a'); std::cin.putback('p');
std::cin.putback('\n'); std::cin.putback('s'); std::cin.putback('s'); std::cin.putback('a'); std::cin.putback('p'); std::cin.putback('\n'); std::cin.putback('s'); std::cin.putback('s'); std::cin.putback('a'); std::cin.putback('p');
// Run Cryfs // Run Cryfs
cryfs::Cli(keyGenerator, cpputils::SCrypt::TestSettings, console, _httpClient()).main(_args.size(), _args.data()); return cryfs::Cli(keyGenerator, cpputils::SCrypt::TestSettings, console, _httpClient()).main(_args.size(), _args.data());
} }
void EXPECT_EXIT_WITH_HELP_MESSAGE(std::vector<const char*> args, const std::string &message = "") { void EXPECT_EXIT_WITH_HELP_MESSAGE(std::vector<const char*> args, const std::string &message, cryfs::ErrorCode errorCode) {
EXPECT_RUN_ERROR(args, (message+".*Usage").c_str()); EXPECT_RUN_ERROR(args, (".*Usage:.*"+message).c_str(), errorCode);
} }
void EXPECT_RUN_ERROR(std::vector<const char*> args, const char *message) { void EXPECT_RUN_ERROR(std::vector<const char*> args, const char* message, cryfs::ErrorCode errorCode) {
EXPECT_EXIT( cpputils::CaptureStderrRAII capturedStderr;
run(args), int exit_code = run(args);
::testing::ExitedWithCode(1), capturedStderr.EXPECT_MATCHES(string(".*") + message + ".*");
message EXPECT_EQ(exitCode(errorCode), exit_code);
);
} }
void EXPECT_RUN_SUCCESS(std::vector<const char*> args, const boost::filesystem::path &mountDir) { void EXPECT_RUN_SUCCESS(std::vector<const char*> args, const boost::filesystem::path &mountDir) {