Merge from release/0.10
This commit is contained in:
commit
58fd7c3956
@ -479,7 +479,7 @@ jobs:
|
|||||||
OMP_NUM_THREADS: "1"
|
OMP_NUM_THREADS: "1"
|
||||||
CXXFLAGS: "-O2 -fsanitize=thread -fno-omit-frame-pointer"
|
CXXFLAGS: "-O2 -fsanitize=thread -fno-omit-frame-pointer"
|
||||||
BUILD_TYPE: "Debug"
|
BUILD_TYPE: "Debug"
|
||||||
GTEST_ARGS: "--gtest_filter=-LoggingTest.LoggingAlsoWorksAfterFork:AssertTest_DebugBuild.*:CliTest_Setup.*:CliTest_IntegrityCheck.*:*/CliTest_WrongEnvironment.*:CliTest_Unmount.*"
|
GTEST_ARGS: "--gtest_filter=-LoggingTest.LoggingAlsoWorksAfterFork:AssertTest_DebugBuild.*:SignalCatcherTest.*_thenDies:CliTest_Setup.*:CliTest_IntegrityCheck.*:*/CliTest_WrongEnvironment.*:CliTest_Unmount.*"
|
||||||
CMAKE_FLAGS: ""
|
CMAKE_FLAGS: ""
|
||||||
RUN_TESTS: true
|
RUN_TESTS: true
|
||||||
clang_tidy:
|
clang_tidy:
|
||||||
|
@ -52,6 +52,10 @@ void BlobOnBlocks::flush() {
|
|||||||
_datatree->flush();
|
_datatree->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t BlobOnBlocks::numNodes() const {
|
||||||
|
return _datatree->numNodes();
|
||||||
|
}
|
||||||
|
|
||||||
const BlockId &BlobOnBlocks::blockId() const {
|
const BlockId &BlobOnBlocks::blockId() const {
|
||||||
return _datatree->blockId();
|
return _datatree->blockId();
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,8 @@ public:
|
|||||||
|
|
||||||
void flush() override;
|
void flush() override;
|
||||||
|
|
||||||
|
uint32_t numNodes() const override;
|
||||||
|
|
||||||
cpputils::unique_ref<parallelaccessdatatreestore::DataTreeRef> releaseTree();
|
cpputils::unique_ref<parallelaccessdatatreestore::DataTreeRef> releaseTree();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include <cpp-utils/assert/assert.h>
|
#include <cpp-utils/assert/assert.h>
|
||||||
#include "impl/LeafTraverser.h"
|
#include "impl/LeafTraverser.h"
|
||||||
#include <boost/thread.hpp>
|
#include <boost/thread.hpp>
|
||||||
|
#include <blobstore/implementations/onblocks/utils/Math.h>
|
||||||
|
|
||||||
using blockstore::BlockId;
|
using blockstore::BlockId;
|
||||||
using blobstore::onblocks::datanodestore::DataNodeStore;
|
using blobstore::onblocks::datanodestore::DataNodeStore;
|
||||||
@ -64,7 +65,16 @@ unique_ref<DataNode> DataTree::releaseRootNode() {
|
|||||||
return std::move(_rootNode);
|
return std::move(_rootNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO Test numLeaves(), for example also two configurations with same number of bytes but different number of leaves (last leaf has 0 bytes)
|
uint32_t DataTree::numNodes() const {
|
||||||
|
uint32_t numNodesCurrentLevel = numLeaves();
|
||||||
|
uint32_t totalNumNodes = numNodesCurrentLevel;
|
||||||
|
for(size_t level = 0; level < _rootNode->depth(); ++level) {
|
||||||
|
numNodesCurrentLevel = blobstore::onblocks::utils::ceilDivision(numNodesCurrentLevel, static_cast<uint32_t>(_nodeStore->layout().maxChildrenPerInnerNode()));
|
||||||
|
totalNumNodes += numNodesCurrentLevel;
|
||||||
|
}
|
||||||
|
return totalNumNodes;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t DataTree::numLeaves() const {
|
uint32_t DataTree::numLeaves() const {
|
||||||
shared_lock<shared_mutex> lock(_treeStructureMutex);
|
shared_lock<shared_mutex> lock(_treeStructureMutex);
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ public:
|
|||||||
|
|
||||||
void resizeNumBytes(uint64_t newNumBytes);
|
void resizeNumBytes(uint64_t newNumBytes);
|
||||||
|
|
||||||
|
uint32_t numNodes() const;
|
||||||
uint32_t numLeaves() const;
|
uint32_t numLeaves() const;
|
||||||
uint64_t numBytes() const;
|
uint64_t numBytes() const;
|
||||||
|
|
||||||
|
@ -54,6 +54,10 @@ public:
|
|||||||
return _baseTree->flush();
|
return _baseTree->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t numNodes() const {
|
||||||
|
return _baseTree->numNodes();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
datatreestore::DataTree *_baseTree;
|
datatreestore::DataTree *_baseTree;
|
||||||
|
@ -26,6 +26,8 @@ public:
|
|||||||
|
|
||||||
virtual void flush() = 0;
|
virtual void flush() = 0;
|
||||||
|
|
||||||
|
virtual uint32_t numNodes() const = 0;
|
||||||
|
|
||||||
//TODO Test tryRead
|
//TODO Test tryRead
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,11 +2,14 @@
|
|||||||
#include "IntegrityBlockStore2.h"
|
#include "IntegrityBlockStore2.h"
|
||||||
#include "KnownBlockVersions.h"
|
#include "KnownBlockVersions.h"
|
||||||
#include <cpp-utils/data/SerializationHelper.h>
|
#include <cpp-utils/data/SerializationHelper.h>
|
||||||
|
#include <cpp-utils/process/SignalCatcher.h>
|
||||||
|
#include <cpp-utils/io/ProgressBar.h>
|
||||||
|
|
||||||
using cpputils::Data;
|
using cpputils::Data;
|
||||||
using cpputils::unique_ref;
|
using cpputils::unique_ref;
|
||||||
using cpputils::serialize;
|
using cpputils::serialize;
|
||||||
using cpputils::deserialize;
|
using cpputils::deserialize;
|
||||||
|
using cpputils::SignalCatcher;
|
||||||
using std::string;
|
using std::string;
|
||||||
using boost::optional;
|
using boost::optional;
|
||||||
using boost::none;
|
using boost::none;
|
||||||
@ -195,22 +198,32 @@ void IntegrityBlockStore2::forEachBlock(std::function<void (const BlockId &)> ca
|
|||||||
|
|
||||||
#ifndef CRYFS_NO_COMPATIBILITY
|
#ifndef CRYFS_NO_COMPATIBILITY
|
||||||
void IntegrityBlockStore2::migrateFromBlockstoreWithoutVersionNumbers(BlockStore2 *baseBlockStore, const boost::filesystem::path &integrityFilePath, uint32_t myClientId) {
|
void IntegrityBlockStore2::migrateFromBlockstoreWithoutVersionNumbers(BlockStore2 *baseBlockStore, const boost::filesystem::path &integrityFilePath, uint32_t myClientId) {
|
||||||
std::cout << "Migrating file system for integrity features. Please don't interrupt this process. This can take a while..." << std::flush;
|
SignalCatcher signalCatcher;
|
||||||
|
|
||||||
KnownBlockVersions knownBlockVersions(integrityFilePath, myClientId);
|
KnownBlockVersions knownBlockVersions(integrityFilePath, myClientId);
|
||||||
baseBlockStore->forEachBlock([&baseBlockStore, &knownBlockVersions] (const BlockId &blockId) {
|
uint64_t numProcessedBlocks = 0;
|
||||||
|
cpputils::ProgressBar progressbar("Migrating file system for integrity features. This can take a while...", baseBlockStore->numBlocks());
|
||||||
|
baseBlockStore->forEachBlock([&] (const BlockId &blockId) {
|
||||||
|
if (signalCatcher.signal_occurred()) {
|
||||||
|
throw std::runtime_error("Caught signal");
|
||||||
|
}
|
||||||
migrateBlockFromBlockstoreWithoutVersionNumbers(baseBlockStore, blockId, &knownBlockVersions);
|
migrateBlockFromBlockstoreWithoutVersionNumbers(baseBlockStore, blockId, &knownBlockVersions);
|
||||||
|
progressbar.update(++numProcessedBlocks);
|
||||||
});
|
});
|
||||||
std::cout << "done" << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntegrityBlockStore2::migrateBlockFromBlockstoreWithoutVersionNumbers(blockstore::BlockStore2* baseBlockStore, const blockstore::BlockId& blockId, KnownBlockVersions *knownBlockVersions) {
|
void IntegrityBlockStore2::migrateBlockFromBlockstoreWithoutVersionNumbers(blockstore::BlockStore2* baseBlockStore, const blockstore::BlockId& blockId, KnownBlockVersions *knownBlockVersions) {
|
||||||
uint64_t version = knownBlockVersions->incrementVersion(blockId);
|
|
||||||
|
|
||||||
auto data_ = baseBlockStore->load(blockId);
|
auto data_ = baseBlockStore->load(blockId);
|
||||||
if (data_ == boost::none) {
|
if (data_ == boost::none) {
|
||||||
LOG(WARN, "Block not found, but was returned from forEachBlock before");
|
LOG(WARN, "Block not found, but was returned from forEachBlock before");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (0 != _readFormatHeader(*data_)) {
|
||||||
|
// already migrated
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t version = knownBlockVersions->incrementVersion(blockId);
|
||||||
cpputils::Data data = std::move(*data_);
|
cpputils::Data data = std::move(*data_);
|
||||||
cpputils::Data dataWithHeader = _prependHeaderToData(blockId, knownBlockVersions->myClientId(), version, data);
|
cpputils::Data dataWithHeader = _prependHeaderToData(blockId, knownBlockVersions->myClientId(), version, data);
|
||||||
baseBlockStore->store(blockId, dataWithHeader);
|
baseBlockStore->store(blockId, dataWithHeader);
|
||||||
|
@ -11,6 +11,7 @@ set(SOURCES
|
|||||||
crypto/hash/Hash.cpp
|
crypto/hash/Hash.cpp
|
||||||
process/daemonize.cpp
|
process/daemonize.cpp
|
||||||
process/subprocess.cpp
|
process/subprocess.cpp
|
||||||
|
process/SignalCatcher.cpp
|
||||||
tempfile/TempFile.cpp
|
tempfile/TempFile.cpp
|
||||||
tempfile/TempDir.cpp
|
tempfile/TempDir.cpp
|
||||||
network/HttpClient.cpp
|
network/HttpClient.cpp
|
||||||
@ -22,10 +23,12 @@ set(SOURCES
|
|||||||
io/IOStreamConsole.cpp
|
io/IOStreamConsole.cpp
|
||||||
io/NoninteractiveConsole.cpp
|
io/NoninteractiveConsole.cpp
|
||||||
io/pipestream.cpp
|
io/pipestream.cpp
|
||||||
|
io/ProgressBar.cpp
|
||||||
thread/LoopThread.cpp
|
thread/LoopThread.cpp
|
||||||
thread/ThreadSystem.cpp
|
thread/ThreadSystem.cpp
|
||||||
thread/debugging_nonwindows.cpp
|
thread/debugging_nonwindows.cpp
|
||||||
thread/debugging_windows.cpp
|
thread/debugging_windows.cpp
|
||||||
|
thread/LeftRight.cpp
|
||||||
random/Random.cpp
|
random/Random.cpp
|
||||||
random/RandomGeneratorThread.cpp
|
random/RandomGeneratorThread.cpp
|
||||||
random/OSRandomGenerator.cpp
|
random/OSRandomGenerator.cpp
|
||||||
|
35
src/cpp-utils/io/ProgressBar.cpp
Normal file
35
src/cpp-utils/io/ProgressBar.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include "ProgressBar.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <mutex>
|
||||||
|
#include "IOStreamConsole.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
namespace cpputils {
|
||||||
|
|
||||||
|
ProgressBar::ProgressBar(const char* preamble, uint64_t max_value)
|
||||||
|
: ProgressBar(std::make_shared<IOStreamConsole>(), preamble, max_value) {}
|
||||||
|
|
||||||
|
ProgressBar::ProgressBar(std::shared_ptr<Console> console, const char* preamble, uint64_t max_value)
|
||||||
|
: _console(std::move(console))
|
||||||
|
, _preamble(string("\r") + preamble + " ")
|
||||||
|
, _max_value(max_value)
|
||||||
|
, _lastPercentage(std::numeric_limits<decltype(_lastPercentage)>::max()) {
|
||||||
|
ASSERT(_max_value > 0, "Progress bar can't handle max_value of 0");
|
||||||
|
|
||||||
|
_console->print("\n");
|
||||||
|
|
||||||
|
// show progress bar. _lastPercentage is different to zero, so it shows.
|
||||||
|
update(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProgressBar::update(uint64_t value) {
|
||||||
|
const size_t percentage = (100 * value) / _max_value;
|
||||||
|
if (percentage != _lastPercentage) {
|
||||||
|
_console->print(_preamble + std::to_string(percentage) + "%");
|
||||||
|
_lastPercentage = percentage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
src/cpp-utils/io/ProgressBar.h
Normal file
31
src/cpp-utils/io/ProgressBar.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef MESSMER_CPPUTILS_IO_PROGRESSBAR_H
|
||||||
|
#define MESSMER_CPPUTILS_IO_PROGRESSBAR_H
|
||||||
|
|
||||||
|
#include <cpp-utils/macros.h>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include "Console.h"
|
||||||
|
|
||||||
|
namespace cpputils {
|
||||||
|
|
||||||
|
class ProgressBar final {
|
||||||
|
public:
|
||||||
|
explicit ProgressBar(std::shared_ptr<Console> console, const char* preamble, uint64_t max_value);
|
||||||
|
explicit ProgressBar(const char* preamble, uint64_t max_value);
|
||||||
|
|
||||||
|
void update(uint64_t value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<Console> _console;
|
||||||
|
std::string _preamble;
|
||||||
|
uint64_t _max_value;
|
||||||
|
size_t _lastPercentage;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(ProgressBar);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
239
src/cpp-utils/process/SignalCatcher.cpp
Normal file
239
src/cpp-utils/process/SignalCatcher.cpp
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
#include "SignalCatcher.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
#include <cpp-utils/assert/assert.h>
|
||||||
|
#include <cpp-utils/thread/LeftRight.h>
|
||||||
|
|
||||||
|
using std::make_unique;
|
||||||
|
using std::vector;
|
||||||
|
using std::pair;
|
||||||
|
|
||||||
|
namespace cpputils {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void got_signal(int signal);
|
||||||
|
|
||||||
|
using SignalHandlerFunction = void(int);
|
||||||
|
|
||||||
|
constexpr SignalHandlerFunction* signal_catcher_function = &got_signal;
|
||||||
|
|
||||||
|
#if !defined(_MSC_VER)
|
||||||
|
|
||||||
|
class SignalHandlerRAII final {
|
||||||
|
public:
|
||||||
|
SignalHandlerRAII(int signal)
|
||||||
|
: _old_handler(), _signal(signal) {
|
||||||
|
struct sigaction new_signal_handler{};
|
||||||
|
std::memset(&new_signal_handler, 0, sizeof(new_signal_handler));
|
||||||
|
new_signal_handler.sa_handler = signal_catcher_function; // NOLINT(cppcoreguidelines-pro-type-union-access)
|
||||||
|
new_signal_handler.sa_flags = SA_RESTART;
|
||||||
|
int error = sigfillset(&new_signal_handler.sa_mask); // block all signals while signal handler is running
|
||||||
|
if (0 != error) {
|
||||||
|
throw std::runtime_error("Error calling sigfillset. Errno: " + std::to_string(errno));
|
||||||
|
}
|
||||||
|
_sigaction(_signal, &new_signal_handler, &_old_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
~SignalHandlerRAII() {
|
||||||
|
// reset to old signal handler
|
||||||
|
struct sigaction removed_handler{};
|
||||||
|
_sigaction(_signal, &_old_handler, &removed_handler);
|
||||||
|
if (signal_catcher_function != removed_handler.sa_handler) { // NOLINT(cppcoreguidelines-pro-type-union-access)
|
||||||
|
ASSERT(false, "Signal handler screwup. We just replaced a signal handler that wasn't our own.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void _sigaction(int signal, struct sigaction *new_handler, struct sigaction *old_handler) {
|
||||||
|
int error = sigaction(signal, new_handler, old_handler);
|
||||||
|
if (0 != error) {
|
||||||
|
throw std::runtime_error("Error calling sigaction. Errno: " + std::to_string(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sigaction _old_handler;
|
||||||
|
int _signal;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(SignalHandlerRAII);
|
||||||
|
};
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
class SignalHandlerRAII final {
|
||||||
|
public:
|
||||||
|
SignalHandlerRAII(int signal)
|
||||||
|
: _old_handler(nullptr), _signal(signal) {
|
||||||
|
_old_handler = ::signal(_signal, signal_catcher_function);
|
||||||
|
if (_old_handler == SIG_ERR) {
|
||||||
|
throw std::logic_error("Error calling signal(). Errno: " + std::to_string(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~SignalHandlerRAII() {
|
||||||
|
// reset to old signal handler
|
||||||
|
SignalHandlerFunction* error = ::signal(_signal, _old_handler);
|
||||||
|
if (error == SIG_ERR) {
|
||||||
|
throw std::logic_error("Error resetting signal(). Errno: " + std::to_string(errno));
|
||||||
|
}
|
||||||
|
if (error != signal_catcher_function) {
|
||||||
|
throw std::logic_error("Signal handler screwup. We just replaced a signal handler that wasn't our own.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
SignalHandlerFunction* _old_handler;
|
||||||
|
int _signal;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(SignalHandlerRAII);
|
||||||
|
};
|
||||||
|
|
||||||
|
// The Linux default behavior (i.e. the way we set up sigaction above) is to disable signal processing while the signal
|
||||||
|
// handler is running and to re-enable the custom handler once processing is finished. The Windows default behavior
|
||||||
|
// is to reset the handler to the default handler directly before executing the handler, i.e. the handler will only
|
||||||
|
// be called once. To fix this, we use this RAII class on Windows, of which an instance will live in the signal handler.
|
||||||
|
// In its constructor, it disables signal handling, and in its destructor it re-sets the custom handler.
|
||||||
|
// This is not perfect since there is a small time window between calling the signal handler and calling the constructor
|
||||||
|
// of this class, but it's the best we can do.
|
||||||
|
class SignalHandlerRunningRAII final {
|
||||||
|
public:
|
||||||
|
SignalHandlerRunningRAII(int signal) : _signal(signal) {
|
||||||
|
SignalHandlerFunction* old_handler = ::signal(_signal, SIG_IGN);
|
||||||
|
if (old_handler == SIG_ERR) {
|
||||||
|
throw std::logic_error("Error disabling signal(). Errno: " + std::to_string(errno));
|
||||||
|
}
|
||||||
|
if (old_handler != SIG_DFL) {
|
||||||
|
// see description above, we expected the signal handler to be reset.
|
||||||
|
throw std::logic_error("We expected windows to reset the signal handler but it didn't. Did the Windows API change?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~SignalHandlerRunningRAII() {
|
||||||
|
SignalHandlerFunction* old_handler = ::signal(_signal, signal_catcher_function);
|
||||||
|
if (old_handler == SIG_ERR) {
|
||||||
|
throw std::logic_error("Error resetting signal() after calling handler. Errno: " + std::to_string(errno));
|
||||||
|
}
|
||||||
|
if (old_handler != SIG_IGN) {
|
||||||
|
throw std::logic_error("Weird, we just did set the signal handler to ignore. Why isn't it still ignore?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _signal;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class SignalCatcherRegistry final {
|
||||||
|
public:
|
||||||
|
void add(int signal, std::atomic<bool>* signal_occurred_flag) {
|
||||||
|
_catchers.write([&] (auto& catchers) {
|
||||||
|
catchers.emplace_back(signal, signal_occurred_flag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(std::atomic<bool>* catcher) {
|
||||||
|
_catchers.write([&] (auto& catchers) {
|
||||||
|
auto found = std::find_if(catchers.rbegin(), catchers.rend(), [catcher] (const auto& entry) {return entry.second == catcher;});
|
||||||
|
ASSERT(found != catchers.rend(), "Signal handler not found");
|
||||||
|
catchers.erase(--found.base()); // decrement because it's a reverse iterator
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
~SignalCatcherRegistry() {
|
||||||
|
ASSERT(0 == _catchers.read([] (auto& catchers) {return catchers.size();}), "Leftover signal catchers that weren't destroyed");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::atomic<bool>* find(int signal) {
|
||||||
|
// this is called in a signal handler and must be mutex-free.
|
||||||
|
return _catchers.read([&](auto& catchers) {
|
||||||
|
auto found = std::find_if(catchers.rbegin(), catchers.rend(), [signal](const auto& entry) {return entry.first == signal; });
|
||||||
|
ASSERT(found != catchers.rend(), "Signal handler not found");
|
||||||
|
return found->second;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static SignalCatcherRegistry& singleton() {
|
||||||
|
static SignalCatcherRegistry _singleton;
|
||||||
|
return _singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SignalCatcherRegistry() = default;
|
||||||
|
|
||||||
|
// using LeftRight datastructure because we need mutex-free reads. Signal handlers can't use mutexes.
|
||||||
|
LeftRight<vector<pair<int, std::atomic<bool>*>>> _catchers;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(SignalCatcherRegistry);
|
||||||
|
};
|
||||||
|
|
||||||
|
void got_signal(int signal) {
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
// Only needed on Windows, Linux does this by default. See comment on SignalHandlerRunningRAII class.
|
||||||
|
SignalHandlerRunningRAII disable_signal_processing_while_handler_running_and_reset_handler_afterwards(signal);
|
||||||
|
#endif
|
||||||
|
std::atomic<bool>* catcher = SignalCatcherRegistry::singleton().find(signal);
|
||||||
|
*catcher = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SignalCatcherRegisterer final {
|
||||||
|
public:
|
||||||
|
SignalCatcherRegisterer(int signal, std::atomic<bool>* catcher)
|
||||||
|
: _catcher(catcher) {
|
||||||
|
SignalCatcherRegistry::singleton().add(signal, _catcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
~SignalCatcherRegisterer() {
|
||||||
|
SignalCatcherRegistry::singleton().remove(_catcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<bool>* _catcher;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(SignalCatcherRegisterer);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace details {
|
||||||
|
|
||||||
|
class SignalCatcherImpl final {
|
||||||
|
public:
|
||||||
|
SignalCatcherImpl(int signal, std::atomic<bool>* signal_occurred_flag)
|
||||||
|
: _registerer(signal, signal_occurred_flag)
|
||||||
|
, _handler(signal) {
|
||||||
|
// note: the order of the members ensures that:
|
||||||
|
// - when registering the signal handler fails, the registerer will be destroyed, unregistering the signal_occurred_flag,
|
||||||
|
// i.e. there is no leak.
|
||||||
|
|
||||||
|
// Allow only the set of signals that is supported on all platforms, see for Windows: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal?view=vs-2017
|
||||||
|
ASSERT(signal == SIGABRT || signal == SIGFPE || signal == SIGILL || signal == SIGINT || signal == SIGSEGV || signal == SIGTERM, "Unknown signal");
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
SignalCatcherRegisterer _registerer;
|
||||||
|
SignalHandlerRAII _handler;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(SignalCatcherImpl);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalCatcher::SignalCatcher(std::initializer_list<int> signals)
|
||||||
|
: _signal_occurred(false)
|
||||||
|
, _impls() {
|
||||||
|
// note: the order of the members ensures that:
|
||||||
|
// - when the signal handler is set, the _signal_occurred flag is already initialized.
|
||||||
|
// - the _signal_occurred flag will not be destructed as long as the signal handler might be called (i.e. as long as _impls lives)
|
||||||
|
|
||||||
|
_impls.reserve(signals.size());
|
||||||
|
for (int signal : signals) {
|
||||||
|
_impls.emplace_back(make_unique<details::SignalCatcherImpl>(signal, &_signal_occurred));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalCatcher::~SignalCatcher() {}
|
||||||
|
|
||||||
|
}
|
42
src/cpp-utils/process/SignalCatcher.h
Normal file
42
src/cpp-utils/process/SignalCatcher.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef MESSMER_CPPUTILS_PROCESS_SIGNALCATCHER_H_
|
||||||
|
#define MESSMER_CPPUTILS_PROCESS_SIGNALCATCHER_H_
|
||||||
|
|
||||||
|
#include <cpp-utils/macros.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <csignal>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace cpputils {
|
||||||
|
|
||||||
|
namespace details {
|
||||||
|
class SignalCatcherImpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* While an instance of this class is in scope, the specified signal (e.g. SIGINT)
|
||||||
|
* is caught and doesn't exit the application. You can poll if the signal occurred.
|
||||||
|
*/
|
||||||
|
class SignalCatcher final {
|
||||||
|
public:
|
||||||
|
SignalCatcher(): SignalCatcher({SIGINT, SIGTERM}) {}
|
||||||
|
|
||||||
|
SignalCatcher(std::initializer_list<int> signals);
|
||||||
|
~SignalCatcher();
|
||||||
|
|
||||||
|
bool signal_occurred() const {
|
||||||
|
return _signal_occurred;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// note: _signal_occurred must be initialized before _impl because _impl might use it
|
||||||
|
std::atomic<bool> _signal_occurred;
|
||||||
|
std::vector<std::unique_ptr<details::SignalCatcherImpl>> _impls;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(SignalCatcher);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
1
src/cpp-utils/thread/LeftRight.cpp
Normal file
1
src/cpp-utils/thread/LeftRight.cpp
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "LeftRight.h"
|
165
src/cpp-utils/thread/LeftRight.h
Normal file
165
src/cpp-utils/thread/LeftRight.h
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
#include <atomic>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <cpp-utils/macros.h>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace cpputils {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
struct IncrementRAII final {
|
||||||
|
public:
|
||||||
|
explicit IncrementRAII(std::atomic<int32_t> *counter): _counter(counter) {
|
||||||
|
++(*_counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
~IncrementRAII() {
|
||||||
|
--(*_counter);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::atomic<int32_t> *_counter;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(IncrementRAII);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeftRight wait-free readers synchronization primitive
|
||||||
|
// https://hal.archives-ouvertes.fr/hal-01207881/document
|
||||||
|
template <class T>
|
||||||
|
class LeftRight final {
|
||||||
|
public:
|
||||||
|
LeftRight()
|
||||||
|
: _writeMutex()
|
||||||
|
, _foregroundCounterIndex{0}
|
||||||
|
, _foregroundDataIndex{0}
|
||||||
|
, _counters{{{0}, {0}}}
|
||||||
|
, _data{{{}, {}}}
|
||||||
|
, _inDestruction(false) {}
|
||||||
|
|
||||||
|
~LeftRight() {
|
||||||
|
// from now on, no new readers/writers will be accepted (see asserts in read()/write())
|
||||||
|
_inDestruction = true;
|
||||||
|
|
||||||
|
// wait until any potentially running writers are finished
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(_writeMutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait until any potentially running readers are finished
|
||||||
|
while (_counters[0].load() != 0 || _counters[1].load() != 0) {
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
auto read(F&& readFunc) const {
|
||||||
|
if(_inDestruction.load()) {
|
||||||
|
throw std::logic_error("Issued LeftRight::read() after the destructor started running");
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::IncrementRAII _increment_counter(&_counters[_foregroundCounterIndex.load()]); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
|
||||||
|
return readFunc(_data[_foregroundDataIndex.load()]); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throwing from write would result in invalid state
|
||||||
|
template <typename F>
|
||||||
|
auto write(F&& writeFunc) {
|
||||||
|
if(_inDestruction.load()) {
|
||||||
|
throw std::logic_error("Issued LeftRight::read() after the destructor started running");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(_writeMutex);
|
||||||
|
return _write(writeFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <class F>
|
||||||
|
auto _write(const F& writeFunc) {
|
||||||
|
/*
|
||||||
|
* Assume, A is in background and B in foreground. In simplified terms, we want to do the following:
|
||||||
|
* 1. Write to A (old background)
|
||||||
|
* 2. Switch A/B
|
||||||
|
* 3. Write to B (new background)
|
||||||
|
*
|
||||||
|
* More detailed algorithm (explanations on why this is important are below in code):
|
||||||
|
* 1. Write to A
|
||||||
|
* 2. Switch A/B data pointers
|
||||||
|
* 3. Wait until A counter is zero
|
||||||
|
* 4. Switch A/B counters
|
||||||
|
* 5. Wait until B counter is zero
|
||||||
|
* 6. Write to B
|
||||||
|
*/
|
||||||
|
|
||||||
|
auto localDataIndex = _foregroundDataIndex.load();
|
||||||
|
|
||||||
|
// 1. Write to A
|
||||||
|
_callWriteFuncOnBackgroundInstance(writeFunc, localDataIndex);
|
||||||
|
|
||||||
|
// 2. Switch A/B data pointers
|
||||||
|
localDataIndex = localDataIndex ^ 1;
|
||||||
|
_foregroundDataIndex = localDataIndex;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 3. Wait until A counter is zero
|
||||||
|
*
|
||||||
|
* In the previous write run, A was foreground and B was background.
|
||||||
|
* There was a time after switching _foregroundDataIndex (B to foreground) and before switching _foregroundCounterIndex,
|
||||||
|
* in which new readers could have read B but incremented A's counter.
|
||||||
|
*
|
||||||
|
* In this current run, we just switched _foregroundDataIndex (A back to foreground), but before writing to
|
||||||
|
* the new background B, we have to make sure A's counter was zero briefly, so all these old readers are gone.
|
||||||
|
*/
|
||||||
|
auto localCounterIndex = _foregroundCounterIndex.load();
|
||||||
|
_waitForBackgroundCounterToBeZero(localCounterIndex);
|
||||||
|
|
||||||
|
/*
|
||||||
|
*4. Switch A/B counters
|
||||||
|
*
|
||||||
|
* Now that we know all readers on B are really gone, we can switch the counters and have new readers
|
||||||
|
* increment A's counter again, which is the correct counter since they're reading A.
|
||||||
|
*/
|
||||||
|
localCounterIndex = localCounterIndex ^ 1;
|
||||||
|
_foregroundCounterIndex = localCounterIndex;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 5. Wait until B counter is zero
|
||||||
|
*
|
||||||
|
* This waits for all the readers on B that came in while both data and counter for B was in foreground,
|
||||||
|
* i.e. normal readers that happened outside of that brief gap between switching data and counter.
|
||||||
|
*/
|
||||||
|
_waitForBackgroundCounterToBeZero(localCounterIndex);
|
||||||
|
|
||||||
|
// 6. Write to B
|
||||||
|
_callWriteFuncOnBackgroundInstance(writeFunc, localDataIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
auto _callWriteFuncOnBackgroundInstance(const F& writeFunc, uint8_t localDataIndex) {
|
||||||
|
try {
|
||||||
|
return writeFunc(_data[localDataIndex ^ 1]); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
|
||||||
|
} catch (...) {
|
||||||
|
// recover invariant by copying from the foreground instance
|
||||||
|
_data[localDataIndex ^ 1] = _data[localDataIndex]; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
|
||||||
|
// rethrow
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _waitForBackgroundCounterToBeZero(uint8_t counterIndex) {
|
||||||
|
while (_counters[counterIndex ^ 1].load() != 0) { // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
|
||||||
|
std::this_thread::yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mutex _writeMutex;
|
||||||
|
std::atomic<uint8_t> _foregroundCounterIndex;
|
||||||
|
std::atomic<uint8_t> _foregroundDataIndex;
|
||||||
|
mutable std::array<std::atomic<int32_t>, 2> _counters;
|
||||||
|
std::array<T, 2> _data;
|
||||||
|
std::atomic<bool> _inDestruction;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -32,6 +32,7 @@ CryConfig::CryConfig()
|
|||||||
, _exclusiveClientId(none)
|
, _exclusiveClientId(none)
|
||||||
#ifndef CRYFS_NO_COMPATIBILITY
|
#ifndef CRYFS_NO_COMPATIBILITY
|
||||||
, _hasVersionNumbers(true)
|
, _hasVersionNumbers(true)
|
||||||
|
, _hasParentPointers(true)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -53,6 +54,7 @@ CryConfig CryConfig::load(const Data &data) {
|
|||||||
cfg._exclusiveClientId = pt.get_optional<uint32_t>("cryfs.exclusiveClientId");
|
cfg._exclusiveClientId = pt.get_optional<uint32_t>("cryfs.exclusiveClientId");
|
||||||
#ifndef CRYFS_NO_COMPATIBILITY
|
#ifndef CRYFS_NO_COMPATIBILITY
|
||||||
cfg._hasVersionNumbers = pt.get<bool>("cryfs.migrations.hasVersionNumbers", false);
|
cfg._hasVersionNumbers = pt.get<bool>("cryfs.migrations.hasVersionNumbers", false);
|
||||||
|
cfg._hasParentPointers = pt.get<bool>("cryfs.migrations.hasParentPointers", false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
optional<string> filesystemIdOpt = pt.get_optional<string>("cryfs.filesystemId");
|
optional<string> filesystemIdOpt = pt.get_optional<string>("cryfs.filesystemId");
|
||||||
@ -81,6 +83,7 @@ Data CryConfig::save() const {
|
|||||||
}
|
}
|
||||||
#ifndef CRYFS_NO_COMPATIBILITY
|
#ifndef CRYFS_NO_COMPATIBILITY
|
||||||
pt.put<bool>("cryfs.migrations.hasVersionNumbers", _hasVersionNumbers);
|
pt.put<bool>("cryfs.migrations.hasVersionNumbers", _hasVersionNumbers);
|
||||||
|
pt.put<bool>("cryfs.migrations.hasParentPointers", _hasParentPointers);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
stringstream stream;
|
stringstream stream;
|
||||||
@ -172,6 +175,14 @@ bool CryConfig::HasVersionNumbers() const {
|
|||||||
void CryConfig::SetHasVersionNumbers(bool value) {
|
void CryConfig::SetHasVersionNumbers(bool value) {
|
||||||
_hasVersionNumbers = value;
|
_hasVersionNumbers = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CryConfig::HasParentPointers() const {
|
||||||
|
return _hasParentPointers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CryConfig::SetHasParentPointers(bool value) {
|
||||||
|
_hasParentPointers = value;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,11 @@ public:
|
|||||||
// Version numbers cannot be disabled, but the file system will be migrated to version numbers automatically.
|
// Version numbers cannot be disabled, but the file system will be migrated to version numbers automatically.
|
||||||
bool HasVersionNumbers() const;
|
bool HasVersionNumbers() const;
|
||||||
void SetHasVersionNumbers(bool value);
|
void SetHasVersionNumbers(bool value);
|
||||||
|
|
||||||
|
// This is a trigger to recognize old file systems that didn't have version numbers.
|
||||||
|
// Version numbers cannot be disabled, but the file system will be migrated to version numbers automatically.
|
||||||
|
bool HasParentPointers() const;
|
||||||
|
void SetHasParentPointers(bool value);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static CryConfig load(const cpputils::Data &data);
|
static CryConfig load(const cpputils::Data &data);
|
||||||
@ -73,6 +78,7 @@ private:
|
|||||||
boost::optional<uint32_t> _exclusiveClientId;
|
boost::optional<uint32_t> _exclusiveClientId;
|
||||||
#ifndef CRYFS_NO_COMPATIBILITY
|
#ifndef CRYFS_NO_COMPATIBILITY
|
||||||
bool _hasVersionNumbers;
|
bool _hasVersionNumbers;
|
||||||
|
bool _hasParentPointers;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
CryConfig &operator=(const CryConfig &rhs) = delete;
|
CryConfig &operator=(const CryConfig &rhs) = delete;
|
||||||
|
@ -80,7 +80,14 @@ unique_ref<fsblobstore::FsBlobStore> CryDevice::MigrateOrCreateFsBlobStore(uniqu
|
|||||||
if ("" == rootBlobId) {
|
if ("" == rootBlobId) {
|
||||||
return make_unique_ref<FsBlobStore>(std::move(blobStore));
|
return make_unique_ref<FsBlobStore>(std::move(blobStore));
|
||||||
}
|
}
|
||||||
return FsBlobStore::migrateIfNeeded(std::move(blobStore), BlockId::FromString(rootBlobId));
|
if (!configFile->config()->HasParentPointers()) {
|
||||||
|
auto result = FsBlobStore::migrate(std::move(blobStore), BlockId::FromString(rootBlobId));
|
||||||
|
// Don't migrate again if it was successful
|
||||||
|
configFile->config()->SetHasParentPointers(true);
|
||||||
|
configFile->save();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return make_unique_ref<FsBlobStore>(std::move(blobStore));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -106,6 +113,7 @@ unique_ref<BlockStore2> CryDevice::CreateIntegrityEncryptedBlockStore(unique_ref
|
|||||||
if (!configFile->config()->HasVersionNumbers()) {
|
if (!configFile->config()->HasVersionNumbers()) {
|
||||||
IntegrityBlockStore2::migrateFromBlockstoreWithoutVersionNumbers(encryptedBlockStore.get(), integrityFilePath, myClientId);
|
IntegrityBlockStore2::migrateFromBlockstoreWithoutVersionNumbers(encryptedBlockStore.get(), integrityFilePath, myClientId);
|
||||||
configFile->config()->SetBlocksizeBytes(configFile->config()->BlocksizeBytes() + IntegrityBlockStore2::HEADER_LENGTH - blockstore::BlockId::BINARY_LENGTH); // Minus BlockId size because EncryptedBlockStore doesn't store the BlockId anymore (that was moved to IntegrityBlockStore)
|
configFile->config()->SetBlocksizeBytes(configFile->config()->BlocksizeBytes() + IntegrityBlockStore2::HEADER_LENGTH - blockstore::BlockId::BINARY_LENGTH); // Minus BlockId size because EncryptedBlockStore doesn't store the BlockId anymore (that was moved to IntegrityBlockStore)
|
||||||
|
// Don't migrate again if it was successful
|
||||||
configFile->config()->SetHasVersionNumbers(true);
|
configFile->config()->SetHasVersionNumbers(true);
|
||||||
configFile->save();
|
configFile->save();
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,13 @@
|
|||||||
#include "FileBlob.h"
|
#include "FileBlob.h"
|
||||||
#include "DirBlob.h"
|
#include "DirBlob.h"
|
||||||
#include "SymlinkBlob.h"
|
#include "SymlinkBlob.h"
|
||||||
|
#include <cryfs/config/CryConfigFile.h>
|
||||||
|
#include <cpp-utils/io/ProgressBar.h>
|
||||||
|
#include <cpp-utils/process/SignalCatcher.h>
|
||||||
|
|
||||||
using cpputils::unique_ref;
|
using cpputils::unique_ref;
|
||||||
using cpputils::make_unique_ref;
|
using cpputils::make_unique_ref;
|
||||||
|
using cpputils::SignalCatcher;
|
||||||
using blobstore::BlobStore;
|
using blobstore::BlobStore;
|
||||||
using blockstore::BlockId;
|
using blockstore::BlockId;
|
||||||
using boost::none;
|
using boost::none;
|
||||||
@ -31,35 +35,43 @@ boost::optional<unique_ref<FsBlob>> FsBlobStore::load(const blockstore::BlockId
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef CRYFS_NO_COMPATIBILITY
|
#ifndef CRYFS_NO_COMPATIBILITY
|
||||||
unique_ref<FsBlobStore> FsBlobStore::migrateIfNeeded(unique_ref<BlobStore> blobStore, const blockstore::BlockId &rootBlobId) {
|
unique_ref<FsBlobStore> FsBlobStore::migrate(unique_ref<BlobStore> blobStore, const blockstore::BlockId &rootBlobId) {
|
||||||
|
SignalCatcher signalCatcher;
|
||||||
|
|
||||||
auto rootBlob = blobStore->load(rootBlobId);
|
auto rootBlob = blobStore->load(rootBlobId);
|
||||||
if (rootBlob == none) {
|
if (rootBlob == none) {
|
||||||
throw std::runtime_error("Could not load root blob");
|
throw std::runtime_error("Could not load root blob");
|
||||||
}
|
}
|
||||||
uint16_t format = FsBlobView::getFormatVersionHeader(**rootBlob);
|
|
||||||
|
|
||||||
auto fsBlobStore = make_unique_ref<FsBlobStore>(std::move(blobStore));
|
auto fsBlobStore = make_unique_ref<FsBlobStore>(std::move(blobStore));
|
||||||
if (format == 0) {
|
|
||||||
// migration needed
|
uint64_t migratedBlocks = 0;
|
||||||
std::cout << "Migrating file system for conflict resolution features. Please don't interrupt this process. This can take a while..." << std::flush;
|
cpputils::ProgressBar progressbar("Migrating file system for conflict resolution features. This can take a while...", fsBlobStore->numBlocks());
|
||||||
fsBlobStore->_migrate(std::move(*rootBlob), blockstore::BlockId::Null());
|
fsBlobStore->_migrate(std::move(*rootBlob), blockstore::BlockId::Null(), &signalCatcher, [&] (uint32_t numNodes) {
|
||||||
std::cout << "done" << std::endl;
|
migratedBlocks += numNodes;
|
||||||
}
|
progressbar.update(migratedBlocks);
|
||||||
|
});
|
||||||
|
|
||||||
return fsBlobStore;
|
return fsBlobStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FsBlobStore::_migrate(unique_ref<blobstore::Blob> node, const blockstore::BlockId &parentId) {
|
void FsBlobStore::_migrate(unique_ref<blobstore::Blob> node, const blockstore::BlockId &parentId, SignalCatcher* signalCatcher, std::function<void(uint32_t numNodes)> perBlobCallback) {
|
||||||
FsBlobView::migrate(node.get(), parentId);
|
FsBlobView::migrate(node.get(), parentId);
|
||||||
|
perBlobCallback(node->numNodes());
|
||||||
if (FsBlobView::blobType(*node) == FsBlobView::BlobType::DIR) {
|
if (FsBlobView::blobType(*node) == FsBlobView::BlobType::DIR) {
|
||||||
DirBlob dir(std::move(node), _getLstatSize());
|
DirBlob dir(std::move(node), _getLstatSize());
|
||||||
vector<fspp::Dir::Entry> children;
|
vector<fspp::Dir::Entry> children;
|
||||||
dir.AppendChildrenTo(&children);
|
dir.AppendChildrenTo(&children);
|
||||||
for (const auto &child : children) {
|
for (const auto &child : children) {
|
||||||
|
if (signalCatcher->signal_occurred()) {
|
||||||
|
// on a SIGINT or SIGTERM, cancel migration but gracefully shutdown, i.e. call destructors.
|
||||||
|
throw std::runtime_error("Caught signal");
|
||||||
|
}
|
||||||
auto childEntry = dir.GetChild(child.name);
|
auto childEntry = dir.GetChild(child.name);
|
||||||
ASSERT(childEntry != none, "Couldn't load child, although it was returned as a child in the list.");
|
ASSERT(childEntry != none, "Couldn't load child, although it was returned as a child in the list.");
|
||||||
auto childBlob = _baseBlobStore->load(childEntry->blockId());
|
auto childBlob = _baseBlobStore->load(childEntry->blockId());
|
||||||
ASSERT(childBlob != none, "Couldn't load child blob");
|
ASSERT(childBlob != none, "Couldn't load child blob");
|
||||||
_migrate(std::move(*childBlob), dir.blockId());
|
_migrate(std::move(*childBlob), dir.blockId(), signalCatcher, perBlobCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
#include "FileBlob.h"
|
#include "FileBlob.h"
|
||||||
#include "DirBlob.h"
|
#include "DirBlob.h"
|
||||||
#include "SymlinkBlob.h"
|
#include "SymlinkBlob.h"
|
||||||
|
#ifndef CRYFS_NO_COMPATIBILITY
|
||||||
|
#include <cpp-utils/process/SignalCatcher.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace cryfs {
|
namespace cryfs {
|
||||||
namespace fsblobstore {
|
namespace fsblobstore {
|
||||||
@ -29,13 +32,13 @@ namespace cryfs {
|
|||||||
uint64_t virtualBlocksizeBytes() const;
|
uint64_t virtualBlocksizeBytes() const;
|
||||||
|
|
||||||
#ifndef CRYFS_NO_COMPATIBILITY
|
#ifndef CRYFS_NO_COMPATIBILITY
|
||||||
static cpputils::unique_ref<FsBlobStore> migrateIfNeeded(cpputils::unique_ref<blobstore::BlobStore> blobStore, const blockstore::BlockId &blockId);
|
static cpputils::unique_ref<FsBlobStore> migrate(cpputils::unique_ref<blobstore::BlobStore> blobStore, const blockstore::BlockId &blockId);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
#ifndef CRYFS_NO_COMPATIBILITY
|
#ifndef CRYFS_NO_COMPATIBILITY
|
||||||
void _migrate(cpputils::unique_ref<blobstore::Blob> node, const blockstore::BlockId &parentId);
|
void _migrate(cpputils::unique_ref<blobstore::Blob> node, const blockstore::BlockId &parentId, cpputils::SignalCatcher* signalCatcher, std::function<void(uint32_t numNodes)> perBlobCallback);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::function<fspp::num_bytes_t(const blockstore::BlockId &)> _getLstatSize();
|
std::function<fspp::num_bytes_t(const blockstore::BlockId &)> _getLstatSize();
|
||||||
|
@ -10,7 +10,11 @@ namespace cryfs {
|
|||||||
void FsBlobView::migrate(blobstore::Blob *blob, const blockstore::BlockId &parentId) {
|
void FsBlobView::migrate(blobstore::Blob *blob, const blockstore::BlockId &parentId) {
|
||||||
constexpr unsigned int OLD_HEADER_SIZE = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint8_t);
|
constexpr unsigned int OLD_HEADER_SIZE = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint8_t);
|
||||||
|
|
||||||
ASSERT(FsBlobView::getFormatVersionHeader(*blob) == 0, "Block already migrated");
|
if(FsBlobView::getFormatVersionHeader(*blob) != 0) {
|
||||||
|
// blob already migrated
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Resize blob and move data back
|
// Resize blob and move data back
|
||||||
cpputils::Data data = blob->readAll();
|
cpputils::Data data = blob->readAll();
|
||||||
blob->resize(blob->size() + blockstore::BlockId::BINARY_LENGTH);
|
blob->resize(blob->size() + blockstore::BlockId::BINARY_LENGTH);
|
||||||
|
@ -85,6 +85,10 @@ namespace cryfs {
|
|||||||
return _baseBlob->flush();
|
return _baseBlob->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t numNodes() const override {
|
||||||
|
return _baseBlob->numNodes();
|
||||||
|
}
|
||||||
|
|
||||||
cpputils::unique_ref<blobstore::Blob> releaseBaseBlob() {
|
cpputils::unique_ref<blobstore::Blob> releaseBaseBlob() {
|
||||||
return std::move(_baseBlob);
|
return std::move(_baseBlob);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include <cpp-utils/thread/debugging.h>
|
#include <cpp-utils/thread/debugging.h>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include "InvalidFilesystem.h"
|
#include "InvalidFilesystem.h"
|
||||||
|
#include <codecvt>
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
@ -291,6 +292,9 @@ void Fuse::_removeAndWarnIfExists(vector<string> *fuseOptions, const std::string
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Fuse::_run(const bf::path &mountdir, const vector<string> &fuseOptions) {
|
void Fuse::_run(const bf::path &mountdir, const vector<string> &fuseOptions) {
|
||||||
|
// Avoid encoding errors for non-utf8 characters, see https://github.com/cryfs/cryfs/issues/247
|
||||||
|
bf::path::imbue(std::locale(std::locale(), new std::codecvt_utf8_utf16<wchar_t>()));
|
||||||
|
|
||||||
_mountdir = mountdir;
|
_mountdir = mountdir;
|
||||||
|
|
||||||
ASSERT(_argv.size() == 0, "Filesystem already started");
|
ASSERT(_argv.size() == 0, "Filesystem already started");
|
||||||
|
@ -164,6 +164,18 @@ int main(int argc, char* argv[]) {
|
|||||||
// TODO Show more info about error
|
// TODO Show more info about error
|
||||||
throw std::runtime_error("Error loading config file.");
|
throw std::runtime_error("Error loading config file.");
|
||||||
}
|
}
|
||||||
|
const auto& config_ = config->configFile.config();
|
||||||
|
std::cout << "Loading filesystem of version " << config_->Version() << std::endl;
|
||||||
|
#ifndef CRYFS_NO_COMPATIBILITY
|
||||||
|
const bool is_correct_format = config_->Version() == CryConfig::FilesystemFormatVersion && !config_->HasParentPointers() && !config_->HasVersionNumbers();
|
||||||
|
#else
|
||||||
|
const bool is_correct_format = config_->Version() == CryConfig::FilesystemFormatVersion;
|
||||||
|
#endif
|
||||||
|
if (!is_correct_format) {
|
||||||
|
// TODO At this point, the cryfs.config file was already switched to 0.10 format. We should probably not do that.
|
||||||
|
std::cerr << "The filesystem is not in the 0.10 format. It needs to be migrated. The cryfs-stats tool unfortunately can't handle this, please mount and unmount the filesystem once." << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
cout << "Listing all blocks..." << flush;
|
cout << "Listing all blocks..." << flush;
|
||||||
set<BlockId> unaccountedBlocks = _getAllBlockIds(basedir, *config, localStateDir);
|
set<BlockId> unaccountedBlocks = _getAllBlockIds(basedir, *config, localStateDir);
|
||||||
|
@ -21,50 +21,68 @@ INSTANTIATE_TEST_CASE_P(EmptyLastLeaf, DataTreeTest_NumStoredBytes_P, Values(0u)
|
|||||||
INSTANTIATE_TEST_CASE_P(HalfFullLastLeaf, DataTreeTest_NumStoredBytes_P, Values(5u, 10u));
|
INSTANTIATE_TEST_CASE_P(HalfFullLastLeaf, DataTreeTest_NumStoredBytes_P, Values(5u, 10u));
|
||||||
INSTANTIATE_TEST_CASE_P(FullLastLeaf, DataTreeTest_NumStoredBytes_P, Values(static_cast<uint32_t>(DataNodeLayout(DataTreeTest_NumStoredBytes::BLOCKSIZE_BYTES).maxBytesPerLeaf())));
|
INSTANTIATE_TEST_CASE_P(FullLastLeaf, DataTreeTest_NumStoredBytes_P, Values(static_cast<uint32_t>(DataNodeLayout(DataTreeTest_NumStoredBytes::BLOCKSIZE_BYTES).maxBytesPerLeaf())));
|
||||||
|
|
||||||
|
//TODO Test numLeaves() and numNodes() also two configurations with same number of bytes but different number of leaves (last leaf has 0 bytes)
|
||||||
|
|
||||||
TEST_P(DataTreeTest_NumStoredBytes_P, SingleLeaf) {
|
TEST_P(DataTreeTest_NumStoredBytes_P, SingleLeaf) {
|
||||||
BlockId blockId = CreateLeafWithSize(GetParam())->blockId();
|
BlockId blockId = CreateLeafWithSize(GetParam())->blockId();
|
||||||
auto tree = treeStore.load(blockId).value();
|
auto tree = treeStore.load(blockId).value();
|
||||||
EXPECT_EQ(GetParam(), tree->numBytes());
|
EXPECT_EQ(GetParam(), tree->numBytes());
|
||||||
|
EXPECT_EQ(1, tree->numLeaves());
|
||||||
|
EXPECT_EQ(1, tree->numNodes());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(DataTreeTest_NumStoredBytes_P, TwoLeafTree) {
|
TEST_P(DataTreeTest_NumStoredBytes_P, TwoLeafTree) {
|
||||||
BlockId blockId = CreateTwoLeafWithSecondLeafSize(GetParam())->blockId();
|
BlockId blockId = CreateTwoLeafWithSecondLeafSize(GetParam())->blockId();
|
||||||
auto tree = treeStore.load(blockId).value();
|
auto tree = treeStore.load(blockId).value();
|
||||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
||||||
|
EXPECT_EQ(2, tree->numLeaves());
|
||||||
|
EXPECT_EQ(3, tree->numNodes());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(DataTreeTest_NumStoredBytes_P, FullTwolevelTree) {
|
TEST_P(DataTreeTest_NumStoredBytes_P, FullTwolevelTree) {
|
||||||
BlockId blockId = CreateFullTwoLevelWithLastLeafSize(GetParam())->blockId();
|
BlockId blockId = CreateFullTwoLevelWithLastLeafSize(GetParam())->blockId();
|
||||||
auto tree = treeStore.load(blockId).value();
|
auto tree = treeStore.load(blockId).value();
|
||||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*(nodeStore->layout().maxChildrenPerInnerNode()-1) + GetParam(), tree->numBytes());
|
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*(nodeStore->layout().maxChildrenPerInnerNode()-1) + GetParam(), tree->numBytes());
|
||||||
|
EXPECT_EQ(nodeStore->layout().maxChildrenPerInnerNode(), tree->numLeaves());
|
||||||
|
EXPECT_EQ(1 + nodeStore->layout().maxChildrenPerInnerNode(), tree->numNodes());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(DataTreeTest_NumStoredBytes_P, ThreeLevelTreeWithOneChild) {
|
TEST_P(DataTreeTest_NumStoredBytes_P, ThreeLevelTreeWithOneChild) {
|
||||||
BlockId blockId = CreateThreeLevelWithOneChildAndLastLeafSize(GetParam())->blockId();
|
BlockId blockId = CreateThreeLevelWithOneChildAndLastLeafSize(GetParam())->blockId();
|
||||||
auto tree = treeStore.load(blockId).value();
|
auto tree = treeStore.load(blockId).value();
|
||||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
||||||
|
EXPECT_EQ(2, tree->numLeaves());
|
||||||
|
EXPECT_EQ(4, tree->numNodes());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(DataTreeTest_NumStoredBytes_P, ThreeLevelTreeWithTwoChildren) {
|
TEST_P(DataTreeTest_NumStoredBytes_P, ThreeLevelTreeWithTwoChildren) {
|
||||||
BlockId blockId = CreateThreeLevelWithTwoChildrenAndLastLeafSize(GetParam())->blockId();
|
BlockId blockId = CreateThreeLevelWithTwoChildrenAndLastLeafSize(GetParam())->blockId();
|
||||||
auto tree = treeStore.load(blockId).value();
|
auto tree = treeStore.load(blockId).value();
|
||||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
||||||
|
EXPECT_EQ(2 + nodeStore->layout().maxChildrenPerInnerNode(), tree->numLeaves());
|
||||||
|
EXPECT_EQ(5 + nodeStore->layout().maxChildrenPerInnerNode(), tree->numNodes());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(DataTreeTest_NumStoredBytes_P, ThreeLevelTreeWithThreeChildren) {
|
TEST_P(DataTreeTest_NumStoredBytes_P, ThreeLevelTreeWithThreeChildren) {
|
||||||
BlockId blockId = CreateThreeLevelWithThreeChildrenAndLastLeafSize(GetParam())->blockId();
|
BlockId blockId = CreateThreeLevelWithThreeChildrenAndLastLeafSize(GetParam())->blockId();
|
||||||
auto tree = treeStore.load(blockId).value();
|
auto tree = treeStore.load(blockId).value();
|
||||||
EXPECT_EQ(2*nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
EXPECT_EQ(2*nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes());
|
||||||
|
EXPECT_EQ(2 + 2*nodeStore->layout().maxChildrenPerInnerNode(), tree->numLeaves());
|
||||||
|
EXPECT_EQ(6 + 2*nodeStore->layout().maxChildrenPerInnerNode(), tree->numNodes());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(DataTreeTest_NumStoredBytes_P, FullThreeLevelTree) {
|
TEST_P(DataTreeTest_NumStoredBytes_P, FullThreeLevelTree) {
|
||||||
BlockId blockId = CreateFullThreeLevelWithLastLeafSize(GetParam())->blockId();
|
BlockId blockId = CreateFullThreeLevelWithLastLeafSize(GetParam())->blockId();
|
||||||
auto tree = treeStore.load(blockId).value();
|
auto tree = treeStore.load(blockId).value();
|
||||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode()*(nodeStore->layout().maxChildrenPerInnerNode()-1) + nodeStore->layout().maxBytesPerLeaf()*(nodeStore->layout().maxChildrenPerInnerNode()-1) + GetParam(), tree->numBytes());
|
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode()*(nodeStore->layout().maxChildrenPerInnerNode()-1) + nodeStore->layout().maxBytesPerLeaf()*(nodeStore->layout().maxChildrenPerInnerNode()-1) + GetParam(), tree->numBytes());
|
||||||
|
EXPECT_EQ(nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode(), tree->numLeaves());
|
||||||
|
EXPECT_EQ(1 + nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode(), tree->numNodes());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(DataTreeTest_NumStoredBytes_P, FourLevelMinDataTree) {
|
TEST_P(DataTreeTest_NumStoredBytes_P, FourLevelMinDataTree) {
|
||||||
BlockId blockId = CreateFourLevelMinDataWithLastLeafSize(GetParam())->blockId();
|
BlockId blockId = CreateFourLevelMinDataWithLastLeafSize(GetParam())->blockId();
|
||||||
auto tree = treeStore.load(blockId).value();
|
auto tree = treeStore.load(blockId).value();
|
||||||
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode() + GetParam(), tree->numBytes());
|
EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf()*nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode() + GetParam(), tree->numBytes());
|
||||||
|
EXPECT_EQ(1 + nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode(), tree->numLeaves());
|
||||||
|
EXPECT_EQ(5 + nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode(), tree->numNodes());
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ set(SOURCES
|
|||||||
process/daemonize_include_test.cpp
|
process/daemonize_include_test.cpp
|
||||||
process/subprocess_include_test.cpp
|
process/subprocess_include_test.cpp
|
||||||
process/SubprocessTest.cpp
|
process/SubprocessTest.cpp
|
||||||
|
process/SignalCatcherTest.cpp
|
||||||
tempfile/TempFileTest.cpp
|
tempfile/TempFileTest.cpp
|
||||||
tempfile/TempFileIncludeTest.cpp
|
tempfile/TempFileIncludeTest.cpp
|
||||||
tempfile/TempDirIncludeTest.cpp
|
tempfile/TempDirIncludeTest.cpp
|
||||||
@ -28,6 +29,7 @@ set(SOURCES
|
|||||||
io/ConsoleTest_Print.cpp
|
io/ConsoleTest_Print.cpp
|
||||||
io/ConsoleTest_Ask.cpp
|
io/ConsoleTest_Ask.cpp
|
||||||
io/ConsoleTest_AskPassword.cpp
|
io/ConsoleTest_AskPassword.cpp
|
||||||
|
io/ProgressBarTest.cpp
|
||||||
random/RandomIncludeTest.cpp
|
random/RandomIncludeTest.cpp
|
||||||
lock/LockPoolIncludeTest.cpp
|
lock/LockPoolIncludeTest.cpp
|
||||||
lock/ConditionBarrierIncludeTest.cpp
|
lock/ConditionBarrierIncludeTest.cpp
|
||||||
@ -55,6 +57,7 @@ set(SOURCES
|
|||||||
system/HomedirTest.cpp
|
system/HomedirTest.cpp
|
||||||
system/EnvTest.cpp
|
system/EnvTest.cpp
|
||||||
thread/debugging_test.cpp
|
thread/debugging_test.cpp
|
||||||
|
thread/LeftRightTest.cpp
|
||||||
value_type/ValueTypeTest.cpp
|
value_type/ValueTypeTest.cpp
|
||||||
either_test.cpp
|
either_test.cpp
|
||||||
)
|
)
|
||||||
|
66
test/cpp-utils/io/ProgressBarTest.cpp
Normal file
66
test/cpp-utils/io/ProgressBarTest.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#include <cpp-utils/io/ProgressBar.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
using cpputils::ProgressBar;
|
||||||
|
using std::make_shared;
|
||||||
|
|
||||||
|
class MockConsole final: public cpputils::Console {
|
||||||
|
public:
|
||||||
|
void EXPECT_OUTPUT(const char* expected) {
|
||||||
|
EXPECT_EQ(expected, _output);
|
||||||
|
_output = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(const std::string& text) override {
|
||||||
|
_output += text;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int ask(const std::string&, const std::vector<std::string>&) override {
|
||||||
|
EXPECT_TRUE(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool askYesNo(const std::string&, bool) override {
|
||||||
|
EXPECT_TRUE(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string askPassword(const std::string&) override {
|
||||||
|
EXPECT_TRUE(false);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string _output;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(ProgressBarTest, testProgressBar) {
|
||||||
|
auto console = make_shared<MockConsole>();
|
||||||
|
|
||||||
|
ProgressBar bar(console, "Preamble", 2000);
|
||||||
|
console->EXPECT_OUTPUT("\n\rPreamble 0%");
|
||||||
|
|
||||||
|
// when updating to 0, doesn't reprint
|
||||||
|
bar.update(0);
|
||||||
|
console->EXPECT_OUTPUT("");
|
||||||
|
|
||||||
|
// update to half
|
||||||
|
bar.update(1000);
|
||||||
|
console->EXPECT_OUTPUT("\rPreamble 50%");
|
||||||
|
|
||||||
|
// when updating to same value, doesn't reprint
|
||||||
|
bar.update(1000);
|
||||||
|
console->EXPECT_OUTPUT("");
|
||||||
|
|
||||||
|
// when updating to value with same percentage, doesn't reprint
|
||||||
|
bar.update(1001);
|
||||||
|
console->EXPECT_OUTPUT("");
|
||||||
|
|
||||||
|
// update to 0
|
||||||
|
bar.update(0);
|
||||||
|
console->EXPECT_OUTPUT("\rPreamble 0%");
|
||||||
|
|
||||||
|
// update to full
|
||||||
|
bar.update(2000);
|
||||||
|
console->EXPECT_OUTPUT("\rPreamble 100%");
|
||||||
|
}
|
193
test/cpp-utils/process/SignalCatcherTest.cpp
Normal file
193
test/cpp-utils/process/SignalCatcherTest.cpp
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
#include <cpp-utils/process/SignalCatcher.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <csignal>
|
||||||
|
|
||||||
|
using cpputils::SignalCatcher;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void raise_signal(int signal) {
|
||||||
|
int error = ::raise(signal);
|
||||||
|
if (error != 0) {
|
||||||
|
throw std::runtime_error("Error raising signal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenNoSignalCatcher_whenRaisingSigint_thenDies) {
|
||||||
|
EXPECT_DEATH(
|
||||||
|
raise_signal(SIGINT),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenNoSignalCatcher_whenRaisingSigterm_thenDies) {
|
||||||
|
EXPECT_DEATH(
|
||||||
|
raise_signal(SIGTERM),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenSigIntCatcher_whenRaisingSigInt_thenCatches) {
|
||||||
|
SignalCatcher catcher({SIGINT});
|
||||||
|
|
||||||
|
EXPECT_FALSE(catcher.signal_occurred());
|
||||||
|
raise_signal(SIGINT);
|
||||||
|
EXPECT_TRUE(catcher.signal_occurred());
|
||||||
|
|
||||||
|
// raise again
|
||||||
|
raise_signal(SIGINT);
|
||||||
|
EXPECT_TRUE(catcher.signal_occurred());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenSigTermCatcher_whenRaisingSigTerm_thenCatches) {
|
||||||
|
SignalCatcher catcher({SIGTERM});
|
||||||
|
|
||||||
|
EXPECT_FALSE(catcher.signal_occurred());
|
||||||
|
raise_signal(SIGTERM);
|
||||||
|
EXPECT_TRUE(catcher.signal_occurred());
|
||||||
|
|
||||||
|
// raise again
|
||||||
|
raise_signal(SIGTERM);
|
||||||
|
EXPECT_TRUE(catcher.signal_occurred());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenSigIntAndSigTermCatcher_whenRaisingSigInt_thenCatches) {
|
||||||
|
SignalCatcher catcher({SIGINT, SIGTERM});
|
||||||
|
|
||||||
|
EXPECT_FALSE(catcher.signal_occurred());
|
||||||
|
raise_signal(SIGINT);
|
||||||
|
EXPECT_TRUE(catcher.signal_occurred());
|
||||||
|
|
||||||
|
// raise again
|
||||||
|
raise_signal(SIGINT);
|
||||||
|
EXPECT_TRUE(catcher.signal_occurred());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenSigIntAndSigTermCatcher_whenRaisingSigTerm_thenCatches) {
|
||||||
|
SignalCatcher catcher({SIGINT, SIGTERM});
|
||||||
|
|
||||||
|
EXPECT_FALSE(catcher.signal_occurred());
|
||||||
|
raise_signal(SIGTERM);
|
||||||
|
EXPECT_TRUE(catcher.signal_occurred());
|
||||||
|
|
||||||
|
// raise again
|
||||||
|
raise_signal(SIGTERM);
|
||||||
|
EXPECT_TRUE(catcher.signal_occurred());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenSigIntAndSigTermCatcher_whenRaisingSigIntAndSigTerm_thenCatches) {
|
||||||
|
SignalCatcher catcher({SIGINT, SIGTERM});
|
||||||
|
|
||||||
|
EXPECT_FALSE(catcher.signal_occurred());
|
||||||
|
raise_signal(SIGTERM);
|
||||||
|
EXPECT_TRUE(catcher.signal_occurred());
|
||||||
|
|
||||||
|
raise_signal(SIGINT);
|
||||||
|
EXPECT_TRUE(catcher.signal_occurred());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenSigIntCatcherAndSigTermCatcher_whenRaisingSignalsInOrder_thenCorrectCatcherCatches) {
|
||||||
|
SignalCatcher sigintCatcher({SIGINT});
|
||||||
|
SignalCatcher sigtermCatcher({SIGTERM});
|
||||||
|
|
||||||
|
EXPECT_FALSE(sigintCatcher.signal_occurred());
|
||||||
|
raise_signal(SIGINT);
|
||||||
|
EXPECT_TRUE(sigintCatcher.signal_occurred());
|
||||||
|
|
||||||
|
EXPECT_FALSE(sigtermCatcher.signal_occurred());
|
||||||
|
raise_signal(SIGTERM);
|
||||||
|
EXPECT_TRUE(sigtermCatcher.signal_occurred());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenSigIntCatcherAndSigTermCatcher_whenRaisingSignalsInReverseOrder_thenCorrectCatcherCatches) {
|
||||||
|
SignalCatcher sigintCatcher({SIGINT});
|
||||||
|
SignalCatcher sigtermCatcher({SIGTERM});
|
||||||
|
|
||||||
|
EXPECT_FALSE(sigtermCatcher.signal_occurred());
|
||||||
|
raise_signal(SIGTERM);
|
||||||
|
EXPECT_TRUE(sigtermCatcher.signal_occurred());
|
||||||
|
|
||||||
|
EXPECT_FALSE(sigintCatcher.signal_occurred());
|
||||||
|
raise_signal(SIGINT);
|
||||||
|
EXPECT_TRUE(sigintCatcher.signal_occurred());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenNestedSigIntCatchers_whenRaisingSignals_thenCorrectCatcherCatches) {
|
||||||
|
SignalCatcher outerCatcher({SIGINT});
|
||||||
|
{
|
||||||
|
SignalCatcher middleCatcher({SIGINT});
|
||||||
|
|
||||||
|
EXPECT_FALSE(middleCatcher.signal_occurred());
|
||||||
|
raise_signal(SIGINT);
|
||||||
|
EXPECT_TRUE(middleCatcher.signal_occurred());
|
||||||
|
|
||||||
|
{
|
||||||
|
SignalCatcher innerCatcher({SIGINT});
|
||||||
|
|
||||||
|
EXPECT_FALSE(innerCatcher.signal_occurred());
|
||||||
|
raise_signal(SIGINT);
|
||||||
|
EXPECT_TRUE(innerCatcher.signal_occurred());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_FALSE(outerCatcher.signal_occurred());
|
||||||
|
raise_signal(SIGINT);
|
||||||
|
EXPECT_TRUE(outerCatcher.signal_occurred());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenExpiredSigIntCatcher_whenRaisingSigInt_thenDies) {
|
||||||
|
{
|
||||||
|
SignalCatcher catcher({SIGINT});
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_DEATH(
|
||||||
|
raise_signal(SIGINT),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenExpiredSigTermCatcher_whenRaisingSigTerm_thenDies) {
|
||||||
|
{
|
||||||
|
SignalCatcher catcher({SIGTERM});
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_DEATH(
|
||||||
|
raise_signal(SIGTERM),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenExpiredSigIntCatcherAndSigTermCatcher_whenRaisingSigTerm_thenDies) {
|
||||||
|
{
|
||||||
|
SignalCatcher sigIntCatcher({SIGTERM});
|
||||||
|
SignalCatcher sigTermCatcer({SIGTERM});
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_DEATH(
|
||||||
|
raise_signal(SIGTERM),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenSigTermCatcherAndExpiredSigIntCatcher_whenRaisingSigTerm_thenCatches) {
|
||||||
|
SignalCatcher sigTermCatcher({SIGTERM});
|
||||||
|
{
|
||||||
|
SignalCatcher sigIntCatcher({SIGINT});
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_FALSE(sigTermCatcher.signal_occurred());
|
||||||
|
raise_signal(SIGTERM);
|
||||||
|
EXPECT_TRUE(sigTermCatcher.signal_occurred());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SignalCatcherTest, givenSigTermCatcherAndExpiredSigIntCatcher_whenRaisingSigInt_thenDies) {
|
||||||
|
SignalCatcher sigTermCacher({SIGTERM});
|
||||||
|
{
|
||||||
|
SignalCatcher sigIntCatcher({SIGINT});
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_DEATH(
|
||||||
|
raise_signal(SIGINT),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
204
test/cpp-utils/thread/LeftRightTest.cpp
Normal file
204
test/cpp-utils/thread/LeftRightTest.cpp
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
#include <cpp-utils/thread/LeftRight.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using cpputils::LeftRight;
|
||||||
|
using std::vector;
|
||||||
|
|
||||||
|
TEST(LeftRightTest, givenInt_whenWritingAndReading_thenChangesArePresent) {
|
||||||
|
LeftRight<int> obj;
|
||||||
|
|
||||||
|
obj.write([] (auto& obj) {obj = 5;});
|
||||||
|
int read = obj.read([] (auto& obj) {return obj;});
|
||||||
|
EXPECT_EQ(5, read);
|
||||||
|
|
||||||
|
// check changes are also present in background copy
|
||||||
|
obj.write([] (auto&) {}); // this switches to the background copy
|
||||||
|
read = obj.read([] (auto& obj) {return obj;});
|
||||||
|
EXPECT_EQ(5, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LeftRightTest, givenVector_whenWritingAndReading_thenChangesArePresent) {
|
||||||
|
LeftRight<vector<int>> obj;
|
||||||
|
|
||||||
|
obj.write([] (auto& obj) {obj.push_back(5);});
|
||||||
|
vector<int> read = obj.read([] (auto& obj) {return obj;});
|
||||||
|
EXPECT_EQ((vector<int>{5}), read);
|
||||||
|
|
||||||
|
obj.write([] (auto& obj) {obj.push_back(6);});
|
||||||
|
read = obj.read([] (auto& obj) {return obj;});
|
||||||
|
EXPECT_EQ((vector<int>{5, 6}), read);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LeftRightTest, readsCanBeConcurrent) {
|
||||||
|
LeftRight<int> obj;
|
||||||
|
std::atomic<int> num_running_readers{0};
|
||||||
|
|
||||||
|
std::thread reader1([&] () {
|
||||||
|
obj.read([&] (auto&) {
|
||||||
|
++num_running_readers;
|
||||||
|
while(num_running_readers.load() < 2) {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread reader2([&] () {
|
||||||
|
obj.read([&] (auto&) {
|
||||||
|
++num_running_readers;
|
||||||
|
while(num_running_readers.load() < 2) {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// the threads only finish after both entered the read function.
|
||||||
|
// if LeftRight didn't allow concurrency, this would cause a deadlock.
|
||||||
|
reader1.join();
|
||||||
|
reader2.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LeftRightTest, writesCanBeConcurrentWithReads_readThenWrite) {
|
||||||
|
LeftRight<int> obj;
|
||||||
|
std::atomic<bool> reader_running{false};
|
||||||
|
std::atomic<bool> writer_running{false};
|
||||||
|
|
||||||
|
std::thread reader([&] () {
|
||||||
|
obj.read([&] (auto&) {
|
||||||
|
reader_running = true;
|
||||||
|
while(!writer_running.load()) {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread writer([&] () {
|
||||||
|
// run read first, write second
|
||||||
|
while (!reader_running.load()) {}
|
||||||
|
|
||||||
|
obj.write([&] (auto&) {
|
||||||
|
writer_running = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// the threads only finish after both entered the read function.
|
||||||
|
// if LeftRight didn't allow concurrency, this would cause a deadlock.
|
||||||
|
reader.join();
|
||||||
|
writer.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LeftRightTest, writesCanBeConcurrentWithReads_writeThenRead) {
|
||||||
|
LeftRight<int> obj;
|
||||||
|
std::atomic<bool> writer_running{false};
|
||||||
|
std::atomic<bool> reader_running{false};
|
||||||
|
|
||||||
|
std::thread writer([&] () {
|
||||||
|
obj.read([&] (auto&) {
|
||||||
|
writer_running = true;
|
||||||
|
while(!reader_running.load()) {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread reader([&] () {
|
||||||
|
// run write first, read second
|
||||||
|
while (!writer_running.load()) {}
|
||||||
|
|
||||||
|
obj.read([&] (auto&) {
|
||||||
|
reader_running = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// the threads only finish after both entered the read function.
|
||||||
|
// if LeftRight didn't allow concurrency, this would cause a deadlock.
|
||||||
|
writer.join();
|
||||||
|
reader.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LeftRightTest, writesCannotBeConcurrentWithWrites) {
|
||||||
|
LeftRight<int> obj;
|
||||||
|
std::atomic<bool> first_writer_started{false};
|
||||||
|
std::atomic<bool> first_writer_finished{false};
|
||||||
|
|
||||||
|
std::thread writer1([&] () {
|
||||||
|
obj.write([&] (auto&) {
|
||||||
|
first_writer_started = true;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
|
first_writer_finished = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread writer2([&] () {
|
||||||
|
// make sure the other writer runs first
|
||||||
|
while (!first_writer_started.load()) {}
|
||||||
|
|
||||||
|
obj.write([&] (auto&) {
|
||||||
|
// expect the other writer finished before this one starts
|
||||||
|
EXPECT_TRUE(first_writer_finished.load());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
writer1.join();
|
||||||
|
writer2.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
class MyException : std::exception {};
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LeftRightTest, whenReadThrowsException_thenThrowsThrough) {
|
||||||
|
LeftRight<int> obj;
|
||||||
|
|
||||||
|
EXPECT_THROW(
|
||||||
|
obj.read([](auto&) {throw MyException();}),
|
||||||
|
MyException
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LeftRightTest, whenWriteThrowsException_thenThrowsThrough) {
|
||||||
|
LeftRight<int> obj;
|
||||||
|
|
||||||
|
EXPECT_THROW(
|
||||||
|
obj.write([](auto&) {throw MyException();}),
|
||||||
|
MyException
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LeftRightTest, givenInt_whenWriteThrowsException_thenResetsToOldState) {
|
||||||
|
LeftRight<int> obj;
|
||||||
|
|
||||||
|
obj.write([](auto& obj) {obj = 5;});
|
||||||
|
|
||||||
|
EXPECT_THROW(
|
||||||
|
obj.write([](auto& obj) {
|
||||||
|
obj = 6;
|
||||||
|
throw MyException();
|
||||||
|
}),
|
||||||
|
MyException
|
||||||
|
);
|
||||||
|
|
||||||
|
// check reading it returns old value
|
||||||
|
int read = obj.read([] (auto& obj) {return obj;});
|
||||||
|
EXPECT_EQ(5, read);
|
||||||
|
|
||||||
|
// check changes are also present in background copy
|
||||||
|
obj.write([] (auto&) {}); // this switches to the background copy
|
||||||
|
read = obj.read([] (auto& obj) {return obj;});
|
||||||
|
EXPECT_EQ(5, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LeftRightTest, givenVector_whenWriteThrowsException_thenResetsToOldState) {
|
||||||
|
LeftRight<vector<int>> obj;
|
||||||
|
|
||||||
|
obj.write([](auto& obj) {obj.push_back(5);});
|
||||||
|
|
||||||
|
EXPECT_THROW(
|
||||||
|
obj.write([](auto& obj) {
|
||||||
|
obj.push_back(6);
|
||||||
|
throw MyException();
|
||||||
|
}),
|
||||||
|
MyException
|
||||||
|
);
|
||||||
|
|
||||||
|
// check reading it returns old value
|
||||||
|
vector<int> read = obj.read([] (auto& obj) {return obj;});
|
||||||
|
EXPECT_EQ((vector<int>{5}), read);
|
||||||
|
|
||||||
|
// check changes are also present in background copy
|
||||||
|
obj.write([] (auto&) {}); // this switches to the background copy
|
||||||
|
read = obj.read([] (auto& obj) {return obj;});
|
||||||
|
EXPECT_EQ((vector<int>{5}), read);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user