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:
* 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.
* 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
--------------

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 <cpp-utils/io/NoninteractiveConsole.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 Factor out class handling askPassword
@ -169,7 +170,7 @@ namespace cryfs {
//TODO Test
string password = _askPasswordFromStdin("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;
}
@ -199,18 +200,13 @@ namespace cryfs {
}
CryConfigFile Cli::_loadOrCreateConfig(const ProgramOptions &options) {
try {
auto configFile = _determineConfigFile(options);
auto config = _loadOrCreateConfigFile(configFile, options.cipher(), options.blocksizeBytes(), options.allowFilesystemUpgrade());
if (config == none) {
std::cerr << "Could not load config file. Did you enter the correct password?" << std::endl;
exit(1);
}
return std::move(*config);
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
exit(1);
auto configFile = _determineConfigFile(options);
auto config = _loadOrCreateConfigFile(configFile, options.cipher(), options.blocksizeBytes(),
options.allowFilesystemUpgrade());
if (config == none) {
throw CryfsException("Could not load config file. Did you enter the correct password?", ErrorCode::WrongPassword);
}
return std::move(*config);
}
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
auto _rootDir = device->Load("/"); // this might throw an exception if the root blob doesn't exist
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);
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
}
@ -293,39 +289,40 @@ namespace cryfs {
}
void Cli::_sanityChecks(const ProgramOptions &options) {
_checkDirAccessible(options.baseDir(), "base directory");
_checkDirAccessible(options.mountDir(), "mount directory");
_checkDirAccessible(options.baseDir(), "base directory", ErrorCode::InaccessibleBaseDir);
_checkDirAccessible(options.mountDir(), "mount directory", ErrorCode::InaccessibleMountDir);
_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)) {
bool create = _console->askYesNo("Could not find " + name + ". Do you want to create it?", false);
if (create) {
if (!bf::create_directory(dir)) {
throw std::runtime_error("Error creating "+name);
throw CryfsException("Error creating "+name, errorCode);
}
} 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)) {
throw std::runtime_error(name+" is not a directory.");
throw CryfsException(name+" is not a directory.", errorCode);
}
auto file = _checkDirWriteable(dir, name);
_checkDirReadable(dir, file, name);
auto file = _checkDirWriteable(dir, name, errorCode);
_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";
try {
return make_shared<TempFile>(path);
} 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");
try {
bool found = false;
@ -340,13 +337,13 @@ namespace cryfs {
throw std::runtime_error("Error accessing "+name+".");
}
} 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) {
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[]) {
cpputils::showBacktraceOnSigSegv();
_showVersion();
ProgramOptions options = program_options::Parser(argc, argv).parse(CryCiphers::supportedCipherNames());
try {
_showVersion();
ProgramOptions options = program_options::Parser(argc, argv).parse(CryCiphers::supportedCipherNames());
_sanityChecks(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) {
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 <cryfs/filesystem/CryDevice.h>
#include "CallAfterTimeout.h"
#include <cryfs/ErrorCodes.h>
namespace cryfs {
class Cli final {
@ -35,9 +36,9 @@ 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);
std::shared_ptr<cpputils::TempFile> _checkDirWriteable(const boost::filesystem::path &dir, const std::string &name);
void _checkDirReadable(const boost::filesystem::path &dir, std::shared_ptr<cpputils::TempFile> tempfile, const std::string &name);
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, ErrorCode errorCode);
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);
void _sanityCheckFilesystem(CryDevice *device);

View File

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

View File

@ -3,12 +3,14 @@
#include <iostream>
#include <boost/optional.hpp>
#include <cryfs/config/CryConfigConsole.h>
#include <cryfs/CryfsException.h>
#include <cryfs-cli/Environment.h>
namespace po = boost::program_options;
namespace bf = boost::filesystem;
using namespace cryfs::program_options;
using cryfs::CryConfigConsole;
using cryfs::CryfsException;
using std::pair;
using std::vector;
using std::cerr;
@ -35,12 +37,10 @@ ProgramOptions Parser::parse(const vector<string> &supportedCiphers) const {
po::variables_map vm = _parseOptionsOrShowHelp(options.first, supportedCiphers);
if (!vm.count("base-dir")) {
std::cerr << "Please specify a base directory.\n";
_showHelpAndExit();
_showHelpAndExit("Please specify a base directory.", ErrorCode::InvalidArguments);
}
if (!vm.count("mount-dir")) {
std::cerr << "Please specify a mount directory.\n";
_showHelpAndExit();
_showHelpAndExit("Please specify a mount directory.", ErrorCode::InvalidArguments);
}
bf::path baseDir = bf::absolute(vm["base-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) {
if (std::find(supportedCiphers.begin(), supportedCiphers.end(), cipher) == supportedCiphers.end()) {
std::cerr << "Invalid cipher: " << cipher << std::endl;
exit(1);
throw CryfsException("Invalid cipher: " + cipher, ErrorCode::InvalidArguments);
}
}
po::variables_map Parser::_parseOptionsOrShowHelp(const vector<string> &options, const vector<string> &supportedCiphers) {
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) {
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())
.options(desc).positional(positional_desc).run(), vm);
if (vm.count("help")) {
_showHelpAndExit();
_showHelpAndExit("", ErrorCode::Success);
}
if (vm.count("show-ciphers")) {
_showCiphersAndExit(supportedCiphers);
@ -159,28 +165,32 @@ void Parser::_addPositionalOptionForBaseDir(po::options_description *desc, po::p
for (const auto &cipher : supportedCiphers) {
std::cerr << cipher << "\n";
}
exit(0);
throw CryfsException("", ErrorCode::Success);
}
[[noreturn]] void Parser::_showHelpAndExit() {
cerr << "Usage: cryfs [options] baseDir mountPoint [-- [FUSE Mount Options]]\n";
po::options_description desc;
_addAllowedOptions(&desc);
cerr << desc << endl;
cerr << "Environment variables:\n"
<< " " << Environment::FRONTEND_KEY << "=" << Environment::FRONTEND_NONINTERACTIVE << "\n"
<< "\tWork better together with tools.\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"
<< "\task you to enter a new password a second time (password confirmation).\n"
<< " " << Environment::NOUPDATECHECK_KEY << "=true\n"
<< "\tBy default, CryFS connects to the internet to check for known\n"
<< "\tsecurity vulnerabilities and new versions. This option disables this.\n"
<< endl;
exit(1);
void Parser::_showHelp() {
cerr << "Usage: cryfs [options] baseDir mountPoint [-- [FUSE Mount Options]]\n";
po::options_description desc;
_addAllowedOptions(&desc);
cerr << desc << endl;
cerr << "Environment variables:\n"
<< " " << Environment::FRONTEND_KEY << "=" << Environment::FRONTEND_NONINTERACTIVE << "\n"
<< "\tWork better together with tools.\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"
<< "\task you to enter a new password a second time (password confirmation).\n"
<< " " << Environment::NOUPDATECHECK_KEY << "=true\n"
<< "\tBy default, CryFS connects to the internet to check for known\n"
<< "\tsecurity vulnerabilities and new versions. This option disables this.\n"
<< 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
exit(0);
throw CryfsException("", ErrorCode::Success);
}

View File

@ -4,6 +4,7 @@
#include "ProgramOptions.h"
#include <boost/program_options.hpp>
#include <cryfs/ErrorCodes.h>
namespace cryfs {
namespace program_options {
@ -19,7 +20,8 @@ namespace cryfs {
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);
[[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 _showVersionAndExit();
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
# cryfs.cpp
CryfsException.cpp
config/crypto/outer/OuterConfig.cpp
config/crypto/outer/OuterEncryptor.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 <gitversion/gitversion.h>
#include <gitversion/VersionCompare.h>
#include "../CryfsException.h"
namespace bf = boost::filesystem;
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) {
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)) {
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 (!_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 {
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 cryfs::ErrorCode;
TEST_F(CliTest_ShowingHelp, HelpLongOption) {
EXPECT_EXIT_WITH_HELP_MESSAGE({"--help"});
EXPECT_EXIT_WITH_HELP_MESSAGE({"--help"}, "", ErrorCode::Success);
}
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) {
EXPECT_EXIT_WITH_HELP_MESSAGE({"-h"});
EXPECT_EXIT_WITH_HELP_MESSAGE({"-h"}, "", ErrorCode::Success);
}
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) {
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) {
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 std::vector;
using cpputils::TempFile;
using cryfs::ErrorCode;
struct TestConfig {
bool externalConfigfile;
@ -41,10 +42,11 @@ public:
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(
args(),
expectedError
expectedError,
errorCode
);
}
@ -85,7 +87,7 @@ TEST_P(CliTest_WrongEnvironment, NoErrorCondition) {
TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir) {
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) {
@ -100,26 +102,26 @@ bf::path make_relative(const bf::path &path) {
TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir_MountDirRelative) {
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) {
mountdir = 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) {
basedir = make_relative(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) {
_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(*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) {
@ -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.
ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(true));
::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");
}
@ -143,7 +145,7 @@ TEST_P(CliTest_WrongEnvironment, BaseDir_DoesntExist_Create) {
TEST_P(CliTest_WrongEnvironment, BaseDir_IsNotDirectory) {
TempFile basedirfile;
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) {
@ -155,29 +157,29 @@ TEST_P(CliTest_WrongEnvironment, BaseDir_AllPermissions) {
TEST_P(CliTest_WrongEnvironment, BaseDir_NoReadPermission) {
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) {
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) {
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) {
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) {
_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(*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) {
@ -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.
ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(true));
::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");
}
@ -201,7 +203,7 @@ TEST_P(CliTest_WrongEnvironment, MountDir_DoesntExist_Create) {
TEST_P(CliTest_WrongEnvironment, MountDir_IsNotDirectory) {
TempFile mountdirfile;
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) {
@ -213,20 +215,20 @@ TEST_P(CliTest_WrongEnvironment, MountDir_AllPermissions) {
TEST_P(CliTest_WrongEnvironment, MountDir_NoReadPermission) {
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) {
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) {
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) {
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 <cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h>
#include <gitversion/gitversion.h>
#include <cryfs/CryfsException.h>
#include <cpp-utils/testutils/CaptureStderrRAII.h>
using namespace cryfs;
using namespace cryfs::program_options;
@ -10,6 +12,7 @@ using std::vector;
using std::string;
using boost::none;
namespace bf = boost::filesystem;
using cpputils::CaptureStderrRAII;
class ProgramOptionsParserTest: public ProgramOptionsTestBase {
public:
@ -20,39 +23,58 @@ public:
};
TEST_F(ProgramOptionsParserTest, MissingAllOptions) {
EXPECT_DEATH(
parse({"./myExecutable"}),
"Usage:"
);
CaptureStderrRAII captureStderr;
try {
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) {
EXPECT_DEATH(
parse({"./myExecutable", "/home/user/baseDir"}),
"Usage:"
);
CaptureStderrRAII captureStderr;
try {
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) {
EXPECT_DEATH(
parse({"./myExecutable", "--help"}),
"Usage:"
);
CaptureStderrRAII captureStderr;
try {
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) {
EXPECT_DEATH(
parse({"./myExecutable", "-h"}),
"Usage:"
);
CaptureStderrRAII captureStderr;
try {
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) {
EXPECT_EXIT(
parse({"./myExecutable", "--show-ciphers"}),
::testing::ExitedWithCode(0),
"aes-256-gcm"
);
CaptureStderrRAII captureStderr;
try {
parse({"./myExecutable", "--show-ciphers"});
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) {
@ -136,10 +158,13 @@ TEST_F(ProgramOptionsParserTest, CipherNotGiven) {
}
TEST_F(ProgramOptionsParserTest, InvalidCipher) {
EXPECT_DEATH(
parse({"./myExecutable", "/home/user/baseDir", "--cipher", "invalid-cipher", "/home/user/mountDir"}),
"Invalid cipher: invalid-cipher"
);
try {
parse({"./myExecutable", "/home/user/baseDir", "--cipher", "invalid-cipher", "/home/user/mountDir"});
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) {

View File

@ -12,6 +12,8 @@
#include <cpp-utils/process/subprocess.h>
#include <cpp-utils/network/FakeHttpClient.h>
#include "../../cryfs/testutils/MockConsole.h"
#include <cryfs/ErrorCodes.h>
#include <cpp-utils/testutils/CaptureStderrRAII.h>
class CliTest : public ::testing::Test {
public:
@ -31,7 +33,7 @@ public:
return httpClient;
}
void run(std::vector<const char*> args) {
int run(std::vector<const char*> args) {
std::vector<const char*> _args;
_args.reserve(args.size()+1);
_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');
// 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 = "") {
EXPECT_RUN_ERROR(args, (message+".*Usage").c_str());
void EXPECT_EXIT_WITH_HELP_MESSAGE(std::vector<const char*> args, const std::string &message, cryfs::ErrorCode errorCode) {
EXPECT_RUN_ERROR(args, (".*Usage:.*"+message).c_str(), errorCode);
}
void EXPECT_RUN_ERROR(std::vector<const char*> args, const char *message) {
EXPECT_EXIT(
run(args),
::testing::ExitedWithCode(1),
message
);
void EXPECT_RUN_ERROR(std::vector<const char*> args, const char* message, cryfs::ErrorCode errorCode) {
cpputils::CaptureStderrRAII capturedStderr;
int exit_code = run(args);
capturedStderr.EXPECT_MATCHES(string(".*") + message + ".*");
EXPECT_EQ(exitCode(errorCode), exit_code);
}
void EXPECT_RUN_SUCCESS(std::vector<const char*> args, const boost::filesystem::path &mountDir) {