From ae680a5bdcb244bcaed9c74efb82ba5e18168e24 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Sat, 16 Mar 2019 17:59:55 -0700 Subject: [PATCH 01/15] Stop FuseThread without using signals --- test/fspp/testutils/FuseThread.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/fspp/testutils/FuseThread.cpp b/test/fspp/testutils/FuseThread.cpp index b73df2f4..277a2dac 100644 --- a/test/fspp/testutils/FuseThread.cpp +++ b/test/fspp/testutils/FuseThread.cpp @@ -29,9 +29,7 @@ void FuseThread::start(const bf::path &mountDir, const vector &fuseOptio } void FuseThread::stop() { - if (0 != pthread_kill(_child.native_handle(), SIGINT)) { - throw std::runtime_error("Error sending stop signal"); - } + _fuse->stop(); bool thread_stopped = _child.try_join_for(seconds(10)); ASSERT(thread_stopped, "FuseThread could not be stopped"); //Wait until it is properly shutdown (busy waiting is simple and doesn't hurt much here) From 9ee345e16af3f0de5128e269cd2501ca62c18c9b Mon Sep 17 00:00:00 2001 From: Andy Weidenbaum Date: Mon, 18 Mar 2019 12:53:20 +1300 Subject: [PATCH 02/15] Implement pthread_getname_np_gcompat for musl (#255) --- src/cpp-utils/thread/debugging.h | 4 ++ src/cpp-utils/thread/debugging_nonwindows.cpp | 49 +++++++++++++++ test/cpp-utils/thread/debugging_test.cpp | 61 ++++++++++--------- 3 files changed, 85 insertions(+), 29 deletions(-) diff --git a/src/cpp-utils/thread/debugging.h b/src/cpp-utils/thread/debugging.h index 9965649c..d70e5f2f 100644 --- a/src/cpp-utils/thread/debugging.h +++ b/src/cpp-utils/thread/debugging.h @@ -9,7 +9,11 @@ namespace cpputils { void set_thread_name(const char* name); std::string get_thread_name(); + +#if defined(__GLIBC__) || defined(__APPLE__) || defined(_MSC_VER) +// this is not supported on musl systems, that's why we ifdef it for glibc only. std::string get_thread_name(std::thread* thread); +#endif } diff --git a/src/cpp-utils/thread/debugging_nonwindows.cpp b/src/cpp-utils/thread/debugging_nonwindows.cpp index 3ed543ff..4afb4045 100644 --- a/src/cpp-utils/thread/debugging_nonwindows.cpp +++ b/src/cpp-utils/thread/debugging_nonwindows.cpp @@ -5,6 +5,14 @@ #include #include #include +#if !(defined(__GLIBC__) || defined(__APPLE__)) +// for pthread_getname_np_gcompat +#include // errno +#include // O_CLOEXEC, O_RDONLY +#include // open, read +#include +#include +#endif namespace cpputils { @@ -28,9 +36,47 @@ void set_thread_name(const char* name) { } namespace { +#if !(defined(__GLIBC__) || defined(__APPLE__)) + +struct OpenFileRAII final { + explicit OpenFileRAII(const char* filename) : fd(::open(filename, O_RDONLY | O_CLOEXEC)) {} + ~OpenFileRAII() { + int result = close(fd); + if (result != 0) { + throw std::runtime_error("Error closing file. Errno: " + std::to_string(errno)); + } + } + + int fd; +}; + +// get the name of a thread +int pthread_getname_np_gcompat(pthread_t thread, char *name, size_t len) { + ASSERT(thread == pthread_self(), "On musl systems, it's only supported to get the name of the current thread."); + + auto file = OpenFileRAII("/proc/thread-self/comm"); + ssize_t n; + if (file.fd < 0) + return errno; + n = read(file.fd, name, len); + if (n < 0) + return errno; + // if the trailing newline was not read, the buffer was too small + if (n == 0 || name[n - 1] != '\n') + return ERANGE; + // null-terminate string + name[n - 1] = '\0'; + return 0; +} +#endif + std::string get_thread_name(pthread_t thread) { char name[MAX_NAME_LEN]; +#if defined(__GLIBC__) || defined(__APPLE__) int result = pthread_getname_np(thread, name, MAX_NAME_LEN); +#else + int result = pthread_getname_np_gcompat(thread, name, MAX_NAME_LEN); +#endif if (0 != result) { throw std::runtime_error("Error getting thread name with pthread_getname_np. Code: " + std::to_string(result)); } @@ -39,16 +85,19 @@ std::string get_thread_name(pthread_t thread) { name[MAX_NAME_LEN - 1] = '\0'; return name; } + } std::string get_thread_name() { return get_thread_name(pthread_self()); } +#if defined(__GLIBC__) || defined(__APPLE__) std::string get_thread_name(std::thread* thread) { ASSERT(thread->joinable(), "Thread not running"); return get_thread_name(thread->native_handle()); } +#endif } diff --git a/test/cpp-utils/thread/debugging_test.cpp b/test/cpp-utils/thread/debugging_test.cpp index ec4f1e3b..caf5f458 100644 --- a/test/cpp-utils/thread/debugging_test.cpp +++ b/test/cpp-utils/thread/debugging_test.cpp @@ -11,6 +11,24 @@ TEST(ThreadDebuggingTest_ThreadName, givenMainThread_whenSettingAndGetting_thenD get_thread_name(); } +TEST(ThreadDebuggingTest_ThreadName, givenMainThread_whenGettingFromInside_thenIsCorrect) { + set_thread_name("my_thread_name"); + string name = get_thread_name(); + EXPECT_EQ("my_thread_name", name); +} + +TEST(ThreadDebuggingTest_ThreadName, givenChildThread_whenGettingFromInside_thenIsCorrect) { + std::thread child([] { + set_thread_name("my_thread_name"); + string name = get_thread_name(); + EXPECT_EQ("my_thread_name", name); + }); + child.join(); +} + + +#if defined(__GLIBC__) || defined(__APPLE__) || defined(_MSC_VER) +// disabled on musl because getting the thread name for a child thread doesn't work there TEST(ThreadDebuggingTest_ThreadName, givenChildThread_whenSettingAndGetting_thenDoesntCrash) { ConditionBarrier nameIsChecked; @@ -27,37 +45,22 @@ TEST(ThreadDebuggingTest_ThreadName, givenChildThread_whenSettingAndGetting_then EXPECT_TRUE(child_didnt_crash); } -TEST(ThreadDebuggingTest_ThreadName, givenMainThread_whenGettingFromInside_thenIsCorrect) { - set_thread_name("my_thread_name"); - string name = get_thread_name(); - EXPECT_EQ("my_thread_name", name); -} - -TEST(ThreadDebuggingTest_ThreadName, givenChildThread_whenGettingFromInside_thenIsCorrect) { - std::thread child([] { - set_thread_name("my_thread_name"); - string name = get_thread_name(); - EXPECT_EQ("my_thread_name", name); - }); - child.join(); -} - TEST(ThreadDebuggingTest_ThreadName, givenChildThread_whenGettingFromOutside_thenIsCorrect) { - ConditionBarrier nameIsSet; - ConditionBarrier nameIsChecked; + ConditionBarrier nameIsSet; + ConditionBarrier nameIsChecked; - std::thread child([&] { - set_thread_name("my_thread_name"); - nameIsSet.release(); - nameIsChecked.wait(); - }); + std::thread child([&] { + set_thread_name("my_thread_name"); + nameIsSet.release(); + nameIsChecked.wait(); + }); - nameIsSet.wait(); - set_thread_name("outer_thread_name"); // just to make sure the next line doesn't read the outer thread name - string name = get_thread_name(&child); - EXPECT_EQ("my_thread_name", name); + nameIsSet.wait(); + set_thread_name("outer_thread_name"); // just to make sure the next line doesn't read the outer thread name + string name = get_thread_name(&child); + EXPECT_EQ("my_thread_name", name); - nameIsChecked.release(); - child.join(); + nameIsChecked.release(); + child.join(); } - +#endif From 2150446a2c0291c1c0252b1924de9d36bb6d16e3 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Sat, 23 Mar 2019 22:01:26 -0700 Subject: [PATCH 03/15] Use paths relative to executable location to find subprocess executables --- ChangeLog.txt | 1 + appveyor.yml | 2 +- test/CMakeLists.txt | 1 + test/blobstore/CMakeLists.txt | 2 +- test/blockstore/CMakeLists.txt | 2 +- test/cpp-utils/CMakeLists.txt | 2 +- test/cpp-utils/assert/backtrace_test.cpp | 12 +++++++--- test/cpp-utils/process/SubprocessTest.cpp | 13 ++++++++--- test/cryfs-cli/CMakeLists.txt | 2 +- test/cryfs/CMakeLists.txt | 2 +- test/fspp/CMakeLists.txt | 2 +- test/gitversion/CMakeLists.txt | 2 +- test/my-gtest-main/CMakeLists.txt | 13 +++++++++++ test/my-gtest-main/my-gtest-main.cpp | 27 +++++++++++++++++++++++ test/my-gtest-main/my-gtest-main.h | 5 +++++ test/parallelaccessstore/CMakeLists.txt | 2 +- vendor/googletest/CMakeLists.txt | 1 - 17 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 test/my-gtest-main/CMakeLists.txt create mode 100644 test/my-gtest-main/my-gtest-main.cpp create mode 100644 test/my-gtest-main/my-gtest-main.h diff --git a/ChangeLog.txt b/ChangeLog.txt index 25eed6ba..1b179dda 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -7,6 +7,7 @@ Fixed bugs: Other: * Updated to crypto++ 8.1 +* Unit tests can now be run from any directory Version 0.10.0 diff --git a/appveyor.yml b/appveyor.yml index 9f2857ae..7085040e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -36,7 +36,7 @@ build_script: - cmd: cmake --build . --config %CONFIGURATION% - cmd: .\test\gitversion\gitversion-test.exe # cpp-utils-test disables ThreadDebuggingTest_ThreadName.*_thenIsCorrect because the appveyor image is too old to support the API needed for that - - cmd: cd .\test\cpp-utils\ && .\cpp-utils-test.exe --gtest_filter=-ThreadDebuggingTest_ThreadName.*_thenIsCorrect && cd ..\.. + - cmd: .\test\cpp-utils\cpp-utils-test.exe --gtest_filter=-ThreadDebuggingTest_ThreadName.*_thenIsCorrect #- cmd: .\test\fspp\fspp-test.exe - cmd: .\test\parallelaccessstore\parallelaccessstore-test.exe - cmd: .\test\blockstore\blockstore-test.exe diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d8d77fa8..4879389e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,7 @@ if (BUILD_TESTING) include_directories(../src) + add_subdirectory(my-gtest-main) add_subdirectory(gitversion) add_subdirectory(cpp-utils) if (NOT MSVC) diff --git a/test/blobstore/CMakeLists.txt b/test/blobstore/CMakeLists.txt index f9eeb8d7..05e98b8d 100644 --- a/test/blobstore/CMakeLists.txt +++ b/test/blobstore/CMakeLists.txt @@ -27,7 +27,7 @@ set(SOURCES ) add_executable(${PROJECT_NAME} ${SOURCES}) -target_link_libraries(${PROJECT_NAME} googletest blobstore) +target_link_libraries(${PROJECT_NAME} my-gtest-main googletest blobstore) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) diff --git a/test/blockstore/CMakeLists.txt b/test/blockstore/CMakeLists.txt index 54167aef..ca63acce 100644 --- a/test/blockstore/CMakeLists.txt +++ b/test/blockstore/CMakeLists.txt @@ -42,7 +42,7 @@ set(SOURCES ) add_executable(${PROJECT_NAME} ${SOURCES}) -target_link_libraries(${PROJECT_NAME} googletest blockstore) +target_link_libraries(${PROJECT_NAME} my-gtest-main googletest blockstore) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) diff --git a/test/cpp-utils/CMakeLists.txt b/test/cpp-utils/CMakeLists.txt index 831683c9..9a0c71cd 100644 --- a/test/cpp-utils/CMakeLists.txt +++ b/test/cpp-utils/CMakeLists.txt @@ -69,7 +69,7 @@ target_activate_cpp14(${PROJECT_NAME}_exit_signal) target_link_libraries(${PROJECT_NAME}_exit_signal cpp-utils) add_executable(${PROJECT_NAME} ${SOURCES}) -target_link_libraries(${PROJECT_NAME} googletest cpp-utils) +target_link_libraries(${PROJECT_NAME} my-gtest-main googletest cpp-utils) add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}_exit_status ${PROJECT_NAME}_exit_signal) add_test(${PROJECT_NAME} ${PROJECT_NAME}) diff --git a/test/cpp-utils/assert/backtrace_test.cpp b/test/cpp-utils/assert/backtrace_test.cpp index 769ae8a2..77d18bc1 100644 --- a/test/cpp-utils/assert/backtrace_test.cpp +++ b/test/cpp-utils/assert/backtrace_test.cpp @@ -2,18 +2,24 @@ #include #include "cpp-utils/assert/backtrace.h" #include "cpp-utils/process/subprocess.h" +#include +#include "my-gtest-main.h" using std::string; using testing::HasSubstr; +namespace bf = boost::filesystem; namespace { std::string call_process_exiting_with(const std::string& kind, const std::string& signal = "") { #if defined(_MSC_VER) - constexpr const char* executable = "cpp-utils-test_exit_signal.exe"; + auto executable = get_executable().parent_path() / "cpp-utils-test_exit_signal.exe"; #else - constexpr const char* executable = "./test/cpp-utils/cpp-utils-test_exit_signal"; + auto executable = get_executable().parent_path() / "cpp-utils-test_exit_signal"; #endif - const std::string command = std::string(executable) + " \"" + kind + "\" \"" + signal + "\" 2>&1"; + if (!bf::exists(executable)) { + throw std::runtime_error(executable.string() + " not found."); + } + const std::string command = executable.string() + " \"" + kind + "\" \"" + signal + "\" 2>&1"; auto result = cpputils::Subprocess::call(command); return result.output; } diff --git a/test/cpp-utils/process/SubprocessTest.cpp b/test/cpp-utils/process/SubprocessTest.cpp index 9a1c857c..c9ed0a83 100644 --- a/test/cpp-utils/process/SubprocessTest.cpp +++ b/test/cpp-utils/process/SubprocessTest.cpp @@ -1,19 +1,26 @@ #include #include +#include #include +#include "my-gtest-main.h" using cpputils::Subprocess; using cpputils::SubprocessError; +using std::string; +namespace bf = boost::filesystem; namespace { std::string exit_with_message_and_status(const char* message, int status) { #if defined(_MSC_VER) - constexpr const char* executable = "cpp-utils-test_exit_status.exe"; + auto executable = get_executable().parent_path() / "cpp-utils-test_exit_status.exe"; #else - constexpr const char* executable = "./test/cpp-utils/cpp-utils-test_exit_status"; + auto executable = get_executable().parent_path() / "cpp-utils-test_exit_status"; #endif - return std::string(executable) + " \"" + message + "\" " + std::to_string(status); + if (!bf::exists(executable)) { + throw std::runtime_error(executable.string() + " not found."); + } + return executable.string() + " \"" + message + "\" " + std::to_string(status); } } diff --git a/test/cryfs-cli/CMakeLists.txt b/test/cryfs-cli/CMakeLists.txt index b120419a..2d0b38c5 100644 --- a/test/cryfs-cli/CMakeLists.txt +++ b/test/cryfs-cli/CMakeLists.txt @@ -16,7 +16,7 @@ set(SOURCES ) add_executable(${PROJECT_NAME} ${SOURCES}) -target_link_libraries(${PROJECT_NAME} googletest cryfs-cli cryfs-unmount fspp-fuse) +target_link_libraries(${PROJECT_NAME} my-gtest-main googletest cryfs-cli cryfs-unmount fspp-fuse) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) diff --git a/test/cryfs/CMakeLists.txt b/test/cryfs/CMakeLists.txt index 463f13e6..77a025f4 100644 --- a/test/cryfs/CMakeLists.txt +++ b/test/cryfs/CMakeLists.txt @@ -24,7 +24,7 @@ set(SOURCES ) add_executable(${PROJECT_NAME} ${SOURCES}) -target_link_libraries(${PROJECT_NAME} googletest cryfs) +target_link_libraries(${PROJECT_NAME} my-gtest-main googletest cryfs) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) diff --git a/test/fspp/CMakeLists.txt b/test/fspp/CMakeLists.txt index 8f94cf2b..dabff1f1 100644 --- a/test/fspp/CMakeLists.txt +++ b/test/fspp/CMakeLists.txt @@ -102,7 +102,7 @@ set(SOURCES testutils/OpenFileHandle.cpp testutils/OpenFileHandle.h) add_executable(${PROJECT_NAME} ${SOURCES}) -target_link_libraries(${PROJECT_NAME} googletest fspp-interface fspp-fuse) +target_link_libraries(${PROJECT_NAME} my-gtest-main googletest fspp-interface fspp-fuse) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) diff --git a/test/gitversion/CMakeLists.txt b/test/gitversion/CMakeLists.txt index ea3221a6..51a5ccc1 100644 --- a/test/gitversion/CMakeLists.txt +++ b/test/gitversion/CMakeLists.txt @@ -6,7 +6,7 @@ set(SOURCES ) add_executable(${PROJECT_NAME} ${SOURCES}) -target_link_libraries(${PROJECT_NAME} googletest gitversion) +target_link_libraries(${PROJECT_NAME} my-gtest-main googletest gitversion) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) diff --git a/test/my-gtest-main/CMakeLists.txt b/test/my-gtest-main/CMakeLists.txt new file mode 100644 index 00000000..1d1e7e08 --- /dev/null +++ b/test/my-gtest-main/CMakeLists.txt @@ -0,0 +1,13 @@ +project (my-gtest-main) + +set(SOURCES + my-gtest-main.cpp +) + +add_library(${PROJECT_NAME} STATIC ${SOURCES}) +target_link_libraries(${PROJECT_NAME} PUBLIC googletest cpp-utils) +target_add_boost(${PROJECT_NAME} filesystem system) +target_include_directories(${PROJECT_NAME} PUBLIC .) + +target_enable_style_warnings(${PROJECT_NAME}) +target_activate_cpp14(${PROJECT_NAME}) diff --git a/test/my-gtest-main/my-gtest-main.cpp b/test/my-gtest-main/my-gtest-main.cpp new file mode 100644 index 00000000..c526e1a4 --- /dev/null +++ b/test/my-gtest-main/my-gtest-main.cpp @@ -0,0 +1,27 @@ +#include "my-gtest-main.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include + +namespace { + boost::optional executable; +} + +const boost::filesystem::path& get_executable() { + ASSERT(executable != boost::none, "Executable path not set"); + return *executable; +} + + +int main(int argc, char** argv) { + executable = boost::filesystem::path(argv[0]); + + // Since Google Mock depends on Google Test, InitGoogleMock() is + // also responsible for initializing Google Test. Therefore there's + // no need for calling testing::InitGoogleTest() separately. + testing::InitGoogleMock(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/my-gtest-main/my-gtest-main.h b/test/my-gtest-main/my-gtest-main.h new file mode 100644 index 00000000..d77b379f --- /dev/null +++ b/test/my-gtest-main/my-gtest-main.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +const boost::filesystem::path& get_executable(); diff --git a/test/parallelaccessstore/CMakeLists.txt b/test/parallelaccessstore/CMakeLists.txt index 0ec0b5e2..16170d17 100644 --- a/test/parallelaccessstore/CMakeLists.txt +++ b/test/parallelaccessstore/CMakeLists.txt @@ -6,7 +6,7 @@ set(SOURCES ) add_executable(${PROJECT_NAME} ${SOURCES}) -target_link_libraries(${PROJECT_NAME} googletest parallelaccessstore) +target_link_libraries(${PROJECT_NAME} my-gtest-main googletest parallelaccessstore) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) diff --git a/vendor/googletest/CMakeLists.txt b/vendor/googletest/CMakeLists.txt index 2b4b5d24..6c7e98ae 100644 --- a/vendor/googletest/CMakeLists.txt +++ b/vendor/googletest/CMakeLists.txt @@ -10,7 +10,6 @@ if (BUILD_TESTING) project (googletest) add_library(${PROJECT_NAME} dummy.cpp) target_link_libraries(${PROJECT_NAME} PUBLIC gtest gmock) - target_link_libraries(${PROJECT_NAME} PRIVATE gmock_main) target_include_directories(${PROJECT_NAME} SYSTEM INTERFACE ${gtest_INCLUDE_DIRS}/include SYSTEM ${gmock_INCLUDE_DIRS}/include) # Disable "missing override" warning because gmock MOCK_METHOD() don't use override :( From 0abe985dc020c113c61445ff7292790b4ba08630 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Sat, 23 Mar 2019 22:02:27 -0700 Subject: [PATCH 04/15] Mention musl fixes in ChangeLog --- ChangeLog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog.txt b/ChangeLog.txt index 1b179dda..cc2cf9cb 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -5,6 +5,9 @@ Fixed bugs: * When trying to migrate a file system from CryFS 0.9.3 or older, show an error message suggesting to first open it with 0.9.10 because we can't load that anymore. * The '--unmount-idle' parameter works again +Compatibility: +* Fixed some incompatibilities with systems using the musl libc + Other: * Updated to crypto++ 8.1 * Unit tests can now be run from any directory From e07a1c56048a398fab6df388e4828e2a738bed01 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Sun, 17 Mar 2019 01:08:57 -0700 Subject: [PATCH 05/15] Extract SignalHandler from SignalCatcher and also use it for backtrace --- .circleci/config.yml | 2 +- src/cpp-utils/CMakeLists.txt | 1 + src/cpp-utils/assert/backtrace_nonwindows.cpp | 19 +-- src/cpp-utils/process/SignalCatcher.cpp | 114 +------------- src/cpp-utils/process/SignalCatcher.h | 1 + src/cpp-utils/process/SignalHandler.cpp | 1 + src/cpp-utils/process/SignalHandler.h | 140 ++++++++++++++++++ test/cpp-utils/CMakeLists.txt | 1 + test/cpp-utils/process/SignalHandlerTest.cpp | 132 +++++++++++++++++ 9 files changed, 286 insertions(+), 125 deletions(-) create mode 100644 src/cpp-utils/process/SignalHandler.cpp create mode 100644 src/cpp-utils/process/SignalHandler.h create mode 100644 test/cpp-utils/process/SignalHandlerTest.cpp diff --git a/.circleci/config.yml b/.circleci/config.yml index ff163f73..6a5663e7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -479,7 +479,7 @@ jobs: OMP_NUM_THREADS: "1" CXXFLAGS: "-O2 -fsanitize=thread -fno-omit-frame-pointer" BUILD_TYPE: "Debug" - GTEST_ARGS: "--gtest_filter=-LoggingTest.LoggingAlsoWorksAfterFork:AssertTest_DebugBuild.*:SignalCatcherTest.*_thenDies:CliTest_Setup.*:CliTest_IntegrityCheck.*:*/CliTest_WrongEnvironment.*:CliTest_Unmount.*" + GTEST_ARGS: "--gtest_filter=-LoggingTest.LoggingAlsoWorksAfterFork:AssertTest_DebugBuild.*:SignalCatcherTest.*_thenDies:SignalHandlerTest.*_thenDies:SignalHandlerTest.givenMultipleSigIntHandlers_whenRaising_thenCatchesCorrectSignal:CliTest_Setup.*:CliTest_IntegrityCheck.*:*/CliTest_WrongEnvironment.*:CliTest_Unmount.*" CMAKE_FLAGS: "" RUN_TESTS: true clang_tidy: diff --git a/src/cpp-utils/CMakeLists.txt b/src/cpp-utils/CMakeLists.txt index 500e6ec1..2ecd690c 100644 --- a/src/cpp-utils/CMakeLists.txt +++ b/src/cpp-utils/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES process/daemonize.cpp process/subprocess.cpp process/SignalCatcher.cpp + process/SignalHandler.cpp tempfile/TempFile.cpp tempfile/TempDir.cpp network/HttpClient.cpp diff --git a/src/cpp-utils/assert/backtrace_nonwindows.cpp b/src/cpp-utils/assert/backtrace_nonwindows.cpp index 8e26df5a..c495d0b6 100644 --- a/src/cpp-utils/assert/backtrace_nonwindows.cpp +++ b/src/cpp-utils/assert/backtrace_nonwindows.cpp @@ -11,6 +11,7 @@ #include #include #include "../logging/logging.h" +#include // TODO Add file and line number on non-windows @@ -95,21 +96,15 @@ namespace { LOG(ERR, "SIGABRT\n{}", backtrace()); exit(1); } - void set_handler(int signum, void(*handler)(int)) { - auto result = signal(signum, handler); -#pragma GCC diagnostic push // SIG_ERR uses old style casts -#pragma GCC diagnostic ignored "-Wold-style-cast" - if (SIG_ERR == result) { - LOG(ERR, "Failed to set signal {} handler. Errno: {}", signum, errno); - } -#pragma GCC diagnostic pop - } } void showBacktraceOnCrash() { - set_handler(SIGSEGV, &sigsegv_handler); - set_handler(SIGABRT, &sigabrt_handler); - set_handler(SIGILL, &sigill_handler); + // the signal handler RAII objects will be initialized on first call (which will register the signal handler) + // and destroyed on program exit (which will unregister the signal handler) + + static SignalHandlerRAII<&sigsegv_handler> segv(SIGSEGV); + static SignalHandlerRAII<&sigabrt_handler> abrt(SIGABRT); + static SignalHandlerRAII<&sigill_handler> ill(SIGILL); } } diff --git a/src/cpp-utils/process/SignalCatcher.cpp b/src/cpp-utils/process/SignalCatcher.cpp index a89345e3..ae37d2d9 100644 --- a/src/cpp-utils/process/SignalCatcher.cpp +++ b/src/cpp-utils/process/SignalCatcher.cpp @@ -1,4 +1,5 @@ #include "SignalCatcher.h" +#include "SignalHandler.h" #include #include @@ -16,117 +17,6 @@ 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, details::SignalCatcherImpl* signal_occurred_flag) { @@ -211,7 +101,7 @@ public: private: std::atomic* _signal_occurred_flag; SignalCatcherRegisterer _registerer; - SignalHandlerRAII _handler; + SignalHandlerRAII<&got_signal> _handler; DISALLOW_COPY_AND_ASSIGN(SignalCatcherImpl); }; diff --git a/src/cpp-utils/process/SignalCatcher.h b/src/cpp-utils/process/SignalCatcher.h index 87d3c948..e8ae4614 100644 --- a/src/cpp-utils/process/SignalCatcher.h +++ b/src/cpp-utils/process/SignalCatcher.h @@ -37,6 +37,7 @@ private: DISALLOW_COPY_AND_ASSIGN(SignalCatcher); }; + } #endif diff --git a/src/cpp-utils/process/SignalHandler.cpp b/src/cpp-utils/process/SignalHandler.cpp new file mode 100644 index 00000000..26eafe4d --- /dev/null +++ b/src/cpp-utils/process/SignalHandler.cpp @@ -0,0 +1 @@ +#include "SignalHandler.h" diff --git a/src/cpp-utils/process/SignalHandler.h b/src/cpp-utils/process/SignalHandler.h new file mode 100644 index 00000000..ed8752d9 --- /dev/null +++ b/src/cpp-utils/process/SignalHandler.h @@ -0,0 +1,140 @@ +#pragma once +#ifndef MESSMER_CPPUTILS_PROCESS_SIGNALHANDLER_H_ +#define MESSMER_CPPUTILS_PROCESS_SIGNALHANDLER_H_ + +#include +#include +#include + +// TODO Test SignalHandler + +/** + * A SignalHandlerRAII instance replaces the signal handler for the given signal with the given handler + * as long as it is alive and sets it to the previous handler once it dies. + * This way, it can be used for stacking different signal handlers on top of each other. + */ + +namespace cpputils { + +using SignalHandlerFunction = void(int); + +#if !defined(_MSC_VER) + +template +class SignalHandlerRAII final { +public: + explicit 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 = handler; // 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 (handler != 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 +namespace details { +// 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. +template +class SignalHandlerRunningRAII final { +public: + explicit 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, &details::wrap_signal_handler); + 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; +}; + +template +void wrap_signal_handler(int signal) { + SignalHandlerRunningRAII disable_signal_processing_while_handler_running_and_reset_handler_afterwards(signal); + (*handler)(signal); +} +} + +template +class SignalHandlerRAII final { +public: + explicit SignalHandlerRAII(int signal) + : _old_handler(nullptr), _signal(signal) { + _old_handler = ::signal(_signal, &details::wrap_signal_handler); + 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 != &details::wrap_signal_handler) { + 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); +}; + +#endif + +} + +#endif diff --git a/test/cpp-utils/CMakeLists.txt b/test/cpp-utils/CMakeLists.txt index 9a0c71cd..02cc9d5d 100644 --- a/test/cpp-utils/CMakeLists.txt +++ b/test/cpp-utils/CMakeLists.txt @@ -17,6 +17,7 @@ set(SOURCES process/subprocess_include_test.cpp process/SubprocessTest.cpp process/SignalCatcherTest.cpp + process/SignalHandlerTest.cpp tempfile/TempFileTest.cpp tempfile/TempFileIncludeTest.cpp tempfile/TempDirIncludeTest.cpp diff --git a/test/cpp-utils/process/SignalHandlerTest.cpp b/test/cpp-utils/process/SignalHandlerTest.cpp new file mode 100644 index 00000000..194b2550 --- /dev/null +++ b/test/cpp-utils/process/SignalHandlerTest.cpp @@ -0,0 +1,132 @@ +#include +#include + +using namespace cpputils; + +namespace { +std::atomic triggered; + +void trigger(int signal) { + triggered = signal; +} + +void raise_signal(int signal) { + int error = ::raise(signal); + if (error != 0) { + throw std::runtime_error("Error raising signal"); + } +} + +TEST(SignalHandlerTest, givenNoSignalHandler_whenRaisingSigint_thenDies) { + EXPECT_DEATH( + raise_signal(SIGINT), + "" + ); +} + +TEST(SignalHandlerTest, givenNoSignalHandler_whenRaisingSigterm_thenDies) { + EXPECT_DEATH( + raise_signal(SIGTERM), + "" + ); +} + +TEST(SignalHandlerTest, givenSigIntHandler_whenRaisingSigInt_thenCatches) { + triggered = 0; + + SignalHandlerRAII<&trigger> handler(SIGINT); + + raise_signal(SIGINT); + EXPECT_EQ(SIGINT, triggered); +} + +TEST(SignalHandlerTest, givenSigIntHandler_whenRaisingSigTerm_thenDies) { + SignalHandlerRAII<&trigger> handler(SIGINT); + + EXPECT_DEATH( + raise_signal(SIGTERM), + "" + ); +} + +TEST(SignalHandlerTest, givenSigTermHandler_whenRaisingSigTerm_thenCatches) { + triggered = 0; + + SignalHandlerRAII<&trigger> handler(SIGTERM); + + raise_signal(SIGTERM); + EXPECT_EQ(SIGTERM, triggered); +} + +TEST(SignalHandlerTest, givenSigTermHandler_whenRaisingSigInt_thenDies) { + SignalHandlerRAII<&trigger> handler(SIGTERM); + + EXPECT_DEATH( + raise_signal(SIGINT), + "" + ); +} + +TEST(SignalHandlerTest, givenSigIntAndSigTermHandlers_whenRaising_thenCatchesCorrectSignal) { + triggered = 0; + + SignalHandlerRAII<&trigger> handler1(SIGINT); + SignalHandlerRAII<&trigger> handler2(SIGTERM); + + raise_signal(SIGINT); + EXPECT_EQ(SIGINT, triggered); + + raise_signal(SIGTERM); + EXPECT_EQ(SIGTERM, triggered); + + raise_signal(SIGINT); + EXPECT_EQ(SIGINT, triggered); +} + +std::atomic triggered_count_1; +std::atomic triggered_count_2; + +void trigger1(int) { + ++triggered_count_1; +} + +void trigger2(int) { + ++triggered_count_2; +} + +TEST(SignalHandlerTest, givenMultipleSigIntHandlers_whenRaising_thenCatchesCorrectSignal) { + triggered_count_1 = 0; + triggered_count_2 = 0; + + { + SignalHandlerRAII<&trigger1> handler1(SIGINT); + + { + SignalHandlerRAII<&trigger2> handler2(SIGINT); + + raise_signal(SIGINT); + EXPECT_EQ(0, triggered_count_1); + EXPECT_EQ(1, triggered_count_2); + + raise_signal(SIGINT); + EXPECT_EQ(0, triggered_count_1); + EXPECT_EQ(2, triggered_count_2); + } + + raise_signal(SIGINT); + EXPECT_EQ(1, triggered_count_1); + EXPECT_EQ(2, triggered_count_2); + + raise_signal(SIGINT); + EXPECT_EQ(2, triggered_count_1); + EXPECT_EQ(2, triggered_count_2); + + } + + EXPECT_DEATH( + raise_signal(SIGINT), + "" + ); +} + +} From 96ae461d3401865c7b2e9088436db25101fa0dc5 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Sun, 24 Mar 2019 18:11:46 -0700 Subject: [PATCH 06/15] Remove superfluous SignalHandlerRunningRAII (it is already called inside SignalHandler.h) --- src/cpp-utils/process/SignalCatcher.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/cpp-utils/process/SignalCatcher.cpp b/src/cpp-utils/process/SignalCatcher.cpp index ae37d2d9..c3fccb30 100644 --- a/src/cpp-utils/process/SignalCatcher.cpp +++ b/src/cpp-utils/process/SignalCatcher.cpp @@ -110,10 +110,6 @@ private: namespace { 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 SignalCatcherRegistry::singleton().find(signal)->setSignalOccurred(); } } From 807f0dc01b5f82c744b10a8adad80339fe2309f3 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Wed, 27 Feb 2019 23:34:49 -0800 Subject: [PATCH 07/15] Use libunwind instead of libbacktrace to build stack traces. This fixes a segfault issue with platforms using libexecinfo and is generally more portable. --- .circleci/config.yml | 50 ++++++++++ CMakeLists.txt | 3 +- ChangeLog.txt | 1 + README.md | 5 +- cmake-utils/FindLibunwind.cmake | 44 +++++++++ src/cpp-utils/CMakeLists.txt | 15 +-- src/cpp-utils/assert/backtrace_nonwindows.cpp | 93 +++++++++++-------- src/cpp-utils/process/SignalCatcher.cpp | 3 +- src/cpp-utils/process/SignalHandler.cpp | 65 +++++++++++++ src/cpp-utils/process/SignalHandler.h | 6 ++ test/cpp-utils/assert/backtrace_test.cpp | 31 +++++-- 11 files changed, 254 insertions(+), 62 deletions(-) create mode 100644 cmake-utils/FindLibunwind.cmake diff --git a/.circleci/config.yml b/.circleci/config.yml index 6a5663e7..7b90b925 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -86,6 +86,50 @@ references: cmake --version /usr/local/bin/$CC --version /usr/local/bin/$CXX --version + upgrade_libunwind_pre: &upgrade_libunwind_pre + restore_cache: + keys: + # Find the most recent cache from any branch + - v1_upgrade_libunwind_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} + upgrade_libunwind_post: &upgrade_libunwind_post + save_cache: + key: v1_upgrade_libunwind_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} + paths: + - /tmp/libunwind-1.2.1 + upgrade_libunwind: &upgrade_libunwind + run: + name: Upgrade Libunwind + command: | + # We need to install libunwind manually because Circle CI uses Ubuntu 14.04 and the default libunwind version + # on that Ubuntu suffers from http://savannah.nongnu.org/bugs/?43752. + # This isn't an issue for any later version of Ubuntu. + + # Detect number of CPU cores + export NUMCORES=`nproc` + echo Using $NUMCORES cores + # Download and prepare libunwind (only if not already present from cache) + if [ ! -d "/tmp/libunwind-1.2.1" ]; then + echo "Didn't find libunwind in cache. Downloading and building." + wget -O /tmp/libunwind-1.2.1.tar.gz http://download.savannah.nongnu.org/releases/libunwind/libunwind-1.2.1.tar.gz + if [ $(sha512sum /tmp/libunwind-1.2.1.tar.gz | awk '{print $1;}') == "af7c280d2a963779a4a2711887618bc96383011e4e5d52e4085aa7fb351e55e357468f6ff85e66a216f1c6826538f498335a917a5970575c93be74c96316319b" ]; then + echo Correct sha512sum + else + echo Wrong sha512sum + sha512sum /tmp/libunwind-1.2.1.tar.gz + exit 1 + fi + echo Extracting... + tar -xf /tmp/libunwind-1.2.1.tar.gz -C /tmp + rm -rf /tmp/libunwind-1.2.1.tar.gz + cd /tmp/libunwind-1.2.1 + ./configure + make -j${NUMCORES} + else + echo Found libunwind in cache. Use cache and build. + fi + # Compile and install libunwind (if cached, this should be fast) + cd /tmp/libunwind-1.2.1 + sudo make -j${NUMCORES} install upgrade_boost_pre: &upgrade_boost_pre restore_cache: keys: @@ -189,6 +233,9 @@ references: - <<: *container_setup_pre - <<: *container_setup - <<: *container_setup_post + - <<: *upgrade_libunwind_pre + - <<: *upgrade_libunwind + - <<: *upgrade_libunwind_post - <<: *upgrade_boost_pre - <<: *upgrade_boost - <<: *upgrade_boost_post @@ -489,6 +536,9 @@ jobs: - <<: *container_setup_pre - <<: *container_setup - <<: *container_setup_post + - <<: *upgrade_libunwind_pre + - <<: *upgrade_libunwind + - <<: *upgrade_libunwind_post - <<: *upgrade_boost_pre - <<: *upgrade_boost - <<: *upgrade_boost_post diff --git a/CMakeLists.txt b/CMakeLists.txt index 45682b44..00018bfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,8 @@ cmake_policy(SET CMP0054 NEW) project(cryfs) -include(cmake-utils/utils.cmake) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake-utils) +include(utils) require_gcc_version(5.0) require_clang_version(4.0) diff --git a/ChangeLog.txt b/ChangeLog.txt index cc2cf9cb..e1f9d1e8 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -7,6 +7,7 @@ Fixed bugs: Compatibility: * Fixed some incompatibilities with systems using the musl libc +* Use libunwind instead of libbacktrace to build stack traces. This fixes a segfault issue with platforms using libexecinfo and is generally more portable. Other: * Updated to crypto++ 8.1 diff --git a/README.md b/README.md index 1309fc0d..41a99aef 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,15 @@ Requirements - libFUSE version >= 2.8.6 (including development headers), on Mac OS X instead install osxfuse from https://osxfuse.github.io/ - Python >= 2.7 - OpenMP + - Libunwind You can use the following commands to install these requirements # Ubuntu - $ sudo apt-get install git g++ cmake make libcurl4-openssl-dev libboost-filesystem-dev libboost-system-dev libboost-chrono-dev libboost-program-options-dev libboost-thread-dev libssl-dev libfuse-dev python + $ sudo apt install git g++ cmake make libcurl4-openssl-dev libboost-filesystem-dev libboost-system-dev libboost-chrono-dev libboost-program-options-dev libboost-thread-dev libssl-dev libfuse-dev python libunwind-dev # Fedora - sudo dnf install git gcc-c++ cmake make libcurl-devel boost-devel boost-static openssl-devel fuse-devel python + sudo dnf install git gcc-c++ cmake make libcurl-devel boost-devel boost-static openssl-devel fuse-devel python libunwind-devel # Macintosh brew install cmake boost openssl libomp diff --git a/cmake-utils/FindLibunwind.cmake b/cmake-utils/FindLibunwind.cmake new file mode 100644 index 00000000..a41f7dd6 --- /dev/null +++ b/cmake-utils/FindLibunwind.cmake @@ -0,0 +1,44 @@ +# Taken from https://github.com/monero-project/monero/blob/31bdf7bd113c2576fe579ef3a25a2d8fef419ffc/cmake/FindLibunwind.cmake +# modifications: +# - remove linkage against gcc_eh because it was causing segfaults in various of our unit tests + +# - Try to find libunwind +# Once done this will define +# +# LIBUNWIND_FOUND - system has libunwind +# LIBUNWIND_INCLUDE_DIR - the libunwind include directory +# LIBUNWIND_LIBRARIES - Link these to use libunwind +# LIBUNWIND_DEFINITIONS - Compiler switches required for using libunwind + +# Copyright (c) 2006, Alexander Dymo, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +find_path(LIBUNWIND_INCLUDE_DIR libunwind.h + /usr/include + /usr/local/include + ) + +find_library(LIBUNWIND_LIBRARIES NAMES unwind ) +if(NOT LIBUNWIND_LIBRARIES STREQUAL "LIBUNWIND_LIBRARIES-NOTFOUND") + if (CMAKE_COMPILER_IS_GNUCC) + set(LIBUNWIND_LIBRARIES "${LIBUNWIND_LIBRARIES}") + endif() +endif() + +# some versions of libunwind need liblzma, and we don't use pkg-config +# so we just look whether liblzma is installed, and add it if it is. +# It might not be actually needed, but doesn't hurt if it is not. +# We don't need any headers, just the lib, as it's privately needed. +message(STATUS "looking for liblzma") +find_library(LIBLZMA_LIBRARIES lzma ) +if(NOT LIBLZMA_LIBRARIES STREQUAL "LIBLZMA_LIBRARIES-NOTFOUND") + message(STATUS "liblzma found") + set(LIBUNWIND_LIBRARIES "${LIBUNWIND_LIBRARIES};${LIBLZMA_LIBRARIES}") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Libunwind "Could not find libunwind" LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARIES) +# show the LIBUNWIND_INCLUDE_DIR and LIBUNWIND_LIBRARIES variables only in the advanced view +mark_as_advanced(LIBUNWIND_INCLUDE_DIR LIBUNWIND_LIBRARIES ) \ No newline at end of file diff --git a/src/cpp-utils/CMakeLists.txt b/src/cpp-utils/CMakeLists.txt index 2ecd690c..e1303bd8 100644 --- a/src/cpp-utils/CMakeLists.txt +++ b/src/cpp-utils/CMakeLists.txt @@ -61,13 +61,14 @@ set(SOURCES add_library(${PROJECT_NAME} STATIC ${SOURCES}) - -if(NOT MSVC) - find_package(Backtrace REQUIRED) - target_include_directories(${PROJECT_NAME} PUBLIC ${Backtrace_INCLUDE_DIRS}) - target_link_libraries(${PROJECT_NAME} PUBLIC ${Backtrace_LIBRARIES}) -else() - target_link_libraries(${PROJECT_NAME} PUBLIC DbgHelp) +if(MSVC) + target_link_libraries(${PROJECT_NAME} PUBLIC DbgHelp) +elseif(NOT APPLE) + # note: We use the libunwind code on Apple, but we don't seem to need these lines to link against it. + find_package(Libunwind REQUIRED) + target_include_directories(${PROJECT_NAME} PRIVATE ${LIBUNWIND_INCLUDE_DIR}) + target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBUNWIND_LIBRARIES}) + target_compile_definitions(${PROJECT_NAME} PRIVATE ${LIBUNWIND_DEFINITIONS}) endif() if (NOT MSVC) diff --git a/src/cpp-utils/assert/backtrace_nonwindows.cpp b/src/cpp-utils/assert/backtrace_nonwindows.cpp index c495d0b6..5411bd55 100644 --- a/src/cpp-utils/assert/backtrace_nonwindows.cpp +++ b/src/cpp-utils/assert/backtrace_nonwindows.cpp @@ -1,18 +1,15 @@ #if !defined(_MSC_VER) -#include "backtrace.h" -#include #include -#include -#include #include -#include #include -#include -#include + #include "../logging/logging.h" #include +#define UNW_LOCAL_ONLY +#include + // TODO Add file and line number on non-windows using std::string; @@ -30,7 +27,12 @@ namespace { demangledName = abi::__cxa_demangle(mangledName.c_str(), NULL, NULL, &status); if (status == 0) { result = demangledName; + } else if (status == -2) { + // mangledName was not a c++ mangled name, probably because it's a C name like for static + // initialization or stuff. Let's just return the name instead. + result = mangledName; } else { + // other error result = "[demangling error " + std::to_string(status) + "]" + mangledName; } free(demangledName); @@ -41,46 +43,55 @@ namespace { } } - void pretty_print(std::ostream& str, const void *addr) { - Dl_info info; - if (0 == dladdr(addr, &info)) { - str << "[failed parsing line]"; - } else { - if (nullptr == info.dli_fname) { - str << "[no dli_fname]"; - } else { - str << info.dli_fname; - } - str << ":" << std::hex << info.dli_fbase << " "; - if (nullptr == info.dli_sname) { - str << "[no symbol name]"; - } else if (info.dli_sname[0] == '_') { - // is a mangled name - str << demangle(info.dli_sname); - } else { - // is not a mangled name - str << info.dli_sname; - } - str << " : " << std::hex << info.dli_saddr; - } - } + void pretty_print(std::ostringstream& str, unw_cursor_t* cursor) { + constexpr unsigned int MAXNAMELEN=256; + char name[MAXNAMELEN]; + unw_word_t offp = 0, ip = 0; - string backtrace_to_string(void *array[], size_t size) { - ostringstream result; - for (size_t i = 0; i < size; ++i) { - result << "#" << std::dec << i << " "; - pretty_print(result, array[i]); - result << "\n"; + int status = unw_get_reg(cursor, UNW_REG_IP, &ip); + if (0 != status) { + str << "[unw_get_reg error: " << status << "]: "; + } else { + str << "0x" << std::hex << ip << ": "; } - return result.str(); + + status = unw_get_proc_name(cursor, name, MAXNAMELEN, &offp); + if (0 != status) { + str << "[unw_get_proc_name error: " << status << "]"; + } else { + str << demangle(name); + } + str << " +0x" << std::hex << offp; } } string backtrace() { - constexpr unsigned int MAX_SIZE = 100; - void *array[MAX_SIZE]; - size_t size = ::backtrace(array, MAX_SIZE); - return backtrace_to_string(array, size); + std::ostringstream result; + + unw_context_t uc; + int status = unw_getcontext(&uc); + if (0 != status) { + return "[unw_getcontext error: " + std::to_string(status) + "]"; + } + + unw_cursor_t cursor; + status = unw_init_local(&cursor, &uc); + if (0 != status) { + return "[unw_init_local error: " + std::to_string(status) + "]"; + } + + + size_t line = 0; + while ((status = unw_step(&cursor)) > 0) { + result << "#" << std::dec << (line++) << " "; + pretty_print(result, &cursor); + result << "\n"; + } + if (status != 0) { + result << "[unw_step error :" << status << "]"; + } + + return result.str(); } namespace { diff --git a/src/cpp-utils/process/SignalCatcher.cpp b/src/cpp-utils/process/SignalCatcher.cpp index c3fccb30..ea718a5d 100644 --- a/src/cpp-utils/process/SignalCatcher.cpp +++ b/src/cpp-utils/process/SignalCatcher.cpp @@ -88,7 +88,8 @@ public: , _registerer(signal, this) , _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, + // - when registering the signal handler, the SignalCatcher impl already has a valid _signal_occurred_flag set. + // - when registering the signal handler fails, the _registerer will be destroyed again, unregistering this SignalCatcherImpl, // 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 diff --git a/src/cpp-utils/process/SignalHandler.cpp b/src/cpp-utils/process/SignalHandler.cpp index 26eafe4d..cb04186f 100644 --- a/src/cpp-utils/process/SignalHandler.cpp +++ b/src/cpp-utils/process/SignalHandler.cpp @@ -1 +1,66 @@ #include "SignalHandler.h" + +#if !defined(_MSC_VER) + +namespace cpputils { +namespace detail { +namespace { + +std::atomic already_checked_for_libunwind_bug(false); + +} + +sigset_t _sigemptyset() { + sigset_t result; + int error = sigemptyset(&result); + if (0 != error) { + throw std::runtime_error("Error calling sigemptyset. Errno: " + std::to_string(errno)); + } + return result; +} + +void _sigmask(sigset_t* new_value, sigset_t* old_value) { + int error = pthread_sigmask(SIG_SETMASK, new_value, old_value); + if (0 != error) { + throw std::runtime_error("Error calling pthread_sigmask. Errno: " + std::to_string(errno)); + } +} + +// Check that we're not running into http://savannah.nongnu.org/bugs/?43752 +void check_against_libunwind_bug() { + if (already_checked_for_libunwind_bug.exchange(true)) { + return; + } + + // set new signal handler + sigset_t old_value = _sigemptyset(); + sigset_t new_value = _sigemptyset(); + + _sigmask(&new_value, &old_value); + + sigset_t before_exception = _sigemptyset(); + _sigmask(nullptr, &before_exception); + + // throw an exception + try { + throw std::runtime_error("Some exception"); + } catch (const std::exception &e) {} + + sigset_t after_exception = _sigemptyset(); + _sigmask(nullptr, &after_exception); + + // reset to old signal handler + _sigmask(&old_value, nullptr); + + // check that the exception didn't screw up the signal mask + if (0 != std::memcmp(&before_exception, &after_exception, sizeof(sigset_t))) { // NOLINT(cppcoreguidelines-pro-type-union-access) + ASSERT(false, + "Throwing an exception screwed up the signal mask. You likely ran into this bug: http://savannah.nongnu.org/bugs/?43752 . Please build CryFS against a newer version of libunwind or build libunwind with --disable-cxx-exceptions."); + } +} + + +} +} + +#endif diff --git a/src/cpp-utils/process/SignalHandler.h b/src/cpp-utils/process/SignalHandler.h index ed8752d9..4af13568 100644 --- a/src/cpp-utils/process/SignalHandler.h +++ b/src/cpp-utils/process/SignalHandler.h @@ -20,11 +20,17 @@ using SignalHandlerFunction = void(int); #if !defined(_MSC_VER) +namespace detail { +void check_against_libunwind_bug(); +} + template class SignalHandlerRAII final { public: explicit SignalHandlerRAII(int signal) : _old_handler(), _signal(signal) { + detail::check_against_libunwind_bug(); + struct sigaction new_signal_handler{}; std::memset(&new_signal_handler, 0, sizeof(new_signal_handler)); new_signal_handler.sa_handler = handler; // NOLINT(cppcoreguidelines-pro-type-union-access) diff --git a/test/cpp-utils/assert/backtrace_test.cpp b/test/cpp-utils/assert/backtrace_test.cpp index 77d18bc1..708edaa7 100644 --- a/test/cpp-utils/assert/backtrace_test.cpp +++ b/test/cpp-utils/assert/backtrace_test.cpp @@ -31,11 +31,6 @@ TEST(BacktraceTest, ContainsBacktrace) { } #if !(defined(_MSC_VER) && defined(NDEBUG)) -TEST(BacktraceTest, ContainsExecutableName) { - string backtrace = cpputils::backtrace(); - EXPECT_THAT(backtrace, HasSubstr("cpp-utils-test")); -} - TEST(BacktraceTest, ContainsTopLevelLine) { string backtrace = cpputils::backtrace(); EXPECT_THAT(backtrace, HasSubstr("BacktraceTest")); @@ -92,22 +87,38 @@ TEST(BacktraceTest, DoesntCrashOnCaughtException) { #if !(defined(_MSC_VER) && defined(NDEBUG)) TEST(BacktraceTest, ShowBacktraceOnNullptrAccess) { auto output = call_process_exiting_with_nullptr_violation(); - EXPECT_THAT(output, HasSubstr("cpp-utils-test_exit_signal")); +#if !defined(_MSC_VER) + EXPECT_THAT(output, HasSubstr("cpputils::(anonymous namespace)::sig")); +#else + EXPECT_THAT(output, HasSubstr("handle_exit_signal")); +#endif } TEST(BacktraceTest, ShowBacktraceOnSigSegv) { auto output = call_process_exiting_with_sigsegv(); - EXPECT_THAT(output, HasSubstr("cpp-utils-test_exit_signal")); +#if defined(_MSC_VER) + EXPECT_THAT(output, HasSubstr("handle_exit_signal")); +#else + EXPECT_THAT(output, HasSubstr("cpputils::(anonymous namespace)::sigsegv_handler(int)")); +#endif } TEST(BacktraceTest, ShowBacktraceOnUnhandledException) { auto output = call_process_exiting_with_exception("my_exception_message"); - EXPECT_THAT(output, HasSubstr("cpp-utils-test_exit_signal")); +#if defined(_MSC_VER) + EXPECT_THAT(output, HasSubstr("handle_exit_signal")); +#else + EXPECT_THAT(output, HasSubstr("cpputils::(anonymous namespace)::sigabrt_handler(int)")); +#endif } TEST(BacktraceTest, ShowBacktraceOnSigIll) { auto output = call_process_exiting_with_sigill(); - EXPECT_THAT(output, HasSubstr("cpp-utils-test_exit_signal")); +#if defined(_MSC_VER) + EXPECT_THAT(output, HasSubstr("handle_exit_signal")); +#else + EXPECT_THAT(output, HasSubstr("cpputils::(anonymous namespace)::sigill_handler(int)")); +#endif } #else TEST(BacktraceTest, ShowBacktraceOnNullptrAccess) { @@ -134,7 +145,7 @@ TEST(BacktraceTest, ShowBacktraceOnSigIll) { #if !defined(_MSC_VER) TEST(BacktraceTest, ShowBacktraceOnSigAbrt) { auto output = call_process_exiting_with_sigabrt(); - EXPECT_THAT(output, HasSubstr("cpp-utils-test_exit_signal")); + EXPECT_THAT(output, HasSubstr("cpputils::(anonymous namespace)::sigabrt_handler(int)")); } TEST(BacktraceTest, ShowBacktraceOnSigAbrt_ShowsCorrectSignalName) { From f741af578cf4b1fb5f5a12abcc5f159d5db7b068 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Sun, 24 Mar 2019 22:41:47 -0700 Subject: [PATCH 08/15] Remove duplicate CI job --- .circleci/config.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7b90b925..f92a541c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -466,18 +466,6 @@ jobs: GTEST_ARGS: "" CMAKE_FLAGS: "-DUSE_WERROR=on" RUN_TESTS: false - gcc_werror: - <<: *job_definition - environment: - CC: gcc-8 - CXX: g++-8 - BUILD_TOOLSET: gcc - APT_COMPILER_PACKAGE: "g++-8" - CXXFLAGS: "-Werror" - BUILD_TYPE: "Release" - GTEST_ARGS: "" - CMAKE_FLAGS: "" - RUN_TESTS: false no_compatibility: <<: *job_definition environment: From 2c7b5f333ce71f3e2c81ed7721694a3fd39ce487 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Fri, 29 Mar 2019 21:21:37 -0700 Subject: [PATCH 09/15] Exclude any vendor libraries from the all build target --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 00018bfb..fdbff715 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ if(MSVC) add_definitions(/bigobj) endif() -add_subdirectory(vendor) +add_subdirectory(vendor EXCLUDE_FROM_ALL) add_subdirectory(src) add_subdirectory(doc) add_subdirectory(test) From 1409b061b7c2e3a6ebaec6acede66890b8cc11ac Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Fri, 29 Mar 2019 00:35:31 -0700 Subject: [PATCH 10/15] Update to DokanY 1.2.1 and Boost 1.67 on Windows --- ChangeLog.txt | 1 + README.md | 2 +- appveyor.yml | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index e1f9d1e8..90d2170d 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -11,6 +11,7 @@ Compatibility: Other: * Updated to crypto++ 8.1 +* Updated to DokanY 1.2.1 * Unit tests can now be run from any directory diff --git a/README.md b/README.md index 41a99aef..ffea44e6 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ Building on Windows (experimental) Build with Visual Studio 2017 and pass in the following flags to CMake: - -DDOKAN_PATH=[dokan library location, e.g. "C:\Program Files\Dokan\DokanLibrary-1.1.0"] + -DDOKAN_PATH=[dokan library location, e.g. "C:\Program Files\Dokan\DokanLibrary-1.2.1"] -DBOOST_ROOT=[path to root of boost installation] If you set these variables correctly in the `CMakeSettings.json` file, you should be able to open the cryfs source folder with Visual Studio 2017. diff --git a/appveyor.yml b/appveyor.yml index 7085040e..46ef3d44 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,14 +25,14 @@ init: - cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\%VisualStudioVersion%\Community\VC\Auxiliary\Build\vcvars%arch%.bat" install: - - choco install -y dokany --version 1.1.0.2000 --installargs INSTALLDEVFILES=1 + - choco install -y dokany --version 1.2.1.2000 --installargs INSTALLDEVFILES=1 - cmake --version build_script: - cmd: mkdir build - cmd: cd build # note: The cmake+ninja workflow requires us to set build type in both cmake commands ('cmake' and 'cmake --build'), otherwise the cryfs.exe will depend on debug versions of the visual studio c++ runtime (i.e. msvcp140d.dll) - - cmd: cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DBUILD_TESTING=on -DBOOST_ROOT="C:/Libraries/boost_1_65_1" -DDOKAN_PATH="C:/Program Files/Dokan/DokanLibrary-1.1.0" + - cmd: cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DBUILD_TESTING=on -DBOOST_ROOT="C:/Libraries/boost_1_67_0" -DDOKAN_PATH="C:/Program Files/Dokan/DokanLibrary-1.2.1" - cmd: cmake --build . --config %CONFIGURATION% - cmd: .\test\gitversion\gitversion-test.exe # cpp-utils-test disables ThreadDebuggingTest_ThreadName.*_thenIsCorrect because the appveyor image is too old to support the API needed for that From 276e7f08e4bbfb3544d71b6f1f7adbe1ca592465 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Mon, 1 Apr 2019 19:18:49 -0700 Subject: [PATCH 11/15] Switch from libunwind to boost::stacktrace --- .circleci/config.yml | 68 ++-------- ChangeLog.txt | 2 +- README.md | 7 +- cmake-utils/utils.cmake | 4 +- src/cpp-utils/CMakeLists.txt | 17 ++- src/cpp-utils/assert/backtrace_nonwindows.cpp | 123 ++++-------------- src/cpp-utils/process/SignalHandler.cpp | 65 --------- src/cpp-utils/process/SignalHandler.h | 6 - test/cpp-utils/assert/backtrace_test.cpp | 35 +++-- 9 files changed, 73 insertions(+), 254 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f92a541c..b9e70445 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -86,60 +86,16 @@ references: cmake --version /usr/local/bin/$CC --version /usr/local/bin/$CXX --version - upgrade_libunwind_pre: &upgrade_libunwind_pre - restore_cache: - keys: - # Find the most recent cache from any branch - - v1_upgrade_libunwind_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} - upgrade_libunwind_post: &upgrade_libunwind_post - save_cache: - key: v1_upgrade_libunwind_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} - paths: - - /tmp/libunwind-1.2.1 - upgrade_libunwind: &upgrade_libunwind - run: - name: Upgrade Libunwind - command: | - # We need to install libunwind manually because Circle CI uses Ubuntu 14.04 and the default libunwind version - # on that Ubuntu suffers from http://savannah.nongnu.org/bugs/?43752. - # This isn't an issue for any later version of Ubuntu. - - # Detect number of CPU cores - export NUMCORES=`nproc` - echo Using $NUMCORES cores - # Download and prepare libunwind (only if not already present from cache) - if [ ! -d "/tmp/libunwind-1.2.1" ]; then - echo "Didn't find libunwind in cache. Downloading and building." - wget -O /tmp/libunwind-1.2.1.tar.gz http://download.savannah.nongnu.org/releases/libunwind/libunwind-1.2.1.tar.gz - if [ $(sha512sum /tmp/libunwind-1.2.1.tar.gz | awk '{print $1;}') == "af7c280d2a963779a4a2711887618bc96383011e4e5d52e4085aa7fb351e55e357468f6ff85e66a216f1c6826538f498335a917a5970575c93be74c96316319b" ]; then - echo Correct sha512sum - else - echo Wrong sha512sum - sha512sum /tmp/libunwind-1.2.1.tar.gz - exit 1 - fi - echo Extracting... - tar -xf /tmp/libunwind-1.2.1.tar.gz -C /tmp - rm -rf /tmp/libunwind-1.2.1.tar.gz - cd /tmp/libunwind-1.2.1 - ./configure - make -j${NUMCORES} - else - echo Found libunwind in cache. Use cache and build. - fi - # Compile and install libunwind (if cached, this should be fast) - cd /tmp/libunwind-1.2.1 - sudo make -j${NUMCORES} install upgrade_boost_pre: &upgrade_boost_pre restore_cache: keys: # Find the most recent cache from any branch - - v3_upgrade_boost_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} + - v4_upgrade_boost_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} upgrade_boost_post: &upgrade_boost_post save_cache: - key: v3_upgrade_boost_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} + key: v4_upgrade_boost_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} paths: - - /tmp/boost_1_57_0 + - /tmp/boost_1_65_1 upgrade_boost: &upgrade_boost run: name: Upgrade Boost @@ -148,10 +104,10 @@ references: export NUMCORES=`nproc` echo Using $NUMCORES cores # Download and prepare boost (only if not already present from cache) - if [ ! -d "/tmp/boost_1_57_0" ]; then + if [ ! -d "/tmp/boost_1_65_1" ]; then echo "Didn't find boost in cache. Downloading and building." - wget -O /tmp/boost.tar.bz2 https://sourceforge.net/projects/boost/files/boost/1.57.0/boost_1_57_0.tar.bz2/download - if [ $(sha512sum /tmp/boost.tar.bz2 | awk '{print $1;}') == "61881440fd89644c43c6e3bc6292e9fed75a6d3a76f98654b189d0ed4e1087d77b585884e882270c08bf9f7132b173bfc1fde05848e06aa78ba7f1008d10714d" ]; then + wget -O /tmp/boost.tar.bz2 https://sourceforge.net/projects/boost/files/boost/1.65.1/boost_1_65_1.tar.bz2/download + if [ $(sha512sum /tmp/boost.tar.bz2 | awk '{print $1;}') == "a9e6866d3bb3e7c198f442ff09f5322f58064dca79bc420f2f0168eb63964226dfbc4f034a5a5e5958281fdf7518a1b057c894fbda0b61fced59c1661bf30f1a" ]; then echo Correct sha512sum else echo Wrong sha512sum @@ -161,14 +117,14 @@ references: echo Extracting... tar -xf /tmp/boost.tar.bz2 -C /tmp rm -rf boost.tar.bz2 - cd /tmp/boost_1_57_0 + cd /tmp/boost_1_65_1 ./bootstrap.sh --with-toolset=${BUILD_TOOLSET} --with-libraries=filesystem,thread,chrono,program_options cd .. else echo Found boost in cache. Use cache and build. fi # Compile and install boost (if cached, this should be fast) - cd /tmp/boost_1_57_0 + cd /tmp/boost_1_65_1 sudo ./b2 toolset=${BUILD_TOOLSET} link=static cxxflags=-fPIC -d0 -j$NUMCORES install build_pre: &build_pre restore_cache: @@ -233,9 +189,6 @@ references: - <<: *container_setup_pre - <<: *container_setup - <<: *container_setup_post - - <<: *upgrade_libunwind_pre - - <<: *upgrade_libunwind - - <<: *upgrade_libunwind_post - <<: *upgrade_boost_pre - <<: *upgrade_boost - <<: *upgrade_boost_post @@ -514,7 +467,7 @@ jobs: OMP_NUM_THREADS: "1" CXXFLAGS: "-O2 -fsanitize=thread -fno-omit-frame-pointer" BUILD_TYPE: "Debug" - GTEST_ARGS: "--gtest_filter=-LoggingTest.LoggingAlsoWorksAfterFork:AssertTest_DebugBuild.*:SignalCatcherTest.*_thenDies:SignalHandlerTest.*_thenDies:SignalHandlerTest.givenMultipleSigIntHandlers_whenRaising_thenCatchesCorrectSignal:CliTest_Setup.*:CliTest_IntegrityCheck.*:*/CliTest_WrongEnvironment.*:CliTest_Unmount.*" + GTEST_ARGS: "--gtest_filter=-LoggingTest.LoggingAlsoWorksAfterFork:AssertTest_*:BacktraceTest.*:SignalCatcherTest.*_thenDies:SignalHandlerTest.*_thenDies:SignalHandlerTest.givenMultipleSigIntHandlers_whenRaising_thenCatchesCorrectSignal:CliTest_Setup.*:CliTest_IntegrityCheck.*:*/CliTest_WrongEnvironment.*:CliTest_Unmount.*" CMAKE_FLAGS: "" RUN_TESTS: true clang_tidy: @@ -524,9 +477,6 @@ jobs: - <<: *container_setup_pre - <<: *container_setup - <<: *container_setup_post - - <<: *upgrade_libunwind_pre - - <<: *upgrade_libunwind - - <<: *upgrade_libunwind_post - <<: *upgrade_boost_pre - <<: *upgrade_boost - <<: *upgrade_boost_post diff --git a/ChangeLog.txt b/ChangeLog.txt index 90d2170d..3088ecbc 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -7,7 +7,7 @@ Fixed bugs: Compatibility: * Fixed some incompatibilities with systems using the musl libc -* Use libunwind instead of libbacktrace to build stack traces. This fixes a segfault issue with platforms using libexecinfo and is generally more portable. +* Use boost::stacktrace instead of libbacktrace to build stack traces. This fixes a segfault issue with platforms using libexecinfo and is generally more portable. Other: * Updated to crypto++ 8.1 diff --git a/README.md b/README.md index ffea44e6..b0f4a684 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Requirements - GCC version >= 5.0 or Clang >= 4.0 - CMake version >= 3.0 - libcurl4 (including development headers) - - Boost libraries version >= 1.57 (including development headers) + - Boost libraries version >= 1.65.1 (including development headers) - filesystem - system - chrono @@ -56,15 +56,14 @@ Requirements - libFUSE version >= 2.8.6 (including development headers), on Mac OS X instead install osxfuse from https://osxfuse.github.io/ - Python >= 2.7 - OpenMP - - Libunwind You can use the following commands to install these requirements # Ubuntu - $ sudo apt install git g++ cmake make libcurl4-openssl-dev libboost-filesystem-dev libboost-system-dev libboost-chrono-dev libboost-program-options-dev libboost-thread-dev libssl-dev libfuse-dev python libunwind-dev + $ sudo apt install git g++ cmake make libcurl4-openssl-dev libboost-filesystem-dev libboost-system-dev libboost-chrono-dev libboost-program-options-dev libboost-thread-dev libssl-dev libfuse-dev python # Fedora - sudo dnf install git gcc-c++ cmake make libcurl-devel boost-devel boost-static openssl-devel fuse-devel python libunwind-devel + sudo dnf install git gcc-c++ cmake make libcurl-devel boost-devel boost-static openssl-devel fuse-devel python # Macintosh brew install cmake boost openssl libomp diff --git a/cmake-utils/utils.cmake b/cmake-utils/utils.cmake index 72cac168..da4dff8c 100644 --- a/cmake-utils/utils.cmake +++ b/cmake-utils/utils.cmake @@ -108,7 +108,7 @@ endfunction(target_enable_style_warnings) function(target_add_boost TARGET) # Load boost libraries if(NOT DEFINED Boost_USE_STATIC_LIBS OR Boost_USE_STATIC_LIBS) - # Many supported systems don't have boost >= 1.57. Better link it statically. + # Many supported systems don't have boost >= 1.65.1. Better link it statically. message(STATUS "Boost will be statically linked") set(Boost_USE_STATIC_LIBS ON) else(NOT DEFINED Boost_USE_STATIC_LIBS OR Boost_USE_STATIC_LIBS) @@ -116,7 +116,7 @@ function(target_add_boost TARGET) set(Boost_USE_STATIC_LIBS OFF) endif(NOT DEFINED Boost_USE_STATIC_LIBS OR Boost_USE_STATIC_LIBS) set(BOOST_THREAD_VERSION 4) - find_package(Boost 1.57.0 + find_package(Boost 1.65.1 REQUIRED COMPONENTS ${ARGN}) target_include_directories(${TARGET} SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) diff --git a/src/cpp-utils/CMakeLists.txt b/src/cpp-utils/CMakeLists.txt index e1303bd8..80688108 100644 --- a/src/cpp-utils/CMakeLists.txt +++ b/src/cpp-utils/CMakeLists.txt @@ -63,12 +63,17 @@ add_library(${PROJECT_NAME} STATIC ${SOURCES}) if(MSVC) target_link_libraries(${PROJECT_NAME} PUBLIC DbgHelp) -elseif(NOT APPLE) - # note: We use the libunwind code on Apple, but we don't seem to need these lines to link against it. - find_package(Libunwind REQUIRED) - target_include_directories(${PROJECT_NAME} PRIVATE ${LIBUNWIND_INCLUDE_DIR}) - target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBUNWIND_LIBRARIES}) - target_compile_definitions(${PROJECT_NAME} PRIVATE ${LIBUNWIND_DEFINITIONS}) +elseif (APPLE) + target_compile_definitions(${PROJECT_NAME} PRIVATE BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED) +else() + find_program(ADDR2LINE addr2line) + if ("${ADDR2LINE}" STREQUAL "ADDR2LINE-NOTFOUND") + message(WARNING "addr2line not found. Backtraces will be reduced.") + else() + message(STATUS "addr2line found. Using it for backtraces.") + target_compile_definitions(${PROJECT_NAME} PRIVATE BOOST_STACKTRACE_USE_ADDR2LINE) + target_compile_definitions(${PROJECT_NAME} PRIVATE BOOST_STACKTRACE_ADDR2LINE_LOCATION=${ADDR2LINE}) + endif() endif() if (NOT MSVC) diff --git a/src/cpp-utils/assert/backtrace_nonwindows.cpp b/src/cpp-utils/assert/backtrace_nonwindows.cpp index 5411bd55..8fbcac60 100644 --- a/src/cpp-utils/assert/backtrace_nonwindows.cpp +++ b/src/cpp-utils/assert/backtrace_nonwindows.cpp @@ -1,16 +1,12 @@ #if !defined(_MSC_VER) #include -#include #include #include "../logging/logging.h" #include -#define UNW_LOCAL_ONLY -#include - -// TODO Add file and line number on non-windows +#include using std::string; using std::ostringstream; @@ -18,105 +14,36 @@ using namespace cpputils::logging; namespace cpputils { -namespace { - std::string demangle(const string &mangledName) { - string result; - int status = -10; - char *demangledName = nullptr; - try { - demangledName = abi::__cxa_demangle(mangledName.c_str(), NULL, NULL, &status); - if (status == 0) { - result = demangledName; - } else if (status == -2) { - // mangledName was not a c++ mangled name, probably because it's a C name like for static - // initialization or stuff. Let's just return the name instead. - result = mangledName; - } else { - // other error - result = "[demangling error " + std::to_string(status) + "]" + mangledName; - } - free(demangledName); - return result; - } catch (...) { - free(demangledName); - throw; - } - } - - void pretty_print(std::ostringstream& str, unw_cursor_t* cursor) { - constexpr unsigned int MAXNAMELEN=256; - char name[MAXNAMELEN]; - unw_word_t offp = 0, ip = 0; - - int status = unw_get_reg(cursor, UNW_REG_IP, &ip); - if (0 != status) { - str << "[unw_get_reg error: " << status << "]: "; - } else { - str << "0x" << std::hex << ip << ": "; - } - - status = unw_get_proc_name(cursor, name, MAXNAMELEN, &offp); - if (0 != status) { - str << "[unw_get_proc_name error: " << status << "]"; - } else { - str << demangle(name); - } - str << " +0x" << std::hex << offp; - } +string backtrace() { + std::ostringstream str; + str << boost::stacktrace::stacktrace(); + return str.str(); } - string backtrace() { - std::ostringstream result; - - unw_context_t uc; - int status = unw_getcontext(&uc); - if (0 != status) { - return "[unw_getcontext error: " + std::to_string(status) + "]"; - } - - unw_cursor_t cursor; - status = unw_init_local(&cursor, &uc); - if (0 != status) { - return "[unw_init_local error: " + std::to_string(status) + "]"; - } - - - size_t line = 0; - while ((status = unw_step(&cursor)) > 0) { - result << "#" << std::dec << (line++) << " "; - pretty_print(result, &cursor); - result << "\n"; - } - if (status != 0) { - result << "[unw_step error :" << status << "]"; - } - - return result.str(); - } - namespace { - void sigsegv_handler(int) { - LOG(ERR, "SIGSEGV\n{}", backtrace()); - exit(1); - } - void sigill_handler(int) { - LOG(ERR, "SIGILL\n{}", backtrace()); - exit(1); - } - void sigabrt_handler(int) { - LOG(ERR, "SIGABRT\n{}", backtrace()); - exit(1); - } +void sigsegv_handler(int) { + LOG(ERR, "SIGSEGV\n{}", backtrace()); + exit(1); +} +void sigill_handler(int) { + LOG(ERR, "SIGILL\n{}", backtrace()); + exit(1); +} +void sigabrt_handler(int) { + LOG(ERR, "SIGABRT\n{}", backtrace()); + exit(1); +} } - void showBacktraceOnCrash() { - // the signal handler RAII objects will be initialized on first call (which will register the signal handler) - // and destroyed on program exit (which will unregister the signal handler) +void showBacktraceOnCrash() { + // the signal handler RAII objects will be initialized on first call (which will register the signal handler) + // and destroyed on program exit (which will unregister the signal handler) + + static SignalHandlerRAII<&sigsegv_handler> segv(SIGSEGV); + static SignalHandlerRAII<&sigabrt_handler> abrt(SIGABRT); + static SignalHandlerRAII<&sigill_handler> ill(SIGILL); +} - static SignalHandlerRAII<&sigsegv_handler> segv(SIGSEGV); - static SignalHandlerRAII<&sigabrt_handler> abrt(SIGABRT); - static SignalHandlerRAII<&sigill_handler> ill(SIGILL); - } } #endif diff --git a/src/cpp-utils/process/SignalHandler.cpp b/src/cpp-utils/process/SignalHandler.cpp index cb04186f..26eafe4d 100644 --- a/src/cpp-utils/process/SignalHandler.cpp +++ b/src/cpp-utils/process/SignalHandler.cpp @@ -1,66 +1 @@ #include "SignalHandler.h" - -#if !defined(_MSC_VER) - -namespace cpputils { -namespace detail { -namespace { - -std::atomic already_checked_for_libunwind_bug(false); - -} - -sigset_t _sigemptyset() { - sigset_t result; - int error = sigemptyset(&result); - if (0 != error) { - throw std::runtime_error("Error calling sigemptyset. Errno: " + std::to_string(errno)); - } - return result; -} - -void _sigmask(sigset_t* new_value, sigset_t* old_value) { - int error = pthread_sigmask(SIG_SETMASK, new_value, old_value); - if (0 != error) { - throw std::runtime_error("Error calling pthread_sigmask. Errno: " + std::to_string(errno)); - } -} - -// Check that we're not running into http://savannah.nongnu.org/bugs/?43752 -void check_against_libunwind_bug() { - if (already_checked_for_libunwind_bug.exchange(true)) { - return; - } - - // set new signal handler - sigset_t old_value = _sigemptyset(); - sigset_t new_value = _sigemptyset(); - - _sigmask(&new_value, &old_value); - - sigset_t before_exception = _sigemptyset(); - _sigmask(nullptr, &before_exception); - - // throw an exception - try { - throw std::runtime_error("Some exception"); - } catch (const std::exception &e) {} - - sigset_t after_exception = _sigemptyset(); - _sigmask(nullptr, &after_exception); - - // reset to old signal handler - _sigmask(&old_value, nullptr); - - // check that the exception didn't screw up the signal mask - if (0 != std::memcmp(&before_exception, &after_exception, sizeof(sigset_t))) { // NOLINT(cppcoreguidelines-pro-type-union-access) - ASSERT(false, - "Throwing an exception screwed up the signal mask. You likely ran into this bug: http://savannah.nongnu.org/bugs/?43752 . Please build CryFS against a newer version of libunwind or build libunwind with --disable-cxx-exceptions."); - } -} - - -} -} - -#endif diff --git a/src/cpp-utils/process/SignalHandler.h b/src/cpp-utils/process/SignalHandler.h index 4af13568..ed8752d9 100644 --- a/src/cpp-utils/process/SignalHandler.h +++ b/src/cpp-utils/process/SignalHandler.h @@ -20,17 +20,11 @@ using SignalHandlerFunction = void(int); #if !defined(_MSC_VER) -namespace detail { -void check_against_libunwind_bug(); -} - template class SignalHandlerRAII final { public: explicit SignalHandlerRAII(int signal) : _old_handler(), _signal(signal) { - detail::check_against_libunwind_bug(); - struct sigaction new_signal_handler{}; std::memset(&new_signal_handler, 0, sizeof(new_signal_handler)); new_signal_handler.sa_handler = handler; // NOLINT(cppcoreguidelines-pro-type-union-access) diff --git a/test/cpp-utils/assert/backtrace_test.cpp b/test/cpp-utils/assert/backtrace_test.cpp index 708edaa7..a5b46709 100644 --- a/test/cpp-utils/assert/backtrace_test.cpp +++ b/test/cpp-utils/assert/backtrace_test.cpp @@ -25,12 +25,8 @@ namespace { } } -TEST(BacktraceTest, ContainsBacktrace) { - string backtrace = cpputils::backtrace(); - EXPECT_THAT(backtrace, HasSubstr("#1")); -} - #if !(defined(_MSC_VER) && defined(NDEBUG)) + TEST(BacktraceTest, ContainsTopLevelLine) { string backtrace = cpputils::backtrace(); EXPECT_THAT(backtrace, HasSubstr("BacktraceTest")); @@ -85,12 +81,21 @@ TEST(BacktraceTest, DoesntCrashOnCaughtException) { } #if !(defined(_MSC_VER) && defined(NDEBUG)) +TEST(BacktraceTest, ContainsBacktrace) { + string backtrace = cpputils::backtrace(); +#if defined(_MSC_VER) + EXPECT_THAT(backtrace, HasSubstr("testing::Test::Run")); +#else + EXPECT_THAT(backtrace, HasSubstr("BacktraceTest_ContainsBacktrace_Test::TestBody")); +#endif +} + TEST(BacktraceTest, ShowBacktraceOnNullptrAccess) { auto output = call_process_exiting_with_nullptr_violation(); -#if !defined(_MSC_VER) - EXPECT_THAT(output, HasSubstr("cpputils::(anonymous namespace)::sig")); -#else +#if defined(_MSC_VER) EXPECT_THAT(output, HasSubstr("handle_exit_signal")); +#else + EXPECT_THAT(output, HasSubstr("cpputils::backtrace")); #endif } @@ -99,7 +104,7 @@ TEST(BacktraceTest, ShowBacktraceOnSigSegv) { #if defined(_MSC_VER) EXPECT_THAT(output, HasSubstr("handle_exit_signal")); #else - EXPECT_THAT(output, HasSubstr("cpputils::(anonymous namespace)::sigsegv_handler(int)")); + EXPECT_THAT(output, HasSubstr("cpputils::backtrace")); #endif } @@ -108,19 +113,23 @@ TEST(BacktraceTest, ShowBacktraceOnUnhandledException) { #if defined(_MSC_VER) EXPECT_THAT(output, HasSubstr("handle_exit_signal")); #else - EXPECT_THAT(output, HasSubstr("cpputils::(anonymous namespace)::sigabrt_handler(int)")); + EXPECT_THAT(output, HasSubstr("cpputils::backtrace")); #endif } TEST(BacktraceTest, ShowBacktraceOnSigIll) { auto output = call_process_exiting_with_sigill(); #if defined(_MSC_VER) - EXPECT_THAT(output, HasSubstr("handle_exit_signal")); + EXPECT_THAT(output, HasSubstr("handle_exit_signal")); #else - EXPECT_THAT(output, HasSubstr("cpputils::(anonymous namespace)::sigill_handler(int)")); + EXPECT_THAT(output, HasSubstr("cpputils::backtrace")); #endif } #else +TEST(BacktraceTest, ContainsBacktrace) { + string backtrace = cpputils::backtrace(); + EXPECT_THAT(backtrace, HasSubstr("#1")); +} TEST(BacktraceTest, ShowBacktraceOnNullptrAccess) { auto output = call_process_exiting_with_nullptr_violation(); EXPECT_THAT(output, HasSubstr("#1")); @@ -145,7 +154,7 @@ TEST(BacktraceTest, ShowBacktraceOnSigIll) { #if !defined(_MSC_VER) TEST(BacktraceTest, ShowBacktraceOnSigAbrt) { auto output = call_process_exiting_with_sigabrt(); - EXPECT_THAT(output, HasSubstr("cpputils::(anonymous namespace)::sigabrt_handler(int)")); + EXPECT_THAT(output, HasSubstr("cpputils::backtrace")); } TEST(BacktraceTest, ShowBacktraceOnSigAbrt_ShowsCorrectSignalName) { From 39fa5c00df5ba181abeb2e8736215d97e36fa2bd Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Mon, 1 Apr 2019 19:39:29 -0700 Subject: [PATCH 12/15] Fix test cases on musl --- test/fspp/fuse/flush/FuseFlushErrorTest.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/fspp/fuse/flush/FuseFlushErrorTest.cpp b/test/fspp/fuse/flush/FuseFlushErrorTest.cpp index d9e5364f..56a0e930 100644 --- a/test/fspp/fuse/flush/FuseFlushErrorTest.cpp +++ b/test/fspp/fuse/flush/FuseFlushErrorTest.cpp @@ -14,7 +14,13 @@ using fspp::fuse::FuseErrnoException; class FuseFlushErrorTest: public FuseFlushTest, public WithParamInterface { }; -INSTANTIATE_TEST_CASE_P(FuseFlushErrorTest, FuseFlushErrorTest, Values(EBADF, EINTR, EIO)); +INSTANTIATE_TEST_CASE_P(FuseFlushErrorTest, FuseFlushErrorTest, Values( + EBADF, +#if defined(__GLIBC__) || defined(__APPLE__) + // musl has different handling for EINTR, see https://ewontfix.com/4/ + EINTR, +#endif + EIO)); TEST_P(FuseFlushErrorTest, ReturnErrorFromFlush) { ReturnIsFileOnLstat(FILENAME); From 951f76af15020fdbe46d84c7394c66c2366b2f2b Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Wed, 3 Apr 2019 18:36:53 -0700 Subject: [PATCH 13/15] Fix building with Boost 1.67 --- ChangeLog.txt | 1 + src/cpp-utils/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 3088ecbc..c652bd1a 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -4,6 +4,7 @@ Fixed bugs: * If file system migration encounters files or folders with the wrong format in the base directory, it now just ignores them instead of crashing. * When trying to migrate a file system from CryFS 0.9.3 or older, show an error message suggesting to first open it with 0.9.10 because we can't load that anymore. * The '--unmount-idle' parameter works again +* Fix building with boost 1.67 Compatibility: * Fixed some incompatibilities with systems using the musl libc diff --git a/src/cpp-utils/CMakeLists.txt b/src/cpp-utils/CMakeLists.txt index 80688108..f66f99f8 100644 --- a/src/cpp-utils/CMakeLists.txt +++ b/src/cpp-utils/CMakeLists.txt @@ -91,6 +91,6 @@ target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_DL_LIBS}) target_link_libraries(${PROJECT_NAME} PUBLIC spdlog cryptopp) -target_add_boost(${PROJECT_NAME} filesystem system thread) +target_add_boost(${PROJECT_NAME} filesystem system thread chrono) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) From e732cc03f6e374455f0c3e7c2ec82d1f2d99acdb Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Wed, 3 Apr 2019 18:37:11 -0700 Subject: [PATCH 14/15] Mark 0.10.1 as released --- ChangeLog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index c652bd1a..7c2fe39b 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,4 +1,4 @@ -Version 0.10.1 (unreleased) +Version 0.10.1 --------------- Fixed bugs: * If file system migration encounters files or folders with the wrong format in the base directory, it now just ignores them instead of crashing. From 0ee707397d8d049990c19722916d840c8ff18fb6 Mon Sep 17 00:00:00 2001 From: Sebastian Messmer Date: Wed, 3 Apr 2019 18:40:31 -0700 Subject: [PATCH 15/15] Prepare changelog for 0.10.2 --- ChangeLog.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog.txt b/ChangeLog.txt index 7c2fe39b..9faa688a 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,7 @@ +Version 0.10.2 (unreleased) +--------------- + + Version 0.10.1 --------------- Fixed bugs: