pax_global_header00006660000000000000000000000064134770126710014522gustar00rootroot0000000000000052 comment=cf3023406969b14610df03a043fca8a078c9c195 .circleci/000077500000000000000000000000001347701267100127215ustar00rootroot00000000000000.circleci/config.yml000066400000000000000000000442611347701267100147200ustar00rootroot00000000000000version: 2.0 references: container_config: &container_config machine: true cache_init: &cache_init run: name: Initialize Cache command: | echo "${APT_COMPILER_PACKAGE}_${BUILD_TOOLSET}_${CXX}_${CC}_${BUILD_TYPE}_${CXXFLAGS}" > /tmp/_build_env_vars echo Build env vars used for cache keys: cat /tmp/_build_env_vars container_setup_pre: &container_setup_pre restore_cache: keys: # Find the most recent cache from any branch - v4_container_setup_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} container_setup_post: &container_setup_post save_cache: # Add _aptcache_contents to cache key so that it is re-uploaded each time the cache changes. key: v4_container_setup_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}_{{ checksum "/tmp/_aptcache_contents" }} paths: - /tmp/aptcache container_setup: &container_setup run: name: Setup Environment command: | if [ -d "/tmp/aptcache" ]; then echo Using packages from apt cache sudo cp -R /tmp/aptcache/* /var/cache/apt/archives/ else echo No apt cache found fi sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - sudo touch /etc/apt/sources.list.d/clang.list sudo chmod o+w /etc/apt/sources.list.d/clang.list cat > /etc/apt/sources.list.d/clang.list << EOF deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-4.0 main deb-src http://apt.llvm.org/trusty/ llvm-toolchain-trusty-4.0 main deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-5.0 main deb-src http://apt.llvm.org/trusty/ llvm-toolchain-trusty-5.0 main deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-6.0 main deb-src http://apt.llvm.org/trusty/ llvm-toolchain-trusty-6.0 main deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main deb-src http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-8 main deb-src http://apt.llvm.org/trusty/ llvm-toolchain-trusty-8 main EOF sudo chmod o-w /etc/apt/sources.list.d/clang.list DEBIAN_FRONTEND=noninteractive sudo apt-get update -qq DEBIAN_FRONTEND=noninteractive sudo apt-get install -y git ccache $APT_COMPILER_PACKAGE cmake3 make libcurl4-openssl-dev libssl-dev libfuse-dev python # Use /dev/urandom when /dev/random is accessed to use less entropy sudo cp -a /dev/urandom /dev/random if [ "${BUILD_TOOLSET}" = "clang" ]; then # They aren't set automatically unfortunately sudo ln -s /usr/bin/$CC /usr/bin/clang sudo ln -s /usr/bin/$CXX /usr/bin/clang++ sudo ln -s /usr/bin/clang-tidy-8 /usr/bin/clang-tidy sudo ln -s /usr/bin/run-clang-tidy-8.py /usr/bin/run-clang-tidy.py # Need a c++14 compliant STL for clang sudo apt-get install -y g++-5 sudo apt-get remove g++-4.8 gcc-4.8 fi # Setup ccache sudo ln -s /usr/bin/ccache /usr/local/bin/$CC sudo ln -s /usr/bin/ccache /usr/local/bin/$CXX sudo mkdir /ccache_data sudo chown circleci:circleci /ccache_data echo 'export CCACHE_COMPILERCHECK=content' >> $BASH_ENV echo 'export CCACHE_COMPRESS=1' >> $BASH_ENV echo 'export CCACHE_DIR=/ccache_data' >> $BASH_ENV echo 'export CCACHE_SLOPPINESS=include_file_mtime' >> $BASH_ENV sudo mkdir -p /tmp/aptcache sudo cp -R /var/cache/apt/archives/* /tmp/aptcache/ ls /tmp/aptcache > /tmp/_aptcache_contents echo echo System Info: cat /etc/issue uname -a cmake --version /usr/local/bin/$CC --version /usr/local/bin/$CXX --version upgrade_boost_pre: &upgrade_boost_pre restore_cache: keys: # Find the most recent cache from any branch - v4_upgrade_boost_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} upgrade_boost_post: &upgrade_boost_post save_cache: key: v4_upgrade_boost_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} paths: - /tmp/boost_1_65_1 upgrade_boost: &upgrade_boost run: name: Upgrade Boost command: | # Detect number of CPU cores export NUMCORES=`nproc` echo Using $NUMCORES cores # Download and prepare boost (only if not already present from cache) 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.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 sha512sum boost.tar.bz2 exit 1 fi echo Extracting... tar -xf /tmp/boost.tar.bz2 -C /tmp rm -rf boost.tar.bz2 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_65_1 sudo ./b2 toolset=${BUILD_TOOLSET} link=static cxxflags=-fPIC -d0 -j$NUMCORES install build_pre: &build_pre restore_cache: keys: # Find most recent cache from any revision on the same branch (cache keys are prefix matched) # CIRCLE_PR_NUMBER is only set if this is a pull request. - v3_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}_{{ .Branch }}_{{ .Environment.CIRCLE_PR_NUMBER }} # Fallback to less specific caches if the one above wasn't found - v3_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}_{{ .Branch }} - v3_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }} build_post: &build_post save_cache: key: v3_build_cache_{{ checksum "/tmp/_build_env_vars" }}_{{ arch }}_{{ .Branch }}_{{ .Environment.CIRCLE_PR_NUMBER }}_{{ .Revision }} paths: - /ccache_data build: &build run: name: Build command: | export NUMCORES=`nproc` && if [ ! -n "$NUMCORES" ]; then export NUMCORES=`sysctl -n hw.ncpu`; fi echo Using $NUMCORES cores # Use ccache export CXX=/usr/local/bin/$CXX export CC=/usr/local/bin/$CC ccache --max-size=512M ccache --show-stats # Disable OpenMP if it is clang, because Ubuntu 14.04 doesn't have the libomp-dev package needed to support OpenMP for clang. if [[ ${APT_COMPILER_PACKAGE} == clang* ]]; then OPENMP_PARAMS="-DDISABLE_OPENMP=ON" else OPENMP_PARAMS="" fi # Build mkdir cmake cd cmake cmake .. -DBUILD_TESTING=on -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${OPENMP_PARAMS} ${CMAKE_FLAGS} make -j$NUMCORES ccache --show-stats test: &test run: name: Test no_output_timeout: 120m command: | if "${RUN_TESTS}"; then cd cmake ./test/gitversion/gitversion-test ${GTEST_ARGS} ./test/cpp-utils/cpp-utils-test ${GTEST_ARGS} if [ ! "$DISABLE_BROKEN_ASAN_TESTS" = true ] ; then ./test/fspp/fspp-test ${GTEST_ARGS} ; fi ./test/parallelaccessstore/parallelaccessstore-test ${GTEST_ARGS} ./test/blockstore/blockstore-test ${GTEST_ARGS} ./test/blobstore/blobstore-test ${GTEST_ARGS} ./test/cryfs/cryfs-test ${GTEST_ARGS} ./test/cryfs-cli/cryfs-cli-test ${GTEST_ARGS} fi job_definition: &job_definition <<: *container_config steps: - <<: *cache_init - <<: *container_setup_pre - <<: *container_setup - <<: *container_setup_post - <<: *upgrade_boost_pre - <<: *upgrade_boost - <<: *upgrade_boost_post - checkout - <<: *build_pre - <<: *build - <<: *build_post - <<: *test enable_for_tags: &enable_for_tags filters: tags: only: /.*/ jobs: gcc_5_debug: <<: *job_definition environment: CC: gcc-5 CXX: g++-5 BUILD_TOOLSET: gcc APT_COMPILER_PACKAGE: "g++-5" CXXFLAGS: "" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true gcc_5_release: <<: *job_definition environment: CC: gcc-5 CXX: g++-5 BUILD_TOOLSET: gcc APT_COMPILER_PACKAGE: "g++-5" CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true gcc_6_debug: <<: *job_definition environment: CC: gcc-6 CXX: g++-6 BUILD_TOOLSET: gcc APT_COMPILER_PACKAGE: "g++-6" CXXFLAGS: "" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true gcc_6_release: <<: *job_definition environment: CC: gcc-6 CXX: g++-6 BUILD_TOOLSET: gcc APT_COMPILER_PACKAGE: "g++-6" CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true gcc_7_debug: <<: *job_definition environment: CC: gcc-7 CXX: g++-7 BUILD_TOOLSET: gcc APT_COMPILER_PACKAGE: "g++-7" CXXFLAGS: "" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true gcc_7_release: <<: *job_definition environment: CC: gcc-7 CXX: g++-7 BUILD_TOOLSET: gcc APT_COMPILER_PACKAGE: "g++-7" CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true gcc_8_debug: <<: *job_definition environment: CC: gcc-8 CXX: g++-8 BUILD_TOOLSET: gcc APT_COMPILER_PACKAGE: "g++-8" CXXFLAGS: "" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true gcc_8_release: <<: *job_definition environment: CC: gcc-8 CXX: g++-8 BUILD_TOOLSET: gcc APT_COMPILER_PACKAGE: "g++-8" CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true gcc_9_debug: <<: *job_definition environment: CC: gcc-9 CXX: g++-9 BUILD_TOOLSET: gcc APT_COMPILER_PACKAGE: "g++-9" CXXFLAGS: "" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true gcc_9_release: <<: *job_definition environment: CC: gcc-9 CXX: g++-9 BUILD_TOOLSET: gcc APT_COMPILER_PACKAGE: "g++-9" CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true clang_4_debug: <<: *job_definition environment: CC: clang-4.0 CXX: clang++-4.0 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-4.0 CXXFLAGS: "" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true clang_4_release: <<: *job_definition environment: CC: clang-4.0 CXX: clang++-4.0 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-4.0 CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true clang_5_debug: <<: *job_definition environment: CC: clang-5.0 CXX: clang++-5.0 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-5.0 CXXFLAGS: "" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true clang_5_release: <<: *job_definition environment: CC: clang-5.0 CXX: clang++-5.0 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-5.0 CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true clang_6_debug: <<: *job_definition environment: CC: clang-6.0 CXX: clang++-6.0 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-6.0 CXXFLAGS: "" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true clang_6_release: <<: *job_definition environment: CC: clang-6.0 CXX: clang++-6.0 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-6.0 CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true clang_7_debug: <<: *job_definition environment: CC: clang-7 CXX: clang++-7 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-7 CXXFLAGS: "" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true clang_7_release: <<: *job_definition environment: CC: clang-7 CXX: clang++-7 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-7 CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true clang_8_debug: <<: *job_definition environment: CC: clang-8 CXX: clang++-8 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-8 CXXFLAGS: "" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true clang_8_release: <<: *job_definition environment: CC: clang-8 CXX: clang++-8 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-8 CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true clang_werror: <<: *job_definition environment: CC: clang-8 CXX: clang++-8 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-8 CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "-DUSE_WERROR=on" RUN_TESTS: false gcc_werror: <<: *job_definition environment: CC: gcc-9 CXX: g++-9 BUILD_TOOLSET: gcc APT_COMPILER_PACKAGE: "g++-9" CXXFLAGS: "" BUILD_TYPE: "Release" GTEST_ARGS: "" CMAKE_FLAGS: "-DUSE_WERROR=on" RUN_TESTS: false no_compatibility: <<: *job_definition environment: CC: clang-8 CXX: clang++-8 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-8 CXXFLAGS: "-DCRYFS_NO_COMPATIBILITY" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true address_sanitizer: <<: *job_definition environment: CC: clang-8 CXX: clang++-8 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-8 CXXFLAGS: "-O2 -fsanitize=address -fno-omit-frame-pointer -fno-common -fsanitize-address-use-after-scope" BUILD_TYPE: "Debug" ASAN_OPTIONS: "detect_leaks=1 check_initialization_order=1 detect_stack_use_after_return=1 detect_invalid_pointer_pairs=1 atexit=1" DISABLE_BROKEN_ASAN_TESTS: true GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true ub_sanitizer: <<: *job_definition environment: CC: clang-8 CXX: clang++-8 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-8 CXXFLAGS: "-O2 -fno-sanitize-recover=undefined,nullability,implicit-conversion,unsigned-integer-overflow -fno-omit-frame-pointer -fno-common" BUILD_TYPE: "Debug" GTEST_ARGS: "" CMAKE_FLAGS: "" RUN_TESTS: true thread_sanitizer: <<: *job_definition environment: CC: clang-8 CXX: clang++-8 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: clang-8 OMP_NUM_THREADS: "1" CXXFLAGS: "-O2 -fsanitize=thread -fno-omit-frame-pointer" BUILD_TYPE: "Debug" 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: <<: *container_config steps: - <<: *cache_init - <<: *container_setup_pre - <<: *container_setup - <<: *container_setup_post - <<: *upgrade_boost_pre - <<: *upgrade_boost - <<: *upgrade_boost_post - checkout - run: name: clang-tidy command: | # realpath, jq are needed for run-clang-tidy.sh, g++ is needed for pyyaml sudo apt-get install realpath g++ jq pip install pyyaml mkdir cmake cd cmake if ! ../run-clang-tidy.sh -fix ; then git diff > /tmp/clang-tidy-fixes exit 1 fi - store_artifacts: path: /tmp/clang-tidy-fixes environment: CC: clang-8 CXX: clang++-8 BUILD_TOOLSET: clang APT_COMPILER_PACKAGE: "clang-8 clang-tidy-8" workflows: version: 2 build_and_test: jobs: - gcc_5_debug: <<: *enable_for_tags - gcc_5_release: <<: *enable_for_tags - gcc_6_debug: <<: *enable_for_tags - gcc_6_release: <<: *enable_for_tags - gcc_7_debug: <<: *enable_for_tags - gcc_7_release: <<: *enable_for_tags - gcc_8_debug: <<: *enable_for_tags - gcc_8_release: <<: *enable_for_tags - gcc_9_debug: <<: *enable_for_tags - gcc_9_release: <<: *enable_for_tags - clang_4_debug: <<: *enable_for_tags - clang_4_release: <<: *enable_for_tags - clang_5_debug: <<: *enable_for_tags - clang_5_release: <<: *enable_for_tags - clang_6_debug: <<: *enable_for_tags - clang_6_release: <<: *enable_for_tags - clang_7_debug: <<: *enable_for_tags - clang_7_release: <<: *enable_for_tags - clang_8_debug: <<: *enable_for_tags - clang_8_release: <<: *enable_for_tags - clang_werror: <<: *enable_for_tags - gcc_werror: <<: *enable_for_tags - no_compatibility: <<: *enable_for_tags - address_sanitizer: <<: *enable_for_tags - ub_sanitizer: <<: *enable_for_tags - thread_sanitizer: <<: *enable_for_tags - clang_tidy: <<: *enable_for_tags .clang-tidy000066400000000000000000000044141347701267100131250ustar00rootroot00000000000000--- # TODO Enable (some of) the explicitly disabled checks. Possibly needs helper types from gsl library or similar to enable full cppcoreguidelines. # TODO Enable more checks (google-*, hicpp-*, llvm-*, modernize-*, mpi-*, performance-*, readability-*) # TODO Maybe just enable * and disable a list instead? Checks: | clang-diagnostic-*, clang-analyzer-*, bugprone-*, cert-*, cppcoreguidelines-*, misc-*, boost-use-to-string, -cert-env33-c, -cert-err58-cpp, -cert-err60-cpp, -bugprone-macro-parentheses, -bugprone-exception-escape, -cppcoreguidelines-owning-memory, -cppcoreguidelines-no-malloc, -cppcoreguidelines-pro-type-const-cast, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-type-reinterpret-cast, -cppcoreguidelines-special-member-functions, -cppcoreguidelines-pro-type-cstyle-cast, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-avoid-goto, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-macro-usage, -cppcoreguidelines-non-private-member-variables-in-classes, -clang-analyzer-optin.cplusplus.VirtualCall, -clang-analyzer-cplusplus.NewDeleteLeaks, -misc-macro-parentheses, -misc-non-private-member-variables-in-classes, -misc-unused-raii WarningsAsErrors: '*' HeaderFilterRegex: '/src/|/test/' CheckOptions: - key: google-readability-braces-around-statements.ShortStatementLines value: '1' - key: google-readability-function-size.StatementThreshold value: '800' - key: google-readability-namespace-comments.ShortNamespaceLines value: '10' - key: google-readability-namespace-comments.SpacesBeforeComments value: '2' - key: modernize-loop-convert.MaxCopySize value: '16' - key: modernize-loop-convert.MinConfidence value: reasonable - key: modernize-loop-convert.NamingStyle value: CamelCase - key: modernize-pass-by-value.IncludeStyle value: llvm - key: modernize-replace-auto-ptr.IncludeStyle value: llvm - key: modernize-use-nullptr.NullMacros value: 'NULL' ... .github/000077500000000000000000000000001347701267100124265ustar00rootroot00000000000000.github/ISSUE_TEMPLATE.md000066400000000000000000000002531347701267100151330ustar00rootroot00000000000000## Expected Behavior ## Actual Behavior ## Steps to Reproduce the Problem 1. 2. 3. ## Specifications - CryFS Version: - Operating System (incl. Version): .gitignore000066400000000000000000000002571347701267100130620ustar00rootroot00000000000000umltest.inner.sh umltest.status /build /cmake /cmake-build-* /.idea *~ src/gitversion/*.pyc src/gitversion/__pycache__ cmake-build-debug cmake-build-release cmake-build-test .travis.yml000066400000000000000000000003501347701267100131750ustar00rootroot00000000000000language: cpp sudo: required os: osx compiler: # - gcc - clang env: - BUILD_TARGET=Debug - BUILD_TARGET=Release - BUILD_TARGET=RelWithDebInfo install: - .travisci/install.sh script: - .travisci/build_and_test.sh cache: ccache .travisci/000077500000000000000000000000001347701267100127705ustar00rootroot00000000000000.travisci/build_and_test.sh000077500000000000000000000024661347701267100163170ustar00rootroot00000000000000#!/bin/bash set -ev # If using gcc on mac, actually use it ("gcc" just links to clang, but "gcc-4.8" is gcc, https://github.com/travis-ci/travis-ci/issues/2423) # Note: This must be here and not in install.sh, because environment variables can't be passed between scripts. if [ "${CXX}" == "g++" ]; then echo Switch to actual g++ and not just the AppleClang symlink export CXX="g++-7" CC="gcc-7" else echo Do not switch to actual g++ because we are not g++ fi # Setup ccache export PATH="/usr/local/opt/ccache/libexec:$PATH" export CCACHE_COMPILERCHECK=content export CCACHE_COMPRESS=1 export CCACHE_SLOPPINESS=include_file_mtime ccache --max-size=512M ccache --show-stats # Detect number of CPU cores export NUMCORES=`sysctl -n hw.ncpu` echo Using $NUMCORES cores echo Using CXX compiler $CXX and C compiler $CC # Setup target directory mkdir cmake cd cmake cmake --version # Build echo Build target: ${BUILD_TARGET} cmake .. -DBUILD_TESTING=on -DCMAKE_BUILD_TYPE=${BUILD_TARGET} make -j$NUMCORES ccache --show-stats # Test ./test/gitversion/gitversion-test ./test/cpp-utils/cpp-utils-test ./test/parallelaccessstore/parallelaccessstore-test ./test/blockstore/blockstore-test ./test/blobstore/blobstore-test ./test/cryfs/cryfs-test # TODO Also run once fixed # ./test/fspp/fspp-test # ./test/cryfs-cli/cryfs-cli-test .travisci/install.sh000077500000000000000000000012031347701267100147710ustar00rootroot00000000000000#!/bin/bash set -e # Install newer GCC if we're running on GCC if [ "${CXX}" == "g++" ]; then # We need to uninstall oclint because it creates a /usr/local/include/c++ symlink that clashes with the gcc5 package # see https://github.com/Homebrew/homebrew-core/issues/21172 brew cask uninstall oclint brew install gcc@7 fi brew cask install osxfuse brew install libomp # By default, travis only fetches the newest 50 commits. We need more in case we're further from the last version tag, so the build doesn't fail because it can't generate the version number. git fetch --unshallow --tags # Setup ccache brew install ccache CMakeLists.txt000066400000000000000000000035711347701267100136340ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.0 FATAL_ERROR) cmake_policy(SET CMP0054 NEW) # TODO Perf test: # - try if setting CRYPTOPP_NATIVE_ARCH=ON and adding -march=native to the compile commands for cryfs source files makes a difference # -> if yes, offer a cmake option to enable both of these project(cryfs) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake-utils) include(utils) require_gcc_version(5.0) require_clang_version(4.0) # Default value is not to build test cases option(BUILD_TESTING "build test cases" OFF) option(CRYFS_UPDATE_CHECKS "let cryfs check for updates and security vulnerabilities" ON) option(DISABLE_OPENMP "allow building without OpenMP libraries. This will cause performance degradations." OFF) # The following options are helpful for development and/or CI option(USE_WERROR "build with -Werror flag") option(USE_CLANG_TIDY "build with clang-tidy checks enabled" OFF) option(USE_IWYU "build with iwyu checks enabled" OFF) option(CLANG_TIDY_WARNINGS_AS_ERRORS "treat clang-tidy warnings as errors" OFF) if(USE_IWYU) # note: for iwyu, we need cmake 3.3 cmake_minimum_required(VERSION 3.3 FATAL_ERROR) endif() if(USE_CLANG_TIDY) # note: for clang-tidy, we need cmake 3.6, or (if the return code should be handled correctly, e.g. on CI), we need 3.8. cmake_minimum_required(VERSION 3.8 FATAL_ERROR) endif() if (MSVC) option(DOKAN_PATH "Location of the Dokan library, e.g. C:\\Program Files\\Dokan\\DokanLibrary-1.1.0" "") endif() # Default value is to build in release mode but with debug symbols if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE INTERNAL "CMAKE_BUILD_TYPE") endif(NOT CMAKE_BUILD_TYPE) # The MSVC version on AppVeyor CI needs this if(MSVC) add_definitions(/bigobj) endif() add_subdirectory(vendor EXCLUDE_FROM_ALL) add_subdirectory(src) add_subdirectory(doc) add_subdirectory(test) add_subdirectory(cpack) CMakeSettings.json000066400000000000000000000042421347701267100144640ustar00rootroot00000000000000{ "configurations": [ { "name": "x86-Debug", "generator": "Ninja", "configurationType": "Debug", "inheritEnvironments": [ "msvc_x86" ], "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", "cmakeCommandArgs": "-DBUILD_TESTING=on -DBOOST_ROOT=C:\\local\\boost_1_68_0 -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", "buildCommandArgs": "-v", "ctestCommandArgs": "" }, { "name": "x86-Release", "generator": "Ninja", "configurationType": "RelWithDebInfo", "inheritEnvironments": [ "msvc_x86" ], "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", "cmakeCommandArgs": "-DBUILD_TESTING=on -DBOOST_ROOT=C:\\local\\boost_1_68_0 -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", "buildCommandArgs": "-v", "ctestCommandArgs": "" }, { "name": "x64-Debug", "generator": "Ninja", "configurationType": "Debug", "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", "cmakeCommandArgs": "-DBUILD_TESTING=on -DBOOST_ROOT=C:\\local\\boost_1_68_0 -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", "buildCommandArgs": "-v", "ctestCommandArgs": "" }, { "name": "x64-Release", "generator": "Ninja", "configurationType": "RelWithDebInfo", "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", "cmakeCommandArgs": "-DBUILD_TESTING=on -DBOOST_ROOT=C:\\local\\boost_1_68_0 -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", "buildCommandArgs": "-v", "ctestCommandArgs": "" } ] }ChangeLog.txt000066400000000000000000000265351347701267100134710ustar00rootroot00000000000000Version 0.10.2 --------------- Fixed bugs: * Fix occasional crash in mkdir() on Windows * Fix a race condition when a file descriptor is closed while there's read/write requests for that file being processed. Improvements: * Better logging when local state can't be loaded Other: * Updated to crypto++ 8.2 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. * 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 * 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 * Updated to DokanY 1.2.1 * Unit tests can now be run from any directory Version 0.10.0 --------------- New Features & Improvements: * Experimental Windows support * Integrity checks ensure you notice when someone modifies your file system. * File system nodes (files, directories, symlinks) store a parent pointer to the directory that contains them. This information can be used in later versions to resolve some synchronization conflicts. * Allow mounting using system mount tool and /etc/fstab (e.g. mount -t fuse.cryfs basedir mountdir) * Performance improvements * Use relatime instead of strictatime (further performance improvement) * Pass fuse options directly to cryfs (i.e. 'cryfs basedir mountdir -o allow_other' instead of 'cryfs basedir mountdir -- -o allow_other') * CryFS tells the operating system to lock the encryption key to memory, i.e. not swap it to the disk (note: this is best-effort and cannot be guaranteed. Hibernation, for example, will still write the encryption key to the disk). * New block size options: 4KB and 16KB * New default block size: 16KB. This should decrease the size of the ciphertext directory for most users. * Increased scrypt hardness to (N=1048576, r=4, p=8) to make it harder to crack the key while allowing cryfs to take advantage of multicore machines. * cryfs-unmount tool to unmount filesystems Fixed bugs: * `du` shows correct file system size on Mac OS X. * On Mac OS X, Finder shows the correct name for the mount directory Version 0.9.10 -------------- Fixed bugs: * Fixed occasional deadlock (https://github.com/cryfs/cryfs/issues/64) * Fix for reading empty files out of bounds * Fixed race condition (https://github.com/cryfs/cryfs/issues/224 and https://github.com/cryfs/cryfs/issues/243) Version 0.9.9 -------------- Improvements: * Add --allow-filesystem-upgrade option which will upgrade old file systems without asking the user. This will be especially helpful for GUI tools. * Add --version option that shows the CryFS version and exits. * When CryFS fails to load a file system, the process stops with a helpful error code, which can be used by GUI tools to show detailed messages. * Only migrate a file system if the underlying storage format changed Version 0.9.8 -------------- Compatibility: * Runs on Debian with FreeBSD kernel * Runs on FreeBSD 11.1 * Works with Crypto++ 6.0 Improvements: * added a man page Fixed bugs: * `du` shows correct file system size * Updated spdlog dependency to fix build on newer systems Version 0.9.7 -------------- Compatibility: * Runs on FreeBSD * Works with Clang++ 3.8 (Debian experimental or newer Ubuntu systems) * Works with GCC 7 Version 0.9.6 --------------- Fixed bugs: * Fix potential deadlock * Fix potential crash Improvements: * Allow building with -DCRYFS_UPDATE_CHECKS=off, which will create an executable with disabled update checks (the alternative to disable them in the environment also still works). * Automatically disable update checks when running in noninteractive mode. * More detailed error reporting if key derivation fails Compatibility: * Compatible with libcurl version >= 7.50.0, and <= 7.21.6 (tested down to 7.19.0) * Compatible with Crypto++ 5.6.4 * Compatible with compilers running under hardening-wrapper Version 0.9.5 --------------- Fixed Bugs: * Fixed a bug that prevented mounting a file system on Mac OS X. * File system operations correctly update the timestamps (access time, modification time and status change time). * Reacts correctly to fsync() and fdatasync() syscalls by flushing the corresponding data to the disk. Improvements: * When mounting an old file system, CryFS will ask before migrating it to the newest version. * Operating system tools like the mount command or /proc/self/mountinfo report correct file system type and also report the base directory. * Compatibility with GCC 6 Version 0.9.4 --------------- Improvements: * Ciphertext blocks are split into subdirectories (before, all were on top level) to reduce number of files per directory. Some unix tools don't work well with directories with too many entries. Fixed Bugs: * Renaming a file to an existing file (i.e. overwriting an existing file) didn't free the allocated memory for the overwritten file * Renaming a file to an existing file could hurt an invariant in the directory layout (directory entries have to be sorted) and doing so could cause files to seemingly disappear. * Fix a potential deadlock in the cache Compatibility: * The generated .deb packages work for any Ubuntu/Debian based distribution, but will not install the package source for automatic updates if it's an unsupported operating system. Version 0.9.3 --------------- New Features: * The ciphertext block size is configurable. You can use the "--blocksize" command line argument. If not specified, CryFS will ask you for a block size when creating a file system. * It's easier for tools and scripts to use CryFS: If an environment variable CRYFS_FRONTEND=noninteractive is set, we don't ask for options (but take default values for everything that's not specified on command line). Furthermore, in noninteractive mode, we won't ask for password confirmation when creating a file system. The password only has to be sent once to stdin. * You can disable the automatic update check by setting CRYFS_NO_UPDATE_CHECK=true in your environment. Fixed Bugs: * Building CryFS from the GitHub tarball (i.e. when there is no .git directory present) works. * A bug in the fstat implementation caused problems with some text editors (e.g. nano) falsely thinking a file changed since they opened it. * When trying to rename a file to an already existing file name, a bug deleted it instead. * Rename operation allows overwriting existing files, as specified in the rename(2) man page. Compatibility: * The generated .deb packages for Debian also work for the Devuan operating system. Version 0.9.2 --------------- * Experimental support for installing CryFS on Mac OS X using homebrew (0.9.2 is not released for Linux) Version 0.9.1 --------------- * Report file system usage statistics to the operating system (e.g. amount of space used). This information can be queried using the 'df' tool on linux. See https://github.com/cryfs/cryfs/commit/68acc27e88ff5209ca55ddb4e91f5a449d77fb54 * Use stronger scrypt parameters when generating the config file key from the user password. This makes it a bit more secure, but also takes a bit longer to load a file system. See https://github.com/cryfs/cryfs/commit/7f1493ab9210319cab008e71d4ee8f4d7d920f39 * Fix a bug where deleting a non-empty directory could leave some blocks over. See https://github.com/cryfs/cryfs/commit/df041ac84511e4560c4f099cd8cc089d08e05737 Version 0.9.0 --------------- (warning) file systems created with earlier CryFS versions are incompatible with this release. * Fully support file access times * Fix: Password is read from stdin, not from glibc getpass(). This enables external tools (e.g. GUIs) to pass in the password without problems. * Remove --extpass parameter, because that encourages tool writers to do bad things like storing a password in a file and using --extpass="cat filename". The password can now be passed in to stdin without problems, so tools should use that. * Works with zuluMount GUI, https://mhogomchungu.github.io/zuluCrypt/ * Introduce version flags for file system entities to allow future CryFS versions to be backwards-compatible even if the format changes. * (for developers) New git repository layout. All subrepositories have been merged to one directory. * (for developers) Using CMake instead of biicode as build system. Version 0.8.6 --------------- * Fix a deadlock that was caused when a very high load of parallel resize operations was issued, see https://github.com/cryfs/cryfs/issues/3 * Fix a bug that prevented deleting symlinks, see https://github.com/cryfs/cryfs/issues/2 * Gracefully accept modifications to the file access times instead of failing, although they're not stored yet (they will be stored in 0.9.0). This should fix https://github.com/cryfs/cryfs/issues/4 Version 0.8.5 --------------- * Fix package manager warning when installing the .deb package * Offer a default configuration when creating new filesystems * If the given base or mount directory doesn't exist, offer to create them Version 0.8.4 --------------- * Offering .deb packages for Debian and Ubuntu * Compatibility with 32bit systems * Support files larger than 4GB Version 0.8.3 --------------- * Ask for password confirmation when creating new filesystem * Check for new CryFS versions and ask the user to update if a new version is available * Implemented a mechanism that can show warnings about security bugs to users of a certain CryFS version. Let's hope this won't be necessary ;) * Compatibility with GCC 4.8 (that allows compiling on Ubuntu 14.04 for example) Version 0.8.2 --------------- * Mount directory, base directory, logfile and config file can be specified as relative paths * Improved error messages Version 0.8.1 --------------- * Config File Encryption: Configuration files are encrypted with two ciphers. The user specifies a password, which is then used with the scrypt KDF to generate the two encryption keys. - Inner level: Encrypts the config data using the user specified cipher. - Outer level: Encrypts the name of the inner cipher and the inner level ciphertext using aes-256-gcm. The config file is padded to hide the size of the configuration data (including the name of the cipher used). * No external config file needed: If the configuration file is not specified as command line parameter, it will be put into the base directory. This way, the filesystem can be mounted with the password only, without specifying a config file on command line. * Logfiles: Added a --logfile option to specify where logs should be written to. If the option is not specified, CryFs logs to syslog. * Running in Background: Fixed daemonization. When CryFs is run without "-f" flag, it will run in background. * Better error messages when base directory is not existing, not readable or not writeable. * Allow --cipher=xxx to specify cipher on command line. If cryfs is creating a new filesystem, it will use this cipher. If it is opening an existing filesystem, it will check whether this is the cipher used by it. * --show-ciphers shows a list of all supported ciphers * --extpass allows using an external program for password input * --unmount-idle x automatically unmounts the filesystem after x minutes without a filesystem operation. LICENSE.txt000066400000000000000000000167431347701267100127240ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. README.md000066400000000000000000000141201347701267100123430ustar00rootroot00000000000000# CryFS [![Build Status](https://travis-ci.org/cryfs/cryfs.svg?branch=master)](https://travis-ci.org/cryfs/cryfs) [![CircleCI](https://circleci.com/gh/cryfs/cryfs/tree/master.svg?style=svg)](https://circleci.com/gh/cryfs/cryfs/tree/master) [![Build status](https://ci.appveyor.com/api/projects/status/84ouutflsnap9dlv/branch/master?svg=true)](https://ci.appveyor.com/project/smessmer/cryfs/branch/master) CryFS encrypts your files, so you can safely store them anywhere. It works well together with cloud services like Dropbox, iCloud, OneDrive and others. See [https://www.cryfs.org](https://www.cryfs.org). Install latest release ====================== Linux ------ This only works for Ubuntu 17.04 and later, and Debian Stretch and later. You can also use CryFS on older versions of these distributions by following the **Building from source** instructions below. sudo apt install cryfs OSX ---- CryFS is distributed via Homebrew. Just do brew cask install osxfuse brew install cryfs Windows (experimental) ---------------------- CryFS has experimental Windows support since the 0.10 release series. To install it, do: 1. Install [DokanY](https://github.com/dokan-dev/dokany/releases) 2. Install [Microsoft Visual C++ Redistributable for Visual Studio 2017](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads) 3. Install [CryFS](https://www.cryfs.org/#download) GUI === Theres some GUI applications with CryFS support. You usually have to install the GUI **and** also CryFS itself for it to work. - [SiriKali](https://mhogomchungu.github.io/sirikali/) - [Plasma Vault](https://www.kde.org/announcements/plasma-5.11.0.php) in KDE Plasma >= 5.11 Building from source ==================== Requirements ------------ - Git (for getting the source code) - GCC version >= 5.0 or Clang >= 4.0 - CMake version >= 3.0 - libcurl4 (including development headers) - Boost libraries version >= 1.65.1 (including development headers) - filesystem - system - chrono - program_options - thread - SSL development libraries (including development headers, e.g. libssl-dev) - libFUSE version >= 2.8.6 (including development headers), on Mac OS X instead install osxfuse from https://osxfuse.github.io/ - Python >= 2.7 - OpenMP 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 # Fedora 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 Build & Install --------------- 1. Clone repository $ git clone https://github.com/cryfs/cryfs.git cryfs $ cd cryfs 2. Build $ mkdir cmake && cd cmake $ cmake .. $ make 3. Install $ sudo make install You can pass the following variables to the *cmake* command (using *-Dvariablename=value*): - **-DCMAKE_BUILD_TYPE**=[Release|Debug]: Whether to run code optimization or add debug symbols. Default: Release - **-DBUILD_TESTING**=[on|off]: Whether to build the test cases (can take a long time). Default: off - **-DCRYFS_UPDATE_CHECKS**=off: Build a CryFS that doesn't check online for updates and security vulnerabilities. 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.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. Troubleshooting --------------- On most systems, CMake should find the libraries automatically. However, that doesn't always work. 1. **Boost headers not found** Pass in the boost include path with cmake .. -DBoost_INCLUDE_DIRS=/path/to/boost/headers If you want to link boost dynamically (e.g. you don't have the static libraries), use the following: cmake .. -DBoost_USE_STATIC_LIBS=off 2. **Fuse/Osxfuse library not found** Pass in the library path with cmake .. -DFUSE_LIB_PATH=/path/to/fuse/or/osxfuse 3. **Fuse/Osxfuse headers not found** Pass in the include path with cmake .. -DCMAKE_CXX_FLAGS="-I/path/to/fuse/or/osxfuse/headers" 4. **Openssl headers not found** Pass in the include path with cmake .. -DCMAKE_C_FLAGS="-I/path/to/openssl/include" 5. **OpenMP not found (osx)** Either build it without OpenMP cmake .. -DDISABLE_OPENMP=on but that will cause slower file system mount times (performance after mounting will be unaffected). If you installed OpenMP with homebrew or macports, it should be autodetected. If that doesn't work for some reason (or you want to use a different installation than the autodetected one), pass in these flags: cmake .. -DOpenMP_CXX_FLAGS='-Xpreprocessor -fopenmp -I/path/to/openmp/include' -DOpenMP_CXX_LIB_NAMES=omp -DOpenMP_omp_LIBRARY=/path/to/libomp.dylib Creating .deb and .rpm packages ------------------------------- There are additional requirements if you want to create packages. They are: - CMake version >= 3.3 - rpmbuild for creating .rpm package 1. Clone repository $ git clone https://github.com/cryfs/cryfs.git cryfs $ cd cryfs 2. Build $ mkdir cmake && cd cmake $ cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=off $ make package Disclaimer ---------------------- On the event of a password leak, you are strongly advised to create a new filesystem and copy all the data over from the previous one. Done this, all copies of the compromised filesystem and config file must be removed (e.g, from the "previous versions" feature of your cloud system) to prevent access to the key (and, as a result, your data) using the leaked password. appveyor.yml000066400000000000000000000036721347701267100134660ustar00rootroot00000000000000image: - Visual Studio 2017 #- Visual Studio 2017 Preview platform: - x64 - x86 #- Any CPU configuration: - Debug - RelWithDebInfo - Release version: '{branch}-{build}' init: - echo %NUMBER_OF_PROCESSORS% - echo %PLATFORM% - echo %APPVEYOR_BUILD_WORKER_IMAGE% - set arch=32 - if "%PLATFORM%"=="x64" ( set arch=64) - set VisualStudioVersion=2017 - if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2017 Preview" ( set VisualStudioVersion=Preview) - cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\%VisualStudioVersion%\Community\VC\Auxiliary\Build\vcvars%arch%.bat" install: - 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_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 - 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 - cmd: .\test\blobstore\blobstore-test.exe - cmd: .\test\cryfs\cryfs-test.exe #- cmd: .\test\cryfs-cli\cryfs-cli-test.exe - cmd: cpack -C %CONFIGURATION% --verbose -G WIX on_failure: - cmd: type C:\projects\cryfs\build\_CPack_Packages\win64\WIX\wix.log artifacts: - path: build/cryfs-*.msi name: CryFS archive.sh000077500000000000000000000004201347701267100130420ustar00rootroot00000000000000#!/bin/bash TAG=$1 GPGHOMEDIR=$2 git archive --format=tgz "$1" > cryfs-$1.tar.gz gpg --homedir "$GPGHOMEDIR" --armor --detach-sign cryfs-$1.tar.gz git archive --format=tar "$1" | xz -9 > cryfs-$1.tar.xz gpg --homedir "$GPGHOMEDIR" --armor --detach-sign cryfs-$1.tar.xz cmake-utils/000077500000000000000000000000001347701267100133045ustar00rootroot00000000000000cmake-utils/FindLibunwind.cmake000066400000000000000000000035561347701267100170530ustar00rootroot00000000000000# 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 )cmake-utils/TargetArch.cmake000066400000000000000000000155161347701267100163420ustar00rootroot00000000000000# This file is taken from https://github.com/axr/solar-cmake/blob/73cfea0db0284c5e2010aca23989046e5bda95c9/TargetArch.cmake # License: # Copyright (c) 2012 Petroules Corporation. All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: # # Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. # Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Based on the Qt 5 processor detection code, so should be very accurate # https://qt.gitorious.org/qt/qtbase/blobs/master/src/corelib/global/qprocessordetection.h # Currently handles arm (v5, v6, v7), x86 (32/64), ia64, and ppc (32/64) # Regarding POWER/PowerPC, just as is noted in the Qt source, # "There are many more known variants/revisions that we do not handle/detect." set(archdetect_c_code " #if defined(__arm__) || defined(__TARGET_ARCH_ARM) #if defined(__ARM_ARCH_7__) \\ || defined(__ARM_ARCH_7A__) \\ || defined(__ARM_ARCH_7R__) \\ || defined(__ARM_ARCH_7M__) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 7) #error cmake_ARCH armv7 #elif defined(__ARM_ARCH_6__) \\ || defined(__ARM_ARCH_6J__) \\ || defined(__ARM_ARCH_6T2__) \\ || defined(__ARM_ARCH_6Z__) \\ || defined(__ARM_ARCH_6K__) \\ || defined(__ARM_ARCH_6ZK__) \\ || defined(__ARM_ARCH_6M__) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 6) #error cmake_ARCH armv6 #elif defined(__ARM_ARCH_5TEJ__) \\ || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 5) #error cmake_ARCH armv5 #else #error cmake_ARCH arm #endif #elif defined(__i386) || defined(__i386__) || defined(_M_IX86) #error cmake_ARCH i386 #elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64) #error cmake_ARCH x86_64 #elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) #error cmake_ARCH ia64 #elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\ || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ || defined(_M_MPPC) || defined(_M_PPC) #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) #error cmake_ARCH ppc64 #else #error cmake_ARCH ppc #endif #endif #error cmake_ARCH unknown ") # Set ppc_support to TRUE before including this file or ppc and ppc64 # will be treated as invalid architectures since they are no longer supported by Apple function(target_architecture output_var) if(APPLE AND CMAKE_OSX_ARCHITECTURES) # On OS X we use CMAKE_OSX_ARCHITECTURES *if* it was set # First let's normalize the order of the values # Note that it's not possible to compile PowerPC applications if you are using # the OS X SDK version 10.6 or later - you'll need 10.4/10.5 for that, so we # disable it by default # See this page for more information: # http://stackoverflow.com/questions/5333490/how-can-we-restore-ppc-ppc64-as-well-as-full-10-4-10-5-sdk-support-to-xcode-4 # Architecture defaults to i386 or ppc on OS X 10.5 and earlier, depending on the CPU type detected at runtime. # On OS X 10.6+ the default is x86_64 if the CPU supports it, i386 otherwise. foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES}) if("${osx_arch}" STREQUAL "ppc" AND ppc_support) set(osx_arch_ppc TRUE) elseif("${osx_arch}" STREQUAL "i386") set(osx_arch_i386 TRUE) elseif("${osx_arch}" STREQUAL "x86_64") set(osx_arch_x86_64 TRUE) elseif("${osx_arch}" STREQUAL "ppc64" AND ppc_support) set(osx_arch_ppc64 TRUE) else() message(FATAL_ERROR "Invalid OS X arch name: ${osx_arch}") endif() endforeach() # Now add all the architectures in our normalized order if(osx_arch_ppc) list(APPEND ARCH ppc) endif() if(osx_arch_i386) list(APPEND ARCH i386) endif() if(osx_arch_x86_64) list(APPEND ARCH x86_64) endif() if(osx_arch_ppc64) list(APPEND ARCH ppc64) endif() else() file(WRITE "${CMAKE_BINARY_DIR}/arch.c" "${archdetect_c_code}") enable_language(C) # Detect the architecture in a rather creative way... # This compiles a small C program which is a series of ifdefs that selects a # particular #error preprocessor directive whose message string contains the # target architecture. The program will always fail to compile (both because # file is not a valid C program, and obviously because of the presence of the # #error preprocessor directives... but by exploiting the preprocessor in this # way, we can detect the correct target architecture even when cross-compiling, # since the program itself never needs to be run (only the compiler/preprocessor) try_run( run_result_unused compile_result_unused "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/arch.c" COMPILE_OUTPUT_VARIABLE ARCH CMAKE_FLAGS CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES} ) # Parse the architecture name from the compiler output string(REGEX MATCH "cmake_ARCH ([a-zA-Z0-9_]+)" ARCH "${ARCH}") # Get rid of the value marker leaving just the architecture name string(REPLACE "cmake_ARCH " "" ARCH "${ARCH}") # If we are compiling with an unknown architecture this variable should # already be set to "unknown" but in the case that it's empty (i.e. due # to a typo in the code), then set it to unknown if (NOT ARCH) set(ARCH unknown) endif() endif() set(${output_var} "${ARCH}" PARENT_SCOPE) endfunction()cmake-utils/utils.cmake000066400000000000000000000223641347701267100154550ustar00rootroot00000000000000include(CheckCXXCompilerFlag) ################################################### # Activate C++14 # # Uses: target_activate_cpp14(buildtarget) ################################################### function(target_activate_cpp14 TARGET) if("${CMAKE_VERSION}" VERSION_GREATER "3.1") set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD 14) set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD_REQUIRED ON) else("${CMAKE_VERSION}" VERSION_GREATER "3.1") check_cxx_compiler_flag("-std=c++14" COMPILER_HAS_CPP14_SUPPORT) if (COMPILER_HAS_CPP14_SUPPORT) target_compile_options(${TARGET} PRIVATE -std=c++14) else(COMPILER_HAS_CPP14_SUPPORT) check_cxx_compiler_flag("-std=c++1y" COMPILER_HAS_CPP14_PARTIAL_SUPPORT) if (COMPILER_HAS_CPP14_PARTIAL_SUPPORT) target_compile_options(${TARGET} PRIVATE -std=c++1y) else() message(FATAL_ERROR "Compiler doesn't support C++14") endif() endif(COMPILER_HAS_CPP14_SUPPORT) endif("${CMAKE_VERSION}" VERSION_GREATER "3.1") # Ideally, we'd like to use libc++ on linux as well, but: # - http://stackoverflow.com/questions/37096062/get-a-basic-c-program-to-compile-using-clang-on-ubuntu-16 # - https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=808086 # so only use it on Apple systems... if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND APPLE) target_compile_options(${TARGET} PUBLIC -stdlib=libc++) endif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND APPLE) endfunction(target_activate_cpp14) # Find clang-tidy executable (for use in target_enable_style_warnings) if (USE_CLANG_TIDY) find_program( CLANG_TIDY_EXE NAMES "clang-tidy" DOC "Path to clang-tidy executable" ) if(NOT CLANG_TIDY_EXE) message(FATAL_ERROR "clang-tidy not found. Please install clang-tidy or run without -DUSE_CLANG_TIDY=on.") else() set(CLANG_TIDY_OPTIONS "-system-headers=0") if (CLANG_TIDY_WARNINGS_AS_ERRORS) set(CLANG_TIDY_OPTIONS "${CLANG_TIDY_OPTIONS}" "-warnings-as-errors=*") endif() message(STATUS "Clang-tidy is enabled. Executable: ${CLANG_TIDY_EXE} Arguments: ${CLANG_TIDY_OPTIONS}") set(CLANG_TIDY_CLI "${CLANG_TIDY_EXE}" "${CLANG_TIDY_OPTIONS}") endif() endif() # Find iwyu (for use in target_enable_style_warnings) if (USE_IWYU) find_program( IWYU_EXE NAMES include-what-you-use iwyu ) if(NOT IWYU_EXE) message(FATAL_ERROR "include-what-you-use not found. Please install iwyu or run without -DUSE_IWYU=on.") else() message(STATUS "iwyu found: ${IWYU_EXE}") set(DO_IWYU "${IWYU_EXE}") endif() endif() ################################################# # Enable style compiler warnings # # Uses: target_enable_style_warnings(buildtarget) ################################################# function(target_enable_style_warnings TARGET) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # TODO elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") target_compile_options(${TARGET} PRIVATE -Wall -Wextra -Wold-style-cast -Wcast-align -Wno-unused-command-line-argument) # TODO consider -Wpedantic -Wchkp -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-promo -Wstrict-null-sentinel -Wstrict-overflow=5 -Wundef -Wno-unused -Wno-variadic-macros -Wno-parentheses -fdiagnostics-show-option -Wconversion and others? elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") target_compile_options(${TARGET} PRIVATE -Wall -Wextra -Wold-style-cast -Wcast-align -Wno-maybe-uninitialized) # TODO consider -Wpedantic -Wchkp -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-promo -Wstrict-null-sentinel -Wstrict-overflow=5 -Wundef -Wno-unused -Wno-variadic-macros -Wno-parentheses -fdiagnostics-show-option -Wconversion and others? endif() if (USE_WERROR) target_compile_options(${TARGET} PRIVATE -Werror) endif() # Enable clang-tidy if(USE_CLANG_TIDY) set_target_properties( ${TARGET} PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_CLI}" ) endif() if(USE_IWYU) set_target_properties( ${TARGET} PROPERTIES CXX_INCLUDE_WHAT_YOU_USE "${DO_IWYU}" ) endif() endfunction(target_enable_style_warnings) ################################################## # Add boost to the project # # Uses: # target_add_boost(buildtarget) # if you're only using header-only boost libs # target_add_boost(buildtarget system filesystem) # list all libraries to link against in the dependencies ################################################## 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.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) message(STATUS "Boost will be dynamically linked") 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.65.1 REQUIRED COMPONENTS ${ARGN}) target_include_directories(${TARGET} SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) target_link_libraries(${TARGET} PUBLIC ${Boost_LIBRARIES}) target_compile_definitions(${TARGET} PUBLIC BOOST_THREAD_VERSION=4) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") # Also link to rt, because boost thread needs that. target_link_libraries(${TARGET} PUBLIC rt) endif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") endfunction(target_add_boost) ################################################## # Specify that a specific minimal version of gcc is required # # Uses: # require_gcc_version(4.9) ################################################## function(require_gcc_version VERSION) if (CMAKE_COMPILER_IS_GNUCXX) execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) if (GCC_VERSION VERSION_LESS ${VERSION}) message(FATAL_ERROR "Needs at least gcc version ${VERSION}, found gcc ${GCC_VERSION}") endif (GCC_VERSION VERSION_LESS ${VERSION}) endif (CMAKE_COMPILER_IS_GNUCXX) endfunction(require_gcc_version) ################################################## # Specify that a specific minimal version of clang is required # # Uses: # require_clang_version(3.5) ################################################## function(require_clang_version VERSION) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${VERSION}) message(FATAL_ERROR "Needs at least clang version ${VERSION}, found clang ${CMAKE_CXX_COMPILER_VERSION}") endif (CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${VERSION}) endif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") endfunction(require_clang_version) ################################################## # Find the location of a library and return its full path in OUTPUT_VARIABLE. # If PATH_VARIABLE points to a defined variable, then the library will only be searched in this path. # If PATH_VARIABLE points to a undefined variable, default system locations will be searched. # # Uses (the following will search for fuse in system locations by default, and if the user passes -DFUSE_LIB_PATH to cmake, it will only search in this path. # find_library_with_path(MYLIBRARY fuse FUSE_LIB_PATH) # target_link_library(target ${MYLIBRARY}) ################################################## function(find_library_with_path OUTPUT_VARIABLE LIBRARY_NAME PATH_VARIABLE) if(${PATH_VARIABLE}) find_library(${OUTPUT_VARIABLE} ${LIBRARY_NAME} PATHS ${${PATH_VARIABLE}} NO_DEFAULT_PATH) if (${OUTPUT_VARIABLE} MATCHES NOTFOUND) message(FATAL_ERROR "Didn't find ${LIBRARY_NAME} in path specified by the ${PATH_VARIABLE} parameter (${${PATH_VARIABLE}}). Pass in the correct path or remove the parameter to try common system locations.") else(${OUTPUT_VARIABLE} MATCHES NOTFOUND) message(STATUS "Found ${LIBRARY_NAME} in user-defined path ${${PATH_VARIABLE}}") endif(${OUTPUT_VARIABLE} MATCHES NOTFOUND) else(${PATH_VARIABLE}) find_library(${OUTPUT_VARIABLE} ${LIBRARY_NAME}) if (${OUTPUT_VARIABLE} MATCHES NOTFOUND) message(FATAL_ERROR "Didn't find ${LIBRARY_NAME} library. If ${LIBRARY_NAME} is installed, try passing in the library location with -D${PATH_VARIABLE}=/path/to/${LIBRARY_NAME}/lib.") else(${OUTPUT_VARIABLE} MATCHES NOTFOUND) message(STATUS "Found ${LIBRARY_NAME} in system location") endif(${OUTPUT_VARIABLE} MATCHES NOTFOUND) endif(${PATH_VARIABLE}) endfunction(find_library_with_path) include(cmake-utils/TargetArch.cmake) function(get_target_architecture output_var) target_architecture(local_output_var) set(${output_var} ${local_output_var} PARENT_SCOPE) endfunction() cpack/000077500000000000000000000000001347701267100121475ustar00rootroot00000000000000cpack/CMakeLists.txt000066400000000000000000000075731347701267100147230ustar00rootroot00000000000000# appends a build number from the APPVEYOR_BUILD_NUMBER environment variable as fourth component to a version number, # i.e. "0.10" becomes "0.10.0.[buildnumber]", "1" becomes "1.0.0.[buildnumber]". function(append_build_number VERSION_NUMBER OUTPUT) string(REPLACE "." ";" VERSION_COMPONENTS ${STRIPPED_VERSION_NUMBER}) list(LENGTH VERSION_COMPONENTS NUM_VERSION_COMPONENTS) if (${NUM_VERSION_COMPONENTS} LESS_EQUAL 0) message(FATAL_ERROR "Didn't find any version components") endif() if (${NUM_VERSION_COMPONENTS} LESS_EQUAL 1) string(APPEND STRIPPED_VERSION_NUMBER ".0") endif() if (${NUM_VERSION_COMPONENTS} LESS_EQUAL 2) string(APPEND STRIPPED_VERSION_NUMBER ".0") endif() if (NOT $ENV{APPVEYOR_BUILD_NUMBER} STREQUAL "") string(APPEND STRIPPED_VERSION_NUMBER ".$ENV{APPVEYOR_BUILD_NUMBER}") endif() set(${OUTPUT} "${STRIPPED_VERSION_NUMBER}" PARENT_SCOPE) endfunction() if("${CMAKE_VERSION}" VERSION_LESS "3.3") # Earlier cmake versions generate .deb packages for which the package manager says they're bad quality # and asks the user whether they really want to install it. Cmake 3.3 fixes this. message(WARNING "Distribution package generation is only supported for CMake version >= 3.3. You're using ${CMAKE_VERSION}. You will be able to build and install CryFS, but you won't be able to generate .deb packages.") else() # Fix debfiles permissions. Unfortunately, git doesn't store file permissions. # When installing the .deb package and these files have the wrong permissions, the package manager complains. execute_process(COMMAND /bin/bash -c "chmod 0755 ${CMAKE_CURRENT_SOURCE_DIR}/debfiles/*") set(CPACK_PACKAGE_NAME "cryfs") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Encrypt your files and store them in the cloud.") set(CPACK_PACKAGE_DESCRIPTION "CryFS encrypts your files, so you can safely store them anywhere. It works well together with cloud services like Dropbox, iCloud, OneDrive and others.") set(CPACK_PACKAGE_CONTACT "Sebastian Messmer ") set(CPACK_PACKAGE_VENDOR "Sebastian Messmer") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE.txt") get_git_version(GITVERSION_VERSION_STRING) if(WIN32 AND NOT UNIX) set(CPACK_GENERATOR WIX) string(REGEX REPLACE "^([0-9\\.]+)([-+][0-9\\.a-zA-Z+-]+)?$" "\\1" STRIPPED_VERSION_NUMBER "${GITVERSION_VERSION_STRING}") append_build_number(${STRIPPED_VERSION_NUMBER} WIX_VERSION_NUMBER) message(STATUS "WIX package version is ${WIX_VERSION_NUMBER}") set(CPACK_PACKAGE_VERSION "${WIX_VERSION_NUMBER}") set(CPACK_WIX_UPGRADE_GUID "8b872ce1-557d-48e6-ac57-9f5e574feabf") set(CPACK_WIX_PRODUCT_GUID "26116061-4f99-4c44-a178-2153fa396308") #set(CPACK_WIX_PRODUCT_ICON "...") set(CPACK_WIX_PROPERTY_ARPURLINFOABOUT "https://www.cryfs.org") set(CPACK_PACKAGE_INSTALL_DIRECTORY "CryFS/${GITVERSION_VERSION_STRING}") set(CPACK_WIX_PATCH_FILE "${CMAKE_CURRENT_SOURCE_DIR}/wix/change_path_env.xml") else() set(CPACK_GENERATOR TGZ DEB RPM) set(CPACK_PACKAGE_VERSION "${GITVERSION_VERSION_STRING}") set(CPACK_STRIP_FILES OFF) set(CPACK_SOURCE_STRIP_FILES OFF) endif() set(CPACK_PACKAGE_EXECUTABLES "cryfs" "CryFS") set(CPACK_DEBIAN_PACKAGE_SECTION "utils") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) # Needs gnupg2, lsb-release for postinst script set(CPACK_DEBIAN_PACKAGE_DEPENDS "fuse, gnupg2, lsb-release") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://www.cryfs.org") set(CPACK_RPM_PACKAGE_LICENSE "LGPLv3") set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION}) set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/bin;/usr/share/man;/usr/share/man/man1") set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/debfiles/postinst;${CMAKE_CURRENT_SOURCE_DIR}/debfiles/postrm") include(CPack) endif() cpack/debfiles/000077500000000000000000000000001347701267100137245ustar00rootroot00000000000000cpack/debfiles/postinst000077500000000000000000000113161347701267100155370ustar00rootroot00000000000000#!/bin/bash # This script is called after the cryfs .deb package is installed. # It sets up the package source so the user gets automatic updates for cryfs. # DEVELOPER WARNING: There is a lot of redundancy between this file and the install.sh script in the cryfs-web repository. Please port modifications to there! set -e DEBIAN_REPO_URL="http://apt.cryfs.org/debian" UBUNTU_REPO_URL="http://apt.cryfs.org/ubuntu" DISTRIBUTION=`lsb_release -s -i` DISTRIBUTION_VERSION=`lsb_release -s -c` containsElement () { local e for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done return 1 } get_repo_url () { if [[ "$DISTRIBUTION" == "Debian" ]] || [[ "$DISTRIBUTION" == "Devuan" ]]; then echo $DEBIAN_REPO_URL elif [[ "$DISTRIBUTION" == "Ubuntu" ]]; then echo $UBUNTU_REPO_URL else echo Not adding package source because $DISTRIBUTION is not supported. Please keep CryFS manually up to date. 1>&2 exit 0 fi } get_apt_config () { apt-config dump|grep "$1 "|sed -e "s/^$1\ \"\([^\"]*\)\"\;/\1/g" } sources_list_dir () { root=$(get_apt_config "Dir") etc=$(get_apt_config "Dir::Etc") sourceparts=$(get_apt_config "Dir::Etc::sourceparts") echo "$root/$etc/$sourceparts" } add_repository () { dir=$(sources_list_dir) repo_url=$(get_repo_url) echo "deb $repo_url $DISTRIBUTION_VERSION main" > $dir/cryfs.list } install_key () { # Key from http://www.cryfs.org/apt.key apt-key add - > /dev/null <&2 exit 1 ;; esac set +e exit 0 cpack/debfiles/postrm000077500000000000000000000015461347701267100152040ustar00rootroot00000000000000#!/bin/bash # This script is called after the cryfs .deb package is uninstalled. # It removes the package source that was used to get automatic updates. set -e get_apt_config () { apt-config dump|grep "$1 "|sed -e "s/^$1\ \"\([^\"]*\)\"\;/\1/g" } sources_list_dir () { root=$(get_apt_config "Dir") etc=$(get_apt_config "Dir::Etc") sourceparts=$(get_apt_config "Dir::Etc::sourceparts") echo $root$etc$sourceparts } remove_repository () { dir=$(sources_list_dir) rm -f $dir/cryfs.list } remove_key () { # Don't fail if key was already removed apt-key rm 549E65B2 2>&1 > /dev/null || true } case "$1" in purge) remove_repository remove_key ;; abort-install|abort-upgrade|remove|upgrade|failed-upgrade) ;; *) echo "postrm called with unknown argument '$1'" >&2 exit 1 ;; esac set +e exit 0 cpack/wix/000077500000000000000000000000001347701267100127565ustar00rootroot00000000000000cpack/wix/change_path_env.xml000066400000000000000000000003271347701267100166130ustar00rootroot00000000000000 doc/000077500000000000000000000000001347701267100116335ustar00rootroot00000000000000doc/CMakeLists.txt000066400000000000000000000010411347701267100143670ustar00rootroot00000000000000project (doc) IF (WIN32) MESSAGE(STATUS "This is Windows. Will not install man page") ELSE (WIN32) INCLUDE(GNUInstallDirs) find_program(GZIP gzip) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/cryfs.1.gz COMMAND ${GZIP} -c ${CMAKE_CURRENT_SOURCE_DIR}/man/cryfs.1 > ${CMAKE_CURRENT_BINARY_DIR}/cryfs.1.gz ) add_custom_target(man ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/cryfs.1.gz) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cryfs.1.gz DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 CONFIGURATIONS Release ) ENDIF(WIN32) doc/man/000077500000000000000000000000001347701267100124065ustar00rootroot00000000000000doc/man/cryfs.1000066400000000000000000000154631347701267100136270ustar00rootroot00000000000000.\" cryfs(1) man page . .TH cryfs 1 . . . .SH NAME cryfs \- cryptographic filesystem for the cloud . . . .SH SYNOPSIS .\" mount/create syntax .B cryfs [\fB\-c\fR \fIfile\fR] [\fB\-f\fR] [\fIoptions\fR] .I basedir mountpoint [\fB\-\-\fR \fIfuse-options\fR] .br .\" show-ciphers syntax .B cryfs \-\-help\fR|\fB\-\-version\fR|\fB\-\-show-ciphers . . . .SH DESCRIPTION . .B CryFS encrypts your files, so you can safely store them anywhere. .PP . The goal of CryFS is not only to keep file contents, but also file sizes, metadata and directory structure confidential. CryFS uses .B encrypted same-size blocks to store both the files themselves and the block's relations to another. These blocks are stored as individual files in the base directory, which can then be synchronized with cloud services such as Dropbox. .PP . The blocks are encrypted using a random key, which is stored in a .B configuration file encrypted by the user's passphrase. By default, it will be stored together with the data in the base directory, but you can choose a different location if you do not want it in your cloud or when using a weak passphrase. . . . .SH USING CRYFS . .SS Selecting base and mount directories . While you can access your files through your .B mount directory, CryFS actually places them in your .B base directory after encrypting. CryFS will encrypt and decrypt your files 'on the fly' as they are accessed, so files will never be stored on the disk in unencrypted form. .PP . You can choose any empty directory as your base, but your mount directory should be outside of any cloud storage, as your cloud may try to sync your (temporarily mounted) unencrypted files as well. . .SS Setup and usage of your encrypted directory . .TP Creating and mounting your encrypted storage use the same command-line syntax: .B cryfs .I basedir mountpoint .PP . If CryFS detects an encrypted storage in the given base directory, you will be asked for the passphrase to unlock and mount it. Otherwise, CryFS will help you with creating one, just follow the on-screen instructions. .PP . .TP After you are done working with your encrypted files, unmount your storage \ with the command .B fusermount -u .I mountpoint . . .SS Changing your passphrase . As the encryption key to your CryFS storage is stored in your configuration file, it would be possible to re-encrypt it using a different passphrase (although this feature has not been implemented yet). .PP . However, this does not change the actual encryption key of your storage, so someone with access to the old passphrase and configuration file (for example through the file history of your cloud or your file system) could still access your files, even those created after the password change. .PP . For this reason, the recommended way to change your passphrase is to create a new CryFS storage with the new passphrase and move your files from the old to the new one. . . . .SH OPTIONS . .SS Getting help . .TP \fB\-h\fR, \fB\-\-help\fR . Show a help message containing short descriptions for all options. . . .TP \fB\-\-show\-ciphers\fR . Show a list of all supported encryption ciphers. . . .TP \fB\-\-version\fR . Show the CryFS version number. . . .SS Encryption parameters . .TP \fB\-\-blocksize\fR \fIarg\fR . Set the block size to \fIarg\fR bytes. Defaults to .BR 32768 . .br \" Intentional space .br A higher block size may help reducing the file count in your base directory (especially when storing large files), but will also waste more space when storing smaller files. . . .TP \fB\-\-cipher\fR \fIarg\fR . Use \fIarg\fR as the cipher for the encryption. Defaults to .BR aes-256-gcm . . . .TP \fB\-c\fR \fIfile\fR, \fB\-\-config\fR \fIfile\fR . Use \fIfile\fR as configuration file for this CryFS storage instead of \fIbasedir\fR/cryfs.config . . .SS General options . .TP \fB\-f\fR, \fB\-\-foreground\fI . Run CryFS in the foreground. Stop using CTRL-C. . . .TP \fB\-\-allow-filesystem-upgrade\fI . Allow upgrading the file system if it was created with an old CryFS version. After the upgrade, older CryFS versions might not be able to use the file system anymore. . . .TP \fB\-\-allow-integrity-violations\fI . By default, CryFS checks for integrity violations, i.e. will notice if an adversary modified or rolled back the file system. Using this flag, you can disable the integrity checks. This can for example be helpful for loading an old snapshot of your file system without CryFS thinking an adversary rolled it back. . . .TP \fB\-\-allow-replaced-filesystem\fI . By default, CryFS remembers file systems it has seen in this base directory and checks that it didn't get replaced by an attacker with an entirely different file system since the last time it was loaded. However, if you do want to replace the file system with an entirely new one, you can pass in this option to disable the check. . . .TP \fB\-\-missing-block-is-integrity-violation\fR=true . When CryFS encounters a missing ciphertext block, it cannot cannot (yet) know if it was deleted by an unauthorized adversary or by a second authorized client. This is one of the restrictions of the integrity checks currently in place. You can enable this flag to treat missing ciphertext blocks as integrity violations, but then your file system will not be usable by multiple clients anymore. By default, this flag is disabled. . . .TP \fB\-\-logfile\fR \fIfile\fR . Write status information to \fIfile\fR. If no logfile is given, CryFS will write them to syslog in background mode, or to stdout in foreground mode. . . .TP \fB\-\-unmount\-idle\fR \fIarg\fR . Unmount automatically after \fIarg\fR minutes of inactivity. . . . .SH ENVIRONMENT . .TP \fBCRYFS_FRONTEND\fR=noninteractive . With this option set, CryFS will only ask for the encryption passphrase once. Instead of asking the user for parameters not specified on the command line, it will just use the default values. CryFS will also not ask you to confirm your passphrase when creating a new CryFS storage. .br \" Intentional space .br Set this environment variable when automating CryFS using external tools or shell scripts. . . .TP \fBCRYFS_NO_UPDATE_CHECK\fR=true . By default, CryFS connects to the internet to check for known security vulnerabilities and new versions. This option disables this. . . .TP \fBCRYFS_LOCAL_STATE_DIR\fR=[path] . Sets the directory cryfs uses to store local state. This local state is used to recognize known file systems and run integrity checks (i.e. check that they haven't been modified by an attacker. Default value: ${HOME}/.cryfs . . . .SH SEE ALSO . .BR mount.fuse (1), .BR fusermount (1) .PP . For more information about the design of CryFS, visit .B https://www.cryfs.org .PP . Visit the development repository at .B https://github.com/cryfs/cryfs for the source code and the full list of contributors to CryFS. . . . .SH AUTHORS . CryFS was created by Sebastian Messmer and contributors. This man page was written by Maximilian Wende. run-clang-tidy.sh000077500000000000000000000016721347701267100142700ustar00rootroot00000000000000#!/bin/bash # Note: Call this from a cmake build directory (e.g. cmake/) for out-of-source builds # Examples: # mkdir cmake && cd cmake && ../run-clang-tidy.sh # mkdir cmake && cd cmake && ../run-clang-tidy.sh -fix # mkdir cmake && cd cmake && ../run-clang-tidy.sh -export-fixes fixes.yaml set -e export NUMCORES=`nproc` && if [ ! -n "$NUMCORES" ]; then export NUMCORES=`sysctl -n hw.ncpu`; fi echo Using ${NUMCORES} cores # Run cmake in current working directory, but on source that is in the same directory as this script file cmake -DBUILD_TESTING=on -DCMAKE_EXPORT_COMPILE_COMMANDS=ON "${0%/*}" # Filter all third party code from the compilation database cat compile_commands.json|jq "map(select(.file | test(\"^$(realpath ${0%/*})/(src|test)/.*$\")))" > compile_commands2.json rm compile_commands.json mv compile_commands2.json compile_commands.json run-clang-tidy.py -j${NUMCORES} -quiet -header-filter "$(realpath ${0%/*})/(src|test)/.*" $@ run-iwyu.sh000077500000000000000000000020451347701267100132250ustar00rootroot00000000000000#!/bin/bash # Note: Call this from a cmake build directory (e.g. cmake/) for out-of-source builds # Examples: # mkdir cmake && cd cmake && ../run-iwqu.sh # mkdir cmake && cd cmake && ../run-iwqu.sh -fix set -e export NUMCORES=`nproc` && if [ ! -n "$NUMCORES" ]; then export NUMCORES=`sysctl -n hw.ncpu`; fi echo Using ${NUMCORES} cores # Run cmake in current working directory, but on source that is in the same directory as this script file cmake -DBUILD_TESTING=on -DCMAKE_EXPORT_COMPILE_COMMANDS=ON "${0%/*}" # Filter all third party code from the compilation database cat compile_commands.json|jq "map(select(.file | test(\"^$(realpath ${0%/*})/(src|test)/.*$\")))" > compile_commands2.json rm compile_commands.json mv compile_commands2.json compile_commands.json if [ "$1" = "-fix" ]; then TMPFILE=/tmp/iwyu.`cat /dev/urandom | tr -cd 'a-f0-9' | head -c 8`.out function cleanup { rm ${TMPFILE} } trap cleanup EXIT iwyu_tool -j${NUMCORES} -p. ${@:2} | tee ${TMPFILE} fix_include < ${TMPFILE} else iwyu_tool -j${NUMCORES} -p. $@ fi src/000077500000000000000000000000001347701267100116555ustar00rootroot00000000000000src/CMakeLists.txt000066400000000000000000000005151347701267100144160ustar00rootroot00000000000000include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(gitversion) add_subdirectory(cpp-utils) add_subdirectory(fspp) add_subdirectory(parallelaccessstore) add_subdirectory(blockstore) add_subdirectory(blobstore) add_subdirectory(cryfs) add_subdirectory(cryfs-cli) add_subdirectory(cryfs-unmount) add_subdirectory(stats) src/blobstore/000077500000000000000000000000001347701267100136505ustar00rootroot00000000000000src/blobstore/CMakeLists.txt000066400000000000000000000024251347701267100164130ustar00rootroot00000000000000project (blobstore) set(SOURCES implementations/onblocks/parallelaccessdatatreestore/ParallelAccessDataTreeStoreAdapter.cpp implementations/onblocks/parallelaccessdatatreestore/DataTreeRef.cpp implementations/onblocks/parallelaccessdatatreestore/ParallelAccessDataTreeStore.cpp implementations/onblocks/utils/Math.cpp implementations/onblocks/BlobStoreOnBlocks.cpp implementations/onblocks/datanodestore/DataNode.cpp implementations/onblocks/datanodestore/DataLeafNode.cpp implementations/onblocks/datanodestore/DataInnerNode.cpp implementations/onblocks/datanodestore/DataNodeStore.cpp implementations/onblocks/datatreestore/impl/algorithms.cpp implementations/onblocks/datatreestore/impl/CachedValue.cpp implementations/onblocks/datatreestore/impl/LeafTraverser.cpp implementations/onblocks/datatreestore/LeafHandle.cpp implementations/onblocks/datatreestore/DataTree.cpp implementations/onblocks/datatreestore/DataTreeStore.cpp implementations/onblocks/BlobOnBlocks.cpp ) add_library(${PROJECT_NAME} STATIC ${SOURCES}) target_link_libraries(${PROJECT_NAME} PUBLIC cpp-utils blockstore) target_add_boost(${PROJECT_NAME} filesystem system thread) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) src/blobstore/implementations/000077500000000000000000000000001347701267100170605ustar00rootroot00000000000000src/blobstore/implementations/onblocks/000077500000000000000000000000001347701267100206725ustar00rootroot00000000000000src/blobstore/implementations/onblocks/BlobOnBlocks.cpp000066400000000000000000000031151347701267100237070ustar00rootroot00000000000000#include "parallelaccessdatatreestore/DataTreeRef.h" #include "BlobOnBlocks.h" #include "datanodestore/DataLeafNode.h" #include "datanodestore/DataNodeStore.h" #include "utils/Math.h" #include #include #include "datatreestore/LeafHandle.h" using cpputils::unique_ref; using cpputils::Data; using blockstore::BlockId; namespace blobstore { namespace onblocks { using parallelaccessdatatreestore::DataTreeRef; BlobOnBlocks::BlobOnBlocks(unique_ref datatree) : _datatree(std::move(datatree)) { } BlobOnBlocks::~BlobOnBlocks() { } // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) uint64_t BlobOnBlocks::size() const { return _datatree->numBytes(); } void BlobOnBlocks::resize(uint64_t numBytes) { _datatree->resizeNumBytes(numBytes); } Data BlobOnBlocks::readAll() const { return _datatree->readAllBytes(); } void BlobOnBlocks::read(void *target, uint64_t offset, uint64_t count) const { return _datatree->readBytes(target, offset, count); } uint64_t BlobOnBlocks::tryRead(void *target, uint64_t offset, uint64_t count) const { return _datatree->tryReadBytes(target, offset, count); } void BlobOnBlocks::write(const void *source, uint64_t offset, uint64_t count) { _datatree->writeBytes(source, offset, count); } void BlobOnBlocks::flush() { _datatree->flush(); } uint32_t BlobOnBlocks::numNodes() const { return _datatree->numNodes(); } const BlockId &BlobOnBlocks::blockId() const { return _datatree->blockId(); } unique_ref BlobOnBlocks::releaseTree() { return std::move(_datatree); } } } src/blobstore/implementations/onblocks/BlobOnBlocks.h000066400000000000000000000033101347701267100233510ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_BLOBONBLOCKS_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_BLOBONBLOCKS_H_ #include "../../interface/Blob.h" #include "datatreestore/LeafHandle.h" #include #include #include namespace blobstore { namespace onblocks { namespace datanodestore { class DataLeafNode; } namespace parallelaccessdatatreestore { class DataTreeRef; } class BlobOnBlocks final: public Blob { public: BlobOnBlocks(cpputils::unique_ref datatree); ~BlobOnBlocks(); const blockstore::BlockId &blockId() const override; uint64_t size() const override; void resize(uint64_t numBytes) override; cpputils::Data readAll() const override; void read(void *target, uint64_t offset, uint64_t size) const override; uint64_t tryRead(void *target, uint64_t offset, uint64_t size) const override; void write(const void *source, uint64_t offset, uint64_t size) override; void flush() override; uint32_t numNodes() const override; cpputils::unique_ref releaseTree(); private: uint64_t _tryRead(void *target, uint64_t offset, uint64_t size) const; void _read(void *target, uint64_t offset, uint64_t count) const; void _traverseLeaves(uint64_t offsetBytes, uint64_t sizeBytes, std::function onExistingLeaf, std::function onCreateLeaf) const; cpputils::unique_ref _datatree; DISALLOW_COPY_AND_ASSIGN(BlobOnBlocks); }; } } #endif src/blobstore/implementations/onblocks/BlobStoreOnBlocks.cpp000066400000000000000000000045471347701267100247360ustar00rootroot00000000000000#include "parallelaccessdatatreestore/DataTreeRef.h" #include "parallelaccessdatatreestore/ParallelAccessDataTreeStore.h" #include #include "datanodestore/DataLeafNode.h" #include "datanodestore/DataNodeStore.h" #include "datatreestore/DataTreeStore.h" #include "datatreestore/DataTree.h" #include "BlobStoreOnBlocks.h" #include "BlobOnBlocks.h" #include #include using cpputils::unique_ref; using cpputils::make_unique_ref; using blockstore::BlockStore; using blockstore::parallelaccess::ParallelAccessBlockStore; using blockstore::BlockId; using cpputils::dynamic_pointer_move; using boost::optional; using boost::none; namespace blobstore { namespace onblocks { using datanodestore::DataNodeStore; using datatreestore::DataTreeStore; using parallelaccessdatatreestore::ParallelAccessDataTreeStore; BlobStoreOnBlocks::BlobStoreOnBlocks(unique_ref blockStore, uint64_t physicalBlocksizeBytes) : _dataTreeStore(make_unique_ref(make_unique_ref(make_unique_ref(make_unique_ref(std::move(blockStore)), physicalBlocksizeBytes)))) { } BlobStoreOnBlocks::~BlobStoreOnBlocks() { } unique_ref BlobStoreOnBlocks::create() { return make_unique_ref(_dataTreeStore->createNewTree()); } optional> BlobStoreOnBlocks::load(const BlockId &blockId) { auto tree = _dataTreeStore->load(blockId); if (tree == none) { return none; } return optional>(make_unique_ref(std::move(*tree))); } void BlobStoreOnBlocks::remove(unique_ref blob) { auto _blob = dynamic_pointer_move(blob); ASSERT(_blob != none, "Passed Blob in BlobStoreOnBlocks::remove() is not a BlobOnBlocks."); _dataTreeStore->remove((*_blob)->releaseTree()); } void BlobStoreOnBlocks::remove(const BlockId &blockId) { _dataTreeStore->remove(blockId); } uint64_t BlobStoreOnBlocks::virtualBlocksizeBytes() const { return _dataTreeStore->virtualBlocksizeBytes(); } uint64_t BlobStoreOnBlocks::numBlocks() const { return _dataTreeStore->numNodes(); } uint64_t BlobStoreOnBlocks::estimateSpaceForNumBlocksLeft() const { return _dataTreeStore->estimateSpaceForNumNodesLeft(); } } } src/blobstore/implementations/onblocks/BlobStoreOnBlocks.h000066400000000000000000000027501347701267100243750ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_BLOCKED_BLOBSTOREONBLOCKS_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_BLOCKED_BLOBSTOREONBLOCKS_H_ #include "../../interface/BlobStore.h" #include "BlobOnBlocks.h" #include namespace blobstore { namespace onblocks { namespace parallelaccessdatatreestore { class ParallelAccessDataTreeStore; } //TODO Make blobstore able to cope with incomplete data (some blocks missing, because they're not synchronized yet) and write test cases for that class BlobStoreOnBlocks final: public BlobStore { public: BlobStoreOnBlocks(cpputils::unique_ref blockStore, uint64_t physicalBlocksizeBytes); ~BlobStoreOnBlocks(); cpputils::unique_ref create() override; boost::optional> load(const blockstore::BlockId &blockId) override; void remove(cpputils::unique_ref blob) override; void remove(const blockstore::BlockId &blockId) override; //TODO Test blocksizeBytes/numBlocks/estimateSpaceForNumBlocksLeft //virtual means "space we can use" as opposed to "space it takes on the disk" (i.e. virtual is without headers, checksums, ...) uint64_t virtualBlocksizeBytes() const override; uint64_t numBlocks() const override; uint64_t estimateSpaceForNumBlocksLeft() const override; private: cpputils::unique_ref _dataTreeStore; DISALLOW_COPY_AND_ASSIGN(BlobStoreOnBlocks); }; } } #endif src/blobstore/implementations/onblocks/datanodestore/000077500000000000000000000000001347701267100235265ustar00rootroot00000000000000src/blobstore/implementations/onblocks/datanodestore/DataInnerNode.cpp000066400000000000000000000064411347701267100267120ustar00rootroot00000000000000#include "DataInnerNode.h" #include "DataNodeStore.h" #include using blockstore::Block; using blockstore::BlockStore; using cpputils::Data; using cpputils::unique_ref; using cpputils::make_unique_ref; using blockstore::BlockId; using std::vector; namespace blobstore { namespace onblocks { namespace datanodestore { DataInnerNode::DataInnerNode(DataNodeView view) : DataNode(std::move(view)) { ASSERT(depth() > 0, "Inner node can't have depth 0. Is this a leaf maybe?"); if (node().FormatVersion() != FORMAT_VERSION_HEADER) { throw std::runtime_error("This node format (" + std::to_string(node().FormatVersion()) + ") is not supported. Was it created with a newer version of CryFS?"); } } DataInnerNode::~DataInnerNode() { } unique_ref DataInnerNode::InitializeNewNode(unique_ref block, const DataNodeLayout &layout, uint8_t depth, const vector &children) { ASSERT(children.size() >= 1, "An inner node must have at least one child"); Data data = _serializeChildren(children); return make_unique_ref(DataNodeView::initialize(std::move(block), layout, DataNode::FORMAT_VERSION_HEADER, depth, children.size(), std::move(data))); } unique_ref DataInnerNode::CreateNewNode(BlockStore *blockStore, const DataNodeLayout &layout, uint8_t depth, const vector &children) { ASSERT(children.size() >= 1, "An inner node must have at least one child"); Data data = _serializeChildren(children); return make_unique_ref(DataNodeView::create(blockStore, layout, DataNode::FORMAT_VERSION_HEADER, depth, children.size(), std::move(data))); } Data DataInnerNode::_serializeChildren(const vector &children) { Data data(sizeof(ChildEntry) * children.size()); uint32_t i = 0; for (const BlockId &child : children) { child.ToBinary(data.dataOffset(i * BlockId::BINARY_LENGTH)); ++i; } return data; } uint32_t DataInnerNode::numChildren() const { return node().Size(); } DataInnerNode::ChildEntry DataInnerNode::readChild(unsigned int index) const { ASSERT(index < numChildren(), "Accessing child out of range"); return ChildEntry(BlockId::FromBinary(static_cast(node().data()) + index * sizeof(ChildEntry))); } void DataInnerNode::_writeChild(unsigned int index, const ChildEntry& child) { ASSERT(index < numChildren(), "Accessing child out of range"); node().write(child.blockId().data().data(), index * sizeof(ChildEntry), sizeof(ChildEntry)); } DataInnerNode::ChildEntry DataInnerNode::readLastChild() const { return readChild(numChildren() - 1); } void DataInnerNode::_writeLastChild(const ChildEntry& child) { _writeChild(numChildren() - 1, child); } void DataInnerNode::addChild(const DataNode &child) { ASSERT(numChildren() < maxStoreableChildren(), "Adding more children than we can store"); ASSERT(child.depth() == depth()-1, "The child that should be added has wrong depth"); node().setSize(node().Size()+1); _writeLastChild(ChildEntry(child.blockId())); } void DataInnerNode::removeLastChild() { ASSERT(node().Size() > 1, "There is no child to remove"); _writeLastChild(ChildEntry(BlockId::Null())); node().setSize(node().Size()-1); } uint32_t DataInnerNode::maxStoreableChildren() const { return node().layout().maxChildrenPerInnerNode(); } } } } src/blobstore/implementations/onblocks/datanodestore/DataInnerNode.h000066400000000000000000000025471347701267100263620ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATAINNERNODE_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATAINNERNODE_H_ #include "DataNode.h" #include "DataInnerNode_ChildEntry.h" namespace blobstore { namespace onblocks { namespace datanodestore { class DataInnerNode final: public DataNode { public: static cpputils::unique_ref InitializeNewNode(cpputils::unique_ref block, const DataNodeLayout &layout, uint8_t depth, const std::vector &children); static cpputils::unique_ref CreateNewNode(blockstore::BlockStore *blockStore, const DataNodeLayout &layout, uint8_t depth, const std::vector &children); using ChildEntry = DataInnerNode_ChildEntry; DataInnerNode(DataNodeView block); ~DataInnerNode(); uint32_t maxStoreableChildren() const; ChildEntry readChild(unsigned int index) const; ChildEntry readLastChild() const; uint32_t numChildren() const; void addChild(const DataNode &child_blockId); void removeLastChild(); private: void _writeChild(unsigned int index, const ChildEntry& child); void _writeLastChild(const ChildEntry& child); static cpputils::Data _serializeChildren(const std::vector &children); DISALLOW_COPY_AND_ASSIGN(DataInnerNode); }; } } } #endif src/blobstore/implementations/onblocks/datanodestore/DataInnerNode_ChildEntry.h000066400000000000000000000015331347701267100305010ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATAINNERNODE_CHILDENTRY_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATAINNERNODE_CHILDENTRY_H_ #include namespace blobstore{ namespace onblocks{ namespace datanodestore{ struct DataInnerNode_ChildEntry final { public: DataInnerNode_ChildEntry(const blockstore::BlockId &blockId): _blockId(blockId) {} const blockstore::BlockId& blockId() const { return _blockId; } DataInnerNode_ChildEntry(const DataInnerNode_ChildEntry&) = delete; DataInnerNode_ChildEntry& operator=(const DataInnerNode_ChildEntry&) = delete; DataInnerNode_ChildEntry(DataInnerNode_ChildEntry&&) = default; DataInnerNode_ChildEntry& operator=(DataInnerNode_ChildEntry&&) = default; private: blockstore::BlockId _blockId; }; } } } #endif src/blobstore/implementations/onblocks/datanodestore/DataLeafNode.cpp000066400000000000000000000054601347701267100265060ustar00rootroot00000000000000#include "DataLeafNode.h" #include "DataInnerNode.h" #include using cpputils::Data; using blockstore::BlockId; using blockstore::BlockStore; using cpputils::unique_ref; using cpputils::make_unique_ref; namespace blobstore { namespace onblocks { namespace datanodestore { DataLeafNode::DataLeafNode(DataNodeView view) : DataNode(std::move(view)) { ASSERT(node().Depth() == 0, "Leaf node must have depth 0. Is it an inner node instead?"); ASSERT(numBytes() <= maxStoreableBytes(), "Leaf says it stores more bytes than it has space for"); if (node().FormatVersion() != FORMAT_VERSION_HEADER) { throw std::runtime_error("This node format is not supported. Was it created with a newer version of CryFS?"); } } DataLeafNode::~DataLeafNode() { } unique_ref DataLeafNode::CreateNewNode(BlockStore *blockStore, const DataNodeLayout &layout, Data data) { ASSERT(data.size() <= layout.maxBytesPerLeaf(), "Data passed in is too large for one leaf."); uint32_t size = data.size(); return make_unique_ref(DataNodeView::create(blockStore, layout, DataNode::FORMAT_VERSION_HEADER, 0, size, std::move(data))); } unique_ref DataLeafNode::OverwriteNode(BlockStore *blockStore, const DataNodeLayout &layout, const BlockId &blockId, Data data) { ASSERT(data.size() == layout.maxBytesPerLeaf(), "Data passed in is too large for one leaf."); uint32_t size = data.size(); return make_unique_ref(DataNodeView::overwrite(blockStore, layout, DataNode::FORMAT_VERSION_HEADER, 0, size, blockId, std::move(data))); } void DataLeafNode::read(void *target, uint64_t offset, uint64_t size) const { ASSERT(offset <= node().Size() && offset + size <= node().Size(), "Read out of valid area"); // Also check offset, because the addition could lead to overflows std::memcpy(target, static_cast(node().data()) + offset, size); } void DataLeafNode::write(const void *source, uint64_t offset, uint64_t size) { ASSERT(offset <= node().Size() && offset + size <= node().Size(), "Write out of valid area"); // Also check offset, because the addition could lead to overflows node().write(source, offset, size); } uint32_t DataLeafNode::numBytes() const { return node().Size(); } void DataLeafNode::resize(uint32_t new_size) { ASSERT(new_size <= maxStoreableBytes(), "Trying to resize to a size larger than the maximal size"); uint32_t old_size = node().Size(); if (new_size < old_size) { fillDataWithZeroesFromTo(new_size, old_size); } node().setSize(new_size); } void DataLeafNode::fillDataWithZeroesFromTo(uint64_t begin, uint64_t end) { Data ZEROES(end-begin); ZEROES.FillWithZeroes(); node().write(ZEROES.data(), begin, end-begin); } uint64_t DataLeafNode::maxStoreableBytes() const { return node().layout().maxBytesPerLeaf(); } } } } src/blobstore/implementations/onblocks/datanodestore/DataLeafNode.h000066400000000000000000000022701347701267100261470ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATALEAFNODE_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATALEAFNODE_H_ #include "DataNode.h" namespace blobstore { namespace onblocks { namespace datanodestore { class DataInnerNode; class DataLeafNode final: public DataNode { public: static cpputils::unique_ref CreateNewNode(blockstore::BlockStore *blockStore, const DataNodeLayout &layout, cpputils::Data data); static cpputils::unique_ref OverwriteNode(blockstore::BlockStore *blockStore, const DataNodeLayout &layout, const blockstore::BlockId &blockId, cpputils::Data data); DataLeafNode(DataNodeView block); ~DataLeafNode(); //Returning uint64_t, because calculations handling this probably need to be done in 64bit to support >4GB blobs. uint64_t maxStoreableBytes() const; void read(void *target, uint64_t offset, uint64_t size) const; void write(const void *source, uint64_t offset, uint64_t size); uint32_t numBytes() const; void resize(uint32_t size); private: void fillDataWithZeroesFromTo(uint64_t begin, uint64_t end); DISALLOW_COPY_AND_ASSIGN(DataLeafNode); }; } } } #endif src/blobstore/implementations/onblocks/datanodestore/DataNode.cpp000066400000000000000000000022011347701267100257040ustar00rootroot00000000000000#include "DataInnerNode.h" #include "DataLeafNode.h" #include "DataNode.h" #include "DataNodeStore.h" #include using blockstore::BlockId; using cpputils::unique_ref; namespace blobstore { namespace onblocks { namespace datanodestore { constexpr uint16_t DataNode::FORMAT_VERSION_HEADER; DataNode::DataNode(DataNodeView node) : _node(std::move(node)) { } DataNode::~DataNode() { } DataNodeView &DataNode::node() { return const_cast(const_cast(this)->node()); } const DataNodeView &DataNode::node() const { return _node; } const BlockId &DataNode::blockId() const { return _node.blockId(); } uint8_t DataNode::depth() const { return _node.Depth(); } unique_ref DataNode::convertToNewInnerNode(unique_ref node, const DataNodeLayout &layout, const DataNode &first_child) { auto block = node->_node.releaseBlock(); blockstore::utils::fillWithZeroes(block.get()); return DataInnerNode::InitializeNewNode(std::move(block), layout, first_child.depth()+1, {first_child.blockId()}); } void DataNode::flush() const { _node.flush(); } } } } src/blobstore/implementations/onblocks/datanodestore/DataNode.h000066400000000000000000000017711347701267100253640ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATANODE_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATANODE_H_ #include "DataNodeView.h" #include namespace blobstore { namespace onblocks { namespace datanodestore { class DataNodeStore; class DataInnerNode; class DataNode { public: virtual ~DataNode(); const blockstore::BlockId &blockId() const; uint8_t depth() const; static cpputils::unique_ref convertToNewInnerNode(cpputils::unique_ref node, const DataNodeLayout &layout, const DataNode &first_child); void flush() const; protected: // The FORMAT_VERSION_HEADER is used to allow future versions to have compatibility. static constexpr uint16_t FORMAT_VERSION_HEADER = 0; DataNode(DataNodeView block); DataNodeView &node(); const DataNodeView &node() const; friend class DataNodeStore; private: DataNodeView _node; DISALLOW_COPY_AND_ASSIGN(DataNode); }; } } } #endif src/blobstore/implementations/onblocks/datanodestore/DataNodeStore.cpp000066400000000000000000000116241347701267100267320ustar00rootroot00000000000000#include "DataInnerNode.h" #include "DataLeafNode.h" #include "DataNodeStore.h" #include #include #include #include using blockstore::BlockStore; using blockstore::Block; using blockstore::BlockId; using cpputils::Data; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::dynamic_pointer_move; using std::runtime_error; using boost::optional; using boost::none; using std::vector; namespace blobstore { namespace onblocks { namespace datanodestore { DataNodeStore::DataNodeStore(unique_ref blockstore, uint64_t physicalBlocksizeBytes) : _blockstore(std::move(blockstore)), _layout(_blockstore->blockSizeFromPhysicalBlockSize(physicalBlocksizeBytes)) { } DataNodeStore::~DataNodeStore() { } unique_ref DataNodeStore::load(unique_ref block) { DataNodeView node(std::move(block)); if (node.Depth() == 0) { return make_unique_ref(std::move(node)); } else if (node.Depth() <= MAX_DEPTH) { return make_unique_ref(std::move(node)); } else { throw runtime_error("Tree is to deep. Data corruption?"); } } unique_ref DataNodeStore::createNewInnerNode(uint8_t depth, const vector &children) { ASSERT(children.size() >= 1, "Inner node must have at least one child"); return DataInnerNode::CreateNewNode(_blockstore.get(), _layout, depth, children); } unique_ref DataNodeStore::createNewLeafNode(Data data) { return DataLeafNode::CreateNewNode(_blockstore.get(), _layout, std::move(data)); } unique_ref DataNodeStore::overwriteLeaf(const BlockId &blockId, Data data) { return DataLeafNode::OverwriteNode(_blockstore.get(), _layout, blockId, std::move(data)); } optional> DataNodeStore::load(const BlockId &blockId) { auto block = _blockstore->load(blockId); if (block == none) { return none; } else { ASSERT((*block)->size() == _layout.blocksizeBytes(), "Loading block of wrong size"); return load(std::move(*block)); } } unique_ref DataNodeStore::createNewNodeAsCopyFrom(const DataNode &source) { ASSERT(source.node().layout().blocksizeBytes() == _layout.blocksizeBytes(), "Source node has wrong layout. Is it from the same DataNodeStore?"); auto newBlock = blockstore::utils::copyToNewBlock(_blockstore.get(), source.node().block()); return load(std::move(newBlock)); } unique_ref DataNodeStore::overwriteNodeWith(unique_ref target, const DataNode &source) { ASSERT(target->node().layout().blocksizeBytes() == _layout.blocksizeBytes(), "Target node has wrong layout. Is it from the same DataNodeStore?"); ASSERT(source.node().layout().blocksizeBytes() == _layout.blocksizeBytes(), "Source node has wrong layout. Is it from the same DataNodeStore?"); auto targetBlock = target->node().releaseBlock(); cpputils::destruct(std::move(target)); // Call destructor blockstore::utils::copyTo(targetBlock.get(), source.node().block()); return DataNodeStore::load(std::move(targetBlock)); } void DataNodeStore::remove(unique_ref node) { BlockId blockId = node->blockId(); cpputils::destruct(std::move(node)); remove(blockId); } void DataNodeStore::remove(const BlockId &blockId) { _blockstore->remove(blockId); } void DataNodeStore::removeSubtree(unique_ref node) { auto leaf = dynamic_pointer_move(node); if (leaf != none) { remove(std::move(*leaf)); return; } auto inner = dynamic_pointer_move(node); ASSERT(inner != none, "Is neither a leaf nor an inner node"); for (uint32_t i = 0; i < (*inner)->numChildren(); ++i) { removeSubtree((*inner)->depth()-1, (*inner)->readChild(i).blockId()); } remove(std::move(*inner)); } void DataNodeStore::removeSubtree(uint8_t depth, const BlockId &blockId) { if (depth == 0) { remove(blockId); } else { auto node = load(blockId); ASSERT(node != none, "Node for removeSubtree not found"); auto inner = dynamic_pointer_move(*node); ASSERT(inner != none, "Is not an inner node, but depth was not zero"); ASSERT((*inner)->depth() == depth, "Wrong depth given"); for (uint32_t i = 0; i < (*inner)->numChildren(); ++i) { removeSubtree(depth-1, (*inner)->readChild(i).blockId()); } remove(std::move(*inner)); } } uint64_t DataNodeStore::numNodes() const { return _blockstore->numBlocks(); } uint64_t DataNodeStore::estimateSpaceForNumNodesLeft() const { return _blockstore->estimateNumFreeBytes() / _layout.blocksizeBytes(); } uint64_t DataNodeStore::virtualBlocksizeBytes() const { return _layout.blocksizeBytes(); } DataNodeLayout DataNodeStore::layout() const { return _layout; } void DataNodeStore::forEachNode(std::function callback) const { _blockstore->forEachBlock(std::move(callback)); } } } } src/blobstore/implementations/onblocks/datanodestore/DataNodeStore.h000066400000000000000000000041721347701267100263770ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATANODESTORE_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATANODESTORE_H_ #include #include #include "DataNodeView.h" #include namespace blockstore{ class Block; class BlockStore; } namespace blobstore { namespace onblocks { namespace datanodestore { class DataNode; class DataLeafNode; class DataInnerNode; class DataNodeStore final { public: DataNodeStore(cpputils::unique_ref blockstore, uint64_t physicalBlocksizeBytes); ~DataNodeStore(); static constexpr uint8_t MAX_DEPTH = 10; DataNodeLayout layout() const; boost::optional> load(const blockstore::BlockId &blockId); static cpputils::unique_ref load(cpputils::unique_ref block); cpputils::unique_ref createNewLeafNode(cpputils::Data data); cpputils::unique_ref createNewInnerNode(uint8_t depth, const std::vector &children); cpputils::unique_ref createNewNodeAsCopyFrom(const DataNode &source); cpputils::unique_ref overwriteNodeWith(cpputils::unique_ref target, const DataNode &source); cpputils::unique_ref overwriteLeaf(const blockstore::BlockId &blockId, cpputils::Data data); void remove(cpputils::unique_ref node); void remove(const blockstore::BlockId &blockId); void removeSubtree(uint8_t depth, const blockstore::BlockId &blockId); void removeSubtree(cpputils::unique_ref node); //TODO Test blocksizeBytes/numBlocks/estimateSpaceForNumBlocksLeft uint64_t virtualBlocksizeBytes() const; uint64_t numNodes() const; uint64_t estimateSpaceForNumNodesLeft() const; //TODO Test overwriteNodeWith(), createNodeAsCopyFrom(), removeSubtree() void forEachNode(std::function callback) const; private: cpputils::unique_ref _blockstore; const DataNodeLayout _layout; DISALLOW_COPY_AND_ASSIGN(DataNodeStore); }; } } } #endif src/blobstore/implementations/onblocks/datanodestore/DataNodeView.h000066400000000000000000000137151347701267100262200ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATANODEVIEW_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATANODESTORE_DATANODEVIEW_H_ #include #include "../BlobStoreOnBlocks.h" #include "DataInnerNode_ChildEntry.h" #include #include #include #include namespace blobstore { namespace onblocks { namespace datanodestore { //TODO Move DataNodeLayout into own file class DataNodeLayout final { public: constexpr DataNodeLayout(uint64_t blocksizeBytes) :_blocksizeBytes( (HEADERSIZE_BYTES + 2*sizeof(DataInnerNode_ChildEntry) <= blocksizeBytes) ? blocksizeBytes : throw std::logic_error("Blocksize too small, not enough space to store two children in an inner node")) { } //Total size of the header static constexpr uint32_t HEADERSIZE_BYTES = 8; //Where in the header is the format version field (used to allow compatibility with future versions of CryFS) static constexpr uint32_t FORMAT_VERSION_OFFSET_BYTES = 0; //format version uses 2 bytes //Where in the header is the depth field static constexpr uint32_t DEPTH_OFFSET_BYTES = 3; // depth uses 1 byte //Where in the header is the size field (for inner nodes: number of children, for leafs: content data size) static constexpr uint32_t SIZE_OFFSET_BYTES = 4; // size uses 4 bytes //Size of a block (header + data region) constexpr uint64_t blocksizeBytes() const { return _blocksizeBytes; } //Number of bytes in the data region of a node constexpr uint64_t datasizeBytes() const { return _blocksizeBytes - HEADERSIZE_BYTES; } //Maximum number of children an inner node can store constexpr uint64_t maxChildrenPerInnerNode() const { return datasizeBytes() / sizeof(DataInnerNode_ChildEntry); } //Maximum number of bytes a leaf can store constexpr uint64_t maxBytesPerLeaf() const { return datasizeBytes(); } private: uint32_t _blocksizeBytes; }; class DataNodeView final { public: DataNodeView(cpputils::unique_ref block): _block(std::move(block)) { } ~DataNodeView() {} static DataNodeView create(blockstore::BlockStore *blockStore, const DataNodeLayout &layout, uint16_t formatVersion, uint8_t depth, uint32_t size, cpputils::Data data) { ASSERT(data.size() <= layout.datasizeBytes(), "Data is too large for node"); cpputils::Data serialized = _serialize(layout, formatVersion, depth, size, std::move(data)); ASSERT(serialized.size() == layout.blocksizeBytes(), "Wrong block size"); auto block = blockStore->create(serialized); return DataNodeView(std::move(block)); } static DataNodeView initialize(cpputils::unique_ref block, const DataNodeLayout &layout, uint16_t formatVersion, uint8_t depth, uint32_t size, cpputils::Data data) { ASSERT(data.size() <= DataNodeLayout(block->size()).datasizeBytes(), "Data is too large for node"); cpputils::Data serialized = _serialize(layout, formatVersion, depth, size, std::move(data)); ASSERT(serialized.size() == block->size(), "Block has wrong size"); block->write(serialized.data(), 0, serialized.size()); return DataNodeView(std::move(block)); } static DataNodeView overwrite(blockstore::BlockStore *blockStore, const DataNodeLayout &layout, uint16_t formatVersion, uint8_t depth, uint32_t size, const blockstore::BlockId &blockId, cpputils::Data data) { ASSERT(data.size() <= layout.datasizeBytes(), "Data is too large for node"); cpputils::Data serialized = _serialize(layout, formatVersion, depth, size, std::move(data)); auto block = blockStore->overwrite(blockId, std::move(serialized)); return DataNodeView(std::move(block)); } DataNodeView(DataNodeView &&rhs) = default; uint16_t FormatVersion() const { return cpputils::deserializeWithOffset(_block->data(), DataNodeLayout::FORMAT_VERSION_OFFSET_BYTES); } void setFormatVersion(uint16_t value) { _block->write(&value, DataNodeLayout::FORMAT_VERSION_OFFSET_BYTES, sizeof(value)); } uint8_t Depth() const { return cpputils::deserializeWithOffset(_block->data(), DataNodeLayout::DEPTH_OFFSET_BYTES); } void setDepth(uint8_t value) { _block->write(&value, DataNodeLayout::DEPTH_OFFSET_BYTES, sizeof(value)); } uint32_t Size() const { return cpputils::deserializeWithOffset(_block->data(), DataNodeLayout::SIZE_OFFSET_BYTES); } void setSize(uint32_t value) { _block->write(&value, DataNodeLayout::SIZE_OFFSET_BYTES, sizeof(value)); } const void *data() const { return static_cast(_block->data()) + DataNodeLayout::HEADERSIZE_BYTES; } void write(const void *source, uint64_t offset, uint64_t size) { _block->write(source, offset + DataNodeLayout::HEADERSIZE_BYTES, size); } DataNodeLayout layout() const { return DataNodeLayout(_block->size()); } cpputils::unique_ref releaseBlock() { return std::move(_block); } const blockstore::Block &block() const { return *_block; } const blockstore::BlockId &blockId() const { return _block->blockId(); } void flush() const { _block->flush(); } private: static cpputils::Data _serialize(const DataNodeLayout &layout, uint16_t formatVersion, uint8_t depth, uint32_t size, cpputils::Data data) { cpputils::Data result(layout.blocksizeBytes()); cpputils::serialize(result.dataOffset(layout.FORMAT_VERSION_OFFSET_BYTES), formatVersion); cpputils::serialize(result.dataOffset(layout.DEPTH_OFFSET_BYTES), depth); cpputils::serialize(result.dataOffset(layout.SIZE_OFFSET_BYTES), size); std::memcpy(result.dataOffset(layout.HEADERSIZE_BYTES), data.data(), data.size()); std::memset(result.dataOffset(layout.HEADERSIZE_BYTES+data.size()), 0, layout.datasizeBytes()-data.size()); return result; } cpputils::unique_ref _block; DISALLOW_COPY_AND_ASSIGN(DataNodeView); }; } } } #endif src/blobstore/implementations/onblocks/datatreestore/000077500000000000000000000000001347701267100235405ustar00rootroot00000000000000src/blobstore/implementations/onblocks/datatreestore/DataTree.cpp000066400000000000000000000352531347701267100257450ustar00rootroot00000000000000#include "DataTree.h" #include "../datanodestore/DataNodeStore.h" #include "../datanodestore/DataInnerNode.h" #include "../datanodestore/DataLeafNode.h" #include "../utils/Math.h" #include "impl/algorithms.h" #include #include #include #include #include "impl/LeafTraverser.h" #include #include using blockstore::BlockId; using blobstore::onblocks::datanodestore::DataNodeStore; using blobstore::onblocks::datanodestore::DataNode; using blobstore::onblocks::datanodestore::DataInnerNode; using blobstore::onblocks::datanodestore::DataLeafNode; using std::function; using boost::shared_mutex; using boost::shared_lock; using boost::unique_lock; using boost::none; using boost::optional; using cpputils::optional_ownership_ptr; using cpputils::unique_ref; using cpputils::Data; using namespace cpputils::logging; //TODO shared_lock currently not enough for traverse because of root replacement. Can be fixed while keeping shared? namespace blobstore { namespace onblocks { namespace datatreestore { DataTree::DataTree(DataNodeStore *nodeStore, unique_ref rootNode) : _treeStructureMutex(), _nodeStore(nodeStore), _rootNode(std::move(rootNode)), _blockId(_rootNode->blockId()), _sizeCache() { } DataTree::~DataTree() { } const BlockId &DataTree::blockId() const { return _blockId; } void DataTree::flush() const { // By grabbing a lock, we ensure that all modifying functions don't run currently and are therefore flushed. // It's only a shared lock, because this doesn't modify the tree structure. shared_lock lock(_treeStructureMutex); // We also have to flush the root node _rootNode->flush(); } unique_ref DataTree::releaseRootNode() { // Lock also ensures that the root node is currently set (traversing unsets it temporarily) // It's a unique lock because this "modifies" tree structure by changing _rootNode. unique_lock lock(_treeStructureMutex); return std::move(_rootNode); } 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(_nodeStore->layout().maxChildrenPerInnerNode())); totalNumNodes += numNodesCurrentLevel; } return totalNumNodes; } uint32_t DataTree::numLeaves() const { shared_lock lock(_treeStructureMutex); return _getOrComputeSizeCache().numLeaves; } uint64_t DataTree::numBytes() const { shared_lock lock(_treeStructureMutex); return _numBytes(); } uint64_t DataTree::_numBytes() const { return _getOrComputeSizeCache().numBytes; } DataTree::SizeCache DataTree::_getOrComputeSizeCache() const { return _sizeCache.getOrCompute([this] () { return _computeSizeCache(*_rootNode); }); } uint32_t DataTree::forceComputeNumLeaves() const { _sizeCache.clear(); return numLeaves(); } DataTree::SizeCache DataTree::_computeSizeCache(const DataNode &node) const { const DataLeafNode *leaf = dynamic_cast(&node); if (leaf != nullptr) { return {1, leaf->numBytes()}; } const DataInnerNode &inner = dynamic_cast(node); uint32_t numLeavesInLeftChildren = static_cast(inner.numChildren()-1) * _leavesPerFullChild(inner); uint64_t numBytesInLeftChildren = numLeavesInLeftChildren * _nodeStore->layout().maxBytesPerLeaf(); auto lastChild = _nodeStore->load(inner.readLastChild().blockId()); ASSERT(lastChild != none, "Couldn't load last child"); SizeCache sizeInRightChild = _computeSizeCache(**lastChild); return SizeCache { numLeavesInLeftChildren + sizeInRightChild.numLeaves, numBytesInLeftChildren + sizeInRightChild.numBytes }; } void DataTree::_traverseLeavesByLeafIndices(uint32_t beginIndex, uint32_t endIndex, bool readOnlyTraversal, function onExistingLeaf, function onCreateLeaf, function onBacktrackFromSubtree) const { if(endIndex <= beginIndex) { return; } // TODO no const cast LeafTraverser(_nodeStore, readOnlyTraversal).traverseAndUpdateRoot(&const_cast(this)->_rootNode, beginIndex, endIndex, onExistingLeaf, onCreateLeaf, onBacktrackFromSubtree); } void DataTree::_traverseLeavesByByteIndices(uint64_t beginByte, uint64_t sizeBytes, bool readOnlyTraversal, function onExistingLeaf, function onCreateLeaf) const { if (sizeBytes == 0) { return; } uint64_t endByte = beginByte + sizeBytes; uint64_t _maxBytesPerLeaf = maxBytesPerLeaf(); uint32_t firstLeaf = beginByte / _maxBytesPerLeaf; uint32_t endLeaf = utils::ceilDivision(endByte, _maxBytesPerLeaf); bool blobIsGrowingFromThisTraversal = false; auto _onExistingLeaf = [&onExistingLeaf, beginByte, endByte, endLeaf, _maxBytesPerLeaf, &blobIsGrowingFromThisTraversal] (uint32_t leafIndex, bool isRightBorderLeaf, LeafHandle leafHandle) { uint64_t indexOfFirstLeafByte = leafIndex * _maxBytesPerLeaf; ASSERT(endByte > indexOfFirstLeafByte, "Traversal went too far right"); uint32_t dataBegin = utils::maxZeroSubtraction(beginByte, indexOfFirstLeafByte); uint32_t dataEnd = std::min(_maxBytesPerLeaf, endByte - indexOfFirstLeafByte); // If we are traversing exactly until the last leaf, then the last leaf wasn't resized by the traversal and might have a wrong size. We have to fix it. if (isRightBorderLeaf) { ASSERT(leafIndex == endLeaf-1, "If we traversed further right, this wouldn't be the right border leaf."); auto leaf = leafHandle.node(); if (leaf->numBytes() < dataEnd) { leaf->resize(dataEnd); blobIsGrowingFromThisTraversal = true; } } onExistingLeaf(indexOfFirstLeafByte, std::move(leafHandle), dataBegin, dataEnd-dataBegin); }; auto _onCreateLeaf = [&onCreateLeaf, _maxBytesPerLeaf, beginByte, firstLeaf, endByte, endLeaf, &blobIsGrowingFromThisTraversal, readOnlyTraversal] (uint32_t leafIndex) -> Data { ASSERT(!readOnlyTraversal, "Cannot create leaves in a read-only traversal"); blobIsGrowingFromThisTraversal = true; uint64_t indexOfFirstLeafByte = leafIndex * _maxBytesPerLeaf; ASSERT(endByte > indexOfFirstLeafByte, "Traversal went too far right"); uint32_t dataBegin = utils::maxZeroSubtraction(beginByte, indexOfFirstLeafByte); uint32_t dataEnd = std::min(_maxBytesPerLeaf, endByte - indexOfFirstLeafByte); ASSERT(leafIndex == firstLeaf || dataBegin == 0, "Only the leftmost leaf can have a gap on the left."); ASSERT(leafIndex == endLeaf-1 || dataEnd == _maxBytesPerLeaf, "Only the rightmost leaf can have a gap on the right"); Data data = onCreateLeaf(indexOfFirstLeafByte + dataBegin, dataEnd-dataBegin); ASSERT(data.size() == dataEnd-dataBegin, "Returned leaf data with wrong size"); // If this leaf is created but only partly in the traversed region (i.e. dataBegin > leafBegin), we have to fill the data before the traversed region with zeroes. if (dataBegin != 0) { Data actualData(dataBegin + data.size()); std::memset(actualData.data(), 0, dataBegin); std::memcpy(actualData.dataOffset(dataBegin), data.data(), data.size()); data = std::move(actualData); } return data; }; auto _onBacktrackFromSubtree = [] (DataInnerNode* /*node*/) {}; _traverseLeavesByLeafIndices(firstLeaf, endLeaf, readOnlyTraversal, _onExistingLeaf, _onCreateLeaf, _onBacktrackFromSubtree); ASSERT(!readOnlyTraversal || !blobIsGrowingFromThisTraversal, "Blob grew from traversal that didn't allow growing (i.e. reading)"); if (blobIsGrowingFromThisTraversal) { _sizeCache.update([endLeaf, endByte] (optional* cache) { *cache = SizeCache{endLeaf, endByte}; }); } } uint32_t DataTree::_leavesPerFullChild(const DataInnerNode &root) const { return utils::intPow(_nodeStore->layout().maxChildrenPerInnerNode(), static_cast(root.depth())-1); } void DataTree::resizeNumBytes(uint64_t newNumBytes) { std::unique_lock lock(_treeStructureMutex); uint32_t newNumLeaves = std::max(UINT64_C(1), utils::ceilDivision(newNumBytes, _nodeStore->layout().maxBytesPerLeaf())); uint32_t newLastLeafSize = newNumBytes - (newNumLeaves-1) * _nodeStore->layout().maxBytesPerLeaf(); uint32_t maxChildrenPerInnerNode = _nodeStore->layout().maxChildrenPerInnerNode(); auto onExistingLeaf = [newLastLeafSize] (uint32_t /*index*/, bool /*isRightBorderLeaf*/, LeafHandle leafHandle) { auto leaf = leafHandle.node(); // This is only called, if the new last leaf was already existing if (leaf->numBytes() != newLastLeafSize) { leaf->resize(newLastLeafSize); } }; auto onCreateLeaf = [newLastLeafSize] (uint32_t /*index*/) -> Data { // This is only called, if the new last leaf was not existing yet return Data(newLastLeafSize).FillWithZeroes(); }; auto onBacktrackFromSubtree = [this, newNumLeaves, maxChildrenPerInnerNode] (DataInnerNode* node) { // This is only called for the right border nodes of the new tree. // When growing size, the following is a no-op. When shrinking, we're deleting the children that aren't needed anymore. uint32_t maxLeavesPerChild = utils::intPow(static_cast(maxChildrenPerInnerNode), (static_cast(node->depth())-1)); uint32_t neededNodesOnChildLevel = utils::ceilDivision(newNumLeaves, maxLeavesPerChild); uint32_t neededSiblings = utils::ceilDivision(neededNodesOnChildLevel, maxChildrenPerInnerNode); uint32_t neededChildrenForRightBorderNode = neededNodesOnChildLevel - (neededSiblings-1) * maxChildrenPerInnerNode; ASSERT(neededChildrenForRightBorderNode <= node->numChildren(), "Node has too few children"); // All children to the right of the new right-border-node are removed including their subtree. while(node->numChildren() > neededChildrenForRightBorderNode) { _nodeStore->removeSubtree(node->depth()-1, node->readLastChild().blockId()); node->removeLastChild(); } }; _traverseLeavesByLeafIndices(newNumLeaves - 1, newNumLeaves, false, onExistingLeaf, onCreateLeaf, onBacktrackFromSubtree); _sizeCache.update([newNumLeaves, newNumBytes] (boost::optional* cache) { *cache = SizeCache{newNumLeaves, newNumBytes}; }); } uint64_t DataTree::maxBytesPerLeaf() const { return _nodeStore->layout().maxBytesPerLeaf(); } uint8_t DataTree::depth() const { shared_lock lock(_treeStructureMutex); return _rootNode->depth(); } void DataTree::readBytes(void *target, uint64_t offset, uint64_t count) const { shared_lock lock(_treeStructureMutex); const uint64_t _size = _numBytes(); if(offset > _size || offset + count > _size) { throw std::runtime_error("BlobOnBlocks::read() read outside blob. Use BlobOnBlocks::tryRead() if this should be allowed."); } const uint64_t read = _tryReadBytes(target, offset, count); if (read != count) { throw std::runtime_error("BlobOnBlocks::read() couldn't read all requested bytes. Use BlobOnBlocks::tryRead() if this should be allowed."); } } Data DataTree::readAllBytes() const { shared_lock lock(_treeStructureMutex); //TODO Querying numBytes can be inefficient. Is this possible without a call to size()? uint64_t count = _numBytes(); Data result(count); _doReadBytes(result.data(), 0, count); return result; } uint64_t DataTree::tryReadBytes(void *target, uint64_t offset, uint64_t count) const { shared_lock lock(_treeStructureMutex); auto result = _tryReadBytes(target, offset, count); return result; } uint64_t DataTree::_tryReadBytes(void *target, uint64_t offset, uint64_t count) const { //TODO Quite inefficient to call size() here, because that has to traverse the tree const uint64_t _size = _numBytes(); const uint64_t realCount = std::max(INT64_C(0), std::min(static_cast(count), static_cast(_size)-static_cast(offset))); _doReadBytes(target, offset, realCount); return realCount; } void DataTree::_doReadBytes(void *target, uint64_t offset, uint64_t count) const { auto onExistingLeaf = [target, offset, count] (uint64_t indexOfFirstLeafByte, LeafHandle leaf, uint32_t leafDataOffset, uint32_t leafDataSize) { ASSERT(indexOfFirstLeafByte+leafDataOffset>=offset && indexOfFirstLeafByte-offset+leafDataOffset <= count && indexOfFirstLeafByte-offset+leafDataOffset+leafDataSize <= count, "Writing to target out of bounds"); //TODO Simplify formula, make it easier to understand leaf.node()->read(static_cast(target) + indexOfFirstLeafByte - offset + leafDataOffset, leafDataOffset, leafDataSize); }; auto onCreateLeaf = [] (uint64_t /*beginByte*/, uint32_t /*count*/) -> Data { ASSERT(false, "Reading shouldn't create new leaves."); }; _traverseLeavesByByteIndices(offset, count, true, onExistingLeaf, onCreateLeaf); } void DataTree::writeBytes(const void *source, uint64_t offset, uint64_t count) { unique_lock lock(_treeStructureMutex); auto onExistingLeaf = [source, offset, count] (uint64_t indexOfFirstLeafByte, LeafHandle leaf, uint32_t leafDataOffset, uint32_t leafDataSize) { ASSERT(indexOfFirstLeafByte+leafDataOffset>=offset && indexOfFirstLeafByte-offset+leafDataOffset <= count && indexOfFirstLeafByte-offset+leafDataOffset+leafDataSize <= count, "Reading from source out of bounds"); if (leafDataOffset == 0 && leafDataSize == leaf.nodeStore()->layout().maxBytesPerLeaf()) { Data leafData(leafDataSize); std::memcpy(leafData.data(), static_cast(source) + indexOfFirstLeafByte - offset, leafDataSize); leaf.nodeStore()->overwriteLeaf(leaf.blockId(), std::move(leafData)); } else { //TODO Simplify formula, make it easier to understand leaf.node()->write(static_cast(source) + indexOfFirstLeafByte - offset + leafDataOffset, leafDataOffset, leafDataSize); } }; auto onCreateLeaf = [source, offset, count] (uint64_t beginByte, uint32_t numBytes) -> Data { ASSERT(beginByte >= offset && beginByte-offset <= count && beginByte-offset+numBytes <= count, "Reading from source out of bounds"); Data result(numBytes); //TODO Simplify formula, make it easier to understand std::memcpy(result.data(), static_cast(source) + beginByte - offset, numBytes); return result; }; _traverseLeavesByByteIndices(offset, count, false, onExistingLeaf, onCreateLeaf); } } } } src/blobstore/implementations/onblocks/datatreestore/DataTree.h000066400000000000000000000071641347701267100254120ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATATREE_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATATREE_H_ #include #include #include #include "../datanodestore/DataNodeView.h" //TODO Replace with C++14 once std::shared_mutex is supported #include #include #include "LeafHandle.h" #include "impl/CachedValue.h" namespace blobstore { namespace onblocks { namespace datanodestore { class DataNodeStore; class DataInnerNode; class DataLeafNode; class DataNode; } namespace datatreestore { //TODO It is strange that DataLeafNode is still part in the public interface of DataTree. This should be separated somehow. class DataTree final { public: DataTree(datanodestore::DataNodeStore *nodeStore, cpputils::unique_ref rootNode); ~DataTree(); const blockstore::BlockId &blockId() const; //Returning uint64_t, because calculations handling this probably need to be done in 64bit to support >4GB blobs. uint64_t maxBytesPerLeaf() const; uint64_t tryReadBytes(void *target, uint64_t offset, uint64_t count) const; void readBytes(void *target, uint64_t offset, uint64_t count) const; cpputils::Data readAllBytes() const; void writeBytes(const void *source, uint64_t offset, uint64_t count); void resizeNumBytes(uint64_t newNumBytes); uint32_t numNodes() const; uint32_t numLeaves() const; uint64_t numBytes() const; uint8_t depth() const; // only used by test cases uint32_t forceComputeNumLeaves() const; void flush() const; private: // This mutex must protect the tree structure, i.e. which nodes exist and how they're connected. // Also protects total number of bytes (i.e. number of leaves + size of last leaf). // It also protects the data in leaf nodes, because writing bytes might grow the blob and change the structure. mutable boost::shared_mutex _treeStructureMutex; datanodestore::DataNodeStore *_nodeStore; cpputils::unique_ref _rootNode; blockstore::BlockId _blockId; // BlockId is stored in a member variable, since _rootNode is nullptr while traversing, but we still want to be able to return the blockId. struct SizeCache final { uint32_t numLeaves; uint64_t numBytes; }; mutable CachedValue _sizeCache; cpputils::unique_ref releaseRootNode(); friend class DataTreeStore; void _traverseLeavesByLeafIndices(uint32_t beginIndex, uint32_t endIndex, bool readOnlyTraversal, std::function onExistingLeaf, std::function onCreateLeaf, std::function onBacktrackFromSubtree) const; void _traverseLeavesByByteIndices(uint64_t beginByte, uint64_t sizeBytes, bool readOnlyTraversal, std::function onExistingLeaf, std::function onCreateLeaf) const; uint32_t _leavesPerFullChild(const datanodestore::DataInnerNode &root) const; SizeCache _getOrComputeSizeCache() const; SizeCache _computeSizeCache(const datanodestore::DataNode &node) const; uint64_t _tryReadBytes(void *target, uint64_t offset, uint64_t count) const; void _doReadBytes(void *target, uint64_t offset, uint64_t count) const; uint64_t _numBytes() const; DISALLOW_COPY_AND_ASSIGN(DataTree); }; } } } #endif src/blobstore/implementations/onblocks/datatreestore/DataTreeStore.cpp000066400000000000000000000023101347701267100267460ustar00rootroot00000000000000#include "DataTreeStore.h" #include "../datanodestore/DataLeafNode.h" #include "DataTree.h" using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::Data; using boost::optional; using boost::none; using blobstore::onblocks::datanodestore::DataNodeStore; namespace blobstore { namespace onblocks { namespace datatreestore { DataTreeStore::DataTreeStore(unique_ref nodeStore) : _nodeStore(std::move(nodeStore)) { } DataTreeStore::~DataTreeStore() { } optional> DataTreeStore::load(const blockstore::BlockId &blockId) { auto node = _nodeStore->load(blockId); if (node == none) { return none; } return make_unique_ref(_nodeStore.get(), std::move(*node)); } unique_ref DataTreeStore::createNewTree() { auto newleaf = _nodeStore->createNewLeafNode(Data(0)); return make_unique_ref(_nodeStore.get(), std::move(newleaf)); } void DataTreeStore::remove(unique_ref tree) { _nodeStore->removeSubtree(tree->releaseRootNode()); } void DataTreeStore::remove(const blockstore::BlockId &blockId) { auto tree = load(blockId); ASSERT(tree != none, "Tree to remove not found"); remove(std::move(*tree)); } } } } src/blobstore/implementations/onblocks/datatreestore/DataTreeStore.h000066400000000000000000000027431347701267100264250ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_DATATREESTORE_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_DATATREESTORE_H_ #include #include #include #include #include #include "../datanodestore/DataNodeStore.h" namespace blobstore { namespace onblocks { namespace datatreestore { class DataTree; class DataTreeStore final { public: DataTreeStore(cpputils::unique_ref nodeStore); ~DataTreeStore(); boost::optional> load(const blockstore::BlockId &blockId); cpputils::unique_ref createNewTree(); void remove(cpputils::unique_ref tree); void remove(const blockstore::BlockId &blockId); //TODO Test blocksizeBytes/numBlocks/estimateSpaceForNumBlocksLeft uint64_t virtualBlocksizeBytes() const; uint64_t numNodes() const; uint64_t estimateSpaceForNumNodesLeft() const; private: cpputils::unique_ref _nodeStore; DISALLOW_COPY_AND_ASSIGN(DataTreeStore); }; inline uint64_t DataTreeStore::numNodes() const { return _nodeStore->numNodes(); } inline uint64_t DataTreeStore::estimateSpaceForNumNodesLeft() const { return _nodeStore->estimateSpaceForNumNodesLeft(); } inline uint64_t DataTreeStore::virtualBlocksizeBytes() const { return _nodeStore->virtualBlocksizeBytes(); } } } } #endif src/blobstore/implementations/onblocks/datatreestore/LeafHandle.cpp000066400000000000000000000026061347701267100262330ustar00rootroot00000000000000#include "LeafHandle.h" #include "../datanodestore/DataLeafNode.h" #include "../datanodestore/DataNodeStore.h" using cpputils::WithOwnership; using cpputils::WithoutOwnership; using boost::none; using cpputils::dynamic_pointer_move; using blobstore::onblocks::datanodestore::DataLeafNode; using blobstore::onblocks::datanodestore::DataNodeStore; using blockstore::BlockId; namespace blobstore { namespace onblocks { namespace datatreestore { LeafHandle::LeafHandle(DataNodeStore *nodeStore, const BlockId &blockId) : _nodeStore(nodeStore), _blockId(blockId), _leaf(cpputils::null()) { } LeafHandle::LeafHandle(DataNodeStore *nodeStore, DataLeafNode *node) : _nodeStore(nodeStore), _blockId(node->blockId()), _leaf(WithoutOwnership(node)) { } DataLeafNode *LeafHandle::node() { if (_leaf.get() == nullptr) { auto loaded = _nodeStore->load(_blockId); ASSERT(loaded != none, "Leaf not found"); auto leaf = dynamic_pointer_move(*loaded); ASSERT(leaf != none, "Loaded leaf is not leaf node"); _leaf = WithOwnership(std::move(*leaf)); } return _leaf.get(); } } } } src/blobstore/implementations/onblocks/datatreestore/LeafHandle.h000066400000000000000000000025341347701267100257000ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_LEAFHANDLE_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_LEAFHANDLE_H_ #include #include #include namespace blobstore { namespace onblocks { namespace datanodestore { class DataNodeStore; class DataLeafNode; } namespace datatreestore { class LeafHandle final { public: LeafHandle(datanodestore::DataNodeStore *nodeStore, const blockstore::BlockId &blockId); LeafHandle(datanodestore::DataNodeStore *nodeStore, datanodestore::DataLeafNode *node); LeafHandle(LeafHandle &&rhs) = default; const blockstore::BlockId &blockId() { return _blockId; } datanodestore::DataLeafNode *node(); datanodestore::DataNodeStore *nodeStore() { return _nodeStore; } private: datanodestore::DataNodeStore *_nodeStore; blockstore::BlockId _blockId; cpputils::optional_ownership_ptr _leaf; DISALLOW_COPY_AND_ASSIGN(LeafHandle); }; } } } #endif src/blobstore/implementations/onblocks/datatreestore/impl/000077500000000000000000000000001347701267100245015ustar00rootroot00000000000000src/blobstore/implementations/onblocks/datatreestore/impl/CachedValue.cpp000066400000000000000000000000321347701267100273440ustar00rootroot00000000000000#include "CachedValue.h" src/blobstore/implementations/onblocks/datatreestore/impl/CachedValue.h000066400000000000000000000020021347701267100270100ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_IMPL_CACHEDVALUE_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_IMPL_CACHEDVALUE_H_ #include #include #include namespace blobstore { namespace onblocks { // TODO Test template class CachedValue final { public: CachedValue() :_cache(boost::none), _mutex() {} T getOrCompute(std::function compute) { boost::upgrade_lock readLock(_mutex); if (_cache == boost::none) { boost::upgrade_to_unique_lock writeLock(readLock); _cache = compute(); } return *_cache; } void update(std::function*)> func) { boost::unique_lock writeLock(_mutex); func(&_cache); } void clear() { update([] (boost::optional* cache) { *cache = boost::none; }); } private: boost::optional _cache; boost::shared_mutex _mutex; }; } } #endif src/blobstore/implementations/onblocks/datatreestore/impl/LeafTraverser.cpp000066400000000000000000000435051347701267100277610ustar00rootroot00000000000000#include "LeafTraverser.h" #include #include "../../datanodestore/DataLeafNode.h" #include "../../datanodestore/DataInnerNode.h" #include "../../datanodestore/DataNodeStore.h" #include "../../utils/Math.h" using std::function; using std::vector; using boost::none; using cpputils::Data; using cpputils::unique_ref; using cpputils::dynamic_pointer_move; using blobstore::onblocks::datanodestore::DataNodeStore; using blobstore::onblocks::datanodestore::DataNode; using blobstore::onblocks::datanodestore::DataInnerNode; using blobstore::onblocks::datanodestore::DataLeafNode; namespace blobstore { namespace onblocks { namespace datatreestore { LeafTraverser::LeafTraverser(DataNodeStore *nodeStore, bool readOnlyTraversal) : _nodeStore(nodeStore), _readOnlyTraversal(readOnlyTraversal) { } void LeafTraverser::traverseAndUpdateRoot(unique_ref* root, uint32_t beginIndex, uint32_t endIndex, function onExistingLeaf, function onCreateLeaf, function onBacktrackFromSubtree) { _traverseAndUpdateRoot(root, beginIndex, endIndex, true, onExistingLeaf, onCreateLeaf, onBacktrackFromSubtree); } void LeafTraverser::_traverseAndUpdateRoot(unique_ref* root, uint32_t beginIndex, uint32_t endIndex, bool isLeftBorderOfTraversal, function onExistingLeaf, function onCreateLeaf, function onBacktrackFromSubtree) { ASSERT(beginIndex <= endIndex, "Invalid parameters"); //TODO Test cases with numLeaves < / >= beginIndex, ideally test all configurations: // beginIndexdepth()); bool increaseTreeDepth = endIndex > maxLeavesForDepth; ASSERT(!_readOnlyTraversal || !increaseTreeDepth, "Tried to grow a tree on a read only traversal"); if ((*root)->depth() == 0) { DataLeafNode *leaf = dynamic_cast(root->get()); ASSERT(leaf != nullptr, "Depth 0 has to be leaf node"); if (increaseTreeDepth && leaf->numBytes() != _nodeStore->layout().maxBytesPerLeaf()) { leaf->resize(_nodeStore->layout().maxBytesPerLeaf()); } if (beginIndex == 0 && endIndex >= 1) { bool isRightBorderLeaf = (endIndex == 1); onExistingLeaf(0, isRightBorderLeaf, LeafHandle(_nodeStore, leaf)); } } else { DataInnerNode *inner = dynamic_cast(root->get()); ASSERT(inner != nullptr, "Depth != 0 has to be leaf node"); _traverseExistingSubtree(inner, std::min(beginIndex, maxLeavesForDepth), std::min(endIndex, maxLeavesForDepth), 0, isLeftBorderOfTraversal, !increaseTreeDepth, increaseTreeDepth, onExistingLeaf, onCreateLeaf, onBacktrackFromSubtree); } // If the traversal goes too far right for a tree this depth, increase tree depth by one and continue traversal. // This is recursive, i.e. will be repeated if the tree is still not deep enough. // We don't increase to the full needed tree depth in one step, because we want the traversal to go as far as possible // and only then increase the depth - this causes the tree to be in consistent shape (balanced) for longer. if (increaseTreeDepth) { ASSERT(!_readOnlyTraversal, "Can't increase tree depth in a read-only traversal"); // TODO Test cases that increase tree depth by 0, 1, 2, ... levels *root = _increaseTreeDepth(std::move(*root)); _traverseAndUpdateRoot(root, std::max(beginIndex, maxLeavesForDepth), endIndex, false, onExistingLeaf, onCreateLeaf, onBacktrackFromSubtree); } else { // Once we're done growing the tree and done with the traversal, we might have to decrease tree depth, // because the callbacks could have deleted nodes (this happens for example when shrinking the tree using a traversal). _whileRootHasOnlyOneChildReplaceRootWithItsChild(root); } } unique_ref LeafTraverser::_increaseTreeDepth(unique_ref root) { ASSERT(!_readOnlyTraversal, "Can't increase tree depth in a read-only traversal"); auto copyOfOldRoot = _nodeStore->createNewNodeAsCopyFrom(*root); return DataNode::convertToNewInnerNode(std::move(root), _nodeStore->layout(), *copyOfOldRoot); } void LeafTraverser::_traverseExistingSubtree(const blockstore::BlockId &blockId, uint8_t depth, uint32_t beginIndex, uint32_t endIndex, uint32_t leafOffset, bool isLeftBorderOfTraversal, bool isRightBorderNode, bool growLastLeaf, function onExistingLeaf, function onCreateLeaf, function onBacktrackFromSubtree) { if (depth == 0) { ASSERT(beginIndex <= 1 && endIndex <= 1, "If root node is a leaf, the (sub)tree has only one leaf - access indices must be 0 or 1."); LeafHandle leafHandle(_nodeStore, blockId); if (growLastLeaf) { if (leafHandle.node()->numBytes() != _nodeStore->layout().maxBytesPerLeaf()) { ASSERT(!_readOnlyTraversal, "Can't grow the last leaf in a read-only traversal"); leafHandle.node()->resize(_nodeStore->layout().maxBytesPerLeaf()); } } if (beginIndex == 0 && endIndex == 1) { onExistingLeaf(leafOffset, isRightBorderNode, std::move(leafHandle)); } } else { auto node = _nodeStore->load(blockId); if (node == none) { throw std::runtime_error("Couldn't find child node " + blockId.ToString()); } auto inner = dynamic_pointer_move(*node); ASSERT(inner != none, "Has to be either leaf or inner node"); ASSERT((*inner)->depth() == depth, "Wrong depth given"); _traverseExistingSubtree(inner->get(), beginIndex, endIndex, leafOffset, isLeftBorderOfTraversal, isRightBorderNode, growLastLeaf, onExistingLeaf, onCreateLeaf, onBacktrackFromSubtree); } } void LeafTraverser::_traverseExistingSubtree(DataInnerNode *root, uint32_t beginIndex, uint32_t endIndex, uint32_t leafOffset, bool isLeftBorderOfTraversal, bool isRightBorderNode, bool growLastLeaf, function onExistingLeaf, function onCreateLeaf, function onBacktrackFromSubtree) { ASSERT(beginIndex <= endIndex, "Invalid parameters"); //TODO Call callbacks for different leaves in parallel. uint32_t leavesPerChild = _maxLeavesForTreeDepth(root->depth()-1); uint32_t beginChild = beginIndex/leavesPerChild; uint32_t endChild = utils::ceilDivision(endIndex, leavesPerChild); ASSERT(endChild <= _nodeStore->layout().maxChildrenPerInnerNode(), "Traversal region would need increasing the tree depth. This should have happened before calling this function."); uint32_t numChildren = root->numChildren(); ASSERT(!growLastLeaf || endChild >= numChildren, "Can only grow last leaf if it exists"); ASSERT(!_readOnlyTraversal || endChild <= numChildren, "Can only traverse out of bounds in a read-only traversal"); bool shouldGrowLastExistingLeaf = growLastLeaf || endChild > numChildren; // If we traverse outside of the valid region (i.e. usually would only traverse to new leaves and not to the last leaf), // we still have to descend to the last old child to fill it with leaves and grow the last old leaf. if (isLeftBorderOfTraversal && beginChild >= numChildren) { ASSERT(numChildren > 0, "Node doesn't have children."); auto childBlockId = root->readLastChild().blockId(); uint32_t childOffset = (numChildren-1) * leavesPerChild; _traverseExistingSubtree(childBlockId, root->depth()-1, leavesPerChild, leavesPerChild, childOffset, true, false, true, [] (uint32_t /*index*/, bool /*isRightBorderNode*/, LeafHandle /*leaf*/) {ASSERT(false, "We don't actually traverse any leaves.");}, [] (uint32_t /*index*/) -> Data {ASSERT(false, "We don't actually traverse any leaves.");}, [] (DataInnerNode* /*node*/) {ASSERT(false, "We don't actually traverse any leaves.");}); } // Traverse existing children for (uint32_t childIndex = beginChild; childIndex < std::min(endChild, numChildren); ++childIndex) { auto childBlockId = root->readChild(childIndex).blockId(); uint32_t childOffset = childIndex * leavesPerChild; uint32_t localBeginIndex = utils::maxZeroSubtraction(beginIndex, childOffset); uint32_t localEndIndex = std::min(leavesPerChild, endIndex - childOffset); bool isFirstChild = (childIndex == beginChild); bool isLastExistingChild = (childIndex == numChildren - 1); bool isLastChild = isLastExistingChild && (numChildren == endChild); ASSERT(localEndIndex <= leavesPerChild, "We don't want the child to add a tree level because it doesn't have enough space for the traversal."); _traverseExistingSubtree(childBlockId, root->depth()-1, localBeginIndex, localEndIndex, leafOffset + childOffset, isLeftBorderOfTraversal && isFirstChild, isRightBorderNode && isLastChild, shouldGrowLastExistingLeaf && isLastExistingChild, onExistingLeaf, onCreateLeaf, onBacktrackFromSubtree); } // Traverse new children (including gap children, i.e. children that are created but not traversed because they're to the right of the current size, but to the left of the traversal region) for (uint32_t childIndex = numChildren; childIndex < endChild; ++childIndex) { ASSERT(!_readOnlyTraversal, "Can't create new children in a read-only traversal"); uint32_t childOffset = childIndex * leavesPerChild; uint32_t localBeginIndex = std::min(leavesPerChild, utils::maxZeroSubtraction(beginIndex, childOffset)); uint32_t localEndIndex = std::min(leavesPerChild, endIndex - childOffset); auto leafCreator = (childIndex >= beginChild) ? onCreateLeaf : _createMaxSizeLeaf(); auto child = _createNewSubtree(localBeginIndex, localEndIndex, leafOffset + childOffset, root->depth() - 1, leafCreator, onBacktrackFromSubtree); root->addChild(*child); } // This is only a backtrack, if we actually visited a leaf here. if (endIndex > beginIndex) { onBacktrackFromSubtree(root); } } unique_ref LeafTraverser::_createNewSubtree(uint32_t beginIndex, uint32_t endIndex, uint32_t leafOffset, uint8_t depth, function onCreateLeaf, function onBacktrackFromSubtree) { ASSERT(!_readOnlyTraversal, "Can't create a new subtree in a read-only traversal"); ASSERT(beginIndex <= endIndex, "Invalid parameters"); if (0 == depth) { ASSERT(beginIndex <= 1 && endIndex == 1, "With depth 0, we can only traverse one or zero leaves (i.e. traverse one leaf or traverse a gap leaf)."); auto leafCreator = (beginIndex == 0) ? onCreateLeaf : _createMaxSizeLeaf(); return _nodeStore->createNewLeafNode(leafCreator(leafOffset)); } uint8_t minNeededDepth = utils::ceilLog(_nodeStore->layout().maxChildrenPerInnerNode(), static_cast(endIndex)); ASSERT(depth >= minNeededDepth, "Given tree depth doesn't fit given number of leaves to create."); uint32_t leavesPerChild = _maxLeavesForTreeDepth(depth-1); uint32_t beginChild = beginIndex/leavesPerChild; uint32_t endChild = utils::ceilDivision(endIndex, leavesPerChild); vector children; children.reserve(endChild); // TODO Remove redundancy of following two for loops by using min/max for calculating the parameters of the recursive call. // Create gap children (i.e. children before the traversal but after the current size) for (uint32_t childIndex = 0; childIndex < beginChild; ++childIndex) { uint32_t childOffset = childIndex * leavesPerChild; auto child = _createNewSubtree(leavesPerChild, leavesPerChild, leafOffset + childOffset, depth - 1, [] (uint32_t /*index*/)->Data {ASSERT(false, "We're only creating gap leaves here, not traversing any.");}, [] (DataInnerNode* /*node*/) {}); ASSERT(child->depth() == depth-1, "Created child node has wrong depth"); children.push_back(child->blockId()); } // Create new children that are traversed for(uint32_t childIndex = beginChild; childIndex < endChild; ++childIndex) { uint32_t childOffset = childIndex * leavesPerChild; uint32_t localBeginIndex = utils::maxZeroSubtraction(beginIndex, childOffset); uint32_t localEndIndex = std::min(leavesPerChild, endIndex - childOffset); auto child = _createNewSubtree(localBeginIndex, localEndIndex, leafOffset + childOffset, depth - 1, onCreateLeaf, onBacktrackFromSubtree); ASSERT(child->depth() == depth-1, "Created child node has wrong depth"); children.push_back(child->blockId()); } ASSERT(children.size() > 0, "No children created"); auto newNode = _nodeStore->createNewInnerNode(depth, children); // This is only a backtrack, if we actually created a leaf here. if (endIndex > beginIndex) { onBacktrackFromSubtree(newNode.get()); } return newNode; } uint32_t LeafTraverser::_maxLeavesForTreeDepth(uint8_t depth) const { return utils::intPow(_nodeStore->layout().maxChildrenPerInnerNode(), static_cast(depth)); } function LeafTraverser::_createMaxSizeLeaf() const { ASSERT(!_readOnlyTraversal, "Can't create a new leaf in a read-only traversal"); uint64_t maxBytesPerLeaf = _nodeStore->layout().maxBytesPerLeaf(); return [maxBytesPerLeaf] (uint32_t /*index*/) -> Data { return Data(maxBytesPerLeaf).FillWithZeroes(); }; } void LeafTraverser::_whileRootHasOnlyOneChildReplaceRootWithItsChild(unique_ref* root) { DataInnerNode *inner = dynamic_cast(root->get()); if (inner != nullptr && inner->numChildren() == 1) { ASSERT(!_readOnlyTraversal, "Can't decrease tree depth in a read-only traversal"); auto newRoot = _whileRootHasOnlyOneChildRemoveRootReturnChild(inner->readChild(0).blockId()); *root = _nodeStore->overwriteNodeWith(std::move(*root), *newRoot); _nodeStore->remove(std::move(newRoot)); } } unique_ref LeafTraverser::_whileRootHasOnlyOneChildRemoveRootReturnChild(const blockstore::BlockId &blockId) { ASSERT(!_readOnlyTraversal, "Can't decrease tree depth in a read-only traversal"); auto current = _nodeStore->load(blockId); ASSERT(current != none, "Node not found"); auto inner = dynamic_pointer_move(*current); if (inner == none) { return std::move(*current); } else if ((*inner)->numChildren() == 1) { auto result = _whileRootHasOnlyOneChildRemoveRootReturnChild((*inner)->readChild(0).blockId()); _nodeStore->remove(std::move(*inner)); return result; } else { return std::move(*inner); } } } } } src/blobstore/implementations/onblocks/datatreestore/impl/LeafTraverser.h000066400000000000000000000104651347701267100274250ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_IMPL_LEAFTRAVERSER_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_IMPL_LEAFTRAVERSER_H_ #include #include #include #include #include "blobstore/implementations/onblocks/datatreestore/LeafHandle.h" namespace blobstore { namespace onblocks { namespace datanodestore { class DataNodeStore; class DataNode; class DataLeafNode; class DataInnerNode; } namespace datatreestore { /** * LeafTraverser can create leaves if they don't exist yet (i.e. endIndex > numLeaves), but * it cannot increase the tree depth. That is, the tree has to be deep enough to allow * creating the number of leaves. */ class LeafTraverser final { public: LeafTraverser(datanodestore::DataNodeStore *nodeStore, bool readOnlyTraversal); void traverseAndUpdateRoot( cpputils::unique_ref* root, uint32_t beginIndex, uint32_t endIndex, std::function onExistingLeaf, std::function onCreateLeaf, std::function onBacktrackFromSubtree); private: datanodestore::DataNodeStore *_nodeStore; const bool _readOnlyTraversal; void _traverseAndUpdateRoot( cpputils::unique_ref* root, uint32_t beginIndex, uint32_t endIndex, bool isLeftBorderOfTraversal, std::function onExistingLeaf, std::function onCreateLeaf, std::function onBacktrackFromSubtree); void _traverseExistingSubtree(datanodestore::DataInnerNode *root, uint32_t beginIndex, uint32_t endIndex, uint32_t leafOffset, bool isLeftBorderOfTraversal, bool isRightBorderNode, bool growLastLeaf, std::function onExistingLeaf, std::function onCreateLeaf, std::function onBacktrackFromSubtree); void _traverseExistingSubtree(const blockstore::BlockId &blockId, uint8_t depth, uint32_t beginIndex, uint32_t endIndex, uint32_t leafOffset, bool isLeftBorderOfTraversal, bool isRightBorderNode, bool growLastLeaf, std::function onExistingLeaf, std::function onCreateLeaf, std::function onBacktrackFromSubtree); cpputils::unique_ref _increaseTreeDepth(cpputils::unique_ref root); cpputils::unique_ref _createNewSubtree(uint32_t beginIndex, uint32_t endIndex, uint32_t leafOffset, uint8_t depth, std::function onCreateLeaf, std::function onBacktrackFromSubtree); uint32_t _maxLeavesForTreeDepth(uint8_t depth) const; std::function _createMaxSizeLeaf() const; void _whileRootHasOnlyOneChildReplaceRootWithItsChild(cpputils::unique_ref* root); cpputils::unique_ref _whileRootHasOnlyOneChildRemoveRootReturnChild(const blockstore::BlockId &blockId); DISALLOW_COPY_AND_ASSIGN(LeafTraverser); }; } } } #endif src/blobstore/implementations/onblocks/datatreestore/impl/algorithms.cpp000066400000000000000000000055201347701267100273600ustar00rootroot00000000000000#include "algorithms.h" #include #include #include "../../datanodestore/DataInnerNode.h" #include "../../datanodestore/DataNodeStore.h" #include using std::function; using cpputils::optional_ownership_ptr; using cpputils::dynamic_pointer_move; using cpputils::unique_ref; using blobstore::onblocks::datanodestore::DataInnerNode; using blobstore::onblocks::datanodestore::DataNode; using blobstore::onblocks::datanodestore::DataNodeStore; using blockstore::BlockId; using boost::optional; using boost::none; namespace blobstore { namespace onblocks { namespace datatreestore { namespace algorithms { optional> getLastChildAsInnerNode(DataNodeStore *nodeStore, const DataInnerNode &node) { BlockId blockId = node.readLastChild().blockId(); auto lastChild = nodeStore->load(blockId); ASSERT(lastChild != none, "Couldn't load last child"); return dynamic_pointer_move(*lastChild); } //Returns the lowest right border node meeting the condition specified (exclusive the leaf). //Returns nullptr, if no inner right border node meets the condition. optional_ownership_ptr GetLowestInnerRightBorderNodeWithConditionOrNull(DataNodeStore *nodeStore, datanodestore::DataNode *rootNode, function condition) { optional_ownership_ptr currentNode = cpputils::WithoutOwnership(dynamic_cast(rootNode)); optional_ownership_ptr result = cpputils::null(); for (unsigned int i=0; i < rootNode->depth(); ++i) { //TODO This unnecessarily loads the leaf node in the last loop run auto lastChild = getLastChildAsInnerNode(nodeStore, *currentNode); if (condition(*currentNode)) { result = std::move(currentNode); } if (lastChild == none) { // lastChild is a leaf ASSERT(static_cast(i) == rootNode->depth()-1, "Couldn't get last child as inner node but we're not deep enough yet for the last child to be a leaf"); break; } currentNode = cpputils::WithOwnership(std::move(*lastChild)); } return result; } optional_ownership_ptr GetLowestRightBorderNodeWithMoreThanOneChildOrNull(DataNodeStore *nodeStore, DataNode *rootNode) { return GetLowestInnerRightBorderNodeWithConditionOrNull(nodeStore, rootNode, [] (const datanodestore::DataInnerNode &node) { return node.numChildren() > 1; }); } optional_ownership_ptr GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNull(datanodestore::DataNodeStore *nodeStore, datanodestore::DataNode *rootNode) { return GetLowestInnerRightBorderNodeWithConditionOrNull(nodeStore, rootNode, [] (const datanodestore::DataInnerNode &node) { return node.numChildren() < node.maxStoreableChildren(); }); } } } } } src/blobstore/implementations/onblocks/datatreestore/impl/algorithms.h000066400000000000000000000023011347701267100270170ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_IMPL_ALGORITHMS_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_IMPL_ALGORITHMS_H_ #include namespace blobstore { namespace onblocks { namespace datanodestore{ class DataNode; class DataInnerNode; class DataNodeStore; } namespace datatreestore { namespace algorithms { //Returns the lowest right border node with at least two children. //Returns nullptr, if all right border nodes have only one child (since the root is a right border node, this means that the whole tree has exactly one leaf) cpputils::optional_ownership_ptr GetLowestRightBorderNodeWithMoreThanOneChildOrNull(datanodestore::DataNodeStore *nodeStore, datanodestore::DataNode *rootNode); //Returns the lowest right border node with less than k children (not considering leaves). //Returns nullptr, if all right border nodes have k children (the tree is full) cpputils::optional_ownership_ptr GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNull(datanodestore::DataNodeStore *nodeStore, datanodestore::DataNode *rootNode); } } } } #endif src/blobstore/implementations/onblocks/parallelaccessdatatreestore/000077500000000000000000000000001347701267100264375ustar00rootroot00000000000000src/blobstore/implementations/onblocks/parallelaccessdatatreestore/DataTreeRef.cpp000066400000000000000000000000311347701267100312630ustar00rootroot00000000000000#include "DataTreeRef.h" src/blobstore/implementations/onblocks/parallelaccessdatatreestore/DataTreeRef.h000066400000000000000000000034331347701267100307410ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_PARALLELACCESSDATATREESTORE_DATATREEREF_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_PARALLELACCESSDATATREESTORE_DATATREEREF_H_ #include #include "../datatreestore/DataTree.h" #include "blobstore/implementations/onblocks/datatreestore/LeafHandle.h" namespace blobstore { namespace onblocks { namespace parallelaccessdatatreestore { class DataTreeRef final: public parallelaccessstore::ParallelAccessStore::ResourceRefBase { public: DataTreeRef(datatreestore::DataTree *baseTree): _baseTree(baseTree) {} const blockstore::BlockId &blockId() const { return _baseTree->blockId(); } uint64_t maxBytesPerLeaf() const { return _baseTree->maxBytesPerLeaf(); } uint32_t numLeaves() const { return _baseTree->numLeaves(); } void resizeNumBytes(uint64_t newNumBytes) { return _baseTree->resizeNumBytes(newNumBytes); } uint64_t numBytes() const { return _baseTree->numBytes(); } uint64_t tryReadBytes(void *target, uint64_t offset, uint64_t count) const { return _baseTree->tryReadBytes(target, offset, count); } void readBytes(void *target, uint64_t offset, uint64_t count) const { return _baseTree->readBytes(target, offset, count); } cpputils::Data readAllBytes() const { return _baseTree->readAllBytes(); } void writeBytes(const void *source, uint64_t offset, uint64_t count) { return _baseTree->writeBytes(source, offset, count); } void flush() { return _baseTree->flush(); } uint32_t numNodes() const { return _baseTree->numNodes(); } private: datatreestore::DataTree *_baseTree; DISALLOW_COPY_AND_ASSIGN(DataTreeRef); }; } } } #endif src/blobstore/implementations/onblocks/parallelaccessdatatreestore/ParallelAccessDataTreeStore.cpp000066400000000000000000000031601347701267100344500ustar00rootroot00000000000000#include "DataTreeRef.h" #include "ParallelAccessDataTreeStore.h" #include "ParallelAccessDataTreeStoreAdapter.h" #include "../datanodestore/DataNodeStore.h" #include "../datanodestore/DataLeafNode.h" using cpputils::unique_ref; using cpputils::make_unique_ref; using boost::optional; using blobstore::onblocks::datatreestore::DataTreeStore; using blockstore::BlockId; namespace blobstore { namespace onblocks { using datatreestore::DataTreeStore; namespace parallelaccessdatatreestore { //TODO Here and for other stores (DataTreeStore, ...): Make small functions inline ParallelAccessDataTreeStore::ParallelAccessDataTreeStore(unique_ref dataTreeStore) : _dataTreeStore(std::move(dataTreeStore)), _parallelAccessStore(make_unique_ref(_dataTreeStore.get())) { } ParallelAccessDataTreeStore::~ParallelAccessDataTreeStore() { } optional> ParallelAccessDataTreeStore::load(const blockstore::BlockId &blockId) { return _parallelAccessStore.load(blockId); } unique_ref ParallelAccessDataTreeStore::createNewTree() { auto dataTree = _dataTreeStore->createNewTree(); BlockId blockId = dataTree->blockId(); return _parallelAccessStore.add(blockId, std::move(dataTree)); // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) } void ParallelAccessDataTreeStore::remove(unique_ref tree) { BlockId blockId = tree->blockId(); return _parallelAccessStore.remove(blockId, std::move(tree)); } void ParallelAccessDataTreeStore::remove(const BlockId &blockId) { return _parallelAccessStore.remove(blockId); } } } } src/blobstore/implementations/onblocks/parallelaccessdatatreestore/ParallelAccessDataTreeStore.h000066400000000000000000000034661347701267100341260ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_PARALLELACCESSDATATREESTORE_PARALLELACCESSDATATREESTORE_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_PARALLELACCESSDATATREESTORE_PARALLELACCESSDATATREESTORE_H_ #include #include #include #include #include "../datatreestore/DataTreeStore.h" namespace blobstore { namespace onblocks { namespace parallelaccessdatatreestore { class DataTreeRef; //TODO Test CachingDataTreeStore class ParallelAccessDataTreeStore final { public: ParallelAccessDataTreeStore(cpputils::unique_ref dataTreeStore); ~ParallelAccessDataTreeStore(); boost::optional> load(const blockstore::BlockId &blockId); cpputils::unique_ref createNewTree(); void remove(cpputils::unique_ref tree); void remove(const blockstore::BlockId &blockId); //TODO Test blocksizeBytes/numBlocks/estimateSpaceForNumBlocksLeft uint64_t virtualBlocksizeBytes() const; uint64_t numNodes() const; uint64_t estimateSpaceForNumNodesLeft() const; private: cpputils::unique_ref _dataTreeStore; parallelaccessstore::ParallelAccessStore _parallelAccessStore; DISALLOW_COPY_AND_ASSIGN(ParallelAccessDataTreeStore); }; inline uint64_t ParallelAccessDataTreeStore::virtualBlocksizeBytes() const { return _dataTreeStore->virtualBlocksizeBytes(); } inline uint64_t ParallelAccessDataTreeStore::numNodes() const { return _dataTreeStore->numNodes(); } inline uint64_t ParallelAccessDataTreeStore::estimateSpaceForNumNodesLeft() const { return _dataTreeStore->estimateSpaceForNumNodesLeft(); } } } } #endif ParallelAccessDataTreeStoreAdapter.cpp000066400000000000000000000000601347701267100356660ustar00rootroot00000000000000src/blobstore/implementations/onblocks/parallelaccessdatatreestore#include "ParallelAccessDataTreeStoreAdapter.h" ParallelAccessDataTreeStoreAdapter.h000066400000000000000000000025701347701267100353430ustar00rootroot00000000000000src/blobstore/implementations/onblocks/parallelaccessdatatreestore#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_PARALLELACCESSDATATREESTORE_PARALLELACCESSDATATREESTOREADAPTER_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_PARALLELACCESSDATATREESTORE_PARALLELACCESSDATATREESTOREADAPTER_H_ #include #include #include "../datatreestore/DataTreeStore.h" #include "../datatreestore/DataTree.h" namespace blobstore { namespace onblocks { namespace parallelaccessdatatreestore { class ParallelAccessDataTreeStoreAdapter final: public parallelaccessstore::ParallelAccessBaseStore { public: ParallelAccessDataTreeStoreAdapter(datatreestore::DataTreeStore *baseDataTreeStore) :_baseDataTreeStore(baseDataTreeStore) { } boost::optional> loadFromBaseStore(const blockstore::BlockId &blockId) override { return _baseDataTreeStore->load(blockId); } void removeFromBaseStore(cpputils::unique_ref dataTree) override { return _baseDataTreeStore->remove(std::move(dataTree)); } void removeFromBaseStore(const blockstore::BlockId &blockId) override { return _baseDataTreeStore->remove(blockId); } private: datatreestore::DataTreeStore *_baseDataTreeStore; DISALLOW_COPY_AND_ASSIGN(ParallelAccessDataTreeStoreAdapter); }; } } } #endif src/blobstore/implementations/onblocks/utils/000077500000000000000000000000001347701267100220325ustar00rootroot00000000000000src/blobstore/implementations/onblocks/utils/Math.cpp000066400000000000000000000000221347701267100234210ustar00rootroot00000000000000#include "Math.h" src/blobstore/implementations/onblocks/utils/Math.h000066400000000000000000000017471347701267100231050ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_UTILS_MATH_H_ #define MESSMER_BLOBSTORE_IMPLEMENTATIONS_ONBLOCKS_UTILS_MATH_H_ #include #include namespace blobstore { namespace onblocks { namespace utils { template inline INT_TYPE intPow(INT_TYPE base, INT_TYPE exponent) { INT_TYPE result = 1; for(INT_TYPE i = 0; i < exponent; ++i) { result *= base; } return result; } template inline INT_TYPE ceilDivision(INT_TYPE dividend, INT_TYPE divisor) { return (dividend + divisor - 1)/divisor; } template inline INT_TYPE maxZeroSubtraction(INT_TYPE minuend, INT_TYPE subtrahend) { if (minuend < subtrahend) { return 0u; } return minuend-subtrahend; } template inline INT_TYPE ceilLog(INT_TYPE base, INT_TYPE value) { return std::ceil(static_cast(std::log(value))/static_cast(std::log(base))); } } } } #endif src/blobstore/interface/000077500000000000000000000000001347701267100156105ustar00rootroot00000000000000src/blobstore/interface/Blob.h000066400000000000000000000015271347701267100166440ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_INTERFACE_BLOB_H_ #define MESSMER_BLOBSTORE_INTERFACE_BLOB_H_ #include #include #include #include namespace blobstore { class Blob { public: virtual ~Blob() {} //TODO Use own Id class for blobstore virtual const blockstore::BlockId &blockId() const = 0; virtual uint64_t size() const = 0; virtual void resize(uint64_t numBytes) = 0; virtual cpputils::Data readAll() const = 0; virtual void read(void *target, uint64_t offset, uint64_t size) const = 0; virtual uint64_t tryRead(void *target, uint64_t offset, uint64_t size) const = 0; virtual void write(const void *source, uint64_t offset, uint64_t size) = 0; virtual void flush() = 0; virtual uint32_t numNodes() const = 0; //TODO Test tryRead }; } #endif src/blobstore/interface/BlobStore.h000066400000000000000000000020151347701267100176520ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_INTERFACE_BLOBSTORE_H_ #define MESSMER_BLOBSTORE_INTERFACE_BLOBSTORE_H_ #include "Blob.h" #include #include #include #include namespace blobstore { //TODO Remove this interface. We'll only use BlobStoreOnBlocks and never a different one. Rename BlobStoreOnBlocks to simply BlobStore. class BlobStore { public: virtual ~BlobStore() {} virtual cpputils::unique_ref create() = 0; virtual boost::optional> load(const blockstore::BlockId &blockId) = 0; virtual void remove(cpputils::unique_ref blob) = 0; virtual void remove(const blockstore::BlockId &blockId) = 0; virtual uint64_t numBlocks() const = 0; virtual uint64_t estimateSpaceForNumBlocksLeft() const = 0; //virtual means "space we can use" as opposed to "space it takes on the disk" (i.e. virtual is without headers, checksums, ...) virtual uint64_t virtualBlocksizeBytes() const = 0; }; } #endif src/blockstore/000077500000000000000000000000001347701267100140245ustar00rootroot00000000000000src/blockstore/CMakeLists.txt000066400000000000000000000031141347701267100165630ustar00rootroot00000000000000project (blockstore) set(SOURCES utils/BlockId.cpp utils/IdWrapper.cpp utils/BlockStoreUtils.cpp utils/FileDoesntExistException.cpp implementations/testfake/FakeBlockStore.cpp implementations/testfake/FakeBlock.cpp implementations/inmemory/InMemoryBlockStore2.cpp implementations/parallelaccess/ParallelAccessBlockStore.cpp implementations/parallelaccess/BlockRef.cpp implementations/parallelaccess/ParallelAccessBlockStoreAdapter.cpp implementations/compressing/CompressingBlockStore.cpp implementations/compressing/CompressedBlock.cpp implementations/compressing/compressors/RunLengthEncoding.cpp implementations/compressing/compressors/Gzip.cpp implementations/encrypted/EncryptedBlockStore2.cpp implementations/ondisk/OnDiskBlockStore2.cpp implementations/caching/CachingBlockStore2.cpp implementations/caching/cache/PeriodicTask.cpp implementations/caching/cache/CacheEntry.cpp implementations/caching/cache/Cache.cpp implementations/caching/cache/QueueMap.cpp implementations/low2highlevel/LowToHighLevelBlock.cpp implementations/low2highlevel/LowToHighLevelBlockStore.cpp implementations/integrity/IntegrityBlockStore2.cpp implementations/integrity/KnownBlockVersions.cpp implementations/integrity/ClientIdAndBlockId.cpp implementations/mock/MockBlockStore.cpp implementations/mock/MockBlock.cpp ) add_library(${PROJECT_NAME} STATIC ${SOURCES}) target_link_libraries(${PROJECT_NAME} PUBLIC cpp-utils) target_add_boost(${PROJECT_NAME} filesystem system thread) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) src/blockstore/implementations/000077500000000000000000000000001347701267100172345ustar00rootroot00000000000000src/blockstore/implementations/caching/000077500000000000000000000000001347701267100206305ustar00rootroot00000000000000src/blockstore/implementations/caching/CachingBlockStore2.cpp000066400000000000000000000124021347701267100247410ustar00rootroot00000000000000#include "CachingBlockStore2.h" #include #include #include using std::string; using std::mutex; using cpputils::Data; using cpputils::unique_ref; using cpputils::make_unique_ref; using boost::optional; using boost::none; using std::unique_lock; using std::mutex; namespace blockstore { namespace caching { constexpr double CachingBlockStore2::MAX_LIFETIME_SEC; CachingBlockStore2::CachedBlock::CachedBlock(const CachingBlockStore2* blockStore, const BlockId &blockId, cpputils::Data data, bool isDirty) : _blockStore(blockStore), _blockId(blockId), _data(std::move(data)), _dirty(isDirty) { } CachingBlockStore2::CachedBlock::~CachedBlock() { if (_dirty) { _blockStore->_baseBlockStore->store(_blockId, _data); } // remove it from the list of blocks not in the base store, if it's on it unique_lock lock(_blockStore->_cachedBlocksNotInBaseStoreMutex); _blockStore->_cachedBlocksNotInBaseStore.erase(_blockId); } const Data& CachingBlockStore2::CachedBlock::read() const { return _data; } void CachingBlockStore2::CachedBlock::markNotDirty() && { _dirty = false; // Prevent writing it back into the base store } void CachingBlockStore2::CachedBlock::write(Data data) { _data = std::move(data); _dirty = true; } CachingBlockStore2::CachingBlockStore2(cpputils::unique_ref baseBlockStore) : _baseBlockStore(std::move(baseBlockStore)), _cachedBlocksNotInBaseStoreMutex(), _cachedBlocksNotInBaseStore(), _cache("blockstore") { } bool CachingBlockStore2::tryCreate(const BlockId &blockId, const Data &data) { //TODO Check if block exists in base store? Performance hit? It's very unlikely it exists. auto popped = _cache.pop(blockId); if (popped != boost::none) { // entry already exists in cache _cache.push(blockId, std::move(*popped)); // push the just popped element back to the cache return false; } else { _cache.push(blockId, make_unique_ref(this, blockId, data.copy(), true)); unique_lock lock(_cachedBlocksNotInBaseStoreMutex); _cachedBlocksNotInBaseStore.insert(blockId); return true; } } bool CachingBlockStore2::remove(const BlockId &blockId) { // TODO Don't write-through but cache remove operations auto popped = _cache.pop(blockId); if (popped != boost::none) { // Remove from base store if it exists in the base store { unique_lock lock(_cachedBlocksNotInBaseStoreMutex); if (_cachedBlocksNotInBaseStore.count(blockId) == 0) { const bool existedInBaseStore = _baseBlockStore->remove(blockId); if (!existedInBaseStore) { throw std::runtime_error("Tried to remove block. Block existed in cache and stated it exists in base store, but wasn't found there."); } } } // Don't write back the cached block when it is destructed std::move(**popped).markNotDirty(); return true; } else { return _baseBlockStore->remove(blockId); } } optional> CachingBlockStore2::_loadFromCacheOrBaseStore(const BlockId &blockId) const { auto popped = _cache.pop(blockId); if (popped != boost::none) { return std::move(*popped); } else { auto loaded = _baseBlockStore->load(blockId); if (loaded == boost::none) { return boost::none; } return make_unique_ref(this, blockId, std::move(*loaded), false); } } optional CachingBlockStore2::load(const BlockId &blockId) const { auto loaded = _loadFromCacheOrBaseStore(blockId); if (loaded == boost::none) { // TODO Cache non-existence? return boost::none; } optional result = (*loaded)->read().copy(); _cache.push(blockId, std::move(*loaded)); return result; } void CachingBlockStore2::store(const BlockId &blockId, const Data &data) { auto popped = _cache.pop(blockId); if (popped != boost::none) { (*popped)->write(data.copy()); } else { popped = make_unique_ref(this, blockId, data.copy(), false); // TODO Instead of storing it to the base store, we could just keep it dirty in the cache // and (if it doesn't exist in base store yet) add it to _cachedBlocksNotInBaseStore _baseBlockStore->store(blockId, data); } _cache.push(blockId, std::move(*popped)); } uint64_t CachingBlockStore2::numBlocks() const { uint64_t numInCacheButNotInBaseStore = 0; { unique_lock lock(_cachedBlocksNotInBaseStoreMutex); numInCacheButNotInBaseStore = _cachedBlocksNotInBaseStore.size(); } return _baseBlockStore->numBlocks() + numInCacheButNotInBaseStore; } uint64_t CachingBlockStore2::estimateNumFreeBytes() const { return _baseBlockStore->estimateNumFreeBytes(); } uint64_t CachingBlockStore2::blockSizeFromPhysicalBlockSize(uint64_t blockSize) const { return _baseBlockStore->blockSizeFromPhysicalBlockSize(blockSize); } void CachingBlockStore2::forEachBlock(std::function callback) const { { unique_lock lock(_cachedBlocksNotInBaseStoreMutex); for (const BlockId &blockId : _cachedBlocksNotInBaseStore) { callback(blockId); } } _baseBlockStore->forEachBlock(std::move(callback)); } void CachingBlockStore2::flush() { _cache.flush(); } } } src/blockstore/implementations/caching/CachingBlockStore2.h000066400000000000000000000044021347701267100244070ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHINGBLOCKSTORE2_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHINGBLOCKSTORE2_H_ #include "../../interface/BlockStore2.h" #include #include "../caching/cache/Cache.h" #include namespace blockstore { namespace caching { class CachingBlockStore2 final: public BlockStore2 { public: CachingBlockStore2(cpputils::unique_ref baseBlockStore); bool tryCreate(const BlockId &blockId, const cpputils::Data &data) override; bool remove(const BlockId &blockId) override; boost::optional load(const BlockId &blockId) const override; void store(const BlockId &blockId, const cpputils::Data &data) override; uint64_t numBlocks() const override; uint64_t estimateNumFreeBytes() const override; uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const override; void forEachBlock(std::function callback) const override; void flush(); private: // TODO Is a cache implementation with onEvict callback instead of destructor simpler? class CachedBlock final { public: CachedBlock(const CachingBlockStore2* blockStore, const BlockId &blockId, cpputils::Data data, bool isDirty); ~CachedBlock(); const cpputils::Data& read() const; void write(cpputils::Data data); void markNotDirty() &&; // only on rvalue because the destructor should be called after calling markNotDirty(). It shouldn't be put back into the cache. private: const CachingBlockStore2* _blockStore; BlockId _blockId; cpputils::Data _data; bool _dirty; DISALLOW_COPY_AND_ASSIGN(CachedBlock); }; boost::optional> _loadFromCacheOrBaseStore(const BlockId &blockId) const; cpputils::unique_ref _baseBlockStore; friend class CachedBlock; // TODO Store CachedBlock directly, without unique_ref mutable std::mutex _cachedBlocksNotInBaseStoreMutex; mutable std::unordered_set _cachedBlocksNotInBaseStore; mutable Cache, 1000> _cache; public: static constexpr double MAX_LIFETIME_SEC = decltype(_cache)::MAX_LIFETIME_SEC; private: DISALLOW_COPY_AND_ASSIGN(CachingBlockStore2); }; } } #endif src/blockstore/implementations/caching/cache/000077500000000000000000000000001347701267100216735ustar00rootroot00000000000000src/blockstore/implementations/caching/cache/Cache.cpp000066400000000000000000000000231347701267100233750ustar00rootroot00000000000000#include "Cache.h" src/blockstore/implementations/caching/cache/Cache.h000066400000000000000000000174451347701267100230620ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_CACHE_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_CACHE_H_ #include "CacheEntry.h" #include "QueueMap.h" #include "PeriodicTask.h" #include #include #include #include #include #include namespace blockstore { namespace caching { template class Cache final { public: //TODO Current MAX_LIFETIME_SEC only considers time since the element was last pushed to the Cache. Also insert a real MAX_LIFETIME_SEC that forces resync of entries that have been pushed/popped often (e.g. the root blob) //TODO Experiment with good values static constexpr double PURGE_LIFETIME_SEC = 0.5; //When an entry has this age, it will be purged from the cache static constexpr double PURGE_INTERVAL = 0.5; // With this interval, we check for entries to purge static constexpr double MAX_LIFETIME_SEC = PURGE_LIFETIME_SEC + PURGE_INTERVAL; // This is the oldest age an entry can reach (given purging works in an ideal world, i.e. with the ideal interval and in zero time) Cache(const std::string& cacheName); ~Cache(); uint32_t size() const; void push(const Key &key, Value value); boost::optional pop(const Key &key); void flush(); private: void _makeSpaceForEntry(std::unique_lock *lock); void _deleteEntry(std::unique_lock *lock); void _deleteOldEntriesParallel(); void _deleteAllEntriesParallel(); void _deleteMatchingEntriesAtBeginningParallel(std::function &)> matches); void _deleteMatchingEntriesAtBeginning(std::function &)> matches); bool _deleteMatchingEntryAtBeginning(std::function &)> matches); mutable std::mutex _mutex; cpputils::LockPool _currentlyFlushingEntries; QueueMap> _cachedBlocks; std::unique_ptr _timeoutFlusher; DISALLOW_COPY_AND_ASSIGN(Cache); }; template constexpr double Cache::PURGE_LIFETIME_SEC; template constexpr double Cache::PURGE_INTERVAL; template constexpr double Cache::MAX_LIFETIME_SEC; template Cache::Cache(const std::string& cacheName): _mutex(), _currentlyFlushingEntries(), _cachedBlocks(), _timeoutFlusher(nullptr) { //Don't initialize timeoutFlusher in the initializer list, //because it then might already call Cache::popOldEntries() before Cache is done constructing. _timeoutFlusher = std::make_unique(std::bind(&Cache::_deleteOldEntriesParallel, this), PURGE_INTERVAL, "flush_" + cacheName); } template Cache::~Cache() { _deleteAllEntriesParallel(); ASSERT(_cachedBlocks.size() == 0, "Error in _deleteAllEntriesParallel()"); } template boost::optional Cache::pop(const Key &key) { std::unique_lock lock(_mutex); cpputils::MutexPoolLock lockEntryFromBeingPopped(&_currentlyFlushingEntries, key, &lock); auto found = _cachedBlocks.pop(key); if (!found) { return boost::none; } return found->releaseValue(); } template void Cache::push(const Key &key, Value value) { std::unique_lock lock(_mutex); ASSERT(_cachedBlocks.size() <= MAX_ENTRIES, "Cache too full"); _makeSpaceForEntry(&lock); _cachedBlocks.push(key, CacheEntry(std::move(value))); } template void Cache::_makeSpaceForEntry(std::unique_lock *lock) { // _deleteEntry releases the lock while the Value destructor is running. // So we can destruct multiple entries in parallel and also call pop() or push() while doing so. // However, if another thread calls push() before we get the lock back, the cache is full again. // That's why we need the while() loop here. while (_cachedBlocks.size() == MAX_ENTRIES) { _deleteEntry(lock); } ASSERT(_cachedBlocks.size() < MAX_ENTRIES, "Removing entry from cache didn't work"); }; template void Cache::_deleteEntry(std::unique_lock *lock) { ASSERT(lock->owns_lock(), "The operations in this function require a locked mutex"); auto key = _cachedBlocks.peekKey(); ASSERT(key != boost::none, "There was no entry to delete"); cpputils::MutexPoolLock lockEntryFromBeingPopped(&_currentlyFlushingEntries, *key); auto value = _cachedBlocks.pop(); // Call destructor outside of the unique_lock, // i.e. pop() and push() can be called here, except for pop() on the element in _currentlyFlushingEntries lock->unlock(); value = boost::none; // Call destructor lockEntryFromBeingPopped.unlock(); // unlock this one first to keep same locking oder (preventing potential deadlock) lock->lock(); }; template void Cache::_deleteAllEntriesParallel() { return _deleteMatchingEntriesAtBeginningParallel([] (const CacheEntry &) { return true; }); } template void Cache::_deleteOldEntriesParallel() { return _deleteMatchingEntriesAtBeginningParallel([] (const CacheEntry &entry) { return entry.ageSeconds() > PURGE_LIFETIME_SEC; }); } template void Cache::_deleteMatchingEntriesAtBeginningParallel(std::function &)> matches) { // Twice the number of cores, so we use full CPU even if half the threads are doing I/O unsigned int numThreads = 2 * (std::max)(1u, std::thread::hardware_concurrency()); std::vector> waitHandles; for (unsigned int i = 0; i < numThreads; ++i) { waitHandles.push_back(std::async(std::launch::async, [this, matches] { _deleteMatchingEntriesAtBeginning(matches); })); } for (auto & waitHandle : waitHandles) { waitHandle.wait(); } }; template void Cache::_deleteMatchingEntriesAtBeginning(std::function &)> matches) { while (_deleteMatchingEntryAtBeginning(matches)) {} } template bool Cache::_deleteMatchingEntryAtBeginning(std::function &)> matches) { // This function can be called in parallel by multiple threads and will then cause the Value destructors // to be called in parallel. The call to _deleteEntry() releases the lock while the Value destructor is running. std::unique_lock lock(_mutex); if (_cachedBlocks.size() > 0 && matches(*_cachedBlocks.peek())) { _deleteEntry(&lock); ASSERT(lock.owns_lock(), "Something strange happened with the lock. It should be locked again when we come back."); return true; } else { return false; } }; template uint32_t Cache::size() const { std::unique_lock lock(_mutex); return _cachedBlocks.size(); }; template void Cache::flush() { //TODO Test flush() return _deleteAllEntriesParallel(); }; } } #endif src/blockstore/implementations/caching/cache/CacheEntry.cpp000066400000000000000000000000301347701267100244150ustar00rootroot00000000000000#include "CacheEntry.h" src/blockstore/implementations/caching/cache/CacheEntry.h000066400000000000000000000020321347701267100240660ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_CACHEENTRY_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_CACHEENTRY_H_ #include #include #include #include namespace blockstore { namespace caching { template class CacheEntry final { public: explicit CacheEntry(Value value): _lastAccess(currentTime()), _value(std::move(value)) { } CacheEntry(CacheEntry&& rhs) noexcept: _lastAccess(std::move(rhs._lastAccess)), _value(std::move(rhs._value)) {} double ageSeconds() const { return static_cast((currentTime() - _lastAccess).total_nanoseconds()) / static_cast(1000000000); } Value releaseValue() { return std::move(_value); } private: boost::posix_time::ptime _lastAccess; Value _value; static boost::posix_time::ptime currentTime() { return boost::posix_time::microsec_clock::local_time(); } DISALLOW_COPY_AND_ASSIGN(CacheEntry); }; } } #endif src/blockstore/implementations/caching/cache/PeriodicTask.cpp000066400000000000000000000014731347701267100247650ustar00rootroot00000000000000#include "PeriodicTask.h" #include using std::function; using namespace cpputils::logging; namespace blockstore { namespace caching { PeriodicTask::PeriodicTask(function task, double intervalSec, std::string threadName) : _task(task), _interval(static_cast(UINT64_C(1000000000) * intervalSec)), _thread(std::bind(&PeriodicTask::_loopIteration, this), std::move(threadName)) { _thread.start(); } bool PeriodicTask::_loopIteration() { //Has to be boost::this_thread::sleep_for and not std::this_thread::sleep_for, because it has to be interruptible. //LoopThread will interrupt this method if it has to be restarted. boost::this_thread::sleep_for(_interval); _task(); return true; // Run another iteration (don't terminate thread) } } } src/blockstore/implementations/caching/cache/PeriodicTask.h000066400000000000000000000014031347701267100244230ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_PERIODICTASK_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_PERIODICTASK_H_ #include #include #include namespace blockstore { namespace caching { class PeriodicTask final { public: PeriodicTask(std::function task, double intervalSec, std::string threadName); private: bool _loopIteration(); std::function _task; boost::chrono::nanoseconds _interval; //This member has to be last, so the thread is destructed first. Otherwise the thread might access elements from a //partly destructed PeriodicTask. cpputils::LoopThread _thread; DISALLOW_COPY_AND_ASSIGN(PeriodicTask); }; } } #endif src/blockstore/implementations/caching/cache/QueueMap.cpp000066400000000000000000000000261347701267100241170ustar00rootroot00000000000000#include "QueueMap.h" src/blockstore/implementations/caching/cache/QueueMap.h000066400000000000000000000065221347701267100235730ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_QUEUEMAP_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_QUEUEMAP_H_ #include #include #include #include #include #include namespace blockstore { namespace caching { //TODO FreeList for performance (malloc is expensive) //TODO Single linked list with pointer to last element (for insertion) should be enough for a queue. No double linked list needed. // But then, popping arbitrary elements needs to be rewritten so that _removeFromQueue() is _removeSuccessorFromQueue() // and the map doesn't store the element itself, but its predecessor. That is, popping might be a bit slower. Test with experiments! // A class that is a queue and a map at the same time. We could also see it as an addressable queue. template class QueueMap final { public: QueueMap(): _entries(), _sentinel(&_sentinel, &_sentinel) { } ~QueueMap() { for (auto &entry : _entries) { entry.second.release(); } } void push(const Key &key, Value value) { auto newEntry = _entries.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(_sentinel.prev, &_sentinel)); if (!newEntry.second) { throw std::logic_error("There is already an element with this key"); } newEntry.first->second.init(&newEntry.first->first, std::move(value)); //The following is ok, because std::unordered_map never invalidates pointers to its entries _sentinel.prev->next = &newEntry.first->second; _sentinel.prev = &newEntry.first->second; } boost::optional pop(const Key &key) { auto found = _entries.find(key); if (found == _entries.end()) { return boost::none; } _removeFromQueue(found->second); auto value = found->second.release(); _entries.erase(found); return value; } boost::optional pop() { if(_sentinel.next == &_sentinel) { return boost::none; } return pop(*_sentinel.next->key); } boost::optional peekKey() { if(_sentinel.next == &_sentinel) { return boost::none; } return *_sentinel.next->key; } boost::optional peek() { if(_sentinel.next == &_sentinel) { return boost::none; } return _sentinel.next->value(); } uint32_t size() const { return _entries.size(); } private: class Entry final { public: Entry(Entry *prev_, Entry *next_): prev(prev_), next(next_), key(nullptr), __value() { } void init(const Key *key_, Value value_) { key = key_; new(__value.data()) Value(std::move(value_)); } Value release() { Value value = std::move(*_value()); _value()->~Value(); return value; } const Value &value() { return *_value(); } Entry *prev; Entry *next; const Key *key; private: Value *_value() { return reinterpret_cast(__value.data()); } alignas(Value) std::array __value; DISALLOW_COPY_AND_ASSIGN(Entry); }; void _removeFromQueue(const Entry &entry) { entry.prev->next = entry.next; entry.next->prev = entry.prev; } std::unordered_map _entries; Entry _sentinel; DISALLOW_COPY_AND_ASSIGN(QueueMap); }; } } #endif src/blockstore/implementations/compressing/000077500000000000000000000000001347701267100215655ustar00rootroot00000000000000src/blockstore/implementations/compressing/CompressedBlock.cpp000066400000000000000000000000351347701267100253460ustar00rootroot00000000000000#include "CompressedBlock.h" src/blockstore/implementations/compressing/CompressedBlock.h000066400000000000000000000113371347701267100250220ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_COMPRESSING_COMPRESSEDBLOCK_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_COMPRESSING_COMPRESSEDBLOCK_H_ #include "cpp-utils/crypto/cryptopp_byte.h" #include "../../interface/Block.h" #include "../../interface/BlockStore.h" #include #include #include namespace blockstore { class BlockStore; namespace compressing { template class CompressingBlockStore; template class CompressedBlock final: public Block { public: static boost::optional> TryCreateNew(BlockStore *baseBlockStore, const BlockId &blockId, cpputils::Data decompressedData); static cpputils::unique_ref Overwrite(BlockStore *baseBlockStore, const BlockId &blockId, cpputils::Data decompressedData); static cpputils::unique_ref Decompress(cpputils::unique_ref baseBlock); CompressedBlock(cpputils::unique_ref baseBlock, cpputils::Data decompressedData); ~CompressedBlock(); const void *data() const override; void write(const void *source, uint64_t offset, uint64_t size) override; void flush() override; size_t size() const override; void resize(size_t newSize) override; cpputils::unique_ref releaseBaseBlock(); private: void _compressToBaseBlock(); cpputils::unique_ref _baseBlock; cpputils::Data _decompressedData; std::mutex _mutex; bool _dataChanged; DISALLOW_COPY_AND_ASSIGN(CompressedBlock); }; template boost::optional>> CompressedBlock::TryCreateNew(BlockStore *baseBlockStore, const BlockId &blockId, cpputils::Data decompressedData) { cpputils::Data compressed = Compressor::Compress(decompressedData); auto baseBlock = baseBlockStore->tryCreate(blockId, std::move(compressed)); if (baseBlock == boost::none) { //TODO Test this code branch return boost::none; } return cpputils::make_unique_ref>(std::move(*baseBlock), std::move(decompressedData)); } template cpputils::unique_ref> CompressedBlock::Overwrite(BlockStore *baseBlockStore, const BlockId &blockId, cpputils::Data decompressedData) { cpputils::Data compressed = Compressor::Compress(decompressedData); auto baseBlock = baseBlockStore->overwrite(blockId, std::move(compressed)); return cpputils::make_unique_ref>(std::move(baseBlock), std::move(decompressedData)); } template cpputils::unique_ref> CompressedBlock::Decompress(cpputils::unique_ref baseBlock) { cpputils::Data decompressed = Compressor::Decompress(baseBlock->data(), baseBlock->size()); return cpputils::make_unique_ref>(std::move(baseBlock), std::move(decompressed)); } template CompressedBlock::CompressedBlock(cpputils::unique_ref baseBlock, cpputils::Data decompressedData) : Block(baseBlock->blockId()), _baseBlock(std::move(baseBlock)), _decompressedData(std::move(decompressedData)), _dataChanged(false) { } template CompressedBlock::~CompressedBlock() { std::unique_lock lock(_mutex); _compressToBaseBlock(); } template const void *CompressedBlock::data() const { return _decompressedData.data(); } template void CompressedBlock::write(const void *source, uint64_t offset, uint64_t size) { std::memcpy(_decompressedData.dataOffset(offset), source, size); _dataChanged = true; } template void CompressedBlock::flush() { std::unique_lock lock(_mutex); _compressToBaseBlock(); return _baseBlock->flush(); } template size_t CompressedBlock::size() const { return _decompressedData.size(); } template void CompressedBlock::resize(size_t newSize) { _decompressedData = cpputils::DataUtils::resize(_decompressedData, newSize); _dataChanged = true; } template cpputils::unique_ref CompressedBlock::releaseBaseBlock() { std::unique_lock lock(_mutex); _compressToBaseBlock(); return std::move(_baseBlock); } template void CompressedBlock::_compressToBaseBlock() { if (_dataChanged) { cpputils::Data compressed = Compressor::Compress(_decompressedData); _baseBlock->resize(compressed.size()); _baseBlock->write(compressed.data(), 0, compressed.size()); _dataChanged = false; } } } } #endif src/blockstore/implementations/compressing/CompressingBlockStore.cpp000066400000000000000000000000431347701267100265470ustar00rootroot00000000000000#include "CompressingBlockStore.h" src/blockstore/implementations/compressing/CompressingBlockStore.h000066400000000000000000000072201347701267100262200ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_COMPRESSING_COMPRESSINGBLOCKSTORE_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_COMPRESSING_COMPRESSINGBLOCKSTORE_H_ #include "../../interface/BlockStore.h" #include "CompressedBlock.h" namespace blockstore { namespace compressing { template class CompressingBlockStore final: public BlockStore { public: CompressingBlockStore(cpputils::unique_ref baseBlockStore); ~CompressingBlockStore(); BlockId createBlockId() override; boost::optional> tryCreate(const BlockId &blockId, cpputils::Data data) override; boost::optional> load(const BlockId &blockId) override; cpputils::unique_ref overwrite(const blockstore::BlockId &blockId, cpputils::Data data) override; void remove(const BlockId &blockId) override; uint64_t numBlocks() const override; uint64_t estimateNumFreeBytes() const override; uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const override; void forEachBlock(std::function callback) const override; private: cpputils::unique_ref _baseBlockStore; DISALLOW_COPY_AND_ASSIGN(CompressingBlockStore); }; template CompressingBlockStore::CompressingBlockStore(cpputils::unique_ref baseBlockStore) : _baseBlockStore(std::move(baseBlockStore)) { } template CompressingBlockStore::~CompressingBlockStore() { } template BlockId CompressingBlockStore::createBlockId() { return _baseBlockStore->createBlockId(); } template boost::optional> CompressingBlockStore::tryCreate(const BlockId &blockId, cpputils::Data data) { auto result = CompressedBlock::TryCreateNew(_baseBlockStore.get(), blockId, std::move(data)); if (result == boost::none) { return boost::none; } return cpputils::unique_ref(std::move(*result)); } template cpputils::unique_ref CompressingBlockStore::overwrite(const blockstore::BlockId &blockId, cpputils::Data data) { return CompressedBlock::Overwrite(_baseBlockStore.get(), blockId, std::move(data)); } template boost::optional> CompressingBlockStore::load(const BlockId &blockId) { auto loaded = _baseBlockStore->load(blockId); if (loaded == boost::none) { return boost::none; } return boost::optional>(CompressedBlock::Decompress(std::move(*loaded))); } template void CompressingBlockStore::remove(const BlockId &blockId) { return _baseBlockStore->remove(blockId); } template uint64_t CompressingBlockStore::numBlocks() const { return _baseBlockStore->numBlocks(); } template uint64_t CompressingBlockStore::estimateNumFreeBytes() const { return _baseBlockStore->estimateNumFreeBytes(); } template void CompressingBlockStore::forEachBlock(std::function callback) const { return _baseBlockStore->forEachBlock(callback); } template uint64_t CompressingBlockStore::blockSizeFromPhysicalBlockSize(uint64_t blockSize) const { //We probably have more since we're compressing, but we don't know exactly how much. //The best we can do is ignore the compression step here. return _baseBlockStore->blockSizeFromPhysicalBlockSize(blockSize); } } } #endif src/blockstore/implementations/compressing/compressors/000077500000000000000000000000001347701267100241445ustar00rootroot00000000000000src/blockstore/implementations/compressing/compressors/Gzip.cpp000066400000000000000000000022161347701267100255620ustar00rootroot00000000000000#include "cpp-utils/crypto/cryptopp_byte.h" #include "Gzip.h" #include using cpputils::Data; namespace blockstore { namespace compressing { Data Gzip::Compress(const Data &data) { CryptoPP::Gzip zipper; zipper.Put(static_cast(data.data()), data.size()); zipper.MessageEnd(); Data compressed(zipper.MaxRetrievable()); zipper.Get(static_cast(compressed.data()), compressed.size()); return compressed; } Data Gzip::Decompress(const void *data, size_t size) { //TODO Change interface to taking cpputils::Data objects (needs changing blockstore so we can read their "class Data", because this is called from CompressedBlock::Decompress()). CryptoPP::Gunzip zipper; zipper.Put(static_cast(data), size); zipper.MessageEnd(); Data decompressed(zipper.MaxRetrievable()); zipper.Get(static_cast(decompressed.data()), decompressed.size()); return decompressed; } } } src/blockstore/implementations/compressing/compressors/Gzip.h000066400000000000000000000007151347701267100252310ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_COMPRESSING_COMPRESSORS_GZIP_H #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_COMPRESSING_COMPRESSORS_GZIP_H #include namespace blockstore { namespace compressing { class Gzip { public: static cpputils::Data Compress(const cpputils::Data &data); static cpputils::Data Decompress(const void *data, size_t size); }; } } #endif src/blockstore/implementations/compressing/compressors/RunLengthEncoding.cpp000066400000000000000000000144121347701267100302270ustar00rootroot00000000000000#include "RunLengthEncoding.h" #include #include using cpputils::Data; using std::string; using std::ostringstream; using std::istringstream; namespace blockstore { namespace compressing { // Alternatively store a run of arbitrary bytes and a run of identical bytes. // Each run is preceded by its length. Length fields are uint16_t. // Example: 2 - 5 - 8 - 10 - 3 - 0 - 2 - 0 // Length 2 arbitrary bytes (values: 5, 8), the next 10 bytes store "3" each, // then 0 arbitrary bytes and 2x "0". Data RunLengthEncoding::Compress(const Data &data) { ostringstream compressed; const uint8_t *current = static_cast(data.data()); const uint8_t *end = static_cast(data.data())+data.size(); while (current < end) { _encodeArbitraryWords(¤t, end, &compressed); ASSERT(current <= end, "Overflow"); if (current == end) { break; } _encodeIdenticalWords(¤t, end, &compressed); ASSERT(current <= end, "Overflow"); } return _extractData(&compressed); } void RunLengthEncoding::_encodeArbitraryWords(const uint8_t **current, const uint8_t* end, ostringstream *output) { uint16_t size = _arbitraryRunLength(*current, end); output->write(reinterpret_cast(&size), sizeof(uint16_t)); output->write(reinterpret_cast(*current), size); *current += size; } uint16_t RunLengthEncoding::_arbitraryRunLength(const uint8_t *start, const uint8_t* end) { // Each stopping of an arbitrary bytes run costs us 5 byte, because we have to store the length // for the identical bytes run (2 byte), the identical byte itself (1 byte) and the length for the next arbitrary bytes run (2 byte). // So to get an advantage from stopping an arbitrary bytes run, at least 6 bytes have to be identical. // realEnd avoids an overflow of the 16bit counter const uint8_t *realEnd = std::min(end, start + std::numeric_limits::max()); // Count the number of identical bytes and return if it finds a run of more than 6 identical bytes. uint8_t lastByte = *start + 1; // Something different from the first byte uint8_t numIdenticalBytes = 1; for(const uint8_t *current = start; current != realEnd; ++current) { if (*current == lastByte) { ++numIdenticalBytes; if (numIdenticalBytes == 6) { return current - start - 5; //-5, because the end pointer for the arbitrary byte run should point to the first identical byte, not the one before. } } else { numIdenticalBytes = 1; } lastByte = *current; } //It wasn't worth stopping the arbitrary bytes run anywhere. The whole region should be an arbitrary run. return realEnd-start; } void RunLengthEncoding::_encodeIdenticalWords(const uint8_t **current, const uint8_t* end, ostringstream *output) { uint16_t size = _countIdenticalBytes(*current, end); output->write(reinterpret_cast(&size), sizeof(uint16_t)); output->write(reinterpret_cast(*current), 1); *current += size; } uint16_t RunLengthEncoding::_countIdenticalBytes(const uint8_t *start, const uint8_t *end) { const uint8_t *realEnd = std::min(end, start + std::numeric_limits::max()); // This prevents overflow of the 16bit counter for (const uint8_t *current = start+1; current != realEnd; ++current) { if (*current != *start) { return current-start; } } // All bytes have been identical return realEnd - start; } Data RunLengthEncoding::_extractData(ostringstream *stream) { string str = stream->str(); Data data(str.size()); std::memcpy(data.data(), str.c_str(), str.size()); return data; } Data RunLengthEncoding::Decompress(const void *data, size_t size) { istringstream stream; _parseData(static_cast(data), size, &stream); ostringstream decompressed; while(_hasData(&stream)) { _decodeArbitraryWords(&stream, &decompressed); if (!_hasData(&stream)) { break; } _decodeIdenticalWords(&stream, &decompressed); } return _extractData(&decompressed); } bool RunLengthEncoding::_hasData(istringstream *str) { str->peek(); return !str->eof(); } void RunLengthEncoding::_parseData(const uint8_t *data, size_t size, istringstream *result) { result->str(string(reinterpret_cast(data), size)); } void RunLengthEncoding::_decodeArbitraryWords(istringstream *stream, ostringstream *decompressed) { uint16_t size; stream->read(reinterpret_cast(&size), sizeof(uint16_t)); ASSERT(stream->good(), "Premature end of stream"); Data run(size); stream->read(static_cast(run.data()), size); ASSERT(stream->good(), "Premature end of stream"); decompressed->write(static_cast(run.data()), run.size()); } void RunLengthEncoding::_decodeIdenticalWords(istringstream *stream, ostringstream *decompressed) { uint16_t size; stream->read(reinterpret_cast(&size), sizeof(uint16_t)); ASSERT(stream->good(), "Premature end of stream"); uint8_t value; stream->read(reinterpret_cast(&value), 1); ASSERT(stream->good(), "Premature end of stream"); Data run(size); std::memset(run.data(), value, run.size()); decompressed->write(static_cast(run.data()), run.size()); } } } src/blockstore/implementations/compressing/compressors/RunLengthEncoding.h000066400000000000000000000025631347701267100277000ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_COMPRESSING_COMPRESSORS_RUNLENGTHENCODING_H #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_COMPRESSING_COMPRESSORS_RUNLENGTHENCODING_H #include namespace blockstore { namespace compressing { class RunLengthEncoding { public: static cpputils::Data Compress(const cpputils::Data &data); static cpputils::Data Decompress(const void *data, size_t size); private: static void _encodeArbitraryWords(const uint8_t **current, const uint8_t* end, std::ostringstream *output); static uint16_t _arbitraryRunLength(const uint8_t *start, const uint8_t* end); static void _encodeIdenticalWords(const uint8_t **current, const uint8_t* end, std::ostringstream *output); static uint16_t _countIdenticalBytes(const uint8_t *start, const uint8_t *end); static bool _hasData(std::istringstream *stream); static cpputils::Data _extractData(std::ostringstream *stream); static void _parseData(const uint8_t *data, size_t size, std::istringstream *result); static void _decodeArbitraryWords(std::istringstream *stream, std::ostringstream *decompressed); static void _decodeIdenticalWords(std::istringstream *stream, std::ostringstream *decompressed); }; } } #endif src/blockstore/implementations/encrypted/000077500000000000000000000000001347701267100212315ustar00rootroot00000000000000src/blockstore/implementations/encrypted/EncryptedBlockStore2.cpp000066400000000000000000000000421347701267100257400ustar00rootroot00000000000000#include "EncryptedBlockStore2.h" src/blockstore/implementations/encrypted/EncryptedBlockStore2.h000066400000000000000000000166461347701267100254260ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_ENCRYPTEDBLOCKSTORE2_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_ENCRYPTEDBLOCKSTORE2_H_ #include "../../interface/BlockStore2.h" #include "cpp-utils/crypto/cryptopp_byte.h" #include #include #include namespace blockstore { namespace encrypted { //TODO Format version headers template class EncryptedBlockStore2 final: public BlockStore2 { public: BOOST_CONCEPT_ASSERT((cpputils::CipherConcept)); EncryptedBlockStore2(cpputils::unique_ref baseBlockStore, const typename Cipher::EncryptionKey &encKey); bool tryCreate(const BlockId &blockId, const cpputils::Data &data) override; bool remove(const BlockId &blockId) override; boost::optional load(const BlockId &blockId) const override; void store(const BlockId &blockId, const cpputils::Data &data) override; uint64_t numBlocks() const override; uint64_t estimateNumFreeBytes() const override; uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const override; void forEachBlock(std::function callback) const override; //This function should only be used by test cases void __setKey(const typename Cipher::EncryptionKey &encKey); private: // This header is prepended to blocks to allow future versions to have compatibility. #ifndef CRYFS_NO_COMPATIBILITY static constexpr uint16_t FORMAT_VERSION_HEADER_OLD = 0; #endif static constexpr uint16_t FORMAT_VERSION_HEADER = 1; cpputils::Data _encrypt(const cpputils::Data &data) const; boost::optional _tryDecrypt(const BlockId &blockId, const cpputils::Data &data) const; static cpputils::Data _prependFormatHeaderToData(const cpputils::Data &data); #ifndef CRYFS_NO_COMPATIBILITY static bool _blockIdHeaderIsCorrect(const BlockId &blockId, const cpputils::Data &data); static cpputils::Data _migrateBlock(const cpputils::Data &data); #endif static void _checkFormatHeader(const cpputils::Data &data); static uint16_t _readFormatHeader(const cpputils::Data &data); cpputils::unique_ref _baseBlockStore; typename Cipher::EncryptionKey _encKey; DISALLOW_COPY_AND_ASSIGN(EncryptedBlockStore2); }; #ifndef CRYFS_NO_COMPATIBILITY template constexpr uint16_t EncryptedBlockStore2::FORMAT_VERSION_HEADER_OLD; #endif template constexpr uint16_t EncryptedBlockStore2::FORMAT_VERSION_HEADER; template inline EncryptedBlockStore2::EncryptedBlockStore2(cpputils::unique_ref baseBlockStore, const typename Cipher::EncryptionKey &encKey) : _baseBlockStore(std::move(baseBlockStore)), _encKey(encKey) { } template inline bool EncryptedBlockStore2::tryCreate(const BlockId &blockId, const cpputils::Data &data) { cpputils::Data encrypted = _encrypt(data); return _baseBlockStore->tryCreate(blockId, encrypted); } template inline bool EncryptedBlockStore2::remove(const BlockId &blockId) { return _baseBlockStore->remove(blockId); } template inline boost::optional EncryptedBlockStore2::load(const BlockId &blockId) const { auto loaded = _baseBlockStore->load(blockId); if (boost::none == loaded) { return boost::optional(boost::none); } return _tryDecrypt(blockId, *loaded); } template inline void EncryptedBlockStore2::store(const BlockId &blockId, const cpputils::Data &data) { cpputils::Data encrypted = _encrypt(data); return _baseBlockStore->store(blockId, encrypted); } template inline uint64_t EncryptedBlockStore2::numBlocks() const { return _baseBlockStore->numBlocks(); } template inline uint64_t EncryptedBlockStore2::estimateNumFreeBytes() const { return _baseBlockStore->estimateNumFreeBytes(); } template inline uint64_t EncryptedBlockStore2::blockSizeFromPhysicalBlockSize(uint64_t blockSize) const { uint64_t baseBlockSize = _baseBlockStore->blockSizeFromPhysicalBlockSize(blockSize); if (baseBlockSize <= Cipher::ciphertextSize(0) + sizeof(FORMAT_VERSION_HEADER)) { return 0; } return Cipher::plaintextSize(baseBlockSize - sizeof(FORMAT_VERSION_HEADER)); } template inline void EncryptedBlockStore2::forEachBlock(std::function callback) const { return _baseBlockStore->forEachBlock(std::move(callback)); } template inline cpputils::Data EncryptedBlockStore2::_encrypt(const cpputils::Data &data) const { cpputils::Data encrypted = Cipher::encrypt(static_cast(data.data()), data.size(), _encKey); return _prependFormatHeaderToData(encrypted); } template inline boost::optional EncryptedBlockStore2::_tryDecrypt(const BlockId &blockId, const cpputils::Data &data) const { _checkFormatHeader(data); boost::optional decrypted = Cipher::decrypt(static_cast(data.dataOffset(sizeof(FORMAT_VERSION_HEADER))), data.size() - sizeof(FORMAT_VERSION_HEADER), _encKey); if (decrypted == boost::none) { // TODO Log warning return boost::none; } #ifndef CRYFS_NO_COMPATIBILITY if (FORMAT_VERSION_HEADER_OLD == _readFormatHeader(data)) { if (!_blockIdHeaderIsCorrect(blockId, *decrypted)) { return boost::none; } *decrypted = _migrateBlock(*decrypted); // no need to write migrated back to block store because // this migration happens in line with a migration in IntegrityBlockStore2 // which then writes it back } #endif return decrypted; } #ifndef CRYFS_NO_COMPATIBILITY template inline cpputils::Data EncryptedBlockStore2::_migrateBlock(const cpputils::Data &data) { return data.copyAndRemovePrefix(BlockId::BINARY_LENGTH); } template inline bool EncryptedBlockStore2::_blockIdHeaderIsCorrect(const BlockId &blockId, const cpputils::Data &data) { return blockId == BlockId::FromBinary(data.data()); } #endif template inline cpputils::Data EncryptedBlockStore2::_prependFormatHeaderToData(const cpputils::Data &data) { cpputils::Data dataWithHeader(sizeof(FORMAT_VERSION_HEADER) + data.size()); cpputils::serialize(dataWithHeader.dataOffset(0), FORMAT_VERSION_HEADER); std::memcpy(dataWithHeader.dataOffset(sizeof(FORMAT_VERSION_HEADER)), data.data(), data.size()); return dataWithHeader; } template inline void EncryptedBlockStore2::_checkFormatHeader(const cpputils::Data &data) { const uint16_t formatVersionHeader = _readFormatHeader(data); #ifndef CRYFS_NO_COMPATIBILITY const bool formatVersionHeaderValid = formatVersionHeader == FORMAT_VERSION_HEADER || formatVersionHeader == FORMAT_VERSION_HEADER_OLD; #else const bool formatVersionHeaderValid = formatVersionHeader == FORMAT_VERSION_HEADER; #endif if (!formatVersionHeaderValid) { throw std::runtime_error("The encrypted block has the wrong format. Was it created with a newer version of CryFS?"); } } template uint16_t EncryptedBlockStore2::_readFormatHeader(const cpputils::Data &data) { return cpputils::deserialize(data.data()); } template void EncryptedBlockStore2::__setKey(const typename Cipher::EncryptionKey &encKey) { _encKey = encKey; } } } #endif src/blockstore/implementations/inmemory/000077500000000000000000000000001347701267100210735ustar00rootroot00000000000000src/blockstore/implementations/inmemory/InMemoryBlockStore2.cpp000066400000000000000000000050661347701267100254170ustar00rootroot00000000000000#include "InMemoryBlockStore2.h" #include #include #include using std::string; using std::mutex; using std::make_pair; using std::vector; using cpputils::Data; using boost::optional; using boost::none; namespace blockstore { namespace inmemory { InMemoryBlockStore2::InMemoryBlockStore2() : _blocks() {} bool InMemoryBlockStore2::tryCreate(const BlockId &blockId, const Data &data) { std::unique_lock lock(_mutex); return _tryCreate(blockId, data); } bool InMemoryBlockStore2::_tryCreate(const BlockId &blockId, const Data &data) { auto result = _blocks.insert(make_pair(blockId, data.copy())); return result.second; // Return if insertion was successful (i.e. blockId didn't exist yet) } bool InMemoryBlockStore2::remove(const BlockId &blockId) { std::unique_lock lock(_mutex); auto found = _blocks.find(blockId); if (found == _blocks.end()) { // BlockId not found return false; } _blocks.erase(found); return true; } optional InMemoryBlockStore2::load(const BlockId &blockId) const { std::unique_lock lock(_mutex); auto found = _blocks.find(blockId); if (found == _blocks.end()) { return boost::none; } return found->second.copy(); } void InMemoryBlockStore2::store(const BlockId &blockId, const Data &data) { std::unique_lock lock(_mutex); auto found = _blocks.find(blockId); if (found == _blocks.end()) { bool success = _tryCreate(blockId, data); if (!success) { throw std::runtime_error("Could neither save nor create the block in InMemoryBlockStore::store()"); } } else { // TODO Would have better performance: found->second.overwriteWith(data) found->second = data.copy(); } } uint64_t InMemoryBlockStore2::numBlocks() const { std::unique_lock lock(_mutex); return _blocks.size(); } uint64_t InMemoryBlockStore2::estimateNumFreeBytes() const { return cpputils::system::get_total_memory(); } uint64_t InMemoryBlockStore2::blockSizeFromPhysicalBlockSize(uint64_t blockSize) const { return blockSize; } vector InMemoryBlockStore2::_allBlockIds() const { std::unique_lock lock(_mutex); vector result; result.reserve(_blocks.size()); for (const auto &entry : _blocks) { result.push_back(entry.first); } return result; } void InMemoryBlockStore2::forEachBlock(std::function callback) const { auto blockIds = _allBlockIds(); for (const auto &blockId : blockIds) { callback(blockId); } } } } src/blockstore/implementations/inmemory/InMemoryBlockStore2.h000066400000000000000000000022631347701267100250600ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCKSTORE2_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_INMEMORY_INMEMORYBLOCKSTORE2_H_ #include "../../interface/BlockStore2.h" #include #include namespace blockstore { namespace inmemory { class InMemoryBlockStore2 final: public BlockStore2 { public: InMemoryBlockStore2(); bool tryCreate(const BlockId &blockId, const cpputils::Data &data) override; bool remove(const BlockId &blockId) override; boost::optional load(const BlockId &blockId) const override; void store(const BlockId &blockId, const cpputils::Data &data) override; uint64_t numBlocks() const override; uint64_t estimateNumFreeBytes() const override; uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const override; void forEachBlock(std::function callback) const override; private: std::vector _allBlockIds() const; bool _tryCreate(const BlockId &blockId, const cpputils::Data &data); std::unordered_map _blocks; mutable std::mutex _mutex; DISALLOW_COPY_AND_ASSIGN(InMemoryBlockStore2); }; } } #endif src/blockstore/implementations/integrity/000077500000000000000000000000001347701267100212525ustar00rootroot00000000000000src/blockstore/implementations/integrity/ClientIdAndBlockId.cpp000066400000000000000000000000371347701267100253240ustar00rootroot00000000000000#include "ClientIdAndBlockId.h"src/blockstore/implementations/integrity/ClientIdAndBlockId.h000066400000000000000000000022471347701267100247760ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_INTEGRITY_CLIENTIDANDBLOCKID_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_INTEGRITY_CLIENTIDANDBLOCKID_H_ #include #include "blockstore/utils/BlockId.h" namespace blockstore { namespace integrity { struct ClientIdAndBlockId final { ClientIdAndBlockId(uint32_t clientId_, BlockId blockId_): clientId(clientId_), blockId(blockId_) {} uint32_t clientId; BlockId blockId; }; } } // Allow using it in std::unordered_set / std::unordered_map namespace std { template<> struct hash { size_t operator()(const blockstore::integrity::ClientIdAndBlockId &ref) const { return std::hash()(ref.clientId) ^ std::hash()(ref.blockId); } }; template<> struct equal_to { size_t operator()(const blockstore::integrity::ClientIdAndBlockId &lhs, const blockstore::integrity::ClientIdAndBlockId &rhs) const { return lhs.clientId == rhs.clientId && lhs.blockId == rhs.blockId; } }; } #endif src/blockstore/implementations/integrity/IntegrityBlockStore2.cpp000066400000000000000000000227701347701267100260160ustar00rootroot00000000000000#include #include "IntegrityBlockStore2.h" #include "KnownBlockVersions.h" #include #include #include using cpputils::Data; using cpputils::unique_ref; using cpputils::serialize; using cpputils::deserialize; using cpputils::SignalCatcher; using std::string; using boost::optional; using boost::none; using namespace cpputils::logging; namespace blockstore { namespace integrity { #ifndef CRYFS_NO_COMPATIBILITY constexpr uint16_t IntegrityBlockStore2::FORMAT_VERSION_HEADER_OLD; #endif constexpr uint16_t IntegrityBlockStore2::FORMAT_VERSION_HEADER; constexpr uint64_t IntegrityBlockStore2::VERSION_ZERO; constexpr unsigned int IntegrityBlockStore2::ID_HEADER_OFFSET; constexpr unsigned int IntegrityBlockStore2::CLIENTID_HEADER_OFFSET; constexpr unsigned int IntegrityBlockStore2::VERSION_HEADER_OFFSET; constexpr unsigned int IntegrityBlockStore2::HEADER_LENGTH; Data IntegrityBlockStore2::_prependHeaderToData(const BlockId& blockId, uint32_t myClientId, uint64_t version, const Data &data) { static_assert(HEADER_LENGTH == sizeof(FORMAT_VERSION_HEADER) + BlockId::BINARY_LENGTH + sizeof(myClientId) + sizeof(version), "Wrong header length"); Data result(data.size() + HEADER_LENGTH); serialize(result.dataOffset(0), FORMAT_VERSION_HEADER); blockId.ToBinary(result.dataOffset(ID_HEADER_OFFSET)); serialize(result.dataOffset(CLIENTID_HEADER_OFFSET), myClientId); serialize(result.dataOffset(VERSION_HEADER_OFFSET), version); std::memcpy(result.dataOffset(HEADER_LENGTH), data.data(), data.size()); return result; } bool IntegrityBlockStore2::_checkHeader(const BlockId &blockId, const Data &data) const { _checkFormatHeader(data); return _checkIdHeader(blockId, data) && _checkVersionHeader(blockId, data); } void IntegrityBlockStore2::_checkFormatHeader(const Data &data) const { if (FORMAT_VERSION_HEADER != _readFormatHeader(data)) { throw std::runtime_error("The versioned block has the wrong format. Was it created with a newer version of CryFS?"); } } bool IntegrityBlockStore2::_checkVersionHeader(const BlockId &blockId, const Data &data) const { uint32_t clientId = _readClientId(data); uint64_t version = _readVersion(data); if(!_knownBlockVersions.checkAndUpdateVersion(clientId, blockId, version)) { integrityViolationDetected("The block version number is too low. Did an attacker try to roll back the block or to re-introduce a deleted block?"); return false; } return true; } bool IntegrityBlockStore2::_checkIdHeader(const BlockId &expectedBlockId, const Data &data) const { BlockId actualBlockId = _readBlockId(data); if (expectedBlockId != actualBlockId) { integrityViolationDetected("The block id is wrong. Did an attacker try to rename some blocks?"); return false; } return true; } uint16_t IntegrityBlockStore2::_readFormatHeader(const Data &data) { return deserialize(data.data()); } uint32_t IntegrityBlockStore2::_readClientId(const Data &data) { return deserialize(data.dataOffset(CLIENTID_HEADER_OFFSET)); } BlockId IntegrityBlockStore2::_readBlockId(const Data &data) { return BlockId::FromBinary(data.dataOffset(ID_HEADER_OFFSET)); } uint64_t IntegrityBlockStore2::_readVersion(const Data &data) { return deserialize(data.dataOffset(VERSION_HEADER_OFFSET)); } Data IntegrityBlockStore2::_removeHeader(const Data &data) { return data.copyAndRemovePrefix(HEADER_LENGTH); } void IntegrityBlockStore2::integrityViolationDetected(const string &reason) const { if (_allowIntegrityViolations) { LOG(WARN, "Integrity violation (but integrity checks are disabled): {}", reason); return; } _knownBlockVersions.setIntegrityViolationOnPreviousRun(true); _onIntegrityViolation(); } IntegrityBlockStore2::IntegrityBlockStore2(unique_ref baseBlockStore, const boost::filesystem::path &integrityFilePath, uint32_t myClientId, bool allowIntegrityViolations, bool missingBlockIsIntegrityViolation, std::function onIntegrityViolation) : _baseBlockStore(std::move(baseBlockStore)), _knownBlockVersions(integrityFilePath, myClientId), _allowIntegrityViolations(allowIntegrityViolations), _missingBlockIsIntegrityViolation(missingBlockIsIntegrityViolation), _onIntegrityViolation(std::move(onIntegrityViolation)) { if (_knownBlockVersions.integrityViolationOnPreviousRun()) { throw IntegrityViolationOnPreviousRun(_knownBlockVersions.path()); } } bool IntegrityBlockStore2::tryCreate(const BlockId &blockId, const Data &data) { uint64_t version = _knownBlockVersions.incrementVersion(blockId); Data dataWithHeader = _prependHeaderToData(blockId, _knownBlockVersions.myClientId(), version, data); return _baseBlockStore->tryCreate(blockId, dataWithHeader); } bool IntegrityBlockStore2::remove(const BlockId &blockId) { _knownBlockVersions.markBlockAsDeleted(blockId); return _baseBlockStore->remove(blockId); } optional IntegrityBlockStore2::load(const BlockId &blockId) const { auto loaded = _baseBlockStore->load(blockId); if (none == loaded) { if (_missingBlockIsIntegrityViolation && _knownBlockVersions.blockShouldExist(blockId)) { integrityViolationDetected("A block that should exist wasn't found. Did an attacker delete it?"); } return optional(none); } #ifndef CRYFS_NO_COMPATIBILITY if (FORMAT_VERSION_HEADER_OLD == _readFormatHeader(*loaded)) { Data migrated = _migrateBlock(blockId, *loaded); if (!_checkHeader(blockId, migrated) && !_allowIntegrityViolations) { return optional(none); } Data content = _removeHeader(migrated); const_cast(this)->store(blockId, content); return optional(_removeHeader(migrated)); } #endif if (!_checkHeader(blockId, *loaded) && !_allowIntegrityViolations) { return optional(none); } return optional(_removeHeader(*loaded)); } #ifndef CRYFS_NO_COMPATIBILITY Data IntegrityBlockStore2::_migrateBlock(const BlockId &blockId, const Data &data) { Data migrated(data.size() + BlockId::BINARY_LENGTH); serialize(migrated.dataOffset(0), FORMAT_VERSION_HEADER); blockId.ToBinary(migrated.dataOffset(ID_HEADER_OFFSET)); std::memcpy(migrated.dataOffset(ID_HEADER_OFFSET + BlockId::BINARY_LENGTH), data.dataOffset(sizeof(FORMAT_VERSION_HEADER)), data.size() - sizeof(FORMAT_VERSION_HEADER)); ASSERT(migrated.size() == sizeof(FORMAT_VERSION_HEADER) + BlockId::BINARY_LENGTH + (data.size() - sizeof(FORMAT_VERSION_HEADER)), "Wrong offset computation"); return migrated; } #endif void IntegrityBlockStore2::store(const BlockId &blockId, const Data &data) { uint64_t version = _knownBlockVersions.incrementVersion(blockId); Data dataWithHeader = _prependHeaderToData(blockId, _knownBlockVersions.myClientId(), version, data); return _baseBlockStore->store(blockId, dataWithHeader); } uint64_t IntegrityBlockStore2::numBlocks() const { return _baseBlockStore->numBlocks(); } uint64_t IntegrityBlockStore2::estimateNumFreeBytes() const { return _baseBlockStore->estimateNumFreeBytes(); } uint64_t IntegrityBlockStore2::blockSizeFromPhysicalBlockSize(uint64_t blockSize) const { uint64_t baseBlockSize = _baseBlockStore->blockSizeFromPhysicalBlockSize(blockSize); if (baseBlockSize <= HEADER_LENGTH) { return 0; } return baseBlockSize - HEADER_LENGTH; } void IntegrityBlockStore2::forEachBlock(std::function callback) const { if (!_missingBlockIsIntegrityViolation) { return _baseBlockStore->forEachBlock(std::move(callback)); } std::unordered_set existingBlocks = _knownBlockVersions.existingBlocks(); _baseBlockStore->forEachBlock([&existingBlocks, callback] (const BlockId &blockId) { callback(blockId); auto found = existingBlocks.find(blockId); if (found != existingBlocks.end()) { existingBlocks.erase(found); } }); if (!existingBlocks.empty()) { integrityViolationDetected("A block that should have existed wasn't found."); } } #ifndef CRYFS_NO_COMPATIBILITY void IntegrityBlockStore2::migrateFromBlockstoreWithoutVersionNumbers(BlockStore2 *baseBlockStore, const boost::filesystem::path &integrityFilePath, uint32_t myClientId) { SignalCatcher signalCatcher; KnownBlockVersions knownBlockVersions(integrityFilePath, myClientId); 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); progressbar.update(++numProcessedBlocks); }); } void IntegrityBlockStore2::migrateBlockFromBlockstoreWithoutVersionNumbers(blockstore::BlockStore2* baseBlockStore, const blockstore::BlockId& blockId, KnownBlockVersions *knownBlockVersions) { auto data_ = baseBlockStore->load(blockId); if (data_ == boost::none) { LOG(WARN, "Block not found, but was returned from forEachBlock before"); return; } if (0 != _readFormatHeader(*data_)) { // already migrated return; } uint64_t version = knownBlockVersions->incrementVersion(blockId); cpputils::Data data = std::move(*data_); cpputils::Data dataWithHeader = _prependHeaderToData(blockId, knownBlockVersions->myClientId(), version, data); baseBlockStore->store(blockId, dataWithHeader); } #endif } } src/blockstore/implementations/integrity/IntegrityBlockStore2.h000066400000000000000000000104751347701267100254620ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_INTEGRITY_INTEGRITYBLOCKSTORE2_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_INTEGRITY_INTEGRITYBLOCKSTORE2_H_ #include "../../interface/BlockStore2.h" #include #include "KnownBlockVersions.h" namespace blockstore { namespace integrity { // This exception is thrown if the filesystem can't be loaded because an integrity violation happened // in one of its earlier runs. // TODO Use block store factory with expected<> result instead of exception throwing. class IntegrityViolationOnPreviousRun final : public std::exception { public: IntegrityViolationOnPreviousRun(boost::filesystem::path stateFile) : _stateFile(std::move(stateFile)) {} const boost::filesystem::path& stateFile() const { return _stateFile; } private: // The state file/directory that has to be deleted so the file system works again boost::filesystem::path _stateFile; }; //TODO Format version headers // This blockstore implements integrity measures. // It depends on being used on top of an encrypted block store that protects integrity of the block contents (i.e. uses an authenticated cipher). class IntegrityBlockStore2 final: public BlockStore2 { public: IntegrityBlockStore2(cpputils::unique_ref baseBlockStore, const boost::filesystem::path &integrityFilePath, uint32_t myClientId, bool allowIntegrityViolations, bool missingBlockIsIntegrityViolation, std::function onIntegrityViolation); bool tryCreate(const BlockId &blockId, const cpputils::Data &data) override; bool remove(const BlockId &blockId) override; boost::optional load(const BlockId &blockId) const override; void store(const BlockId &blockId, const cpputils::Data &data) override; uint64_t numBlocks() const override; uint64_t estimateNumFreeBytes() const override; uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const override; void forEachBlock(std::function callback) const override; private: // This format version is prepended to blocks to allow future versions to have compatibility. #ifndef CRYFS_NO_COMPATIBILITY static constexpr uint16_t FORMAT_VERSION_HEADER_OLD = 0; #endif static constexpr uint16_t FORMAT_VERSION_HEADER = 1; public: static constexpr uint64_t VERSION_ZERO = 0; static constexpr unsigned int ID_HEADER_OFFSET = sizeof(FORMAT_VERSION_HEADER); static constexpr unsigned int CLIENTID_HEADER_OFFSET = ID_HEADER_OFFSET + BlockId::BINARY_LENGTH; static constexpr unsigned int VERSION_HEADER_OFFSET = CLIENTID_HEADER_OFFSET + sizeof(uint32_t); static constexpr unsigned int HEADER_LENGTH = VERSION_HEADER_OFFSET + sizeof(VERSION_ZERO); #ifndef CRYFS_NO_COMPATIBILITY static void migrateFromBlockstoreWithoutVersionNumbers(BlockStore2 *baseBlockStore, const boost::filesystem::path &integrityFilePath, uint32_t myClientId); static void migrateBlockFromBlockstoreWithoutVersionNumbers(BlockStore2* baseBlockStore, const blockstore::BlockId &blockId, KnownBlockVersions *knownBlockVersions); #endif private: static cpputils::Data _prependHeaderToData(const BlockId &blockId, uint32_t myClientId, uint64_t version, const cpputils::Data &data); WARN_UNUSED_RESULT bool _checkHeader(const BlockId &blockId, const cpputils::Data &data) const; void _checkFormatHeader(const cpputils::Data &data) const; WARN_UNUSED_RESULT bool _checkIdHeader(const BlockId &expectedBlockId, const cpputils::Data &data) const; WARN_UNUSED_RESULT bool _checkVersionHeader(const BlockId &blockId, const cpputils::Data &data) const; static uint16_t _readFormatHeader(const cpputils::Data &data); static uint32_t _readClientId(const cpputils::Data &data); static BlockId _readBlockId(const cpputils::Data &data); static uint64_t _readVersion(const cpputils::Data &data); #ifndef CRYFS_NO_COMPATIBILITY static cpputils::Data _migrateBlock(const BlockId &blockId, const cpputils::Data &data); #endif static cpputils::Data _removeHeader(const cpputils::Data &data); void integrityViolationDetected(const std::string &reason) const; cpputils::unique_ref _baseBlockStore; mutable KnownBlockVersions _knownBlockVersions; const bool _allowIntegrityViolations; const bool _missingBlockIsIntegrityViolation; std::function _onIntegrityViolation; DISALLOW_COPY_AND_ASSIGN(IntegrityBlockStore2); }; } } #endif src/blockstore/implementations/integrity/KnownBlockVersions.cpp000066400000000000000000000232011347701267100255540ustar00rootroot00000000000000#include #include #include #include "KnownBlockVersions.h" namespace bf = boost::filesystem; using std::pair; using std::string; using std::unique_lock; using std::mutex; using boost::optional; using boost::none; using cpputils::Data; using cpputils::Serializer; using cpputils::Deserializer; namespace blockstore { namespace integrity { const string KnownBlockVersions::OLD_HEADER = "cryfs.integritydata.knownblockversions;0"; const string KnownBlockVersions::HEADER = "cryfs.integritydata.knownblockversions;1"; constexpr uint32_t KnownBlockVersions::CLIENT_ID_FOR_DELETED_BLOCK; KnownBlockVersions::KnownBlockVersions(const bf::path &stateFilePath, uint32_t myClientId) :_integrityViolationOnPreviousRun(false), _knownVersions(), _lastUpdateClientId(), _stateFilePath(stateFilePath), _myClientId(myClientId), _mutex(), _valid(true) { unique_lock lock(_mutex); ASSERT(_myClientId != CLIENT_ID_FOR_DELETED_BLOCK, "This is not a valid client id"); _loadStateFile(); } KnownBlockVersions::KnownBlockVersions(KnownBlockVersions &&rhs) // NOLINT (intentionally not noexcept) : _integrityViolationOnPreviousRun(false), _knownVersions(), _lastUpdateClientId(), _stateFilePath(), _myClientId(0), _mutex(), _valid(true) { unique_lock rhsLock(rhs._mutex); unique_lock lock(_mutex); _integrityViolationOnPreviousRun = rhs._integrityViolationOnPreviousRun; _knownVersions = std::move(rhs._knownVersions); _lastUpdateClientId = std::move(rhs._lastUpdateClientId); _stateFilePath = std::move(rhs._stateFilePath); _myClientId = rhs._myClientId; rhs._valid = false; } KnownBlockVersions::~KnownBlockVersions() { unique_lock lock(_mutex); if (_valid) { _saveStateFile(); } } void KnownBlockVersions::setIntegrityViolationOnPreviousRun(bool value) { _integrityViolationOnPreviousRun = value; } bool KnownBlockVersions::integrityViolationOnPreviousRun() const { return _integrityViolationOnPreviousRun; } bool KnownBlockVersions::checkAndUpdateVersion(uint32_t clientId, const BlockId &blockId, uint64_t version) { unique_lock lock(_mutex); ASSERT(clientId != CLIENT_ID_FOR_DELETED_BLOCK, "This is not a valid client id"); ASSERT(version > 0, "Version has to be >0"); // Otherwise we wouldn't handle notexisting entries correctly. ASSERT(_valid, "Object not valid due to a std::move"); uint64_t &found = _knownVersions[{clientId, blockId}]; // If the entry doesn't exist, this creates it with value 0. if (found > version) { // This client already published a newer block version. Rollbacks are not allowed. return false; } uint32_t &lastUpdateClientId = _lastUpdateClientId[blockId]; // If entry doesn't exist, this creates it with value 0. However, in this case, found == 0 (and version > 0), which means found != version. if (found == version && lastUpdateClientId != clientId) { // This is a roll back to the "newest" block of client [clientId], which was since then superseded by a version from client _lastUpdateClientId[blockId]. // This is not allowed. return false; } found = version; lastUpdateClientId = clientId; return true; } uint64_t KnownBlockVersions::incrementVersion(const BlockId &blockId) { unique_lock lock(_mutex); uint64_t &found = _knownVersions[{_myClientId, blockId}]; // If the entry doesn't exist, this creates it with value 0. uint64_t newVersion = found + 1; if (newVersion == std::numeric_limits::max()) { // It's *very* unlikely we ever run out of version numbers in 64bit...but just to be sure... throw std::runtime_error("Version overflow"); } found = newVersion; _lastUpdateClientId[blockId] = _myClientId; return found; } void KnownBlockVersions::_loadStateFile() { optional file = Data::LoadFromFile(_stateFilePath); if (file == none) { // File doesn't exist means we loaded empty state. return; } Deserializer deserializer(&*file); const string loaded_header = deserializer.readString(); #ifndef CRYFS_NO_COMPATIBILITY if (OLD_HEADER == loaded_header) { _knownVersions = _deserializeKnownVersions(&deserializer); _lastUpdateClientId = _deserializeLastUpdateClientIds(&deserializer); deserializer.finished(); _saveStateFile(); return; } #endif if (HEADER != loaded_header) { throw std::runtime_error("Invalid local state: Invalid integrity file header."); } _integrityViolationOnPreviousRun = deserializer.readBool(); _knownVersions = _deserializeKnownVersions(&deserializer); _lastUpdateClientId = _deserializeLastUpdateClientIds(&deserializer); deserializer.finished(); }; void KnownBlockVersions::_saveStateFile() const { Serializer serializer( Serializer::StringSize(HEADER) + Serializer::BoolSize() + sizeof(uint64_t) + _knownVersions.size() * (sizeof(uint32_t) + BlockId::BINARY_LENGTH + sizeof(uint64_t)) + sizeof(uint64_t) + _lastUpdateClientId.size() * (BlockId::BINARY_LENGTH + sizeof(uint32_t))); serializer.writeString(HEADER); serializer.writeBool(_integrityViolationOnPreviousRun); _serializeKnownVersions(&serializer, _knownVersions); _serializeLastUpdateClientIds(&serializer, _lastUpdateClientId); serializer.finished().StoreToFile(_stateFilePath); } std::unordered_map KnownBlockVersions::_deserializeKnownVersions(Deserializer *deserializer) { uint64_t numEntries = deserializer->readUint64(); std::unordered_map result; result.reserve(static_cast(1.2 * numEntries)); // Reserve for factor 1.2 more, so the file system doesn't immediately have to resize it on the first new block. for (uint64_t i = 0 ; i < numEntries; ++i) { auto entry = _deserializeKnownVersionsEntry(deserializer); result.insert(entry); } return result; } void KnownBlockVersions::_serializeKnownVersions(Serializer *serializer, const std::unordered_map& knownVersions) { uint64_t numEntries = knownVersions.size(); serializer->writeUint64(numEntries); for (const auto &entry : knownVersions) { _serializeKnownVersionsEntry(serializer, entry); } } pair KnownBlockVersions::_deserializeKnownVersionsEntry(Deserializer *deserializer) { uint32_t clientId = deserializer->readUint32(); BlockId blockId(deserializer->readFixedSizeData()); uint64_t version = deserializer->readUint64(); return {{clientId, blockId}, version}; }; void KnownBlockVersions::_serializeKnownVersionsEntry(Serializer *serializer, const pair &entry) { serializer->writeUint32(entry.first.clientId); serializer->writeFixedSizeData(entry.first.blockId.data()); serializer->writeUint64(entry.second); } std::unordered_map KnownBlockVersions::_deserializeLastUpdateClientIds(Deserializer *deserializer) { uint64_t numEntries = deserializer->readUint64(); std::unordered_map result; result.reserve(static_cast(1.2 * numEntries)); // Reserve for factor 1.2 more, so the file system doesn't immediately have to resize it on the first new block. for (uint64_t i = 0 ; i < numEntries; ++i) { auto entry = _deserializeLastUpdateClientIdEntry(deserializer); result.insert(entry); } return result; } void KnownBlockVersions::_serializeLastUpdateClientIds(Serializer *serializer, const std::unordered_map& lastUpdateClientId) { uint64_t numEntries = lastUpdateClientId.size(); serializer->writeUint64(numEntries); for (const auto &entry : lastUpdateClientId) { _serializeLastUpdateClientIdEntry(serializer, entry); } } pair KnownBlockVersions::_deserializeLastUpdateClientIdEntry(Deserializer *deserializer) { BlockId blockId(deserializer->readFixedSizeData()); uint32_t clientId = deserializer->readUint32(); return {blockId, clientId}; }; void KnownBlockVersions::_serializeLastUpdateClientIdEntry(Serializer *serializer, const pair &entry) { serializer->writeFixedSizeData(entry.first.data()); serializer->writeUint32(entry.second); } uint32_t KnownBlockVersions::myClientId() const { return _myClientId; } uint64_t KnownBlockVersions::getBlockVersion(uint32_t clientId, const BlockId &blockId) const { unique_lock lock(_mutex); return _knownVersions.at({clientId, blockId}); } void KnownBlockVersions::markBlockAsDeleted(const BlockId &blockId) { _lastUpdateClientId[blockId] = CLIENT_ID_FOR_DELETED_BLOCK; } bool KnownBlockVersions::blockShouldExist(const BlockId &blockId) const { auto found = _lastUpdateClientId.find(blockId); if (found == _lastUpdateClientId.end()) { // We've never seen (i.e. loaded) this block. So we can't say it has to exist. return false; } // We've seen the block before. If we didn't delete it, it should exist (only works for single-client scenario). return found->second != CLIENT_ID_FOR_DELETED_BLOCK; } std::unordered_set KnownBlockVersions::existingBlocks() const { std::unordered_set result; for (const auto &entry : _lastUpdateClientId) { if (entry.second != CLIENT_ID_FOR_DELETED_BLOCK) { result.insert(entry.first); } } return result; } const bf::path &KnownBlockVersions::path() const { return _stateFilePath; } } } src/blockstore/implementations/integrity/KnownBlockVersions.h000066400000000000000000000064001347701267100252230ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_INTEGRITY_KNOWNBLOCKVERSIONS_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_INTEGRITY_KNOWNBLOCKVERSIONS_H_ #include #include #include #include #include "ClientIdAndBlockId.h" #include #include #include #include namespace blockstore { namespace integrity { class KnownBlockVersions final { public: KnownBlockVersions(const boost::filesystem::path &stateFilePath, uint32_t myClientId); KnownBlockVersions(KnownBlockVersions &&rhs); // NOLINT (intentionally not noexcept) ~KnownBlockVersions(); WARN_UNUSED_RESULT bool checkAndUpdateVersion(uint32_t clientId, const BlockId &blockId, uint64_t version); uint64_t incrementVersion(const BlockId &blockId); void markBlockAsDeleted(const BlockId &blockId); bool blockShouldExist(const BlockId &blockId) const; std::unordered_set existingBlocks() const; uint64_t getBlockVersion(uint32_t clientId, const BlockId &blockId) const; uint32_t myClientId() const; const boost::filesystem::path &path() const; bool integrityViolationOnPreviousRun() const; void setIntegrityViolationOnPreviousRun(bool value); static constexpr uint32_t CLIENT_ID_FOR_DELETED_BLOCK = 0; private: bool _integrityViolationOnPreviousRun; std::unordered_map _knownVersions; std::unordered_map _lastUpdateClientId; // The client who last updated the block boost::filesystem::path _stateFilePath; uint32_t _myClientId; mutable std::mutex _mutex; bool _valid; static const std::string OLD_HEADER; static const std::string HEADER; void _loadStateFile(); void _saveStateFile() const; static std::unordered_map _deserializeKnownVersions(cpputils::Deserializer *deserializer); static void _serializeKnownVersions(cpputils::Serializer *serializer, const std::unordered_map& knownVersions); static std::pair _deserializeKnownVersionsEntry(cpputils::Deserializer *deserializer); static void _serializeKnownVersionsEntry(cpputils::Serializer *serializer, const std::pair &entry); static std::unordered_map _deserializeLastUpdateClientIds(cpputils::Deserializer *deserializer); static void _serializeLastUpdateClientIds(cpputils::Serializer *serializer, const std::unordered_map& lastUpdateClientIds); static std::pair _deserializeLastUpdateClientIdEntry(cpputils::Deserializer *deserializer); static void _serializeLastUpdateClientIdEntry(cpputils::Serializer *serializer, const std::pair &entry); DISALLOW_COPY_AND_ASSIGN(KnownBlockVersions); }; } } #endif src/blockstore/implementations/low2highlevel/000077500000000000000000000000001347701267100220075ustar00rootroot00000000000000src/blockstore/implementations/low2highlevel/LowToHighLevelBlock.cpp000066400000000000000000000047651347701267100263360ustar00rootroot00000000000000#include "LowToHighLevelBlock.h" using boost::optional; using boost::none; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::Data; namespace DataUtils = cpputils::DataUtils; using std::unique_lock; using std::mutex; namespace blockstore { namespace lowtohighlevel { optional> LowToHighLevelBlock::TryCreateNew(BlockStore2 *baseBlockStore, const BlockId &blockId, Data data) { bool success = baseBlockStore->tryCreate(blockId, data); if (!success) { return none; } return make_unique_ref(blockId, std::move(data), baseBlockStore); } unique_ref LowToHighLevelBlock::Overwrite(BlockStore2 *baseBlockStore, const BlockId &blockId, Data data) { baseBlockStore->store(blockId, data); // TODO Does it make sense to not store here, but only write back in the destructor of LowToHighLevelBlock? Also: What about tryCreate? return make_unique_ref(blockId, std::move(data), baseBlockStore); } optional> LowToHighLevelBlock::Load(BlockStore2 *baseBlockStore, const BlockId &blockId) { optional loadedData = baseBlockStore->load(blockId); if (loadedData == none) { return none; } return make_unique_ref(blockId, std::move(*loadedData), baseBlockStore); } LowToHighLevelBlock::LowToHighLevelBlock(const BlockId &blockId, Data data, BlockStore2 *baseBlockStore) :Block(blockId), _baseBlockStore(baseBlockStore), _data(std::move(data)), _dataChanged(false), _mutex() { } LowToHighLevelBlock::~LowToHighLevelBlock() { unique_lock lock(_mutex); _storeToBaseBlock(); } const void *LowToHighLevelBlock::data() const { return _data.data(); } void LowToHighLevelBlock::write(const void *source, uint64_t offset, uint64_t count) { ASSERT(offset <= size() && offset + count <= size(), "Write outside of valid area"); //Also check offset < size() because of possible overflow in the addition std::memcpy(_data.dataOffset(offset), source, count); _dataChanged = true; } void LowToHighLevelBlock::flush() { unique_lock lock(_mutex); _storeToBaseBlock(); } size_t LowToHighLevelBlock::size() const { return _data.size(); } void LowToHighLevelBlock::resize(size_t newSize) { _data = DataUtils::resize(_data, newSize); _dataChanged = true; } void LowToHighLevelBlock::_storeToBaseBlock() { if (_dataChanged) { _baseBlockStore->store(blockId(), _data); _dataChanged = false; } } } } src/blockstore/implementations/low2highlevel/LowToHighLevelBlock.h000066400000000000000000000032521347701267100257710ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_LOWTOHIGHLEVEL_LOWTOHIGHLEVELBLOCK_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_LOWTOHIGHLEVEL_LOWTOHIGHLEVELBLOCK_H_ #include "../../interface/Block.h" #include #include "../../interface/BlockStore.h" #include "../../interface/BlockStore2.h" #include #include #include #include #include #include #include #include #include #include "LowToHighLevelBlockStore.h" namespace blockstore { namespace lowtohighlevel { class LowToHighLevelBlock final: public Block { public: static boost::optional> TryCreateNew(BlockStore2 *baseBlockStore, const BlockId &blockId, cpputils::Data data); static cpputils::unique_ref Overwrite(BlockStore2 *baseBlockStore, const BlockId &blockId, cpputils::Data data); static boost::optional> Load(BlockStore2 *baseBlockStore, const BlockId &blockId); LowToHighLevelBlock(const BlockId &blockId, cpputils::Data data, BlockStore2 *baseBlockStore); ~LowToHighLevelBlock(); const void *data() const override; void write(const void *source, uint64_t offset, uint64_t count) override; void flush() override; size_t size() const override; void resize(size_t newSize) override; private: BlockStore2 *_baseBlockStore; cpputils::Data _data; bool _dataChanged; std::mutex _mutex; void _storeToBaseBlock(); DISALLOW_COPY_AND_ASSIGN(LowToHighLevelBlock); }; } } #endif src/blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.cpp000066400000000000000000000043051347701267100273410ustar00rootroot00000000000000#include #include "LowToHighLevelBlockStore.h" #include "LowToHighLevelBlock.h" using cpputils::unique_ref; using cpputils::Data; using boost::none; using boost::optional; using std::string; namespace blockstore { namespace lowtohighlevel { LowToHighLevelBlockStore::LowToHighLevelBlockStore(unique_ref baseBlockStore) : _baseBlockStore(std::move(baseBlockStore)) { } BlockId LowToHighLevelBlockStore::createBlockId() { // TODO Is this the right way? return BlockId::Random(); } optional> LowToHighLevelBlockStore::tryCreate(const BlockId &blockId, Data data) { //TODO Easier implementation? This is only so complicated because of the cast LowToHighLevelBlock -> Block auto result = LowToHighLevelBlock::TryCreateNew(_baseBlockStore.get(), blockId, std::move(data)); if (result == none) { return none; } return unique_ref(std::move(*result)); } unique_ref LowToHighLevelBlockStore::overwrite(const BlockId &blockId, Data data) { return unique_ref( LowToHighLevelBlock::Overwrite(_baseBlockStore.get(), blockId, std::move(data)) ); } optional> LowToHighLevelBlockStore::load(const BlockId &blockId) { auto result = optional>(LowToHighLevelBlock::Load(_baseBlockStore.get(), blockId)); if (result == none) { return none; } return unique_ref(std::move(*result)); } void LowToHighLevelBlockStore::remove(const BlockId &blockId) { bool success = _baseBlockStore->remove(blockId); if (!success) { throw std::runtime_error("Couldn't delete block with id " + blockId.ToString()); } } uint64_t LowToHighLevelBlockStore::numBlocks() const { return _baseBlockStore->numBlocks(); } uint64_t LowToHighLevelBlockStore::estimateNumFreeBytes() const { return _baseBlockStore->estimateNumFreeBytes(); } uint64_t LowToHighLevelBlockStore::blockSizeFromPhysicalBlockSize(uint64_t blockSize) const { return _baseBlockStore->blockSizeFromPhysicalBlockSize(blockSize); } void LowToHighLevelBlockStore::forEachBlock(std::function callback) const { _baseBlockStore->forEachBlock(std::move(callback)); } } } src/blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h000066400000000000000000000030641347701267100270070ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_LOWTOHIGHLEVEL_LOWTOHIGHLEVELBLOCKSTORE_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_LOWTOHIGHLEVEL_LOWTOHIGHLEVELBLOCKSTORE_H_ #include "../../interface/BlockStore.h" #include "../../interface/BlockStore2.h" #include #include #include // TODO Think each function through and make sure it's as performant // to use LowToHighLevelBlockStore as to use // OnDiskBlockStore directly (i.e. no additional stores/loads from the disk) // (same for other base block stores) namespace blockstore { namespace lowtohighlevel { class LowToHighLevelBlockStore final: public BlockStore { public: LowToHighLevelBlockStore(cpputils::unique_ref baseBlockStore); BlockId createBlockId() override; boost::optional> tryCreate(const BlockId &blockId, cpputils::Data data) override; cpputils::unique_ref overwrite(const blockstore::BlockId &blockId, cpputils::Data data) override; boost::optional> load(const BlockId &blockId) override; void remove(const BlockId &blockId) override; uint64_t numBlocks() const override; uint64_t estimateNumFreeBytes() const override; uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const override; void forEachBlock(std::function callback) const override; private: cpputils::unique_ref _baseBlockStore; DISALLOW_COPY_AND_ASSIGN(LowToHighLevelBlockStore); }; } } #endif src/blockstore/implementations/mock/000077500000000000000000000000001347701267100201655ustar00rootroot00000000000000src/blockstore/implementations/mock/MockBlock.cpp000066400000000000000000000007561347701267100225450ustar00rootroot00000000000000#include "MockBlock.h" #include "MockBlockStore.h" namespace blockstore { namespace mock { void MockBlock::write(const void *source, uint64_t offset, uint64_t size) { _blockStore->_increaseNumWrittenBlocks(blockId()); return _baseBlock->write(source, offset, size); } void MockBlock::resize(size_t newSize) { _blockStore->_increaseNumResizedBlocks(blockId()); return _baseBlock->resize(newSize); } } } src/blockstore/implementations/mock/MockBlock.h000066400000000000000000000025221347701267100222030ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_MOCK_MOCKBLOCK_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_MOCK_MOCKBLOCK_H_ #include #include namespace blockstore { namespace mock { class MockBlockStore; class MockBlock final : public blockstore::Block { public: MockBlock(cpputils::unique_ref baseBlock, MockBlockStore *blockStore) :Block(baseBlock->blockId()), _baseBlock(std::move(baseBlock)), _blockStore(blockStore) { } const void *data() const override { return _baseBlock->data(); } void write(const void *source, uint64_t offset, uint64_t size) override; void flush() override { return _baseBlock->flush(); } size_t size() const override { return _baseBlock->size(); } void resize(size_t newSize) override; cpputils::unique_ref releaseBaseBlock() { return std::move(_baseBlock); } private: cpputils::unique_ref _baseBlock; MockBlockStore *_blockStore; DISALLOW_COPY_AND_ASSIGN(MockBlock); }; } } #endif src/blockstore/implementations/mock/MockBlockStore.cpp000066400000000000000000000000331347701267100235460ustar00rootroot00000000000000#include "MockBlockStore.h"src/blockstore/implementations/mock/MockBlockStore.h000066400000000000000000000136771347701267100232350ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_MOCK_MOCKBLOCKSTORE_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_MOCK_MOCKBLOCKSTORE_H_ #include #include #include "MockBlock.h" namespace blockstore { namespace mock { /** * This is a blockstore that counts the number of loaded, resized, written, ... blocks. * It is used for testing that operations only access few blocks (performance tests). */ class MockBlockStore final : public BlockStore { public: MockBlockStore(cpputils::unique_ref baseBlockStore = cpputils::make_unique_ref()) : _mutex(), _baseBlockStore(std::move(baseBlockStore)), _loadedBlocks(), _createdBlocks(0), _writtenBlocks(), _resizedBlocks(), _removedBlocks() { } BlockId createBlockId() override { return _baseBlockStore->createBlockId(); } boost::optional> tryCreate(const BlockId &blockId, cpputils::Data data) override { _increaseNumCreatedBlocks(); auto base = _baseBlockStore->tryCreate(blockId, std::move(data)); if (base == boost::none) { return boost::none; } return boost::optional>(cpputils::make_unique_ref(std::move(*base), this)); } boost::optional> load(const BlockId &blockId) override { _increaseNumLoadedBlocks(blockId); auto base = _baseBlockStore->load(blockId); if (base == boost::none) { return boost::none; } return boost::optional>(cpputils::make_unique_ref(std::move(*base), this)); } cpputils::unique_ref overwrite(const BlockId &blockId, cpputils::Data data) override { _increaseNumWrittenBlocks(blockId); return _baseBlockStore->overwrite(blockId, std::move(data)); } void remove(const BlockId &blockId) override { _increaseNumRemovedBlocks(blockId); return _baseBlockStore->remove(blockId); } uint64_t numBlocks() const override { return _baseBlockStore->numBlocks(); } uint64_t estimateNumFreeBytes() const override { return _baseBlockStore->estimateNumFreeBytes(); } uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const override { return _baseBlockStore->blockSizeFromPhysicalBlockSize(blockSize); } void forEachBlock(std::function callback) const override { return _baseBlockStore->forEachBlock(callback); } void remove(cpputils::unique_ref block) override { _increaseNumRemovedBlocks(block->blockId()); auto mockBlock = cpputils::dynamic_pointer_move(block); ASSERT(mockBlock != boost::none, "Wrong block type"); return _baseBlockStore->remove((*mockBlock)->releaseBaseBlock()); } void resetCounters() { _loadedBlocks = {}; _createdBlocks = 0; _removedBlocks = {}; _resizedBlocks = {}; _writtenBlocks = {}; } uint64_t createdBlocks() const { return _createdBlocks; } const std::vector &loadedBlocks() const { return _loadedBlocks; } const std::vector &removedBlocks() const { return _removedBlocks; } const std::vector &resizedBlocks() const { return _resizedBlocks; } const std::vector &writtenBlocks() const { return _writtenBlocks; } std::vector distinctWrittenBlocks() const { std::vector result(_writtenBlocks); std::sort(result.begin(), result.end(), [](const BlockId &lhs, const BlockId &rhs) { return std::memcmp(lhs.data().data(), rhs.data().data(), lhs.BINARY_LENGTH) < 0; }); result.erase(std::unique(result.begin(), result.end() ), result.end()); return result; } private: void _increaseNumCreatedBlocks() { std::unique_lock lock(_mutex); _createdBlocks += 1; } void _increaseNumLoadedBlocks(const BlockId &blockId) { std::unique_lock lock(_mutex); _loadedBlocks.push_back(blockId); } void _increaseNumRemovedBlocks(const BlockId &blockId) { std::unique_lock lock(_mutex); _removedBlocks.push_back(blockId); } void _increaseNumResizedBlocks(const BlockId &blockId) { std::unique_lock lock(_mutex); _resizedBlocks.push_back(blockId); } void _increaseNumWrittenBlocks(const BlockId &blockId) { std::unique_lock lock(_mutex); _writtenBlocks.push_back(blockId); } friend class MockBlock; std::mutex _mutex; cpputils::unique_ref _baseBlockStore; std::vector _loadedBlocks; uint64_t _createdBlocks; std::vector _writtenBlocks; std::vector _resizedBlocks; std::vector _removedBlocks; DISALLOW_COPY_AND_ASSIGN(MockBlockStore); }; } } #endif src/blockstore/implementations/ondisk/000077500000000000000000000000001347701267100205235ustar00rootroot00000000000000src/blockstore/implementations/ondisk/OnDiskBlockStore2.cpp000066400000000000000000000123711347701267100244740ustar00rootroot00000000000000#include "OnDiskBlockStore2.h" #include #include using std::string; using boost::optional; using boost::none; using cpputils::Data; namespace blockstore { namespace ondisk { const string OnDiskBlockStore2::FORMAT_VERSION_HEADER_PREFIX = "cryfs;block;"; const string OnDiskBlockStore2::FORMAT_VERSION_HEADER = OnDiskBlockStore2::FORMAT_VERSION_HEADER_PREFIX + "0"; namespace { constexpr size_t PREFIX_LENGTH = 3; constexpr size_t POSTFIX_LENGTH = BlockId::STRING_LENGTH - PREFIX_LENGTH; constexpr const char* ALLOWED_BLOCKID_CHARACTERS = "0123456789ABCDEF"; } boost::filesystem::path OnDiskBlockStore2::_getFilepath(const BlockId &blockId) const { std::string blockIdStr = blockId.ToString(); return _rootDir / blockIdStr.substr(0, PREFIX_LENGTH) / blockIdStr.substr(PREFIX_LENGTH); } Data OnDiskBlockStore2::_checkAndRemoveHeader(const Data &data) { if (!_isAcceptedCryfsHeader(data)) { if (_isOtherCryfsHeader(data)) { throw std::runtime_error("This block is not supported yet. Maybe it was created with a newer version of CryFS?"); } else { throw std::runtime_error("This is not a valid block."); } } Data result(data.size() - formatVersionHeaderSize()); std::memcpy(result.data(), data.dataOffset(formatVersionHeaderSize()), result.size()); return result; } bool OnDiskBlockStore2::_isAcceptedCryfsHeader(const Data &data) { return 0 == std::memcmp(data.data(), FORMAT_VERSION_HEADER.c_str(), formatVersionHeaderSize()); } bool OnDiskBlockStore2::_isOtherCryfsHeader(const Data &data) { return 0 == std::memcmp(data.data(), FORMAT_VERSION_HEADER_PREFIX.c_str(), FORMAT_VERSION_HEADER_PREFIX.size()); } unsigned int OnDiskBlockStore2::formatVersionHeaderSize() { return FORMAT_VERSION_HEADER.size() + 1; // +1 because of the null byte } OnDiskBlockStore2::OnDiskBlockStore2(const boost::filesystem::path& path) : _rootDir(path) {} bool OnDiskBlockStore2::tryCreate(const BlockId &blockId, const Data &data) { auto filepath = _getFilepath(blockId); if (boost::filesystem::exists(filepath)) { return false; } store(blockId, data); return true; } bool OnDiskBlockStore2::remove(const BlockId &blockId) { auto filepath = _getFilepath(blockId); if (!boost::filesystem::is_regular_file(filepath)) { // TODO Is this branch necessary? return false; } bool retval = boost::filesystem::remove(filepath); if (!retval) { cpputils::logging::LOG(cpputils::logging::ERR, "Couldn't find block {} to remove", blockId.ToString()); return false; } if (boost::filesystem::is_empty(filepath.parent_path())) { boost::filesystem::remove(filepath.parent_path()); } return true; } optional OnDiskBlockStore2::load(const BlockId &blockId) const { auto fileContent = Data::LoadFromFile(_getFilepath(blockId)); if (fileContent == none) { return boost::none; } return _checkAndRemoveHeader(*fileContent); } void OnDiskBlockStore2::store(const BlockId &blockId, const Data &data) { Data fileContent(formatVersionHeaderSize() + data.size()); std::memcpy(fileContent.data(), FORMAT_VERSION_HEADER.c_str(), formatVersionHeaderSize()); std::memcpy(fileContent.dataOffset(formatVersionHeaderSize()), data.data(), data.size()); auto filepath = _getFilepath(blockId); boost::filesystem::create_directory(filepath.parent_path()); // TODO Instead create all of them once at fs creation time? fileContent.StoreToFile(filepath); } uint64_t OnDiskBlockStore2::numBlocks() const { uint64_t count = 0; for (auto prefixDir = boost::filesystem::directory_iterator(_rootDir); prefixDir != boost::filesystem::directory_iterator(); ++prefixDir) { if (boost::filesystem::is_directory(prefixDir->path())) { count += std::distance(boost::filesystem::directory_iterator(prefixDir->path()), boost::filesystem::directory_iterator()); } } return count; } uint64_t OnDiskBlockStore2::estimateNumFreeBytes() const { return cpputils::free_disk_space_in_bytes(_rootDir); } uint64_t OnDiskBlockStore2::blockSizeFromPhysicalBlockSize(uint64_t blockSize) const { if(blockSize <= formatVersionHeaderSize()) { return 0; } return blockSize - formatVersionHeaderSize(); } void OnDiskBlockStore2::forEachBlock(std::function callback) const { for (auto prefixDir = boost::filesystem::directory_iterator(_rootDir); prefixDir != boost::filesystem::directory_iterator(); ++prefixDir) { if (!boost::filesystem::is_directory(prefixDir->path())) { continue; } std::string blockIdPrefix = prefixDir->path().filename().string(); if (blockIdPrefix.size() != PREFIX_LENGTH || std::string::npos != blockIdPrefix.find_first_not_of(ALLOWED_BLOCKID_CHARACTERS)) { // directory has wrong length or an invalid character continue; } for (auto block = boost::filesystem::directory_iterator(prefixDir->path()); block != boost::filesystem::directory_iterator(); ++block) { std::string blockIdPostfix = block->path().filename().string(); if (blockIdPostfix.size() != POSTFIX_LENGTH || std::string::npos != blockIdPostfix.find_first_not_of(ALLOWED_BLOCKID_CHARACTERS)) { // filename has wrong length or an invalid character continue; } callback(BlockId::FromString(blockIdPrefix + blockIdPostfix)); } } } } } src/blockstore/implementations/ondisk/OnDiskBlockStore2.h000066400000000000000000000030671347701267100241430ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ONDISK_ONDISKBLOCKSTORE2_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ONDISK_ONDISKBLOCKSTORE2_H_ #include "../../interface/BlockStore2.h" #include #include #include #include namespace blockstore { namespace ondisk { class OnDiskBlockStore2 final: public BlockStore2 { public: explicit OnDiskBlockStore2(const boost::filesystem::path& path); bool tryCreate(const BlockId &blockId, const cpputils::Data &data) override; bool remove(const BlockId &blockId) override; boost::optional load(const BlockId &blockId) const override; void store(const BlockId &blockId, const cpputils::Data &data) override; uint64_t numBlocks() const override; uint64_t estimateNumFreeBytes() const override; uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const override; void forEachBlock(std::function callback) const override; private: boost::filesystem::path _rootDir; static const std::string FORMAT_VERSION_HEADER_PREFIX; static const std::string FORMAT_VERSION_HEADER; boost::filesystem::path _getFilepath(const BlockId &blockId) const; static cpputils::Data _checkAndRemoveHeader(const cpputils::Data &data); static bool _isAcceptedCryfsHeader(const cpputils::Data &data); static bool _isOtherCryfsHeader(const cpputils::Data &data); static unsigned int formatVersionHeaderSize(); DISALLOW_COPY_AND_ASSIGN(OnDiskBlockStore2); }; } } #endif src/blockstore/implementations/parallelaccess/000077500000000000000000000000001347701267100222125ustar00rootroot00000000000000src/blockstore/implementations/parallelaccess/BlockRef.cpp000066400000000000000000000000261347701267100244030ustar00rootroot00000000000000#include "BlockRef.h" src/blockstore/implementations/parallelaccess/BlockRef.h000066400000000000000000000022471347701267100240570ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_PARALLELACCESS_BLOCKREF_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_PARALLELACCESS_BLOCKREF_H_ #include #include "../../interface/Block.h" #include #include namespace blockstore { namespace parallelaccess { class ParallelAccessBlockStore; class BlockRef final: public Block, public parallelaccessstore::ParallelAccessStore::ResourceRefBase { public: //TODO Unneccessarily storing BlockId twice here (in parent class and in _baseBlock). explicit BlockRef(Block *baseBlock): Block(baseBlock->blockId()), _baseBlock(baseBlock) {} const void *data() const override { return _baseBlock->data(); } void write(const void *source, uint64_t offset, uint64_t size) override { return _baseBlock->write(source, offset, size); } void flush() override { return _baseBlock->flush(); } size_t size() const override { return _baseBlock->size(); } void resize(size_t newSize) override { return _baseBlock->resize(newSize); } private: Block *_baseBlock; DISALLOW_COPY_AND_ASSIGN(BlockRef); }; } } #endif src/blockstore/implementations/parallelaccess/ParallelAccessBlockStore.cpp000066400000000000000000000056221347701267100275710ustar00rootroot00000000000000#include "BlockRef.h" #include "ParallelAccessBlockStore.h" #include "ParallelAccessBlockStoreAdapter.h" #include #include #include using std::string; using cpputils::dynamic_pointer_move; using cpputils::make_unique_ref; using boost::none; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::Data; using boost::optional; using boost::none; namespace blockstore { namespace parallelaccess { ParallelAccessBlockStore::ParallelAccessBlockStore(unique_ref baseBlockStore) : _baseBlockStore(std::move(baseBlockStore)), _parallelAccessStore(make_unique_ref(_baseBlockStore.get())) { } BlockId ParallelAccessBlockStore::createBlockId() { return _baseBlockStore->createBlockId(); } optional> ParallelAccessBlockStore::tryCreate(const BlockId &blockId, Data data) { if (_parallelAccessStore.isOpened(blockId)) { return none; // block already exists } auto block = _baseBlockStore->tryCreate(blockId, std::move(data)); if (block == none) { //TODO Test this code branch return none; } return unique_ref(_parallelAccessStore.add(blockId, std::move(*block))); } optional> ParallelAccessBlockStore::load(const BlockId &blockId) { auto block = _parallelAccessStore.load(blockId); if (block == none) { return none; } return unique_ref(std::move(*block)); } unique_ref ParallelAccessBlockStore::overwrite(const BlockId &blockId, Data data) { auto onExists = [&data] (BlockRef *block) { if (block->size() != data.size()) { block->resize(data.size()); } block->write(data.data(), 0, data.size()); }; auto onAdd = [this, blockId, &data] { return _baseBlockStore->overwrite(blockId, data.copy()); // TODO Without copy? }; return _parallelAccessStore.loadOrAdd(blockId, onExists, onAdd); // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) } void ParallelAccessBlockStore::remove(unique_ref block) { BlockId blockId = block->blockId(); auto block_ref = dynamic_pointer_move(block); ASSERT(block_ref != none, "Block is not a BlockRef"); return _parallelAccessStore.remove(blockId, std::move(*block_ref)); } void ParallelAccessBlockStore::remove(const BlockId &blockId) { return _parallelAccessStore.remove(blockId); } uint64_t ParallelAccessBlockStore::numBlocks() const { return _baseBlockStore->numBlocks(); } uint64_t ParallelAccessBlockStore::estimateNumFreeBytes() const { return _baseBlockStore->estimateNumFreeBytes(); } uint64_t ParallelAccessBlockStore::blockSizeFromPhysicalBlockSize(uint64_t blockSize) const { return _baseBlockStore->blockSizeFromPhysicalBlockSize(blockSize); } void ParallelAccessBlockStore::forEachBlock(std::function callback) const { return _baseBlockStore->forEachBlock(callback); } } } src/blockstore/implementations/parallelaccess/ParallelAccessBlockStore.h000066400000000000000000000030521347701267100272310ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_PARALLELACCESS_PARALLELACCESSBLOCKSTORE_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_PARALLELACCESS_PARALLELACCESSBLOCKSTORE_H_ #include #include "BlockRef.h" #include "../../interface/BlockStore.h" #include namespace blockstore { namespace parallelaccess { //TODO Check that this blockstore allows parallel destructing of blocks (otherwise we won't encrypt blocks in parallel) class ParallelAccessBlockStore final: public BlockStore { public: explicit ParallelAccessBlockStore(cpputils::unique_ref baseBlockStore); BlockId createBlockId() override; boost::optional> tryCreate(const BlockId &blockId, cpputils::Data data) override; boost::optional> load(const BlockId &blockId) override; cpputils::unique_ref overwrite(const BlockId &blockId, cpputils::Data data) override; void remove(const BlockId &blockId) override; void remove(cpputils::unique_ref node) override; uint64_t numBlocks() const override; uint64_t estimateNumFreeBytes() const override; uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const override; void forEachBlock(std::function callback) const override; private: cpputils::unique_ref _baseBlockStore; parallelaccessstore::ParallelAccessStore _parallelAccessStore; DISALLOW_COPY_AND_ASSIGN(ParallelAccessBlockStore); }; } } #endif src/blockstore/implementations/parallelaccess/ParallelAccessBlockStoreAdapter.cpp000066400000000000000000000000551347701267100310650ustar00rootroot00000000000000#include "ParallelAccessBlockStoreAdapter.h" src/blockstore/implementations/parallelaccess/ParallelAccessBlockStoreAdapter.h000066400000000000000000000021351347701267100305330ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_PARALLELACCESS_PARALLELACCESSBLOCKSTOREADAPTER_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_PARALLELACCESS_PARALLELACCESSBLOCKSTOREADAPTER_H_ #include #include #include "../../interface/BlockStore.h" namespace blockstore { namespace parallelaccess { class ParallelAccessBlockStoreAdapter final: public parallelaccessstore::ParallelAccessBaseStore { public: explicit ParallelAccessBlockStoreAdapter(BlockStore *baseBlockStore) :_baseBlockStore(baseBlockStore) { } boost::optional> loadFromBaseStore(const BlockId &blockId) override { return _baseBlockStore->load(blockId); } void removeFromBaseStore(cpputils::unique_ref block) override { return _baseBlockStore->remove(std::move(block)); } void removeFromBaseStore(const BlockId &blockId) override { return _baseBlockStore->remove(blockId); } private: BlockStore *_baseBlockStore; DISALLOW_COPY_AND_ASSIGN(ParallelAccessBlockStoreAdapter); }; } } #endif src/blockstore/implementations/testfake/000077500000000000000000000000001347701267100210425ustar00rootroot00000000000000src/blockstore/implementations/testfake/FakeBlock.cpp000066400000000000000000000024041347701267100233670ustar00rootroot00000000000000#include "FakeBlock.h" #include "FakeBlockStore.h" #include #include #include using std::shared_ptr; using std::istream; using std::ostream; using std::ifstream; using std::ofstream; using std::ios; using std::string; using cpputils::Data; namespace blockstore { namespace testfake { FakeBlock::FakeBlock(FakeBlockStore *store, const BlockId &blockId, shared_ptr data, bool dirty) : Block(blockId), _store(store), _data(data), _dataChanged(dirty) { } FakeBlock::~FakeBlock() { flush(); } const void *FakeBlock::data() const { return _data->data(); } void FakeBlock::write(const void *source, uint64_t offset, uint64_t size) { ASSERT(offset <= _data->size() && offset + size <= _data->size(), "Write outside of valid area"); //Also check offset < _data->size() because of possible overflow in the addition std::memcpy(_data->dataOffset(offset), source, size); _dataChanged = true; } size_t FakeBlock::size() const { return _data->size(); } void FakeBlock::resize(size_t newSize) { *_data = cpputils::DataUtils::resize(*_data, newSize); _dataChanged = true; } void FakeBlock::flush() { if(_dataChanged) { _store->updateData(blockId(), *_data); _dataChanged = false; } } } } src/blockstore/implementations/testfake/FakeBlock.h000066400000000000000000000015221347701267100230340ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_TESTFAKE_FAKEBLOCK_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_TESTFAKE_FAKEBLOCK_H_ #include "../../interface/Block.h" #include #include namespace blockstore { namespace testfake { class FakeBlockStore; class FakeBlock final: public Block { public: FakeBlock(FakeBlockStore *store, const BlockId &blockId, std::shared_ptr data, bool dirty); ~FakeBlock(); const void *data() const override; void write(const void *source, uint64_t offset, uint64_t size) override; void flush() override; size_t size() const override; void resize(size_t newSize) override; private: FakeBlockStore *_store; std::shared_ptr _data; bool _dataChanged; DISALLOW_COPY_AND_ASSIGN(FakeBlock); }; } } #endif src/blockstore/implementations/testfake/FakeBlockStore.cpp000066400000000000000000000061341347701267100244100ustar00rootroot00000000000000#include "FakeBlock.h" #include "FakeBlockStore.h" #include #include using std::make_shared; using std::string; using std::mutex; using cpputils::Data; using cpputils::unique_ref; using cpputils::make_unique_ref; using boost::optional; using boost::none; namespace blockstore { namespace testfake { FakeBlockStore::FakeBlockStore() : _blocks(), _used_dataregions_for_blocks(), _mutex() {} BlockId FakeBlockStore::createBlockId() { return BlockId::Random(); } optional> FakeBlockStore::tryCreate(const BlockId &blockId, Data data) { std::unique_lock lock(_mutex); auto insert_result = _blocks.emplace(blockId, std::move(data)); if (!insert_result.second) { return none; } //Return a copy of the stored data return _load(blockId); } unique_ref FakeBlockStore::overwrite(const BlockId &blockId, Data data) { std::unique_lock lock(_mutex); auto insert_result = _blocks.emplace(blockId, data.copy()); if (!insert_result.second) { // If block already exists, overwrite it. insert_result.first->second = std::move(data); } //Return a pointer to the stored FakeBlock auto loaded = _load(blockId); ASSERT(loaded != none, "Block was just created or written. Should exist."); return std::move(*loaded); } optional> FakeBlockStore::load(const BlockId &blockId) { std::unique_lock lock(_mutex); return _load(blockId); } optional> FakeBlockStore::_load(const BlockId &blockId) { //Return a copy of the stored data try { return makeFakeBlockFromData(blockId, _blocks.at(blockId), false); } catch (const std::out_of_range &e) { return none; } } void FakeBlockStore::remove(const BlockId &blockId) { std::unique_lock lock(_mutex); int numRemoved = _blocks.erase(blockId); ASSERT(numRemoved == 1, "Block not found"); } unique_ref FakeBlockStore::makeFakeBlockFromData(const BlockId &blockId, const Data &data, bool dirty) { auto newdata = make_shared(data.copy()); _used_dataregions_for_blocks.push_back(newdata); return make_unique_ref(this, blockId, newdata, dirty); } void FakeBlockStore::updateData(const BlockId &blockId, const Data &data) { std::unique_lock lock(_mutex); auto found = _blocks.find(blockId); if (found == _blocks.end()) { auto insertResult = _blocks.emplace(blockId, data.copy()); ASSERT(true == insertResult.second, "Inserting didn't work"); found = insertResult.first; } Data &stored_data = found->second; stored_data = data.copy(); } uint64_t FakeBlockStore::numBlocks() const { std::unique_lock lock(_mutex); return _blocks.size(); } uint64_t FakeBlockStore::estimateNumFreeBytes() const { return cpputils::system::get_total_memory(); } uint64_t FakeBlockStore::blockSizeFromPhysicalBlockSize(uint64_t blockSize) const { return blockSize; } void FakeBlockStore::forEachBlock(std::function callback) const { for (const auto &entry : _blocks) { callback(entry.first); } } } } src/blockstore/implementations/testfake/FakeBlockStore.h000066400000000000000000000057761347701267100240700ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_TESTFAKE_FAKEBLOCKSTORE_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_TESTFAKE_FAKEBLOCKSTORE_H_ #include "../../interface/BlockStore.h" #include #include #include #include namespace blockstore { namespace testfake { class FakeBlock; /** * This blockstore is meant to be used for unit tests when the module under test needs a blockstore to work with. * It basically is the same as the InMemoryBlockStore, but much less forgiving for programming mistakes. * * InMemoryBlockStore for example simply ignores flushing and gives you access to the same data region each time * you request a block. This is very performant, but also forgiving to mistakes. Say you write over the boundaries * of a block, then you wouldn't notice, since the next time you access the block, the overflow data is (probably) * still there. Or say an application is relying on flushing the block store in the right moment. Since flushing * is a no-op in InMemoryBlockStore, you wouldn't notice either. * * So this FakeBlockStore has a background copy of each block. When you request a block, you will get a copy of * the data (instead of a direct pointer as InMemoryBlockStore does) and flushing will copy the data back to the * background. This way, tests are more likely to fail if they use the blockstore wrongly. */ class FakeBlockStore final: public BlockStore { public: FakeBlockStore(); BlockId createBlockId() override; boost::optional> tryCreate(const BlockId &blockId, cpputils::Data data) override; cpputils::unique_ref overwrite(const blockstore::BlockId &blockId, cpputils::Data data) override; boost::optional> load(const BlockId &blockId) override; void remove(const BlockId &blockId) override; uint64_t numBlocks() const override; uint64_t estimateNumFreeBytes() const override; uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const override; void forEachBlock(std::function callback) const override; void updateData(const BlockId &blockId, const cpputils::Data &data); private: std::unordered_map _blocks; //This vector keeps a handle of the data regions for all created FakeBlock objects. //This way, it is ensured that no two created FakeBlock objects will work on the //same data region. Without this, it could happen that a test case creates a FakeBlock, //destructs it, creates another one, and the new one gets the same memory region. //We want to avoid this for the reasons mentioned above (overflow data). std::vector> _used_dataregions_for_blocks; mutable std::mutex _mutex; cpputils::unique_ref makeFakeBlockFromData(const BlockId &blockId, const cpputils::Data &data, bool dirty); boost::optional> _load(const BlockId &blockId); DISALLOW_COPY_AND_ASSIGN(FakeBlockStore); }; } } #endif src/blockstore/interface/000077500000000000000000000000001347701267100157645ustar00rootroot00000000000000src/blockstore/interface/Block.h000066400000000000000000000017221347701267100171710ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_INTERFACE_BLOCK_H_ #define MESSMER_BLOCKSTORE_INTERFACE_BLOCK_H_ #include "blockstore/utils/BlockId.h" #include namespace blockstore { //TODO Make Block non-virtual class that stores ptr to its blockstore and writes itself back to the blockstore who is offering a corresponding function. // Then ondisk blockstore can be actually create the file on disk in blockstore::create() and cachingblockstore will delay that call to its base block store. class Block { public: virtual ~Block() {} virtual const void *data() const = 0; virtual void write(const void *source, uint64_t offset, uint64_t size) = 0; virtual void flush() = 0; virtual size_t size() const = 0; //TODO Test resize() virtual void resize(size_t newSize) = 0; const BlockId &blockId() const { return _blockId; } protected: Block(const BlockId &blockId) : _blockId(blockId) {} private: const BlockId _blockId; }; } #endif src/blockstore/interface/BlockStore.h000066400000000000000000000036431347701267100202120ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_INTERFACE_BLOCKSTORE_H_ #define MESSMER_BLOCKSTORE_INTERFACE_BLOCKSTORE_H_ #include "Block.h" #include #include #include #include namespace blockstore { class BlockStore { public: virtual ~BlockStore() {} virtual BlockId createBlockId() = 0; //Returns boost::none if id already exists // TODO Can we make data passed in by ref? virtual boost::optional> tryCreate(const BlockId &blockId, cpputils::Data data) = 0; //TODO Use boost::optional (if id doesn't exist) // Return nullptr if block with this id doesn't exists virtual boost::optional> load(const BlockId &blockId) = 0; virtual cpputils::unique_ref overwrite(const blockstore::BlockId &blockId, cpputils::Data data) = 0; virtual void remove(const BlockId &blockId) = 0; virtual uint64_t numBlocks() const = 0; //TODO Test estimateNumFreeBytes in all block stores virtual uint64_t estimateNumFreeBytes() const = 0; // Returns, how much space a block has if we allow it to take the given physical block size (i.e. after removing headers, checksums, whatever else). // This can be used to create blocks with a certain physical block size. virtual uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const = 0; // TODO Test virtual void forEachBlock(std::function callback) const = 0; virtual void remove(cpputils::unique_ref block) { BlockId blockId = block->blockId(); cpputils::destruct(std::move(block)); remove(blockId); } cpputils::unique_ref create(const cpputils::Data &data) { while(true) { //TODO Copy (data.copy()) necessary? auto block = tryCreate(createBlockId(), data.copy()); if (block != boost::none) { return std::move(*block); } } } }; } #endif src/blockstore/interface/BlockStore2.h000066400000000000000000000026441347701267100202740ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_INTERFACE_BLOCKSTORE2_H_ #define MESSMER_BLOCKSTORE_INTERFACE_BLOCKSTORE2_H_ #include "Block.h" #include #include #include #include #include namespace blockstore { class BlockStore2 { public: virtual ~BlockStore2() {} virtual BlockId createBlockId() const { return BlockId::Random(); } WARN_UNUSED_RESULT virtual bool tryCreate(const BlockId &blockId, const cpputils::Data &data) = 0; WARN_UNUSED_RESULT virtual bool remove(const BlockId &blockId) = 0; WARN_UNUSED_RESULT virtual boost::optional load(const BlockId &blockId) const = 0; // Store the block with the given blockId. If it doesn't exist, it is created. virtual void store(const BlockId &blockId, const cpputils::Data &data) = 0; BlockId create(const cpputils::Data& data) { BlockId blockId = createBlockId(); bool success = tryCreate(blockId, data); if (success) { return blockId; } else { return create(data); } } virtual uint64_t numBlocks() const = 0; //TODO Test estimateNumFreeBytes virtual uint64_t estimateNumFreeBytes() const = 0; virtual uint64_t blockSizeFromPhysicalBlockSize(uint64_t blockSize) const = 0; // TODO Test virtual void forEachBlock(std::function callback) const = 0; }; } #endif src/blockstore/utils/000077500000000000000000000000001347701267100151645ustar00rootroot00000000000000src/blockstore/utils/BlockId.cpp000066400000000000000000000000251347701267100171740ustar00rootroot00000000000000#include "BlockId.h" src/blockstore/utils/BlockId.h000066400000000000000000000005061347701267100166450ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_UTILS_BLOCKID_H_ #define MESSMER_BLOCKSTORE_UTILS_BLOCKID_H_ #include "IdWrapper.h" namespace blockstore { struct _BlockIdTag final {}; // TODO Split from a BlobId (i.e. IdWrapper) using BlockId = IdWrapper<_BlockIdTag>; } DEFINE_IDWRAPPER(blockstore::BlockId); #endif src/blockstore/utils/BlockStoreUtils.cpp000066400000000000000000000014461347701267100207650ustar00rootroot00000000000000#include "../interface/BlockStore.h" #include "BlockStoreUtils.h" #include #include #include using cpputils::Data; using cpputils::unique_ref; namespace blockstore { namespace utils { unique_ref copyToNewBlock(BlockStore *blockStore, const Block &block) { Data data(block.size()); std::memcpy(data.data(), block.data(), block.size()); return blockStore->create(data); } void copyTo(Block *target, const Block &source) { ASSERT(target->size() == source.size(), "Can't copy block data when blocks have different sizes"); target->write(source.data(), 0, source.size()); } void fillWithZeroes(Block *target) { Data zeroes(target->size()); zeroes.FillWithZeroes(); target->write(zeroes.data(), 0, target->size()); } } } src/blockstore/utils/BlockStoreUtils.h000066400000000000000000000006441347701267100204310ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_UTILS_BLOCKSTOREUTILS_H_ #define MESSMER_BLOCKSTORE_UTILS_BLOCKSTOREUTILS_H_ #include namespace blockstore { class BlockStore; class Block; namespace utils { cpputils::unique_ref copyToNewBlock(BlockStore *blockStore, const Block &block); void copyTo(Block *target, const Block &source); void fillWithZeroes(Block *target); } } #endif src/blockstore/utils/FileDoesntExistException.cpp000066400000000000000000000005471347701267100226260ustar00rootroot00000000000000#include "FileDoesntExistException.h" namespace bf = boost::filesystem; using std::runtime_error; using std::string; namespace blockstore { FileDoesntExistException::FileDoesntExistException(const bf::path &filepath) : runtime_error(string("The file ")+filepath.string()+" doesn't exist") { } FileDoesntExistException::~FileDoesntExistException() { } } src/blockstore/utils/FileDoesntExistException.h000066400000000000000000000006411347701267100222660ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_UTILS_FILEDOESNTEXISTEXCEPTION_H_ #define MESSMER_BLOCKSTORE_UTILS_FILEDOESNTEXISTEXCEPTION_H_ #include #include namespace blockstore { class FileDoesntExistException final: public std::runtime_error { public: explicit FileDoesntExistException(const boost::filesystem::path &filepath); ~FileDoesntExistException(); }; } #endif src/blockstore/utils/IdWrapper.cpp000066400000000000000000000000271347701267100175640ustar00rootroot00000000000000#include "IdWrapper.h" src/blockstore/utils/IdWrapper.h000066400000000000000000000106361347701267100172400ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_UTILS_IDWRAPPER_H_ #define MESSMER_BLOCKSTORE_UTILS_IDWRAPPER_H_ #include #include #include #include namespace blockstore { // Tag is used to distinguish different concrete IdWrappers template class IdWrapper final { private: using IdData = cpputils::FixedSizeData<16>; public: static constexpr size_t BINARY_LENGTH = IdData::BINARY_LENGTH; static constexpr size_t STRING_LENGTH = IdData::STRING_LENGTH; explicit IdWrapper(const IdData& id); const IdData& data() const; static IdWrapper Random(); static IdWrapper Null(); static IdWrapper FromString(const std::string &data); std::string ToString() const; static IdWrapper FromBinary(const void *source); void ToBinary(void *target) const; private: IdData id_; friend struct std::hash; friend struct std::less; template friend bool operator==(const IdWrapper& lhs, const IdWrapper& rhs); template friend bool operator!=(const IdWrapper& lhs, const IdWrapper& rhs); }; template constexpr size_t IdWrapper::BINARY_LENGTH; template constexpr size_t IdWrapper::STRING_LENGTH; template inline IdWrapper::IdWrapper(const IdData& id): id_(id) {} template inline IdWrapper IdWrapper::Random() { return IdWrapper(cpputils::Random::PseudoRandom().getFixedSize()); } template inline IdWrapper IdWrapper::Null() { return IdWrapper(IdData::Null()); } template inline IdWrapper IdWrapper::FromString(const std::string &data) { return IdWrapper(IdData::FromString(data)); } template inline std::string IdWrapper::ToString() const { return id_.ToString(); } template inline IdWrapper IdWrapper::FromBinary(const void *source) { return IdWrapper(IdData::FromBinary(source)); } template inline void IdWrapper::ToBinary(void *target) const { id_.ToBinary(target); } template inline const typename IdWrapper::IdData& IdWrapper::data() const { return id_; } template inline bool operator==(const IdWrapper& lhs, const IdWrapper& rhs) { return lhs.id_ == rhs.id_; } template inline bool operator!=(const IdWrapper& lhs, const IdWrapper& rhs) { return !operator==(lhs, rhs); } } #define DEFINE_IDWRAPPER(IdWrapper) \ namespace std { \ /*Allow using IdWrapper in std::unordered_map / std::unordered_set */ \ template <> struct hash { \ size_t operator()(const IdWrapper &idWrapper) const { \ /*Ids are random, so it is enough to use the first few bytes as a hash */ \ return cpputils::deserialize(idWrapper.id_.data()); \ } \ }; \ /*Allow using IdWrapper in std::map / std::set */ \ template <> struct less { \ bool operator()(const IdWrapper &lhs, const IdWrapper &rhs) const { \ return 0 > std::memcmp(lhs.id_.data(), rhs.id_.data(), IdWrapper::BINARY_LENGTH); \ } \ }; \ } \ #endif src/cpp-utils/000077500000000000000000000000001347701267100135755ustar00rootroot00000000000000src/cpp-utils/CMakeLists.txt000066400000000000000000000061761347701267100163470ustar00rootroot00000000000000project (cpp-utils) set(SOURCES crypto/symmetric/ciphers.cpp crypto/symmetric/testutils/FakeAuthenticatedCipher.cpp crypto/kdf/Scrypt.cpp crypto/kdf/SCryptParameters.cpp crypto/kdf/PasswordBasedKDF.cpp crypto/RandomPadding.cpp crypto/symmetric/EncryptionKey.cpp crypto/hash/Hash.cpp process/daemonize.cpp process/subprocess.cpp process/SignalCatcher.cpp process/SignalHandler.cpp tempfile/TempFile.cpp tempfile/TempDir.cpp network/HttpClient.cpp network/CurlHttpClient.cpp network/WinHttpClient.cpp network/FakeHttpClient.cpp io/Console.cpp io/DontEchoStdinToStdoutRAII.cpp io/IOStreamConsole.cpp io/NoninteractiveConsole.cpp io/pipestream.cpp io/ProgressBar.cpp thread/LoopThread.cpp thread/ThreadSystem.cpp thread/debugging_nonwindows.cpp thread/debugging_windows.cpp thread/LeftRight.cpp random/Random.cpp random/RandomGeneratorThread.cpp random/OSRandomGenerator.cpp random/PseudoRandomPool.cpp random/RandomDataBuffer.cpp random/RandomGenerator.cpp lock/LockPool.cpp data/SerializationHelper.cpp data/Serializer.cpp data/Deserializer.cpp data/DataFixture.cpp data/DataUtils.cpp data/Data.cpp assert/assert.h assert/backtrace_nonwindows.cpp assert/backtrace_windows.cpp assert/AssertFailed.cpp system/get_total_memory.cpp system/homedir.cpp system/memory_nonwindows.cpp system/memory_windows.cpp system/time.cpp system/diskspace.cpp system/filetime_nonwindows.cpp system/filetime_windows.cpp system/env.cpp value_type/ValueType.cpp ) add_library(${PROJECT_NAME} STATIC ${SOURCES}) if(MSVC) target_link_libraries(${PROJECT_NAME} PUBLIC DbgHelp) 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) find_package(CURL REQUIRED) target_include_directories(${PROJECT_NAME} PUBLIC ${CURL_INCLUDE_DIRS}) target_link_libraries(${PROJECT_NAME} PUBLIC ${CURL_LIBRARIES}) else() target_link_libraries(${PROJECT_NAME} PUBLIC WinHttp) endif() find_package(Threads REQUIRED) target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) 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 chrono) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) src/cpp-utils/assert/000077500000000000000000000000001347701267100150765ustar00rootroot00000000000000src/cpp-utils/assert/AssertFailed.cpp000066400000000000000000000000321347701267100201430ustar00rootroot00000000000000#include "AssertFailed.h" src/cpp-utils/assert/AssertFailed.h000066400000000000000000000007621347701267100176220ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_ASSERT_ASSERTFAILED_H #define MESSMER_CPPUTILS_ASSERT_ASSERTFAILED_H #include #include #include "../macros.h" namespace cpputils { class AssertFailed final: public std::exception { public: AssertFailed(std::string message) : _message(std::move(message)) { } const char *what() const throw() override { return _message.c_str(); } private: std::string _message; }; } #endif src/cpp-utils/assert/assert.cpp000066400000000000000000000000241347701267100170770ustar00rootroot00000000000000#include "assert.h" src/cpp-utils/assert/assert.h000066400000000000000000000032051347701267100165500ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_ASSERT_ASSERT_H #define MESSMER_CPPUTILS_ASSERT_ASSERT_H /** * This implements an ASSERT(expr, msg) macro. * In a debug build, it will crash and halt the program on an assert failure. * In a release build, it will throw an AssertFailed exception instead, which can then be caught. */ #include "AssertFailed.h" #include #include "backtrace.h" #include "../logging/logging.h" namespace cpputils { namespace _assert { inline std::string format(const char *expr, const std::string &message, const char *file, int line) { std::string result = std::string()+"Assertion ["+expr+"] failed in "+file+":"+std::to_string(line)+": "+message+"\n\n" + backtrace(); return result; } inline void assert_fail_release [[noreturn]] (const char *expr, const std::string &message, const char *file, int line) { auto msg = format(expr, message, file, line); using namespace logging; LOG(ERR, msg); throw AssertFailed(msg); } inline void assert_fail_debug [[noreturn]] (const char *expr, const std::string &message, const char *file, int line) { using namespace logging; LOG(ERR, format(expr, message, file, line)); abort(); } } } #ifdef NDEBUG //TODO Check whether disabling assertions in prod affects speed. # define ASSERT(expr, msg) (void)((expr) || (cpputils::_assert::assert_fail_release(#expr, msg, __FILE__, __LINE__),0)) #else # define ASSERT(expr, msg) (void)((expr) || (cpputils::_assert::assert_fail_debug(#expr, msg, __FILE__, __LINE__),0)) #endif #endif src/cpp-utils/assert/backtrace.h000066400000000000000000000005631347701267100171720ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_ASSERT_BACKTRACE_H #define MESSMER_CPPUTILS_ASSERT_BACKTRACE_H #include namespace cpputils { std::string backtrace(); //TODO Refactor (for example: RAII or at least try{}finally{} instead of free()) //TODO Use the following? https://github.com/bombela/backward-cpp void showBacktraceOnCrash(); } #endif src/cpp-utils/assert/backtrace_nonwindows.cpp000066400000000000000000000020741347701267100220110ustar00rootroot00000000000000#if !defined(_MSC_VER) #include #include #include "../logging/logging.h" #include #include using std::string; using std::ostringstream; using namespace cpputils::logging; namespace cpputils { string backtrace() { std::ostringstream str; str << boost::stacktrace::stacktrace(); return str.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 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); } } #endif src/cpp-utils/assert/backtrace_windows.cpp000066400000000000000000000140521347701267100212750ustar00rootroot00000000000000#if defined(_MSC_VER) #include "backtrace.h" #include #include #include "../logging/logging.h" #include using std::string; using std::ostringstream; using namespace cpputils::logging; namespace cpputils { namespace { std::string exception_code_string(DWORD exception_code) { #define HANDLE_CODE(code) case code: return #code; switch (exception_code) { // List of exception codes taken from https://docs.microsoft.com/en-us/windows/desktop/Debug/getexceptioncode HANDLE_CODE(EXCEPTION_ACCESS_VIOLATION) HANDLE_CODE(EXCEPTION_ARRAY_BOUNDS_EXCEEDED) HANDLE_CODE(EXCEPTION_BREAKPOINT) HANDLE_CODE(EXCEPTION_DATATYPE_MISALIGNMENT) HANDLE_CODE(EXCEPTION_FLT_DENORMAL_OPERAND) HANDLE_CODE(EXCEPTION_FLT_DIVIDE_BY_ZERO) HANDLE_CODE(EXCEPTION_FLT_INEXACT_RESULT) HANDLE_CODE(EXCEPTION_FLT_INVALID_OPERATION) HANDLE_CODE(EXCEPTION_FLT_OVERFLOW) HANDLE_CODE(EXCEPTION_FLT_STACK_CHECK) HANDLE_CODE(EXCEPTION_FLT_UNDERFLOW) HANDLE_CODE(EXCEPTION_GUARD_PAGE) HANDLE_CODE(EXCEPTION_ILLEGAL_INSTRUCTION) HANDLE_CODE(EXCEPTION_IN_PAGE_ERROR) HANDLE_CODE(EXCEPTION_INT_DIVIDE_BY_ZERO) HANDLE_CODE(EXCEPTION_INT_OVERFLOW) HANDLE_CODE(EXCEPTION_INVALID_DISPOSITION) HANDLE_CODE(EXCEPTION_INVALID_HANDLE) HANDLE_CODE(EXCEPTION_NONCONTINUABLE_EXCEPTION) HANDLE_CODE(EXCEPTION_PRIV_INSTRUCTION) HANDLE_CODE(EXCEPTION_SINGLE_STEP) HANDLE_CODE(EXCEPTION_STACK_OVERFLOW) HANDLE_CODE(STATUS_UNWIND_CONSOLIDATE) default: std::ostringstream str; str << "UNKNOWN_CODE(0x" << std::hex << exception_code << ")"; return str.str(); } #undef HANDLE_CODE } struct SymInitializeRAII final { const HANDLE process; const bool success; SymInitializeRAII() : process(GetCurrentProcess()) , success(::SymInitialize(process, NULL, TRUE)) { } ~SymInitializeRAII() { ::SymCleanup(process); } }; std::string backtrace_to_string(CONTEXT* context_record) { std::ostringstream backtrace; SymInitializeRAII sym; if (!sym.success) { DWORD error = GetLastError(); backtrace << "[Can't get backtrace. SymInitialize failed with error code " << std::dec << error << "]\n"; } else { // Initialize stack walking. STACKFRAME64 stack_frame; memset(&stack_frame, 0, sizeof(stack_frame)); #if defined(_WIN64) int machine_type = IMAGE_FILE_MACHINE_AMD64; stack_frame.AddrPC.Offset = context_record->Rip; stack_frame.AddrFrame.Offset = context_record->Rbp; stack_frame.AddrStack.Offset = context_record->Rsp; #else int machine_type = IMAGE_FILE_MACHINE_I386; stack_frame.AddrPC.Offset = context_record->Eip; stack_frame.AddrFrame.Offset = context_record->Ebp; stack_frame.AddrStack.Offset = context_record->Esp; #endif stack_frame.AddrPC.Mode = AddrModeFlat; stack_frame.AddrFrame.Mode = AddrModeFlat; stack_frame.AddrStack.Mode = AddrModeFlat; auto symbol_storage = std::make_unique(sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)); PSYMBOL_INFO symbol = (PSYMBOL_INFO)symbol_storage.get(); symbol->SizeOfStruct = sizeof(SYMBOL_INFO); symbol->MaxNameLen = MAX_SYM_NAME; int i = 0; while (StackWalk64(machine_type, sym.process, GetCurrentThread(), &stack_frame, context_record, nullptr, &SymFunctionTableAccess64, &SymGetModuleBase64, nullptr)) { backtrace << "#" << (i++) << " "; DWORD64 displacement = 0; if (SymFromAddr(sym.process, (DWORD64)stack_frame.AddrPC.Offset, &displacement, symbol)) { IMAGEHLP_MODULE64 moduleInfo; std::memset(&moduleInfo, 0, sizeof(IMAGEHLP_MODULE64)); moduleInfo.SizeOfStruct = sizeof(moduleInfo); if (::SymGetModuleInfo64(sym.process, symbol->ModBase, &moduleInfo)) { backtrace << moduleInfo.ModuleName << ":"; } backtrace << "0x" << std::hex << (DWORD64)stack_frame.AddrPC.Offset << ": "; backtrace << symbol->Name << " + 0x" << std::hex << static_cast(displacement); } else { DWORD error = GetLastError(); backtrace << std::hex << (DWORD64)stack_frame.AddrPC.Offset << ": [can't get symbol. SymFromAddr failed with error code " << std::dec << error << "]"; } DWORD dwDisplacement; IMAGEHLP_LINE64 line; SymSetOptions(SYMOPT_LOAD_LINES); line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); if (::SymGetLineFromAddr64(sym.process, (DWORD64)stack_frame.AddrPC.Offset, &dwDisplacement, &line)) { backtrace << " at " << line.FileName << ":" << std::dec << line.LineNumber; } else { DWORD error = GetLastError(); backtrace << " at [file/line unavailable, SymGetLineFromAddr64 failed with error code " << std::dec << error << "]"; } backtrace << "\n"; } } return backtrace.str(); } namespace { bool our_top_level_handler_set = false; LPTOP_LEVEL_EXCEPTION_FILTER previous_top_level_handler = nullptr; } LONG WINAPI TopLevelExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { std::string backtrace = backtrace_to_string(pExceptionInfo->ContextRecord); LOG(ERR, "Top level exception. Code: {}. Backtrace:\n{}", exception_code_string(pExceptionInfo->ExceptionRecord->ExceptionCode), backtrace); if (previous_top_level_handler != nullptr) { // There was already a top level exception handler set when we called showBacktraceOnCrash(). Call it. return (*previous_top_level_handler)(pExceptionInfo); } else { // previous_top_level_handler == nullptr means there was no top level exception handler set when we called showBacktraceOnCrash() // so there's nothing else we need to call. return EXCEPTION_CONTINUE_SEARCH; } } } std::string backtrace() { CONTEXT context; memset(&context, 0, sizeof(CONTEXT)); context.ContextFlags = CONTEXT_FULL; RtlCaptureContext(&context); return backtrace_to_string(&context); } void showBacktraceOnCrash() { if (!our_top_level_handler_set) { previous_top_level_handler = SetUnhandledExceptionFilter(TopLevelExceptionHandler); our_top_level_handler_set = true; } } } #endif src/cpp-utils/crypto/000077500000000000000000000000001347701267100151155ustar00rootroot00000000000000src/cpp-utils/crypto/RandomPadding.cpp000066400000000000000000000025331347701267100203330ustar00rootroot00000000000000#include #include "RandomPadding.h" #include "../logging/logging.h" #include "../random/Random.h" using boost::optional; using namespace cpputils::logging; namespace cpputils { Data RandomPadding::add(const Data &data, size_t targetSize) { uint32_t size = data.size(); if (size >= targetSize - sizeof(size)) { throw std::runtime_error("Data too large. We should increase padding target size."); } Data randomData = Random::PseudoRandom().get(targetSize-sizeof(size)-size); ASSERT(sizeof(size) + size + randomData.size() == targetSize, "Calculated size of randomData incorrectly"); Data result(targetSize); serialize(result.data(), size); std::memcpy(result.dataOffset(sizeof(size)), data.data(), size); std::memcpy(result.dataOffset(sizeof(size)+size), randomData.data(), randomData.size()); return result; } optional RandomPadding::remove(const Data &data) { uint32_t size = deserialize(data.data()); if(sizeof(size) + size >= data.size()) { LOG(ERR, "Config file is invalid: Invalid padding."); return boost::none; }; Data result(size); std::memcpy(result.data(), data.dataOffset(sizeof(size)), size); return result; } } src/cpp-utils/crypto/RandomPadding.h000066400000000000000000000006061347701267100177770ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_RANDOMPADDING_H #define MESSMER_CPPUTILS_CRYPTO_RANDOMPADDING_H #include "../data/Data.h" #include namespace cpputils { //TODO Test class RandomPadding final { public: static Data add(const Data &data, size_t targetSize); static boost::optional remove(const Data &data); }; } #endif src/cpp-utils/crypto/cryptopp_byte.h000066400000000000000000000011761347701267100201760ustar00rootroot00000000000000#pragma once #ifndef _CPPUTILS_CRYPTO_CRYPTOPP_BYTE_H #define _CPPUTILS_CRYPTO_CRYPTOPP_BYTE_H #include // If we're running an older CryptoPP version, CryptoPP::byte isn't defined yet. // Define it. Refer to "byte" type in the global namespace (placed by CryptoPP). // Could also use CRYPTOPP_NO_GLOBAL_BYTE - but don't want to track when it was // introduced. This way seems more reliable, as it is compatible with more of // the Crypto++ versions. #if CRYPTOPP_VERSION < 600 namespace CryptoPP { using byte = ::byte; } #endif /* CRYPTOPP_VERSION < 600 */ #endif /* _CPPUTILS_CRYPTO_CRYPTOPP_BYTE_H */ src/cpp-utils/crypto/hash/000077500000000000000000000000001347701267100160405ustar00rootroot00000000000000src/cpp-utils/crypto/hash/Hash.cpp000066400000000000000000000012521347701267100174270ustar00rootroot00000000000000#include "Hash.h" #include #include using cpputils::Random; using CryptoPP::SHA512; namespace cpputils { namespace hash { Hash hash(const Data& data, Salt salt) { SHA512 hasher; // NOLINT (workaround for clang-warning in libcrypto++) hasher.Update(static_cast(salt.data()), Salt::BINARY_LENGTH); hasher.Update(static_cast(data.data()), data.size()); Digest digest = Digest::Null(); hasher.Final(static_cast(digest.data())); return Hash{ digest, salt }; } Salt generateSalt() { return Random::PseudoRandom().getFixedSize<8>(); } } } src/cpp-utils/crypto/hash/Hash.h000066400000000000000000000006501347701267100170750ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_HASH_HASH_H #define MESSMER_CPPUTILS_CRYPTO_HASH_HASH_H #include #include namespace cpputils { namespace hash { using Digest = FixedSizeData<64>; using Salt = FixedSizeData<8>; struct Hash final { Digest digest; Salt salt; }; Salt generateSalt(); Hash hash(const cpputils::Data& data, Salt salt); } } #endif src/cpp-utils/crypto/kdf/000077500000000000000000000000001347701267100156615ustar00rootroot00000000000000src/cpp-utils/crypto/kdf/PasswordBasedKDF.cpp000066400000000000000000000000361347701267100214520ustar00rootroot00000000000000#include "PasswordBasedKDF.h" src/cpp-utils/crypto/kdf/PasswordBasedKDF.h000066400000000000000000000012401347701267100211150ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_KDF_PASSWORDBASEDKDF_H #define MESSMER_CPPUTILS_CRYPTO_KDF_PASSWORDBASEDKDF_H #include "../../crypto/symmetric/EncryptionKey.h" #include "../../data/Data.h" namespace cpputils { class PasswordBasedKDF { public: virtual ~PasswordBasedKDF() = default; struct KeyResult final { cpputils::EncryptionKey key; cpputils::Data kdfParameters; }; virtual EncryptionKey deriveExistingKey(size_t keySize, const std::string& password, const Data& kdfParameters) = 0; virtual KeyResult deriveNewKey(size_t keySize, const std::string& password) = 0; }; } #endif src/cpp-utils/crypto/kdf/SCryptParameters.cpp000066400000000000000000000024571347701267100216450ustar00rootroot00000000000000#include "SCryptParameters.h" using std::istream; using std::ostream; using cpputils::Data; namespace cpputils { Data SCryptParameters::serialize() const { Serializer serializer(_serializedSize()); serializer.writeUint64(_N); serializer.writeUint32(_r); serializer.writeUint32(_p); serializer.writeTailData(_salt); return serializer.finished(); } size_t SCryptParameters::_serializedSize() const { return _salt.size() + sizeof(uint64_t) + sizeof(uint32_t) + sizeof(uint32_t); } SCryptParameters SCryptParameters::deserialize(const cpputils::Data &data) { Deserializer deserializer(&data); uint64_t N = deserializer.readUint64(); uint32_t r = deserializer.readUint32(); uint32_t p = deserializer.readUint32(); Data salt = deserializer.readTailData(); deserializer.finished(); return SCryptParameters(std::move(salt), N, r, p); } #ifndef CRYFS_NO_COMPATIBILITY SCryptParameters SCryptParameters::deserializeOldFormat(Deserializer *source) { uint64_t N = source->readUint64(); uint32_t r = source->readUint32(); uint32_t p = source->readUint32(); Data salt = source->readData(); return SCryptParameters(std::move(salt), N, r, p); } #endif } src/cpp-utils/crypto/kdf/SCryptParameters.h000066400000000000000000000042311347701267100213020ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_KDF_KEYCONFIG_H #define MESSMER_CPPUTILS_CRYPTO_KDF_KEYCONFIG_H #include "../../data/Data.h" #include "../../data/Serializer.h" #include "../../data/Deserializer.h" #include namespace cpputils { //TODO Test Copy/move constructor and assignment //TODO Test operator==/!= //TODO Use SCryptSettings as a member here instead of storing _N, _r, _p. class SCryptParameters final { public: SCryptParameters(Data salt, uint64_t N, uint32_t r, uint32_t p) : _salt(std::move(salt)), _N(N), _r(r), _p(p) { } SCryptParameters(const SCryptParameters &rhs) :_salt(rhs._salt.copy()), _N(rhs._N), _r(rhs._r), _p(rhs._p) { } SCryptParameters(SCryptParameters &&rhs) = default; SCryptParameters &operator=(const SCryptParameters &rhs) { _salt = rhs._salt.copy(); _N = rhs._N; _r = rhs._r; _p = rhs._p; return *this; } SCryptParameters &operator=(SCryptParameters &&rhs) = default; const Data &salt() const { return _salt; } size_t N() const { return _N; } size_t r() const { return _r; } size_t p() const { return _p; } cpputils::Data serialize() const; static SCryptParameters deserialize(const cpputils::Data &data); #ifndef CRYFS_NO_COMPATIBILITY static SCryptParameters deserializeOldFormat(cpputils::Deserializer *deserializer); size_t serializedSize() const { return _serializedSize(); } #endif private: size_t _serializedSize() const; Data _salt; uint64_t _N; uint32_t _r; uint32_t _p; }; inline bool operator==(const SCryptParameters &lhs, const SCryptParameters &rhs) { return lhs.salt() == rhs.salt() && lhs.N() == rhs.N() && lhs.r() == rhs.r() && lhs.p() == rhs.p(); } inline bool operator!=(const SCryptParameters &lhs, const SCryptParameters &rhs) { return !operator==(lhs, rhs); } } #endif src/cpp-utils/crypto/kdf/Scrypt.cpp000066400000000000000000000034521347701267100176550ustar00rootroot00000000000000#include "Scrypt.h" #include using std::string; namespace cpputils { constexpr SCryptSettings SCrypt::ParanoidSettings; constexpr SCryptSettings SCrypt::DefaultSettings; constexpr SCryptSettings SCrypt::TestSettings; namespace { EncryptionKey _derive(size_t keySize, const std::string& password, const SCryptParameters& kdfParameters) { auto result = EncryptionKey::Null(keySize); size_t status = CryptoPP::Scrypt().DeriveKey( static_cast(result.data()), result.binaryLength(), reinterpret_cast(password.c_str()), password.size(), static_cast(kdfParameters.salt().data()), kdfParameters.salt().size(), kdfParameters.N(), kdfParameters.r(), kdfParameters.p() ); if (status != 1) { throw std::runtime_error("Error running scrypt key derivation. Error code: "+std::to_string(status)); } return result; } SCryptParameters _createNewSCryptParameters(const SCryptSettings& settings) { return SCryptParameters(Random::PseudoRandom().get(settings.SALT_LEN), settings.N, settings.r, settings.p); } } SCrypt::SCrypt(const SCryptSettings& settingsForNewKeys) :_settingsForNewKeys(settingsForNewKeys) { } EncryptionKey SCrypt::deriveExistingKey(size_t keySize, const std::string& password, const Data& kdfParameters) { SCryptParameters parameters = SCryptParameters::deserialize(kdfParameters); auto key = _derive(keySize, password, parameters); return key; } SCrypt::KeyResult SCrypt::deriveNewKey(size_t keySize, const std::string& password) { SCryptParameters kdfParameters = _createNewSCryptParameters(_settingsForNewKeys); auto key = _derive(keySize, password, kdfParameters); return SCrypt::KeyResult { key, kdfParameters.serialize() }; } } src/cpp-utils/crypto/kdf/Scrypt.h000066400000000000000000000022131347701267100173140ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_KDF_SCRYPT_H #define MESSMER_CPPUTILS_CRYPTO_KDF_SCRYPT_H #include "../../macros.h" #include "../../random/Random.h" #include "../../pointer/unique_ref.h" #include "PasswordBasedKDF.h" #include #include "SCryptParameters.h" namespace cpputils { struct SCryptSettings { size_t SALT_LEN; uint64_t N; uint32_t r; uint32_t p; }; class SCrypt final : public PasswordBasedKDF { public: static constexpr SCryptSettings ParanoidSettings = SCryptSettings {32, 1048576, 8, 16}; static constexpr SCryptSettings DefaultSettings = SCryptSettings {32, 1048576, 4, 8}; static constexpr SCryptSettings TestSettings = SCryptSettings {32, 1024, 1, 1}; explicit SCrypt(const SCryptSettings& settingsForNewKeys); EncryptionKey deriveExistingKey(size_t keySize, const std::string& password, const Data& kdfParameters) override; KeyResult deriveNewKey(size_t keySize, const std::string& password) override; private: SCryptSettings _settingsForNewKeys; DISALLOW_COPY_AND_ASSIGN(SCrypt); }; } #endif src/cpp-utils/crypto/symmetric/000077500000000000000000000000001347701267100171315ustar00rootroot00000000000000src/cpp-utils/crypto/symmetric/CFB_Cipher.h000066400000000000000000000061471347701267100211760ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CFBCIPHER_H_ #define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CFBCIPHER_H_ #include "cpp-utils/crypto/cryptopp_byte.h" #include "../../data/FixedSizeData.h" #include "../../data/Data.h" #include "../../random/Random.h" #include #include #include "Cipher.h" #include "EncryptionKey.h" namespace cpputils { template class CFB_Cipher { public: using EncryptionKey = cpputils::EncryptionKey; static constexpr unsigned int KEYSIZE = KeySize; static constexpr unsigned int STRING_KEYSIZE = 2 * KEYSIZE; static constexpr unsigned int ciphertextSize(unsigned int plaintextBlockSize) { return plaintextBlockSize + IV_SIZE; } static constexpr unsigned int plaintextSize(unsigned int ciphertextBlockSize) { return ciphertextBlockSize - IV_SIZE; } static Data encrypt(const CryptoPP::byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey); static boost::optional decrypt(const CryptoPP::byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey); private: static constexpr unsigned int IV_SIZE = BlockCipher::BLOCKSIZE; }; template constexpr unsigned int CFB_Cipher::KEYSIZE; template constexpr unsigned int CFB_Cipher::STRING_KEYSIZE; template Data CFB_Cipher::encrypt(const CryptoPP::byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) { ASSERT(encKey.binaryLength() == KeySize, "Wrong key size"); FixedSizeData iv = Random::PseudoRandom().getFixedSize(); auto encryption = typename CryptoPP::CFB_Mode::Encryption(static_cast(encKey.data()), encKey.binaryLength(), iv.data()); Data ciphertext(ciphertextSize(plaintextSize)); iv.ToBinary(ciphertext.data()); if (plaintextSize > 0) { encryption.ProcessData(static_cast(ciphertext.data()) + IV_SIZE, plaintext, plaintextSize); } return ciphertext; } template boost::optional CFB_Cipher::decrypt(const CryptoPP::byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) { ASSERT(encKey.binaryLength() == KeySize, "Wrong key size"); if (ciphertextSize < IV_SIZE) { return boost::none; } const CryptoPP::byte *ciphertextIV = ciphertext; const CryptoPP::byte *ciphertextData = ciphertext + IV_SIZE; auto decryption = typename CryptoPP::CFB_Mode::Decryption(static_cast(encKey.data()), encKey.binaryLength(), ciphertextIV); Data plaintext(plaintextSize(ciphertextSize)); if (plaintext.size() > 0) { // TODO Shouldn't we pass in ciphertextSize instead of plaintext.size() here as last argument (and also in the if above)? decryption.ProcessData(static_cast(plaintext.data()), ciphertextData, plaintext.size()); } return plaintext; } } #endif src/cpp-utils/crypto/symmetric/Cipher.h000066400000000000000000000020311347701267100205100ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CIPHER_H_ #define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CIPHER_H_ #include #include #include "../../data/Data.h" #include "../../random/Random.h" using std::string; namespace cpputils { template struct CipherConcept { public: BOOST_CONCEPT_USAGE(CipherConcept) { same_type(UINT32_C(0), X::ciphertextSize(UINT32_C(5))); same_type(UINT32_C(0), X::plaintextSize(UINT32_C(5))); same_type(UINT32_C(0), X::KEYSIZE); same_type(UINT32_C(0), X::STRING_KEYSIZE); typename X::EncryptionKey key = X::EncryptionKey::CreateKey(Random::OSRandom(), X::KEYSIZE); same_type(Data(0), X::encrypt(static_cast(nullptr), UINT32_C(0), key)); same_type(boost::optional(Data(0)), X::decrypt(static_cast(nullptr), UINT32_C(0), key)); string name = X::NAME; } private: // Type deduction will fail unless the arguments have the same type. template void same_type(T const&, T const&); }; } #endif src/cpp-utils/crypto/symmetric/EncryptionKey.cpp000066400000000000000000000000331347701267100224340ustar00rootroot00000000000000#include "EncryptionKey.h" src/cpp-utils/crypto/symmetric/EncryptionKey.h000066400000000000000000000072241347701267100221120ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_ENCRYPTIONKEY_H_ #define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_ENCRYPTIONKEY_H_ #include #include #include #include "../cryptopp_byte.h" #include namespace cpputils { /** * Use this to store an encryption key and keep it safe in memory. * It will only keep the key in one memory location, even if the EncryptionKey object is copied or moved. * This one memory location will be prevented from swapping to disk. * Note: This is a best-effort, but not a guarantee. System hibernation might still write the encryption key to the disk. * Also, when (de)serializing the config file or calling a crypto library with the encryption key, it isn't guaranteed * that there aren't any copies made to different memory regions. However, these other memory regions should be short-lived * and therefore much less likely to swap. */ class EncryptionKey final { private: explicit EncryptionKey(std::shared_ptr keyData) : _keyData(std::move(keyData)) { } public: EncryptionKey(const EncryptionKey& rhs) = default; EncryptionKey(EncryptionKey&& rhs) = default; EncryptionKey& operator=(const EncryptionKey& rhs) = default; EncryptionKey& operator=(EncryptionKey&& rhs) = default; size_t binaryLength() const { return _keyData->size(); } size_t stringLength() const { return 2 * binaryLength(); } static EncryptionKey Null(size_t keySize) { auto data = std::make_shared( keySize, make_unique_ref() ); data->FillWithZeroes(); return EncryptionKey(std::move(data)); } static EncryptionKey FromString(const std::string& keyData) { auto data = std::make_shared( Data::FromString(keyData, make_unique_ref()) ); EncryptionKey key(std::move(data)); ASSERT(key.stringLength() == keyData.size(), "Wrong input size for EncryptionKey::FromString()"); return key; } std::string ToString() const { auto result = _keyData->ToString(); ASSERT(result.size() == stringLength(), "Wrong string length"); return result; } static EncryptionKey CreateKey(RandomGenerator &randomGenerator, size_t keySize) { EncryptionKey result(std::make_shared( keySize, make_unique_ref() // the allocator makes sure key data is never swapped to disk )); randomGenerator.write(result._keyData->data(), keySize); return result; } const void *data() const { return _keyData->data(); } void *data() { return const_cast(const_cast(this)->data()); } // TODO Test take/drop EncryptionKey take(size_t numTaken) const { ASSERT(numTaken <= _keyData->size(), "Out of bounds"); auto result = std::make_shared(numTaken, make_unique_ref()); std::memcpy(result->data(), _keyData->data(), numTaken); return EncryptionKey(std::move(result)); } EncryptionKey drop(size_t numDropped) const { ASSERT(numDropped <= _keyData->size(), "Out of bounds"); const size_t resultSize = _keyData->size() - numDropped; auto result = std::make_shared(resultSize, make_unique_ref()); std::memcpy(result->data(), _keyData->dataOffset(numDropped), resultSize); return EncryptionKey(std::move(result)); } private: std::shared_ptr _keyData; }; } #endif src/cpp-utils/crypto/symmetric/GCM_Cipher.h000066400000000000000000000072611347701267100212100ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_GCMCIPHER_H_ #define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_GCMCIPHER_H_ #include "cpp-utils/crypto/cryptopp_byte.h" #include "../../data/FixedSizeData.h" #include "../../data/Data.h" #include "../../random/Random.h" #include #include "Cipher.h" #include "EncryptionKey.h" namespace cpputils { template class GCM_Cipher { public: using EncryptionKey = cpputils::EncryptionKey; static constexpr unsigned int KEYSIZE = KeySize; static constexpr unsigned int STRING_KEYSIZE = 2 * KEYSIZE; static constexpr unsigned int ciphertextSize(unsigned int plaintextBlockSize) { return plaintextBlockSize + IV_SIZE + TAG_SIZE; } static constexpr unsigned int plaintextSize(unsigned int ciphertextBlockSize) { return ciphertextBlockSize - IV_SIZE - TAG_SIZE; } static Data encrypt(const CryptoPP::byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey); static boost::optional decrypt(const CryptoPP::byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey); private: static constexpr unsigned int IV_SIZE = BlockCipher::BLOCKSIZE; static constexpr unsigned int TAG_SIZE = 16; }; template constexpr unsigned int GCM_Cipher::KEYSIZE; template constexpr unsigned int GCM_Cipher::STRING_KEYSIZE; template Data GCM_Cipher::encrypt(const CryptoPP::byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) { ASSERT(encKey.binaryLength() == KeySize, "Wrong key size"); FixedSizeData iv = Random::PseudoRandom().getFixedSize(); typename CryptoPP::GCM::Encryption encryption; encryption.SetKeyWithIV(static_cast(encKey.data()), encKey.binaryLength(), iv.data(), IV_SIZE); Data ciphertext(ciphertextSize(plaintextSize)); iv.ToBinary(ciphertext.data()); CryptoPP::ArraySource(plaintext, plaintextSize, true, new CryptoPP::AuthenticatedEncryptionFilter(encryption, new CryptoPP::ArraySink(static_cast(ciphertext.data()) + IV_SIZE, ciphertext.size() - IV_SIZE), false, TAG_SIZE ) ); return ciphertext; } template boost::optional GCM_Cipher::decrypt(const CryptoPP::byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) { ASSERT(encKey.binaryLength() == KeySize, "Wrong key size"); if (ciphertextSize < IV_SIZE + TAG_SIZE) { return boost::none; } const CryptoPP::byte *ciphertextIV = ciphertext; const CryptoPP::byte *ciphertextData = ciphertext + IV_SIZE; typename CryptoPP::GCM::Decryption decryption; decryption.SetKeyWithIV(static_cast(encKey.data()), encKey.binaryLength(), ciphertextIV, IV_SIZE); Data plaintext(plaintextSize(ciphertextSize)); try { CryptoPP::ArraySource(static_cast(ciphertextData), ciphertextSize - IV_SIZE, true, new CryptoPP::AuthenticatedDecryptionFilter(decryption, new CryptoPP::ArraySink(static_cast(plaintext.data()), plaintext.size()), CryptoPP::AuthenticatedDecryptionFilter::DEFAULT_FLAGS, TAG_SIZE ) ); return plaintext; } catch (const CryptoPP::HashVerificationFilter::HashVerificationFailed &e) { return boost::none; } } } #endif src/cpp-utils/crypto/symmetric/ciphers.cpp000066400000000000000000000021351347701267100212730ustar00rootroot00000000000000#include "ciphers.h" #define DEFINE_CIPHER(InstanceName) \ constexpr const char *InstanceName::NAME; \ namespace cpputils { DEFINE_CIPHER(AES256_GCM); DEFINE_CIPHER(AES256_CFB); DEFINE_CIPHER(AES128_GCM); DEFINE_CIPHER(AES128_CFB); DEFINE_CIPHER(Twofish256_GCM); DEFINE_CIPHER(Twofish256_CFB); DEFINE_CIPHER(Twofish128_GCM); DEFINE_CIPHER(Twofish128_CFB); DEFINE_CIPHER(Serpent256_GCM); DEFINE_CIPHER(Serpent256_CFB); DEFINE_CIPHER(Serpent128_GCM); DEFINE_CIPHER(Serpent128_CFB); DEFINE_CIPHER(Cast256_GCM); DEFINE_CIPHER(Cast256_CFB); #if CRYPTOPP_VERSION != 564 DEFINE_CIPHER(Mars448_GCM); DEFINE_CIPHER(Mars448_CFB); #else # warning "You're using Crypto++ 5.6.4. In this version, the MARS-448 cipher is not available. Your CryFS executable will not be able to load file systems using this cipher. Please use Crypto++ 5.6.3 or 5.6.5+ instead." #endif DEFINE_CIPHER(Mars256_GCM); DEFINE_CIPHER(Mars256_CFB); DEFINE_CIPHER(Mars128_GCM); DEFINE_CIPHER(Mars128_CFB); } src/cpp-utils/crypto/symmetric/ciphers.h000066400000000000000000000057621347701267100207510ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CIPHERS_H_ #define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CIPHERS_H_ #include #include #include #include #include #include "GCM_Cipher.h" #include "CFB_Cipher.h" #define DECLARE_CIPHER(InstanceName, StringName, Mode, Base, Keysize) \ class InstanceName final: public Mode { \ public: \ BOOST_CONCEPT_ASSERT((CipherConcept)); \ static constexpr const char *NAME = StringName; \ } \ namespace cpputils { static_assert(32 == CryptoPP::AES::MAX_KEYLENGTH, "If AES offered larger keys, we should offer a variant with it"); DECLARE_CIPHER(AES256_GCM, "aes-256-gcm", GCM_Cipher, CryptoPP::AES, 32); DECLARE_CIPHER(AES256_CFB, "aes-256-cfb", CFB_Cipher, CryptoPP::AES, 32); DECLARE_CIPHER(AES128_GCM, "aes-128-gcm", GCM_Cipher, CryptoPP::AES, 16); DECLARE_CIPHER(AES128_CFB, "aes-128-cfb", CFB_Cipher, CryptoPP::AES, 16); static_assert(32 == CryptoPP::Twofish::MAX_KEYLENGTH, "If Twofish offered larger keys, we should offer a variant with it"); DECLARE_CIPHER(Twofish256_GCM, "twofish-256-gcm", GCM_Cipher, CryptoPP::Twofish, 32); DECLARE_CIPHER(Twofish256_CFB, "twofish-256-cfb", CFB_Cipher, CryptoPP::Twofish, 32); DECLARE_CIPHER(Twofish128_GCM, "twofish-128-gcm", GCM_Cipher, CryptoPP::Twofish, 16); DECLARE_CIPHER(Twofish128_CFB, "twofish-128-cfb", CFB_Cipher, CryptoPP::Twofish, 16); static_assert(32 == CryptoPP::Serpent::MAX_KEYLENGTH, "If Serpent offered larger keys, we should offer a variant with it"); DECLARE_CIPHER(Serpent256_GCM, "serpent-256-gcm", GCM_Cipher, CryptoPP::Serpent, 32); DECLARE_CIPHER(Serpent256_CFB, "serpent-256-cfb", CFB_Cipher, CryptoPP::Serpent, 32); DECLARE_CIPHER(Serpent128_GCM, "serpent-128-gcm", GCM_Cipher, CryptoPP::Serpent, 16); DECLARE_CIPHER(Serpent128_CFB, "serpent-128-cfb", CFB_Cipher, CryptoPP::Serpent, 16); static_assert(32 == CryptoPP::CAST256::MAX_KEYLENGTH, "If Cast offered larger keys, we should offer a variant with it"); DECLARE_CIPHER(Cast256_GCM, "cast-256-gcm", GCM_Cipher, CryptoPP::CAST256, 32); DECLARE_CIPHER(Cast256_CFB, "cast-256-cfb", CFB_Cipher, CryptoPP::CAST256, 32); #if CRYPTOPP_VERSION != 564 static_assert(56 == CryptoPP::MARS::MAX_KEYLENGTH, "If Mars offered larger keys, we should offer a variant with it"); DECLARE_CIPHER(Mars448_GCM, "mars-448-gcm", GCM_Cipher, CryptoPP::MARS, 56); DECLARE_CIPHER(Mars448_CFB, "mars-448-cfb", CFB_Cipher, CryptoPP::MARS, 56); #endif DECLARE_CIPHER(Mars256_GCM, "mars-256-gcm", GCM_Cipher, CryptoPP::MARS, 32); DECLARE_CIPHER(Mars256_CFB, "mars-256-cfb", CFB_Cipher, CryptoPP::MARS, 32); DECLARE_CIPHER(Mars128_GCM, "mars-128-gcm", GCM_Cipher, CryptoPP::MARS, 16); DECLARE_CIPHER(Mars128_CFB, "mars-128-cfb", CFB_Cipher, CryptoPP::MARS, 16); } #endif src/cpp-utils/crypto/symmetric/testutils/000077500000000000000000000000001347701267100211715ustar00rootroot00000000000000src/cpp-utils/crypto/symmetric/testutils/FakeAuthenticatedCipher.cpp000066400000000000000000000003671347701267100264070ustar00rootroot00000000000000#include "FakeAuthenticatedCipher.h" namespace cpputils { constexpr unsigned int FakeAuthenticatedCipher::KEYSIZE; constexpr unsigned int FakeAuthenticatedCipher::STRING_KEYSIZE; std::random_device FakeAuthenticatedCipher::random_; } src/cpp-utils/crypto/symmetric/testutils/FakeAuthenticatedCipher.h000066400000000000000000000107571347701267100260600ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_TEST_CRYPTO_SYMMETRIC_TESTUTILS_FAKEAUTHENTICATEDCIPHER_H_ #define MESSMER_CPPUTILS_TEST_CRYPTO_SYMMETRIC_TESTUTILS_FAKEAUTHENTICATEDCIPHER_H_ #include "cpp-utils/crypto/cryptopp_byte.h" #include "cpp-utils/crypto/symmetric/Cipher.h" #include "cpp-utils/data/FixedSizeData.h" #include "cpp-utils/data/Data.h" #include "cpp-utils/random/RandomGenerator.h" #include #include namespace cpputils { struct FakeKey { static FakeKey FromString(const std::string& keyData) { return FakeKey{static_cast(std::strtol(keyData.c_str(), nullptr, 10))}; } size_t binaryLength() const { return sizeof(uint64_t); } static FakeKey CreateKey(RandomGenerator &randomGenerator, size_t keySize) { ASSERT(keySize == sizeof(uint64_t), "Wrong key size"); auto data = randomGenerator.getFixedSize(); return FakeKey {*reinterpret_cast(data.data())}; } uint64_t value; }; // This is a fake cipher that uses an indeterministic xor chiffre and a 8-byte checksum for a simple authentication mechanism class FakeAuthenticatedCipher { public: BOOST_CONCEPT_ASSERT((CipherConcept)); using EncryptionKey = FakeKey; static constexpr unsigned int KEYSIZE = sizeof(uint64_t); static constexpr unsigned int STRING_KEYSIZE = 2 * KEYSIZE; static EncryptionKey Key1() { return FakeKey{5}; } static EncryptionKey Key2() { return FakeKey{63}; } static constexpr unsigned int ciphertextSize(unsigned int plaintextBlockSize) { return plaintextBlockSize + sizeof(uint64_t) + sizeof(uint64_t); } static constexpr unsigned int plaintextSize(unsigned int ciphertextBlockSize) { return ciphertextBlockSize - sizeof(uint64_t) - sizeof(uint64_t); } static Data encrypt(const CryptoPP::byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) { Data result(ciphertextSize(plaintextSize)); //Add a random IV uint64_t iv = std::uniform_int_distribution()(random_); serialize(result.data(), iv); //Use xor chiffre on plaintext _xor(static_cast(result.dataOffset(sizeof(uint64_t))), plaintext, plaintextSize, encKey.value ^ iv); //Add checksum information uint64_t checksum = _checksum(static_cast(result.data()), encKey, plaintextSize + sizeof(uint64_t)); serialize(result.dataOffset(plaintextSize + sizeof(uint64_t)), checksum); return result; } static boost::optional decrypt(const CryptoPP::byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) { //We need at least 16 bytes (iv + checksum) if (ciphertextSize < 16) { return boost::none; } //Check checksum uint64_t expectedParity = _checksum(ciphertext, encKey, plaintextSize(ciphertextSize) + sizeof(uint64_t)); uint64_t actualParity = deserialize(ciphertext + plaintextSize(ciphertextSize) + sizeof(uint64_t)); if (expectedParity != actualParity) { return boost::none; } //Decrypt xor chiffre from ciphertext uint64_t iv = deserialize(ciphertext); Data result(plaintextSize(ciphertextSize)); _xor(static_cast(result.data()), ciphertext + sizeof(uint64_t), plaintextSize(ciphertextSize), encKey.value ^ iv); return result; } static constexpr const char *NAME = "FakeAuthenticatedCipher"; private: static uint64_t _checksum(const CryptoPP::byte *data, FakeKey encKey, std::size_t size) { uint64_t checksum = 34343435 * encKey.value; // some init value for (size_t i = 0; i < size; ++i) { checksum ^= (static_cast(data[i]) << (56 - 8 * (i%8))); } return checksum; } static void _xor(CryptoPP::byte *dst, const CryptoPP::byte *src, unsigned int size, uint64_t key) { for (unsigned int i = 0; i < size; ++i) { dst[i] = src[i] ^ ((key >> (56 - 8*(i%8))) & 0xFF); } } static std::random_device random_; }; } #endif src/cpp-utils/data/000077500000000000000000000000001347701267100145065ustar00rootroot00000000000000src/cpp-utils/data/Data.cpp000066400000000000000000000034141347701267100160650ustar00rootroot00000000000000#include "Data.h" #include #include #include using std::istream; using std::ofstream; using std::ifstream; using std::ios; using boost::optional; namespace bf = boost::filesystem; namespace cpputils { optional Data::LoadFromFile(const bf::path &filepath) { ifstream file(filepath.string().c_str(), ios::binary); if (!file.good()) { return boost::none; } optional result(LoadFromStream(file)); if (!file.good()) { throw std::runtime_error("Error reading from file"); } return result; } std::streampos Data::_getStreamSize(istream &stream) { auto current_pos = stream.tellg(); //Retrieve length stream.seekg(0, stream.end); auto endpos = stream.tellg(); //Restore old position stream.seekg(current_pos, stream.beg); return endpos - current_pos; } Data Data::LoadFromStream(istream &stream, size_t size) { Data result(size); stream.read(static_cast(result.data()), result.size()); return result; } Data Data::FromString(const std::string &data, unique_ref allocator) { ASSERT(data.size() % 2 == 0, "hex encoded data cannot have odd number of characters"); Data result(data.size() / 2, std::move(allocator)); { CryptoPP::StringSource _1(data, true, new CryptoPP::HexDecoder( new CryptoPP::ArraySink(static_cast(result._data), result.size()) ) ); } return result; } std::string Data::ToString() const { std::string result; { CryptoPP::ArraySource _1(static_cast(_data), _size, true, new CryptoPP::HexEncoder( new CryptoPP::StringSink(result) ) ); } ASSERT(result.size() == 2 * _size, "Created wrongly sized string"); return result; } } src/cpp-utils/data/Data.h000066400000000000000000000121451347701267100155330ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_DATA_DATA_H_ #define MESSMER_CPPUTILS_DATA_DATA_H_ #include #include #include #include "../macros.h" #include #include #include "../assert/assert.h" #include "../pointer/unique_ref.h" namespace cpputils { struct Allocator { virtual ~Allocator() = default; virtual void* allocate(size_t size) = 0; virtual void free(void* ptr, size_t size) = 0; }; class DefaultAllocator final : public Allocator { public: void* allocate(size_t size) override { // std::malloc has implementation defined behavior for size=0. // Let's define the behavior. return std::malloc((size == 0) ? 1 : size); } void free(void* data, size_t /*size*/) override { std::free(data); } }; class Data final { public: explicit Data(size_t size, unique_ref allocator = make_unique_ref()); ~Data(); Data(Data &&rhs) noexcept; Data &operator=(Data &&rhs) noexcept; Data copy() const; //TODO Test copyAndRemovePrefix Data copyAndRemovePrefix(size_t prefixSize) const; void *data(); const void *data() const; //TODO Test dataOffset void *dataOffset(size_t offset); const void *dataOffset(size_t offset) const; size_t size() const; Data &FillWithZeroes() &; Data &&FillWithZeroes() &&; void StoreToFile(const boost::filesystem::path &filepath) const; static boost::optional LoadFromFile(const boost::filesystem::path &filepath); //TODO Test LoadFromStream/StoreToStream static Data LoadFromStream(std::istream &stream); static Data LoadFromStream(std::istream &stream, size_t size); void StoreToStream(std::ostream &stream) const; // TODO Unify ToString/FromString functions from Data/FixedSizeData using free functions static Data FromString(const std::string &data, unique_ref allocator = make_unique_ref()); std::string ToString() const; private: std::unique_ptr _allocator; size_t _size; void *_data; static std::streampos _getStreamSize(std::istream &stream); void _readFromStream(std::istream &stream); void _free(); DISALLOW_COPY_AND_ASSIGN(Data); }; bool operator==(const Data &lhs, const Data &rhs); bool operator!=(const Data &lhs, const Data &rhs); // --------------------------- // Inline function definitions // --------------------------- inline Data::Data(size_t size, unique_ref allocator) : _allocator(std::move(allocator)), _size(size), _data(_allocator->allocate(_size)) { if (nullptr == _data) { throw std::bad_alloc(); } } inline Data::Data(Data &&rhs) noexcept : _allocator(std::move(rhs._allocator)), _size(rhs._size), _data(rhs._data) { // Make rhs invalid, so the memory doesn't get freed in its destructor. rhs._allocator = nullptr; rhs._data = nullptr; rhs._size = 0; } inline Data &Data::operator=(Data &&rhs) noexcept { _free(); _allocator = std::move(rhs._allocator); _data = rhs._data; _size = rhs._size; rhs._allocator = nullptr; rhs._data = nullptr; rhs._size = 0; return *this; } inline Data::~Data() { _free(); } inline void Data::_free() { if (nullptr != _allocator.get()) { _allocator->free(_data, _size); } _allocator = nullptr; _data = nullptr; _size = 0; } inline Data Data::copy() const { Data copy(_size); std::memcpy(copy._data, _data, _size); return copy; } inline Data Data::copyAndRemovePrefix(size_t prefixSize) const { ASSERT(prefixSize <= _size, "Can't remove more than there is"); Data copy(_size - prefixSize); std::memcpy(copy.data(), dataOffset(prefixSize), copy.size()); return copy; } inline void *Data::data() { return const_cast(const_cast(this)->data()); } inline const void *Data::data() const { return _data; } inline void *Data::dataOffset(size_t offset) { return const_cast(const_cast(this)->dataOffset(offset)); } inline const void *Data::dataOffset(size_t offset) const { return static_cast(data()) + offset; } inline size_t Data::size() const { return _size; } inline Data &Data::FillWithZeroes() & { std::memset(_data, 0, _size); return *this; } inline Data &&Data::FillWithZeroes() && { return std::move(FillWithZeroes()); } inline void Data::StoreToFile(const boost::filesystem::path &filepath) const { std::ofstream file(filepath.string().c_str(), std::ios::binary | std::ios::trunc); if (!file.good()) { throw std::runtime_error("Could not open file for writing"); } StoreToStream(file); if (!file.good()) { throw std::runtime_error("Error writing to file"); } } inline void Data::StoreToStream(std::ostream &stream) const { stream.write(static_cast(_data), _size); } inline Data Data::LoadFromStream(std::istream &stream) { return LoadFromStream(stream, _getStreamSize(stream)); } inline bool operator==(const Data &lhs, const Data &rhs) { return lhs.size() == rhs.size() && 0 == memcmp(lhs.data(), rhs.data(), lhs.size()); } inline bool operator!=(const Data &lhs, const Data &rhs) { return !operator==(lhs, rhs); } } #endif src/cpp-utils/data/DataFixture.cpp000066400000000000000000000015171347701267100174360ustar00rootroot00000000000000#include "DataFixture.h" #include "SerializationHelper.h" namespace cpputils { Data DataFixture::generate(size_t size, long long int seed) { Data result(size); long long int val = seed; for(size_t i=0; i(result.dataOffset(i*sizeof(long long int)), val); } uint64_t alreadyWritten = (size/sizeof(long long int))*sizeof(long long int); val *= 6364136223846793005L; val += 1442695040888963407; char *remainingBytes = reinterpret_cast(&val); //Fill remaining bytes for(size_t i=0; i(result.dataOffset(alreadyWritten + i), remainingBytes[i]); } return result; } } src/cpp-utils/data/DataFixture.h000066400000000000000000000011731347701267100171010ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_DATA_DATAFIXTURE_H_ #define MESSMER_CPPUTILS_DATA_DATAFIXTURE_H_ #include "Data.h" #include "FixedSizeData.h" namespace cpputils { class DataFixture final { public: static Data generate(size_t size, long long int seed = 1); //TODO Test template static FixedSizeData generateFixedSize(long long int seed = 1); }; template FixedSizeData DataFixture::generateFixedSize(long long int seed) { Data data = generate(SIZE, seed); auto result = FixedSizeData::Null(); std::memcpy(result.data(), data.data(), SIZE); return result; } } #endif src/cpp-utils/data/DataUtils.cpp000066400000000000000000000006151347701267100171060ustar00rootroot00000000000000#include "DataUtils.h" namespace cpputils { namespace DataUtils { Data resize(const Data& data, size_t newSize) { Data newData(newSize); newData.FillWithZeroes(); // TODO Only fill region after copied old data with zeroes std::memcpy(newData.data(), data.data(), std::min(newData.size(), data.size())); return newData; } } }src/cpp-utils/data/DataUtils.h000066400000000000000000000007171347701267100165560ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_DATA_DATAUTILS_H #define MESSMER_CPPUTILS_DATA_DATAUTILS_H #include "Data.h" namespace cpputils { namespace DataUtils { //TODO Test //Return a new data object with the given size and initialize as much as possible with the given input data. //If the new data object is larger, then the remaining bytes will be zero filled. Data resize(const Data& data, size_t newSize); } } #endif src/cpp-utils/data/Deserializer.cpp000066400000000000000000000000321347701267100176270ustar00rootroot00000000000000#include "Deserializer.h" src/cpp-utils/data/Deserializer.h000066400000000000000000000105241347701267100173030ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_DATA_DESERIALIZER_H #define MESSMER_CPPUTILS_DATA_DESERIALIZER_H #include "Data.h" #include "../macros.h" #include "../assert/assert.h" #include "FixedSizeData.h" #include "SerializationHelper.h" namespace cpputils { class Deserializer final { public: Deserializer(const Data *source); bool readBool(); uint8_t readUint8(); int8_t readInt8(); uint16_t readUint16(); int16_t readInt16(); uint32_t readUint32(); int32_t readInt32(); uint64_t readUint64(); int64_t readInt64(); std::string readString(); Data readData(); template FixedSizeData readFixedSizeData(); Data readTailData(); void finished(); private: template DataType _read(); Data _readData(size_t size); void _readData(void *target, size_t size); size_t _pos; const Data *_source; DISALLOW_COPY_AND_ASSIGN(Deserializer); }; inline Deserializer::Deserializer(const Data *source): _pos(0), _source(source) { } inline bool Deserializer::readBool() { uint8_t read = readUint8(); if (read == 1) { return true; } else if (read == 0) { return false; } else { throw std::runtime_error("Read invalid bool value"); } } inline uint8_t Deserializer::readUint8() { return _read(); } inline int8_t Deserializer::readInt8() { return _read(); } inline uint16_t Deserializer::readUint16() { return _read(); } inline int16_t Deserializer::readInt16() { return _read(); } inline uint32_t Deserializer::readUint32() { return _read(); } inline int32_t Deserializer::readInt32() { return _read(); } inline uint64_t Deserializer::readUint64() { return _read(); } inline int64_t Deserializer::readInt64() { return _read(); } template inline DataType Deserializer::_read() { static_assert(std::is_pod::value, "Can only deserialize PODs"); if (_pos + sizeof(DataType) > _source->size()) { throw std::runtime_error("Deserialization failed - size overflow"); } DataType result = deserialize(_source->dataOffset(_pos)); _pos += sizeof(DataType); return result; } inline Data Deserializer::readData() { uint64_t size = readUint64(); if (_pos + size > _source->size()) { throw std::runtime_error("Deserialization failed - size overflow"); } return _readData(size); } inline Data Deserializer::readTailData() { uint64_t size = _source->size() - _pos; return _readData(size); } inline Data Deserializer::_readData(size_t size) { Data result(size); _readData(result.data(), size); return result; } inline void Deserializer::_readData(void *target, size_t size) { std::memcpy(static_cast(target), static_cast(_source->dataOffset(_pos)), size); _pos += size; } template inline FixedSizeData Deserializer::readFixedSizeData() { FixedSizeData result(FixedSizeData::Null()); _readData(result.data(), SIZE); return result; } inline std::string Deserializer::readString() { //TODO Test whether that works when string ends (a) at beginning (b) in middle (c) at end of data region const void *nullbytepos = std::memchr(_source->dataOffset(_pos), '\0', _source->size()-_pos); if (nullbytepos == nullptr) { throw std::runtime_error("Deserialization failed - missing nullbyte for string termination"); } uint64_t size = static_cast(nullbytepos) - static_cast(_source->dataOffset(_pos)); std::string result(static_cast(_source->dataOffset(_pos)), size); _pos += size + 1; return result; } inline void Deserializer::finished() { if (_pos != _source->size()) { throw std::runtime_error("Deserialization failed - size not fully used."); } } } #endif src/cpp-utils/data/FixedSizeData.h000066400000000000000000000074371347701267100173560ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_DATA_FIXEDSIZEDATA_H_ #define MESSMER_CPPUTILS_DATA_FIXEDSIZEDATA_H_ #include #include #include #include "../assert/assert.h" namespace cpputils { template class FixedSizeData final { public: //Non-virtual destructor because we want objects to be small ~FixedSizeData() = default; static constexpr size_t BINARY_LENGTH = SIZE; static constexpr size_t STRING_LENGTH = 2 * BINARY_LENGTH; // Hex encoding //TODO Test Null() static FixedSizeData Null(); static FixedSizeData FromString(const std::string &data); std::string ToString() const; static FixedSizeData FromBinary(const void *source); void ToBinary(void *target) const; const unsigned char *data() const; unsigned char *data(); template FixedSizeData take() const; template FixedSizeData drop() const; private: FixedSizeData(): _data() {} template friend class FixedSizeData; std::array _data; }; template bool operator==(const FixedSizeData &lhs, const FixedSizeData &rhs); template bool operator!=(const FixedSizeData &lhs, const FixedSizeData &rhs); // ----- Implementation ----- template constexpr size_t FixedSizeData::BINARY_LENGTH; template constexpr size_t FixedSizeData::STRING_LENGTH; template FixedSizeData FixedSizeData::Null() { FixedSizeData result; std::memset(result._data.data(), 0, BINARY_LENGTH); return result; } template FixedSizeData FixedSizeData::FromString(const std::string &data) { ASSERT(data.size() == STRING_LENGTH, "Wrong string size for parsing FixedSizeData"); FixedSizeData result; { CryptoPP::StringSource _1(data, true, new CryptoPP::HexDecoder( new CryptoPP::ArraySink(result._data.data(), BINARY_LENGTH) ) ); } return result; } template std::string FixedSizeData::ToString() const { std::string result; CryptoPP::ArraySource(_data.data(), BINARY_LENGTH, true, new CryptoPP::HexEncoder( new CryptoPP::StringSink(result) ) ); ASSERT(result.size() == STRING_LENGTH, "Created wrongly sized string"); return result; } template const unsigned char *FixedSizeData::data() const { return _data.data(); } template unsigned char *FixedSizeData::data() { return const_cast(const_cast*>(this)->data()); } template void FixedSizeData::ToBinary(void *target) const { std::memcpy(target, _data.data(), BINARY_LENGTH); } template FixedSizeData FixedSizeData::FromBinary(const void *source) { FixedSizeData result; std::memcpy(result._data.data(), source, BINARY_LENGTH); return result; } template template FixedSizeData FixedSizeData::take() const { static_assert(size <= SIZE, "Out of bounds"); FixedSizeData result; std::memcpy(result._data.data(), _data.data(), size); return result; } template template FixedSizeData FixedSizeData::drop() const { static_assert(size <= SIZE, "Out of bounds"); FixedSizeData result; std::memcpy(result._data.data(), _data.data()+size, SIZE-size); return result; } template bool operator==(const FixedSizeData &lhs, const FixedSizeData &rhs) { return 0 == std::memcmp(lhs.data(), rhs.data(), FixedSizeData::BINARY_LENGTH); } template bool operator!=(const FixedSizeData &lhs, const FixedSizeData &rhs) { return !operator==(lhs, rhs); } } #endif src/cpp-utils/data/SerializationHelper.cpp000066400000000000000000000000411347701267100211620ustar00rootroot00000000000000#include "SerializationHelper.h" src/cpp-utils/data/SerializationHelper.h000066400000000000000000000051021347701267100206320ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_DATA_SERIALIZATIONHELPER_H #define MESSMER_CPPUTILS_DATA_SERIALIZATIONHELPER_H #include #include #include namespace cpputils { namespace details { constexpr bool greater_than(size_t lhs, size_t rhs) { return lhs > rhs; } template struct serialize; // Specialize for 1-byte types for faster performance with direct pointer access (no memcpy). template struct serialize::type> final { static_assert(std::is_pod::value, "Can only serialize PODs"); static void call(void *dst, const DataType &obj) { *static_cast(dst) = obj; } }; // Specialize for larger types with memcpy because unaligned data accesses through pointers are undefined behavior. template struct serialize::type> final { static_assert(std::is_pod::value, "Can only serialize PODs"); static void call(void *dst, const DataType &obj) { std::memcpy(dst, &obj, sizeof(DataType)); } }; template struct deserialize; // Specialize for 1-byte types for faster performance with direct pointer access (no memcpy). template struct deserialize::type> final { static_assert(std::is_pod::value, "Can only serialize PODs"); static DataType call(const void *src) { return *static_cast(src); } }; // Specialize for larger types with memcpy because unaligned data accesses through pointers are undefined behavior. template struct deserialize::type> final { static_assert(std::is_pod::value, "Can only deserialize PODs"); static DataType call(const void *src) { typename std::remove_const::type result{}; std::memcpy(&result, src, sizeof(DataType)); return result; } }; } template inline void serialize(void *dst, const DataType& obj) { return details::serialize::call(dst, obj); } template inline DataType deserialize(const void *src) { return details::deserialize::call(src); } template inline DataType deserializeWithOffset(const void* src, size_t offset) { return deserialize(static_cast(src) + offset); } } #endif src/cpp-utils/data/Serializer.cpp000066400000000000000000000000301347701267100173140ustar00rootroot00000000000000#include "Serializer.h" src/cpp-utils/data/Serializer.h000066400000000000000000000107001347701267100167660ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_DATA_SERIALIZER_H #define MESSMER_CPPUTILS_DATA_SERIALIZER_H #include "Data.h" #include "FixedSizeData.h" #include "../macros.h" #include "../assert/assert.h" #include #include "SerializationHelper.h" namespace cpputils { //TODO Test Serializer/Deserializer //TODO Also test system (big endian/little endian) by adding a serialized data file to the repository and (a) reading it and (b) rewriting and comparing it class Serializer final { public: Serializer(size_t size); void writeBool(bool value); void writeUint8(uint8_t value); void writeInt8(int8_t value); void writeUint16(uint16_t value); void writeInt16(int16_t value); void writeUint32(uint32_t value); void writeInt32(int32_t value); void writeUint64(uint64_t value); void writeInt64(int64_t value); void writeString(const std::string &value); void writeData(const Data &value); template void writeFixedSizeData(const FixedSizeData &value); // Write the data as last element when serializing. // It does not store a data size but limits the size by the size of the serialization result void writeTailData(const Data &value); static size_t BoolSize(); static size_t DataSize(const Data &value); static size_t StringSize(const std::string &value); Data finished(); private: template void _write(DataType obj); void _writeData(const void *data, size_t count); size_t _pos; Data _result; DISALLOW_COPY_AND_ASSIGN(Serializer); }; inline Serializer::Serializer(size_t size): _pos(0), _result(size) { } inline size_t Serializer::BoolSize() { return sizeof(uint8_t); } inline void Serializer::writeBool(bool value) { writeUint8(value ? 1 : 0); } inline void Serializer::writeUint8(uint8_t value) { _write(value); } inline void Serializer::writeInt8(int8_t value) { _write(value); } inline void Serializer::writeUint16(uint16_t value) { _write(value); } inline void Serializer::writeInt16(int16_t value) { _write(value); } inline void Serializer::writeUint32(uint32_t value) { _write(value); } inline void Serializer::writeInt32(int32_t value) { _write(value); } inline void Serializer::writeUint64(uint64_t value) { _write(value); } inline void Serializer::writeInt64(int64_t value) { _write(value); } template inline void Serializer::_write(DataType obj) { if (_pos + sizeof(DataType) > _result.size()) { throw std::runtime_error("Serialization failed - size overflow"); } serialize(_result.dataOffset(_pos), obj); _pos += sizeof(DataType); } inline void Serializer::writeData(const Data &data) { writeUint64(data.size()); _writeData(data.data(), data.size()); } inline size_t Serializer::DataSize(const Data &data) { return sizeof(uint64_t) + data.size(); } template inline void Serializer::writeFixedSizeData(const FixedSizeData &data) { _writeData(data.data(), SIZE); } inline void Serializer::writeTailData(const Data &data) { ASSERT(_pos + data.size() == _result.size(), "Not enough data given to write until the end of the stream"); _writeData(data.data(), data.size()); } inline void Serializer::_writeData(const void *data, size_t count) { if (_pos + count > _result.size()) { throw std::runtime_error("Serialization failed - size overflow"); } std::memcpy(static_cast(_result.dataOffset(_pos)), static_cast(data), count); _pos += count; } inline void Serializer::writeString(const std::string &value) { _writeData(value.c_str(), value.size() + 1); // +1 for the nullbyte } inline size_t Serializer::StringSize(const std::string &value) { return value.size() + 1; // +1 for nullbyte } inline Data Serializer::finished() { if (_pos != _result.size()) { throw std::runtime_error("Serialization failed - size not fully used."); } return std::move(_result); } } #endif src/cpp-utils/io/000077500000000000000000000000001347701267100142045ustar00rootroot00000000000000src/cpp-utils/io/Console.cpp000066400000000000000000000000261347701267100163100ustar00rootroot00000000000000#include "Console.h" src/cpp-utils/io/Console.h000066400000000000000000000013231347701267100157560ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_IO_CONSOLE_H #define MESSMER_CPPUTILS_IO_CONSOLE_H #include #include #include #include #include "../macros.h" #include "../pointer/unique_ref.h" namespace cpputils { class Console { public: virtual ~Console() = default; virtual unsigned int ask(const std::string &question, const std::vector &options) = 0; virtual bool askYesNo(const std::string &question, bool defaultValue) = 0; // NoninteractiveConsole will just return the default value without asking the user. virtual void print(const std::string &output) = 0; virtual std::string askPassword(const std::string &question) = 0; }; } #endif src/cpp-utils/io/DontEchoStdinToStdoutRAII.cpp000066400000000000000000000026141347701267100215730ustar00rootroot00000000000000#include "DontEchoStdinToStdoutRAII.h" #if !defined(_MSC_VER) #include #include namespace cpputils { namespace details { class _DontEchoStdinToStdoutRAII final { public: _DontEchoStdinToStdoutRAII() : _old_state() { tcgetattr(STDIN_FILENO, &_old_state); termios new_state = _old_state; new_state.c_lflag &= ~ECHO; tcsetattr(STDIN_FILENO, TCSANOW, &new_state); } ~_DontEchoStdinToStdoutRAII() { tcsetattr(STDIN_FILENO, TCSANOW, &_old_state); } private: termios _old_state; DISALLOW_COPY_AND_ASSIGN(_DontEchoStdinToStdoutRAII); }; } } #else #include namespace cpputils { namespace details { class _DontEchoStdinToStdoutRAII final { public: _DontEchoStdinToStdoutRAII() : _old_state() { HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); GetConsoleMode(hStdin, &_old_state); SetConsoleMode(hStdin, _old_state & (~ENABLE_ECHO_INPUT)); } ~_DontEchoStdinToStdoutRAII() { HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); SetConsoleMode(hStdin, _old_state); } private: DWORD _old_state; DISALLOW_COPY_AND_ASSIGN(_DontEchoStdinToStdoutRAII); }; } } #endif using cpputils::make_unique_ref; namespace cpputils { DontEchoStdinToStdoutRAII::DontEchoStdinToStdoutRAII() : raii(make_unique_ref()) {} DontEchoStdinToStdoutRAII::~DontEchoStdinToStdoutRAII() {} } src/cpp-utils/io/DontEchoStdinToStdoutRAII.h000066400000000000000000000014351347701267100212400ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_IO_DONTECHOSTDINTOSTDOUTRAII_H #define MESSMER_CPPUTILS_IO_DONTECHOSTDINTOSTDOUTRAII_H #include #include "../macros.h" /** * If you create an instance of this class in your scope, then any user input from stdin * won't be echoed back to stdout until the instance leaves the scope. * This can be very handy for password inputs where you don't want the password to be visible on screen. */ namespace cpputils { namespace details { class _DontEchoStdinToStdoutRAII; } class DontEchoStdinToStdoutRAII final { public: DontEchoStdinToStdoutRAII(); ~DontEchoStdinToStdoutRAII(); private: cpputils::unique_ref raii; DISALLOW_COPY_AND_ASSIGN(DontEchoStdinToStdoutRAII); }; } #endif src/cpp-utils/io/IOStreamConsole.cpp000066400000000000000000000067531347701267100177310ustar00rootroot00000000000000#include "IOStreamConsole.h" #include #include "DontEchoStdinToStdoutRAII.h" #include using std::ostream; using std::istream; using std::string; using std::vector; using std::flush; using std::function; using boost::optional; using boost::none; namespace cpputils { IOStreamConsole::IOStreamConsole(): IOStreamConsole(std::cout, std::cin) { } IOStreamConsole::IOStreamConsole(ostream &output, istream &input): _output(output), _input(input) { } optional IOStreamConsole::_parseInt(const string &str) { try { string trimmed = str; boost::algorithm::trim(trimmed); int parsed = std::stoi(str); if (std::to_string(parsed) != trimmed) { return none; } return parsed; } catch (const std::invalid_argument &e) { return none; } catch (const std::out_of_range &e) { return none; } } function(const string &input)> IOStreamConsole::_parseUIntWithMinMax(unsigned int min, unsigned int max) { return [min, max] (const string &input) { optional parsed = _parseInt(input); if (parsed == none) { return optional(none); } unsigned int value = static_cast(*parsed); if (value < min || value > max) { return optional(none); } return optional(value); }; } template Return IOStreamConsole::_askForChoice(const string &question, function (const string&)> parse) { optional choice = none; do { _output << question << flush; string choiceStr; getline(_input, choiceStr); choice = parse(choiceStr); } while(choice == none); return *choice; } unsigned int IOStreamConsole::ask(const string &question, const vector &options) { if(options.size() == 0) { throw std::invalid_argument("options should have at least one entry"); } _output << question << "\n"; for (size_t i = 0; i < options.size(); ++i) { _output << " [" << (i+1) << "] " << options[i] << "\n"; } int choice = _askForChoice("Your choice [1-" + std::to_string(options.size()) + "]: ", _parseUIntWithMinMax(1, options.size())); return choice-1; } function(const string &input)> IOStreamConsole::_parseYesNo() { return [] (const string &input) { string trimmed = input; boost::algorithm::trim(trimmed); if(trimmed == "Y" || trimmed == "y" || trimmed == "Yes" || trimmed == "yes") { return optional(true); } else if (trimmed == "N" || trimmed == "n" || trimmed == "No" || trimmed == "no") { return optional(false); } else { return optional(none); } }; } bool IOStreamConsole::askYesNo(const string &question, bool /*defaultValue*/) { _output << question << "\n"; return _askForChoice("Your choice [y/n]: ", _parseYesNo()); } void IOStreamConsole::print(const string &output) { _output << output << std::flush; } string IOStreamConsole::askPassword(const string &question) { DontEchoStdinToStdoutRAII _stdin_input_is_hidden_as_long_as_this_is_in_scope; _output << question << std::flush; string result; std::getline(_input, result); _output << std::endl; ASSERT(result.size() == 0 || result[result.size() - 1] != '\n', "Unexpected std::getline() behavior"); return result; } } src/cpp-utils/io/IOStreamConsole.h000066400000000000000000000023171347701267100173660ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_IO_IOSTREAMCONSOLE_H #define MESSMER_CPPUTILS_IO_IOSTREAMCONSOLE_H #include "Console.h" namespace cpputils { class IOStreamConsole final: public Console { public: IOStreamConsole(); IOStreamConsole(std::ostream &output, std::istream &input); unsigned int ask(const std::string &question, const std::vector &options) override; bool askYesNo(const std::string &question, bool defaultValue) override; void print(const std::string &output) override; std::string askPassword(const std::string &question) override; private: template Return _askForChoice(const std::string &question, std::function (const std::string&)> parse); static std::function(const std::string &input)> _parseYesNo(); static std::function(const std::string &input)> _parseUIntWithMinMax(unsigned int min, unsigned int max); static boost::optional _parseInt(const std::string &str); std::ostream &_output; std::istream &_input; DISALLOW_COPY_AND_ASSIGN(IOStreamConsole); }; } #endif src/cpp-utils/io/NoninteractiveConsole.cpp000066400000000000000000000014111347701267100212200ustar00rootroot00000000000000#include "NoninteractiveConsole.h" using std::string; using std::vector; using std::shared_ptr; namespace cpputils { NoninteractiveConsole::NoninteractiveConsole(shared_ptr baseConsole): _baseConsole(std::move(baseConsole)) { } bool NoninteractiveConsole::askYesNo(const string &/*question*/, bool defaultValue) { return defaultValue; } void NoninteractiveConsole::print(const std::string &output) { _baseConsole->print(output); } unsigned int NoninteractiveConsole::ask(const string &/*question*/, const vector &/*options*/) { throw std::logic_error("Tried to ask a multiple choice question in noninteractive mode"); } string NoninteractiveConsole::askPassword(const string &question) { return _baseConsole->askPassword(question); } } src/cpp-utils/io/NoninteractiveConsole.h000066400000000000000000000014401347701267100206670ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_IO_NONINTERACTIVECONSOLE_H #define MESSMER_CPPUTILS_IO_NONINTERACTIVECONSOLE_H #include "Console.h" namespace cpputils { //TODO Add test cases for NoninteractiveConsole class NoninteractiveConsole final: public Console { public: NoninteractiveConsole(std::shared_ptr baseConsole); unsigned int ask(const std::string &question, const std::vector &options) override; bool askYesNo(const std::string &question, bool defaultValue) override; void print(const std::string &output) override; std::string askPassword(const std::string &question) override; private: std::shared_ptr _baseConsole; DISALLOW_COPY_AND_ASSIGN(NoninteractiveConsole); }; } #endif src/cpp-utils/io/ProgressBar.cpp000066400000000000000000000020031347701267100171340ustar00rootroot00000000000000#include "ProgressBar.h" #include #include #include #include "IOStreamConsole.h" using std::string; namespace cpputils { ProgressBar::ProgressBar(const char* preamble, uint64_t max_value) : ProgressBar(std::make_shared(), preamble, max_value) {} ProgressBar::ProgressBar(std::shared_ptr console, const char* preamble, uint64_t max_value) : _console(std::move(console)) , _preamble(string("\r") + preamble + " ") , _max_value(max_value) , _lastPercentage(std::numeric_limits::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; } } } src/cpp-utils/io/ProgressBar.h000066400000000000000000000011721347701267100166070ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_IO_PROGRESSBAR_H #define MESSMER_CPPUTILS_IO_PROGRESSBAR_H #include #include #include #include "Console.h" namespace cpputils { class ProgressBar final { public: explicit ProgressBar(std::shared_ptr 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; std::string _preamble; uint64_t _max_value; size_t _lastPercentage; DISALLOW_COPY_AND_ASSIGN(ProgressBar); }; } #endif src/cpp-utils/io/pipestream.cpp000066400000000000000000000000301347701267100170520ustar00rootroot00000000000000#include "pipestream.h" src/cpp-utils/io/pipestream.h000066400000000000000000000135001347701267100165250ustar00rootroot00000000000000// Original version taken under MIT licence from http://stackoverflow.com/a/12413298/829568 and modified. // ---------------------------------------------------------------------------- // Copyright (C) 2013 Dietmar Kuehl http://www.dietmar-kuehl.de // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, // merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // ---------------------------------------------------------------------------- #pragma once #ifndef MESSMER_CPPUTILS_PIPESTREAM_H #define MESSMER_CPPUTILS_PIPESTREAM_H /** * This is a class that implements a pipe for std::ostream/std::istream. * You can in one thread write to std::ostream and read that data (blocking) from an std::istream. * Reading and writing can happen in different threads. * * Use as follows: * pipestream pipe; * std::istream istream(&pipe); * std::ostream ostream(&pipe); * istream << "Data"; * ostream >> ... */ #include #include #include #include #include #include #include #include #include "../macros.h" //TODO Add test cases namespace cpputils { class pipestream final : public std::streambuf { private: typedef std::streambuf::traits_type traits_type; typedef std::string::size_type string_size_t; std::mutex d_mutex; std::condition_variable d_condition; std::string d_out; std::string d_in; std::string d_tmp; char *d_current; bool d_closed; public: pipestream(string_size_t out_size = 16, string_size_t in_size = 64) : d_mutex() , d_condition() , d_out(std::max(string_size_t(1), out_size), ' ') , d_in(std::max(string_size_t(1), in_size), ' ') , d_tmp(std::max(string_size_t(1), in_size), ' ') , d_current(&this->d_tmp[0]) , d_closed(false) { this->setp(&this->d_out[0], &this->d_out[0] + this->d_out.size() - 1); this->setg(&this->d_in[0], &this->d_in[0], &this->d_in[0]); } void close() { { std::unique_lock lock(this->d_mutex); this->d_closed = true; while (this->pbase() != this->pptr()) { this->internal_sync(lock); } } this->d_condition.notify_all(); } private: int_type underflow() override { if (this->gptr() == this->egptr()) { std::unique_lock lock(this->d_mutex); while (&this->d_tmp[0] == this->d_current && !this->d_closed) { this->d_condition.wait(lock); } if (&this->d_tmp[0] != this->d_current) { std::streamsize size(this->d_current - &this->d_tmp[0]); traits_type::copy(this->eback(), &this->d_tmp[0], this->d_current - &this->d_tmp[0]); this->setg(this->eback(), this->eback(), this->eback() + size); this->d_current = &this->d_tmp[0]; this->d_condition.notify_one(); } } return this->gptr() == this->egptr() ? traits_type::eof() : traits_type::to_int_type(*this->gptr()); } int_type overflow(int_type c) override { std::unique_lock lock(this->d_mutex); if (!traits_type::eq_int_type(c, traits_type::eof())) { *this->pptr() = traits_type::to_char_type(c); this->pbump(1); } return this->internal_sync(lock) ? traits_type::eof() : traits_type::not_eof(c); } int sync() override { std::unique_lock lock(this->d_mutex); return this->internal_sync(lock); } int internal_sync(std::unique_lock &lock) { char *end(&this->d_tmp[0] + this->d_tmp.size()); while (this->d_current == end && !this->d_closed) { this->d_condition.wait(lock); } if (this->d_current != end) { std::streamsize size(std::min(end - d_current, this->pptr() - this->pbase())); traits_type::copy(d_current, this->pbase(), size); this->d_current += size; std::streamsize remain((this->pptr() - this->pbase()) - size); traits_type::move(this->pbase(), this->pptr(), remain); this->setp(this->pbase(), this->epptr()); this->pbump(remain); this->d_condition.notify_one(); return 0; } return traits_type::eof(); } DISALLOW_COPY_AND_ASSIGN(pipestream); }; } #endif src/cpp-utils/lock/000077500000000000000000000000001347701267100145255ustar00rootroot00000000000000src/cpp-utils/lock/CombinedLock.h000066400000000000000000000015411347701267100172300ustar00rootroot00000000000000#ifndef MESSMER_CPPUTILS_LOCK_COMBINEDLOCK_H #define MESSMER_CPPUTILS_LOCK_COMBINEDLOCK_H #include "../macros.h" namespace cpputils { /** * This class is used to combine multiple locks into one, taking care that they are locked/unlocked * in the order they were given to the constructor. */ class CombinedLock final { public: CombinedLock(std::unique_lock *outer, std::unique_lock *inner) : _outer(outer), _inner(inner) { } void lock() { _outer->lock(); _inner->lock(); } void unlock() { _inner->unlock(); _outer->unlock(); } private: std::unique_lock *_outer; std::unique_lock *_inner; DISALLOW_COPY_AND_ASSIGN(CombinedLock); }; } #endifsrc/cpp-utils/lock/ConditionBarrier.h000066400000000000000000000021071347701267100201330ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_LOCK_CONDITIONBARRIER_H #define MESSMER_CPPUTILS_LOCK_CONDITIONBARRIER_H #include #include #include "../macros.h" //TODO Test //TODO Merge lock folder with thread folder namespace cpputils { // Like a condition variable, but without spurious wakeups. // The waiting threads are only woken, when notify() is called. // After a call to release(), future calls to wait() will not block anymore. class ConditionBarrier final { public: ConditionBarrier() :_mutex(), _cv(), _triggered(false) { } void wait() { std::unique_lock lock(_mutex); _cv.wait(lock, [this] { return _triggered; }); } void release() { std::unique_lock lock(_mutex); _triggered = true; _cv.notify_all(); } private: std::mutex _mutex; std::condition_variable _cv; bool _triggered; DISALLOW_COPY_AND_ASSIGN(ConditionBarrier); }; } #endif src/cpp-utils/lock/LockPool.cpp000066400000000000000000000000261347701267100167510ustar00rootroot00000000000000#include "LockPool.h" src/cpp-utils/lock/LockPool.h000066400000000000000000000065731347701267100164330ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_LOCK_LOCKPOOL_H #define MESSMER_CPPUTILS_LOCK_LOCKPOOL_H #include #include #include #include #include "../assert/assert.h" #include "../macros.h" #include "CombinedLock.h" //TODO Test //TODO Rename package to synchronization //TODO Rename to MutexPool namespace cpputils { template class LockPool final { public: LockPool(); ~LockPool(); void lock(const LockName &lockName); void lock(const LockName &lockName, std::unique_lock *lockToFreeWhileWaiting); void release(const LockName &lockName); private: bool _isLocked(const LockName &lockName) const; template void _lock(const LockName &lockName, OuterLock *lockToFreeWhileWaiting); std::vector _lockedLocks; std::mutex _mutex; std::condition_variable_any _cv; DISALLOW_COPY_AND_ASSIGN(LockPool); }; template inline LockPool::LockPool(): _lockedLocks(), _mutex(), _cv() {} template inline LockPool::~LockPool() { ASSERT(_lockedLocks.size() == 0, "Still locks open"); } template inline void LockPool::lock(const LockName &lockName, std::unique_lock *lockToFreeWhileWaiting) { ASSERT(lockToFreeWhileWaiting->owns_lock(), "Given lock must be locked"); std::unique_lock mutexLock(_mutex); // TODO Is shared_lock enough here? // Order of locking/unlocking is important and should be the same order as everywhere else to prevent deadlocks. // Since when entering the function, lockToFreeWhileWaiting is already locked and mutexLock is locked afterwards, // the condition variable should do it in the same order. We use combinedLock for this. CombinedLock combinedLock(lockToFreeWhileWaiting, &mutexLock); _lock(lockName, &combinedLock); ASSERT(mutexLock.owns_lock() && lockToFreeWhileWaiting->owns_lock(), "Locks haven't been correctly relocked"); } template inline void LockPool::lock(const LockName &lockName) { std::unique_lock mutexLock(_mutex); // TODO Is shared_lock enough here? _lock(lockName, &mutexLock); ASSERT(mutexLock.owns_lock(), "Lock hasn't been correctly relocked"); } template template inline void LockPool::_lock(const LockName &lockName, OuterLock *mutexLock) { if (_isLocked(lockName)) { _cv.wait(*mutexLock, [this, &lockName]{ return !_isLocked(lockName); }); } _lockedLocks.push_back(lockName); } template inline bool LockPool::_isLocked(const LockName &lockName) const { return std::find(_lockedLocks.begin(), _lockedLocks.end(), lockName) != _lockedLocks.end(); } template inline void LockPool::release(const LockName &lockName) { std::unique_lock mutexLock(_mutex); auto found = std::find(_lockedLocks.begin(), _lockedLocks.end(), lockName); ASSERT(found != _lockedLocks.end(), "Lock given to release() was not locked"); _lockedLocks.erase(found); _cv.notify_all(); } } #endif src/cpp-utils/lock/MutexPoolLock.h000066400000000000000000000023161347701267100174450ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_LOCK_MUTEXPOOLLOCK_H #define MESSMER_CPPUTILS_LOCK_MUTEXPOOLLOCK_H #include "LockPool.h" namespace cpputils { template class MutexPoolLock final { public: MutexPoolLock(LockPool *pool, const LockName &lockName): _pool(pool), _lockName(lockName) { _pool->lock(_lockName); } MutexPoolLock(LockPool *pool, const LockName &lockName, std::unique_lock *lockToFreeWhileWaiting) : _pool(pool), _lockName(lockName) { _pool->lock(_lockName, lockToFreeWhileWaiting); } MutexPoolLock(MutexPoolLock &&rhs) noexcept: _pool(rhs._pool), _lockName(std::move(rhs._lockName)) { rhs._pool = nullptr; } ~MutexPoolLock() { if (_pool != nullptr) { unlock(); } } void unlock() { ASSERT(_pool != nullptr, "MutexPoolLock is not locked"); _pool->release(_lockName); _pool = nullptr; } private: LockPool *_pool; LockName _lockName; DISALLOW_COPY_AND_ASSIGN(MutexPoolLock); }; } #endif src/cpp-utils/logging/000077500000000000000000000000001347701267100152235ustar00rootroot00000000000000src/cpp-utils/logging/Logger.h000066400000000000000000000024431347701267100166160ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_LOGGING_LOGGER_H #define MESSMER_CPPUTILS_LOGGING_LOGGER_H #if !defined(_MSC_VER) #define SPDLOG_ENABLE_SYSLOG #endif #include #include "../macros.h" namespace cpputils { namespace logging { class Logger final { public: void setLogger(std::shared_ptr logger) { _logger = logger; _logger->set_level(_level); } void reset() { _level = spdlog::level::info; setLogger(_defaultLogger()); } void setLevel(spdlog::level::level_enum level) { _level = level; _logger->set_level(_level); } spdlog::logger *operator->() { return _logger.get(); } private: static std::shared_ptr _defaultLogger() { static auto singleton = spdlog::stderr_logger_mt("Log"); return singleton; } Logger() : _logger(), _level() { reset(); } friend Logger &logger(); std::shared_ptr _logger; spdlog::level::level_enum _level; DISALLOW_COPY_AND_ASSIGN(Logger); }; inline Logger &logger() { static Logger singleton; return singleton; } } } #endif src/cpp-utils/logging/logging.h000066400000000000000000000042641347701267100170300ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_LOGGING_LOGGING_H #define MESSMER_CPPUTILS_LOGGING_LOGGING_H #include "Logger.h" #include #include #if defined(_MSC_VER) #include #endif namespace cpputils { namespace logging { struct ERROR_TYPE {}; struct WARN_TYPE {}; struct INFO_TYPE {}; struct DEBUG_TYPE {}; constexpr ERROR_TYPE ERR {}; constexpr WARN_TYPE WARN {}; constexpr INFO_TYPE INFO {}; constexpr DEBUG_TYPE DEBUG {}; inline void setLogger(std::shared_ptr newLogger) { logger().setLogger(newLogger); } inline void reset() { logger().reset(); } inline void setLevel(ERROR_TYPE) { logger().setLevel(spdlog::level::err); } inline void setLevel(WARN_TYPE) { logger().setLevel(spdlog::level::warn); } inline void setLevel(INFO_TYPE) { logger().setLevel(spdlog::level::info); } inline void setLevel(DEBUG_TYPE) { logger().setLevel(spdlog::level::debug); } template inline void LOG(LogType logType, const std::string &msg) { LOG(logType, msg.c_str()); } template inline void LOG(ERROR_TYPE, const char* fmt, const Args&... args) { logger()->error(fmt, args...); } template inline void LOG(WARN_TYPE, const char* fmt, const Args&... args) { logger()->warn(fmt, args...); } template inline void LOG(INFO_TYPE, const char* fmt, const Args&... args) { logger()->info(fmt, args...); } template inline void LOG(DEBUG_TYPE, const char* fmt, const Args&... args) { logger()->debug(fmt, args...); } inline std::shared_ptr system_logger(const std::string& name) { #if defined(_MSC_VER) return spdlog::create(name); #else return spdlog::syslog_logger(name, name, LOG_PID); #endif } } } #endif src/cpp-utils/macros.h000066400000000000000000000013771347701267100152420ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_MACROS_H_ #define MESSMER_CPPUTILS_MACROS_H_ //TODO If possible, make classes final and destructors non-virtual or delete destructors //TODO Use DISALLOW_COPY_AND_ASSIGN where possible /** * Disallow the copy and assignment constructors of a class */ #define DISALLOW_COPY_AND_ASSIGN(Class) \ Class(const Class &rhs) = delete; \ Class &operator=(const Class &rhs) = delete; /** * Declare a function parameter as intentionally unused to get rid of the compiler warning */ #define UNUSED(expr) (void)(expr) /** * Warn if function result is unused */ #if !defined(_MSC_VER) #define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) #else #define WARN_UNUSED_RESULT _Check_return_ #endif #endif src/cpp-utils/network/000077500000000000000000000000001347701267100152665ustar00rootroot00000000000000src/cpp-utils/network/CurlHttpClient.cpp000066400000000000000000000043451347701267100207040ustar00rootroot00000000000000// Base version taken from https://techoverflow.net/blog/2013/03/15/c-simple-http-download-using-libcurl-easy-api/ #if !defined(_MSC_VER) #include "CurlHttpClient.h" #include #include using boost::none; using boost::optional; using std::string; using std::ostringstream; using std::mutex; using std::unique_lock; namespace cpputils { mutex CurlHttpClient::CurlInitializerRAII::_mutex; uint32_t CurlHttpClient::CurlInitializerRAII::_refcount = 0; CurlHttpClient::CurlInitializerRAII::CurlInitializerRAII() { unique_lock lock(_mutex); if (0 == _refcount) { curl_global_init(CURL_GLOBAL_ALL); } _refcount += 1; } CurlHttpClient::CurlInitializerRAII::~CurlInitializerRAII() { unique_lock lock(_mutex); _refcount -= 1; if (0 == _refcount) { curl_global_cleanup(); } } size_t CurlHttpClient::write_data(void *ptr, size_t size, size_t nmemb, ostringstream *stream) { stream->write(static_cast(ptr), size * nmemb); return size * nmemb; } CurlHttpClient::CurlHttpClient(): curlInitializer(), curl() { curl = curl_easy_init(); } CurlHttpClient::~CurlHttpClient() { curl_easy_cleanup(curl); } string CurlHttpClient::get(const string &url, optional timeoutMsec) { curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // example.com is redirected, so we tell libcurl to follow redirection curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //Prevent "longjmp causes uninitialized stack frame" bug curl_easy_setopt(curl, CURLOPT_ENCODING, "deflate"); ostringstream out; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlHttpClient::write_data); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out); if (timeoutMsec != none) { curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, *timeoutMsec); } // Perform the request, res will get the return code CURLcode res = curl_easy_perform(curl); // Check for errors if (res != CURLE_OK) { throw std::runtime_error("Curl Error " + std::to_string(res) + ": " + curl_easy_strerror(res)); } return out.str(); } } #endif src/cpp-utils/network/CurlHttpClient.h000066400000000000000000000022101347701267100203360ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_NETWORK_CURLHTTPCLIENT_HPP #define MESSMER_CPPUTILS_NETWORK_CURLHTTPCLIENT_HPP #if !defined(_MSC_VER) #include "HttpClient.h" #include "../macros.h" #include #include namespace cpputils { class CurlHttpClient final : public HttpClient { public: CurlHttpClient(); ~CurlHttpClient(); std::string get(const std::string &url, boost::optional timeoutMsec = boost::none) override; private: // When the first object of this class is created, it will initialize curl using curl_global_init(). // When the last object is destroyed, it will deinitialize curl using curl_global_cleanup(). class CurlInitializerRAII final { public: CurlInitializerRAII(); ~CurlInitializerRAII(); private: static std::mutex _mutex; static uint32_t _refcount; DISALLOW_COPY_AND_ASSIGN(CurlInitializerRAII); }; CurlInitializerRAII curlInitializer; CURL *curl; static size_t write_data(void *ptr, size_t size, size_t nmemb, std::ostringstream *stream); DISALLOW_COPY_AND_ASSIGN(CurlHttpClient); }; } #endif #endif src/cpp-utils/network/FakeHttpClient.cpp000066400000000000000000000011201347701267100206310ustar00rootroot00000000000000#include "FakeHttpClient.h" using std::string; using boost::optional; using boost::none; namespace cpputils { FakeHttpClient::FakeHttpClient(): _sites() { } void FakeHttpClient::addWebsite(const string &url, const string &content) { _sites[url] = content; } string FakeHttpClient::get(const string &url, optional timeoutMsec) { UNUSED(timeoutMsec); auto found = _sites.find(url); if (found == _sites.end()) { throw std::runtime_error("Website doesn't exist in FakeHttpClient."); } return found->second; } } src/cpp-utils/network/FakeHttpClient.h000066400000000000000000000011251347701267100203030ustar00rootroot00000000000000#ifndef MESSMER_CPPUTILS_NETWORK_FAKEHTTPCLIENT_H #define MESSMER_CPPUTILS_NETWORK_FAKEHTTPCLIENT_H #include "HttpClient.h" #include "../macros.h" #include namespace cpputils { class FakeHttpClient final : public HttpClient { public: FakeHttpClient(); void addWebsite(const std::string &url, const std::string &content); std::string get(const std::string &url, boost::optional timeoutMsec = boost::none) override; private: std::map _sites; DISALLOW_COPY_AND_ASSIGN(FakeHttpClient); }; } #endif src/cpp-utils/network/HttpClient.cpp000066400000000000000000000000301347701267100200410ustar00rootroot00000000000000#include "HttpClient.h" src/cpp-utils/network/HttpClient.h000066400000000000000000000005501347701267100175150ustar00rootroot00000000000000#ifndef MESSMER_CPPUTILS_NETWORK_HTTPCLIENT_H #define MESSMER_CPPUTILS_NETWORK_HTTPCLIENT_H #include #include namespace cpputils { class HttpClient { public: virtual ~HttpClient() {} virtual std::string get(const std::string& url, boost::optional timeoutMsec = boost::none) = 0; }; }; #endif src/cpp-utils/network/WinHttpClient.cpp000066400000000000000000000214511347701267100205310ustar00rootroot00000000000000#if defined(_MSC_VER) #include "WinHttpClient.h" #include #include #include #include #include #include #include #include using boost::none; using boost::optional; using std::string; using std::wstring; using std::wstring_convert; using std::ostringstream; namespace cpputils { namespace { struct HttpHandleRAII final { HINTERNET handle; HttpHandleRAII(HINTERNET handle_) : handle(handle_) {} HttpHandleRAII(HttpHandleRAII&& rhs) : handle(rhs.handle) { rhs.handle = nullptr; } ~HttpHandleRAII() { if (nullptr != handle) { BOOL success = WinHttpCloseHandle(handle); if (!success) { throw std::runtime_error("Error calling WinHttpCloseHandle. Error code: " + std::to_string(GetLastError())); } } } DISALLOW_COPY_AND_ASSIGN(HttpHandleRAII); }; URL_COMPONENTS parse_url(const wstring &url) { URL_COMPONENTS result; result.dwStructSize = sizeof(result); // Declare fields we want. Setting a field to nullptr and the length to non-zero means the field will be returned. result.lpszScheme = nullptr; result.dwSchemeLength = 1; result.lpszHostName = nullptr; result.dwHostNameLength = 1; result.lpszUserName = nullptr; result.dwUserNameLength = 1; result.lpszPassword = nullptr; result.dwPasswordLength = 1; result.lpszUrlPath = nullptr; result.dwUrlPathLength = 1; result.lpszExtraInfo = nullptr; result.dwExtraInfoLength = 1; BOOL success = WinHttpCrackUrl(url.c_str(), url.size(), ICU_REJECT_USERPWD, &result); if (!success) { throw std::runtime_error("Error parsing url '" + wstring_convert>().to_bytes(url) + "'. Error code: " + std::to_string(GetLastError())); } return result; } INTERNET_PORT get_port_from_url(const URL_COMPONENTS& parsedUrl) { wstring scheme_str(parsedUrl.lpszScheme, parsedUrl.dwSchemeLength); string s_(wstring_convert < std::codecvt_utf8_utf16>().to_bytes(scheme_str)); if (parsedUrl.nScheme == INTERNET_SCHEME_HTTP) { ASSERT(scheme_str == L"http", "Scheme mismatch"); if (parsedUrl.nPort != 80) { throw std::runtime_error("We don't support non-default ports"); } return INTERNET_DEFAULT_HTTP_PORT; } else if (parsedUrl.nScheme == INTERNET_SCHEME_HTTPS) { ASSERT(scheme_str == L"https", "Scheme mismatch"); if (parsedUrl.nPort != 443) { throw std::runtime_error("We don't support non-default ports"); } return INTERNET_DEFAULT_HTTPS_PORT; } else { throw std::runtime_error("Unsupported scheme: " + wstring_convert>().to_bytes(scheme_str)); } } class Request final { public: Request(HttpHandleRAII request) : request_(std::move(request)) {} void set_redirect_policy(DWORD redirectPolicy) { BOOL success = WinHttpSetOption(request_.handle, WINHTTP_OPTION_REDIRECT_POLICY, &redirectPolicy, sizeof(redirectPolicy)); if (!success) { throw std::runtime_error("Error calling WinHttpSetOption. Error code: " + std::to_string(GetLastError())); } } void set_timeouts(long timeoutMsec) { // TODO Timeout should be a total timeout, not per step as we're doing it here. BOOL success = WinHttpSetTimeouts(request_.handle, timeoutMsec, timeoutMsec, timeoutMsec, timeoutMsec); if (!success) { throw std::runtime_error("Error calling WinHttpSetTimeouts. Error code: " + std::to_string(GetLastError())); } } void send() { BOOL success = WinHttpSendRequest(request_.handle, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); if (!success) { throw std::runtime_error("Error calling WinHttpSendRequest. Error code: " + std::to_string(GetLastError())); } } void wait_for_response() { BOOL success = WinHttpReceiveResponse(request_.handle, nullptr); if (!success) { throw std::runtime_error("Error calling WinHttpReceiveResponse. Error code: " + std::to_string(GetLastError())); } } DWORD get_status_code() { DWORD statusCode; DWORD statusCodeSize = sizeof(statusCode); BOOL success = WinHttpQueryHeaders(request_.handle, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX); if (!success) { throw std::runtime_error("Eror calling WinHttpQueryHeaders. Error code: " + std::to_string(GetLastError())); } return statusCode; } string read_response() { ostringstream result; while (true) { DWORD size = num_bytes_readable(); if (size == 0) { break; } cpputils::Data buffer(size + 1); buffer.FillWithZeroes(); DWORD num_read; BOOL success = WinHttpReadData(request_.handle, buffer.data(), buffer.size(), &num_read); if (!success) { throw std::runtime_error("Error calling WinHttpReadData. Error code: " + std::to_string(GetLastError())); } ASSERT(0 != num_read, "Weird behavior of WinHttpReadData.It should never read zero bytes since WinHttpQueryDataAvailable said there are bytes readable."); result.write(reinterpret_cast(buffer.data()), num_read); ASSERT(result.good(), "Error writing to ostringstream"); } return result.str(); } private: DWORD num_bytes_readable() { DWORD result; BOOL success = WinHttpQueryDataAvailable(request_.handle, &result); if (!success) { throw std::runtime_error("Error calling WinHttpQueryDataAvailable. Error code: " + std::to_string(GetLastError())); } return result; } HttpHandleRAII request_; }; struct Connection final { public: Connection(HttpHandleRAII connection) : connection_(std::move(connection)) {} Request create_request(const URL_COMPONENTS& parsedUrl) { const INTERNET_PORT port = get_port_from_url(parsedUrl); const wstring path = wstring(parsedUrl.lpszUrlPath, parsedUrl.dwUrlPathLength) + wstring(parsedUrl.lpszExtraInfo, parsedUrl.dwExtraInfoLength); const DWORD flags = (port == INTERNET_DEFAULT_HTTPS_PORT) ? WINHTTP_FLAG_SECURE : 0; HttpHandleRAII request_handle(WinHttpOpenRequest(connection_.handle, L"GET", path.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags)); if (nullptr == request_handle.handle) { throw std::runtime_error("Error calling WinHttpOpenRequest. Error code: " + std::to_string(GetLastError())); } return Request(std::move(request_handle)); } private: HttpHandleRAII connection_; }; } struct WinHttpSession final { public: WinHttpSession(HttpHandleRAII session) : session_(std::move(session)) {} Connection create_connection(const URL_COMPONENTS& parsedUrl) { const INTERNET_PORT port = get_port_from_url(parsedUrl); const wstring host(parsedUrl.lpszHostName, parsedUrl.dwHostNameLength); HttpHandleRAII connection_handle = WinHttpConnect(session_.handle, host.c_str(), port, 0); if (nullptr == connection_handle.handle) { throw std::runtime_error("Error calling WinHttpConnect. Error code: " + std::to_string(GetLastError())); } return Connection(std::move(connection_handle)); } private: HttpHandleRAII session_; }; namespace { cpputils::unique_ref create_session() { const DWORD dwAccessType = IsWindows8Point1OrGreater() ? WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY; HttpHandleRAII session_handle = WinHttpOpen(L"cpputils::HttpClient", dwAccessType, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if(nullptr == session_handle.handle) { throw std::runtime_error("Error calling WinHttpOpen. Error code: " + std::to_string(GetLastError())); } return cpputils::make_unique_ref(std::move(session_handle)); } } WinHttpClient::WinHttpClient() : session_(create_session()) {} WinHttpClient::~WinHttpClient() {} string WinHttpClient::get(const string &url, optional timeoutMsec) { wstring wurl = wstring_convert>().from_bytes(url); const URL_COMPONENTS parsedUrl = parse_url(wurl); ASSERT(parsedUrl.dwUserNameLength == 0, "Authentication not supported"); ASSERT(parsedUrl.dwPasswordLength == 0, "Authentication not supported"); Connection connection = session_->create_connection(parsedUrl); Request request = connection.create_request(parsedUrl); // allow redirects but not from https to http request.set_redirect_policy(WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP); if (timeoutMsec != none) { request.set_timeouts(*timeoutMsec); } request.send(); request.wait_for_response(); DWORD statusCode = request.get_status_code(); if (statusCode != HTTP_STATUS_OK) { throw std::runtime_error("HTTP Server returned unsupported status code: " + std::to_string(statusCode)); } return request.read_response(); } } #endif src/cpp-utils/network/WinHttpClient.h000066400000000000000000000011071347701267100201720ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_NETWORK_WINHTTPCLIENT_HPP #define MESSMER_CPPUTILS_NETWORK_WINHTTPCLIENT_HPP #if defined(_MSC_VER) #include "HttpClient.h" #include "../macros.h" #include "../pointer/unique_ref.h" namespace cpputils { class WinHttpSession; class WinHttpClient final : public HttpClient { public: WinHttpClient(); ~WinHttpClient(); std::string get(const std::string &url, boost::optional timeoutMsec = boost::none) override; private: unique_ref session_; DISALLOW_COPY_AND_ASSIGN(WinHttpClient); }; } #endif #endif src/cpp-utils/pointer/000077500000000000000000000000001347701267100152555ustar00rootroot00000000000000src/cpp-utils/pointer/cast.h000066400000000000000000000012071347701267100163600ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_POINTER_CAST_H_ #define MESSMER_CPPUTILS_POINTER_CAST_H_ #include namespace cpputils { /** * dynamic_cast implementation for unique_ptr (moving unique_ptr into a unique_ptr of different type) */ //TODO Also allow passing a rvalue reference, otherwise dynamic_pointer_move(func()) won't work template inline std::unique_ptr dynamic_pointer_move(std::unique_ptr &source) { //TODO Deleter DST *casted = dynamic_cast(source.get()); if (casted != nullptr) { std::ignore = source.release(); } return std::unique_ptr(casted); } } #endif src/cpp-utils/pointer/gcc_4_8_compatibility.h000066400000000000000000000024411347701267100215660ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_GCC48COMPATIBILITY_H #define MESSMER_CPPUTILS_GCC48COMPATIBILITY_H #include #if __GNUC__ == 4 && __GNUC_MINOR__ == 8 // Add std::make_unique namespace std { /// Alias template for remove_extent template using remove_extent_t = typename remove_extent<_Tp>::type; template struct _MakeUniq { typedef unique_ptr<_Tp> __single_object; }; template struct _MakeUniq<_Tp[]> { typedef unique_ptr<_Tp[]> __array; }; template struct _MakeUniq<_Tp[_Bound]> { struct __invalid_type { }; }; /// std::make_unique for single objects template inline typename _MakeUniq<_Tp>::__single_object make_unique(_Args&&... __args) { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); } /// std::make_unique for arrays of unknown bound template inline typename _MakeUniq<_Tp>::__array make_unique(size_t __num) { return unique_ptr<_Tp>(new remove_extent_t<_Tp>[__num]()); } /// Disable std::make_unique for arrays of known bound template inline typename _MakeUniq<_Tp>::__invalid_type make_unique(_Args&&...) = delete; } #endif #endif src/cpp-utils/pointer/optional_ownership_ptr.h000066400000000000000000000023711347701267100222410ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_POINTER_OPTIONALOWNERSHIPPOINTER_H_ #define MESSMER_CPPUTILS_POINTER_OPTIONALOWNERSHIPPOINTER_H_ #include "unique_ref.h" #include /** * optional_ownership_ptr can be used to hold a pointer to an instance of an object. * The pointer might or might not have ownership of the object. * * If it has ownership, it will delete the stored object in its destructor. * If it doesn't have ownership, it won't. * * You can create such pointers with * - WithOwnership(ptr) * - WithoutOwnership(ptr) * - null() */ namespace cpputils { template using optional_ownership_ptr = std::unique_ptr>; template optional_ownership_ptr WithOwnership(std::unique_ptr obj) { auto deleter = obj.get_deleter(); return optional_ownership_ptr(obj.release(), deleter); } template optional_ownership_ptr WithOwnership(unique_ref obj) { return WithOwnership(static_cast>(std::move(obj))); } template optional_ownership_ptr WithoutOwnership(T *obj) { return optional_ownership_ptr(obj, [](T*){}); } template optional_ownership_ptr null() { return WithoutOwnership(nullptr); } } #endif src/cpp-utils/pointer/unique_ref.h000066400000000000000000000145521347701267100175770ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_POINTER_UNIQUE_REF_H #define MESSMER_CPPUTILS_POINTER_UNIQUE_REF_H #include #include #include "../macros.h" #include "gcc_4_8_compatibility.h" #include "cast.h" #include "../assert/assert.h" namespace cpputils { /** * unique_ref behaves like unique_ptr, but guarantees that the pointer points to a valid object. * You can create objects using make_unique_ref (works like make_unique for unique_ptr). * * If you happen to already have a unique_ptr, you can call nullcheck(unique_ptr), * which returns optional>. * Take care that this should be used very rarely, since it circumvents parts of the guarantee. * It still protects against null pointers, but it does not guarantee anymore that the pointer points * to a valid object. It might hold an arbitrary non-null memory location. * * Caution: There is one way a unique_ref can actually hold a nullptr. * It will hold a nullptr after its value was moved to another unique_ref. * Never use the old instance after moving! */ template> class unique_ref final { public: using element_type = typename std::unique_ptr::element_type; using deleter_type = typename std::unique_ptr::deleter_type; using pointer = typename std::unique_ptr::pointer; unique_ref(unique_ref&& from) noexcept : _target(std::move(from._target)) { from._target = nullptr; _invariant(); } template unique_ref(unique_ref&& from) noexcept : _target(std::move(from._target)) { from._target = nullptr; _invariant(); } unique_ref& operator=(unique_ref&& from) noexcept { _target = std::move(from._target); from._target = nullptr; _invariant(); return *this; } template unique_ref& operator=(unique_ref&& from) noexcept { _target = std::move(from._target); from._target = nullptr; _invariant(); return *this; } typename std::add_lvalue_reference::type operator*() const& noexcept { _invariant(); return *_target; } typename std::add_rvalue_reference::type operator*() && noexcept { _invariant(); return std::move(*_target); } pointer operator->() const noexcept { return get(); } pointer get() const noexcept { _invariant(); return _target.get(); } template operator std::unique_ptr() && noexcept { _invariant(); return std::move(_target); } template operator std::shared_ptr() && noexcept { _invariant(); return std::move(_target); } void swap(unique_ref& rhs) noexcept { std::swap(_target, rhs._target); } bool is_valid() const noexcept { return _target.get() != nullptr; } deleter_type& get_deleter() noexcept { return _target.get_deleter(); } const deleter_type& get_deleter() const noexcept { return _target.get_deleter(); } private: explicit unique_ref(std::unique_ptr target) noexcept : _target(std::move(target)) {} void _invariant() const noexcept { // TODO Test performance impact of this ASSERT(_target.get() != nullptr, "Member was moved out to another unique_ref. This instance is invalid."); } template friend unique_ref make_unique_ref(Args&&... args); template friend boost::optional> nullcheck(std::unique_ptr ptr) noexcept; template friend class unique_ref; template friend boost::optional> dynamic_pointer_move(unique_ref &source) noexcept; template friend bool operator==(const unique_ref& lhs, const unique_ref& rhs) noexcept; friend struct std::hash>; friend struct std::less>; std::unique_ptr _target; DISALLOW_COPY_AND_ASSIGN(unique_ref); }; template inline unique_ref make_unique_ref(Args&&... args) { return unique_ref(std::make_unique(std::forward(args)...)); } template inline boost::optional> nullcheck(std::unique_ptr ptr) noexcept { if (ptr.get() != nullptr) { return unique_ref(std::move(ptr)); } return boost::none; } template inline void destruct(unique_ref /*ptr*/) { // ptr will be moved in to this function and destructed on return } //TODO Also allow passing a rvalue reference, otherwise dynamic_pointer_move(func()) won't work template inline boost::optional> dynamic_pointer_move(unique_ref &source) noexcept { return nullcheck(dynamic_pointer_move(source._target)); } template inline bool operator==(const unique_ref &lhs, const unique_ref &rhs) noexcept { return lhs._target == rhs._target; } template inline bool operator!=(const unique_ref &lhs, const unique_ref &rhs) noexcept { return !operator==(lhs, rhs); } } namespace std { // NOLINT (intentional change of namespace std) template inline void swap(cpputils::unique_ref& lhs, cpputils::unique_ref& rhs) noexcept { lhs.swap(rhs); } template inline void swap(cpputils::unique_ref&& lhs, cpputils::unique_ref& rhs) noexcept { lhs.swap(rhs); } template inline void swap(cpputils::unique_ref& lhs, cpputils::unique_ref&& rhs) noexcept { lhs.swap(rhs); } // Allow using it in std::unordered_set / std::unordered_map template struct hash> { size_t operator()(const cpputils::unique_ref &ref) const noexcept { return std::hash>()(ref._target); } }; // Allow using it in std::map / std::set template struct less> { bool operator()(const cpputils::unique_ref &lhs, const cpputils::unique_ref &rhs) const noexcept { return lhs._target < rhs._target; } }; } #endif src/cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h000066400000000000000000000013431347701267100263650ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_POINTER_UNIQUE_REF_BOOST_OPTIONAL_GTEST_WORKAROUND_H #define MESSMER_CPPUTILS_POINTER_UNIQUE_REF_BOOST_OPTIONAL_GTEST_WORKAROUND_H /** * This is a workaround for using boost::optional> in gtest. * Without including this file, the linker will fail. */ //TODO Test that this solves the problem (add test unit file that doesn't compile without) #include "unique_ref.h" //gtest/boost::optional workaround for working with optional> namespace cpputils { template inline std::ostream& operator<<(std::ostream& out, const cpputils::unique_ref &ref) { out << ref.get(); return out; } } #include #endif src/cpp-utils/process/000077500000000000000000000000001347701267100152535ustar00rootroot00000000000000src/cpp-utils/process/SignalCatcher.cpp000066400000000000000000000103601347701267100204660ustar00rootroot00000000000000#include "SignalCatcher.h" #include "SignalHandler.h" #include #include #include #include #include using std::make_unique; using std::vector; using std::pair; namespace cpputils { namespace { void got_signal(int signal); class SignalCatcherRegistry final { public: void add(int signal, details::SignalCatcherImpl* signal_occurred_flag) { _catchers.write([&] (auto& catchers) { catchers.emplace_back(signal, signal_occurred_flag); }); } void remove(details::SignalCatcherImpl* 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"); } details::SignalCatcherImpl* 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>> _catchers; DISALLOW_COPY_AND_ASSIGN(SignalCatcherRegistry); }; class SignalCatcherRegisterer final { public: SignalCatcherRegisterer(int signal, details::SignalCatcherImpl* catcher) : _catcher(catcher) { SignalCatcherRegistry::singleton().add(signal, _catcher); } ~SignalCatcherRegisterer() { SignalCatcherRegistry::singleton().remove(_catcher); } private: details::SignalCatcherImpl* _catcher; DISALLOW_COPY_AND_ASSIGN(SignalCatcherRegisterer); }; } namespace details { class SignalCatcherImpl final { public: SignalCatcherImpl(int signal, std::atomic* signal_occurred_flag) : _signal_occurred_flag(signal_occurred_flag) , _registerer(signal, this) , _handler(signal) { // note: the order of the members ensures that: // - 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 ASSERT(signal == SIGABRT || signal == SIGFPE || signal == SIGILL || signal == SIGINT || signal == SIGSEGV || signal == SIGTERM, "Unknown signal"); } void setSignalOccurred() { *_signal_occurred_flag = true; } private: std::atomic* _signal_occurred_flag; SignalCatcherRegisterer _registerer; SignalHandlerRAII<&got_signal> _handler; DISALLOW_COPY_AND_ASSIGN(SignalCatcherImpl); }; } namespace { void got_signal(int signal) { SignalCatcherRegistry::singleton().find(signal)->setSignalOccurred(); } } SignalCatcher::SignalCatcher(std::initializer_list 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(signal, &_signal_occurred)); } } SignalCatcher::~SignalCatcher() {} } src/cpp-utils/process/SignalCatcher.h000066400000000000000000000017221347701267100201350ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_PROCESS_SIGNALCATCHER_H_ #define MESSMER_CPPUTILS_PROCESS_SIGNALCATCHER_H_ #include #include #include #include #include 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 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 _signal_occurred; std::vector> _impls; DISALLOW_COPY_AND_ASSIGN(SignalCatcher); }; } #endif src/cpp-utils/process/SignalHandler.cpp000066400000000000000000000000331347701267100204660ustar00rootroot00000000000000#include "SignalHandler.h" src/cpp-utils/process/SignalHandler.h000066400000000000000000000123471347701267100201460ustar00rootroot00000000000000#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 src/cpp-utils/process/daemonize.cpp000066400000000000000000000026361347701267100177410ustar00rootroot00000000000000#include "daemonize.h" #include "../logging/logging.h" using namespace cpputils::logging; //TODO Test daemonize() #if !defined(_MSC_VER) #include #include #include #include #include #include #include #include #include #include namespace cpputils { void daemonize() { pid_t pid = fork(); if (pid < 0) { exit(EXIT_FAILURE); } if (pid > 0) { //We're the parent process. Exit. exit(EXIT_SUCCESS); } // We're the child process. umask(0); // Create a new SID for the child process pid_t sid = setsid(); if (sid < 0) { LOG(ERR, "Failed to get SID for daemon process"); exit(EXIT_FAILURE); } // Change the current working directory to a directory that's always existin if ((chdir("/")) < 0) { LOG(ERR, "Failed to change working directory for daemon process"); exit(EXIT_FAILURE); } // Close out the standard file descriptors. The process can't use them anyhow. close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); }; } #else #include namespace cpputils { void daemonize() { LOG(INFO, "Process started in the background. You can close this console window now."); if (!FreeConsole()) { LOG(ERR, "Failed to call FreeConsole()"); exit(EXIT_FAILURE); } } } #endif src/cpp-utils/process/daemonize.h000066400000000000000000000002351347701267100173770ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_PROCESS_DAEMONIZE_H #define MESSMER_CPPUTILS_PROCESS_DAEMONIZE_H namespace cpputils { void daemonize(); } #endif src/cpp-utils/process/subprocess.cpp000066400000000000000000000042521347701267100201520ustar00rootroot00000000000000#include "subprocess.h" #include #include #include #include #if defined(__APPLE__) #include constexpr const char* openmode = "r"; #elif !defined(_MSC_VER) #include constexpr const char* openmode = "re"; #else #define popen _popen #define pclose _pclose #define WEXITSTATUS(a) a #define WIFEXITED(a) true constexpr const char* openmode = "r"; #endif using std::string; namespace cpputils { namespace { class SubprocessHandle final { public: SubprocessHandle(const string &command) : _subprocess(popen(command.c_str(), openmode)) { if (!_subprocess) { throw std::runtime_error("Error starting subprocess " + command + ". Errno: " + std::to_string(errno)); } } ~SubprocessHandle() { if (_subprocess != nullptr) { close(); } } string getOutput() { string output; std::array buffer{}; while (fgets(buffer.data(), buffer.size(), _subprocess) != nullptr) { output += buffer.data(); } return output; } int close() { auto returncode = pclose(_subprocess); _subprocess = nullptr; if (returncode == -1) { throw std::runtime_error("Error calling pclose. Errno: " + std::to_string(errno)); } #pragma GCC diagnostic push // WIFEXITSTATUS / WEXITSTATUS use old style casts #pragma GCC diagnostic ignored "-Wold-style-cast" if (!WIFEXITED(returncode)) { // WEXITSTATUS is only valid if WIFEXITED is 0. throw std::runtime_error("WIFEXITED returned " + std::to_string(WIFEXITED(returncode))); } return WEXITSTATUS(returncode); #pragma GCC diagnostic pop } private: FILE *_subprocess; }; } SubprocessResult Subprocess::call(const string &command) { SubprocessHandle subprocess(command); string output = subprocess.getOutput(); int exitcode = subprocess.close(); return SubprocessResult {output, exitcode}; } SubprocessResult Subprocess::check_call(const string &command) { auto result = call(command); if(result.exitcode != 0) { throw SubprocessError("Subprocess \""+command+"\" exited with code "+std::to_string(result.exitcode)); } return result; } } src/cpp-utils/process/subprocess.h000066400000000000000000000013031347701267100176110ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_PROCESS_SUBPROCESS_H #define MESSMER_CPPUTILS_PROCESS_SUBPROCESS_H #include #include #include "../macros.h" namespace cpputils { struct SubprocessResult final { std::string output; int exitcode; }; struct SubprocessError final : public std::runtime_error { SubprocessError(std::string msg): std::runtime_error(std::move(msg)) {} }; //TODO Test class Subprocess final { public: static SubprocessResult call(const std::string &command); static SubprocessResult check_call(const std::string &command); private: DISALLOW_COPY_AND_ASSIGN(Subprocess); }; } #endif src/cpp-utils/random/000077500000000000000000000000001347701267100150555ustar00rootroot00000000000000src/cpp-utils/random/OSRandomGenerator.cpp000066400000000000000000000000371347701267100211120ustar00rootroot00000000000000#include "OSRandomGenerator.h" src/cpp-utils/random/OSRandomGenerator.h000066400000000000000000000013321347701267100205560ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_RANDOM_OSRANDOMGENERATOR_H #define MESSMER_CPPUTILS_RANDOM_OSRANDOMGENERATOR_H #include "cpp-utils/crypto/cryptopp_byte.h" #include "RandomGenerator.h" #include namespace cpputils { class OSRandomGenerator final : public RandomGenerator { public: OSRandomGenerator(); protected: void _get(void *target, size_t bytes) override; private: DISALLOW_COPY_AND_ASSIGN(OSRandomGenerator); }; inline OSRandomGenerator::OSRandomGenerator() {} inline void OSRandomGenerator::_get(void *target, size_t bytes) { CryptoPP::OS_GenerateRandomBlock(true, static_cast(target), bytes); } } #endif src/cpp-utils/random/PseudoRandomPool.cpp000066400000000000000000000002451347701267100210140ustar00rootroot00000000000000#include "PseudoRandomPool.h" namespace cpputils { constexpr size_t PseudoRandomPool::MIN_BUFFER_SIZE; constexpr size_t PseudoRandomPool::MAX_BUFFER_SIZE; }src/cpp-utils/random/PseudoRandomPool.h000066400000000000000000000020431347701267100204570ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_RANDOM_PSEUDORANDOMPOOL_H #define MESSMER_CPPUTILS_RANDOM_PSEUDORANDOMPOOL_H #include #include "RandomGenerator.h" #include "ThreadsafeRandomDataBuffer.h" #include "RandomGeneratorThread.h" #include namespace cpputils { //TODO Test class PseudoRandomPool final : public RandomGenerator { public: PseudoRandomPool(); protected: void _get(void *target, size_t bytes) override; private: static constexpr size_t MIN_BUFFER_SIZE = 1*1024*1024; // 1MB static constexpr size_t MAX_BUFFER_SIZE = 2*1024*1024; // 2MB ThreadsafeRandomDataBuffer _buffer; RandomGeneratorThread _refillThread; DISALLOW_COPY_AND_ASSIGN(PseudoRandomPool); }; inline void PseudoRandomPool::_get(void *target, size_t bytes) { _buffer.get(target, bytes); } inline PseudoRandomPool::PseudoRandomPool(): _buffer(), _refillThread(&_buffer, MIN_BUFFER_SIZE, MAX_BUFFER_SIZE) { _refillThread.start(); } } #endif src/cpp-utils/random/Random.cpp000066400000000000000000000001121347701267100167730ustar00rootroot00000000000000#include "Random.h" namespace cpputils { std::mutex Random::_mutex; }src/cpp-utils/random/Random.h000066400000000000000000000014071347701267100164500ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_RANDOM_RANDOM_H #define MESSMER_CPPUTILS_RANDOM_RANDOM_H #include "PseudoRandomPool.h" #include "OSRandomGenerator.h" #include "../data/FixedSizeData.h" #include "../data/Data.h" #include namespace cpputils { class Random final { public: static PseudoRandomPool &PseudoRandom() { std::unique_lock lock(_mutex); static PseudoRandomPool random; return random; } static OSRandomGenerator &OSRandom() { std::unique_lock lock(_mutex); static OSRandomGenerator random; return random; } private: static std::mutex _mutex; DISALLOW_COPY_AND_ASSIGN(Random); }; } #endif src/cpp-utils/random/RandomDataBuffer.cpp000066400000000000000000000000361347701267100207240ustar00rootroot00000000000000#include "RandomDataBuffer.h" src/cpp-utils/random/RandomDataBuffer.h000066400000000000000000000025151347701267100203750ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_RANDOM_RANDOMDATABUFFER_H #define MESSMER_CPPUTILS_RANDOM_RANDOMDATABUFFER_H #include "../data/Data.h" #include "../assert/assert.h" namespace cpputils { //TODO Test class RandomDataBuffer final { public: RandomDataBuffer(); size_t size() const; void get(void *target, size_t bytes); void add(const Data& data); private: size_t _usedUntil; Data _data; DISALLOW_COPY_AND_ASSIGN(RandomDataBuffer); }; inline RandomDataBuffer::RandomDataBuffer() : _usedUntil(0), _data(0) { } inline size_t RandomDataBuffer::size() const { return _data.size() - _usedUntil; } inline void RandomDataBuffer::get(void *target, size_t numBytes) { ASSERT(size() >= numBytes, "Too many bytes requested. Buffer is smaller."); std::memcpy(target, _data.dataOffset(_usedUntil), numBytes); _usedUntil += numBytes; } inline void RandomDataBuffer::add(const Data& newData) { // Concatenate old and new random data size_t oldSize = size(); Data combined(oldSize + newData.size()); get(combined.data(), oldSize); std::memcpy(combined.dataOffset(oldSize), newData.data(), newData.size()); _data = std::move(combined); _usedUntil = 0; } } #endif src/cpp-utils/random/RandomGenerator.cpp000066400000000000000000000000341347701267100206450ustar00rootroot00000000000000#include "RandomGenerator.h"src/cpp-utils/random/RandomGenerator.h000066400000000000000000000021241347701267100203140ustar00rootroot00000000000000#ifndef MESSMER_CPPUTILS_RANDOM_RANDOMGENERATOR_H #define MESSMER_CPPUTILS_RANDOM_RANDOMGENERATOR_H #include "../data/FixedSizeData.h" #include "../data/Data.h" namespace cpputils { class RandomGenerator { public: RandomGenerator(); template FixedSizeData getFixedSize(); Data get(size_t size); void write(void *target, size_t size); protected: virtual void _get(void *target, size_t bytes) = 0; private: static std::mutex _mutex; DISALLOW_COPY_AND_ASSIGN(RandomGenerator); }; inline RandomGenerator::RandomGenerator() { } inline void RandomGenerator::write(void *target, size_t size) { _get(target, size); } template inline FixedSizeData RandomGenerator::getFixedSize() { FixedSizeData result = FixedSizeData::Null(); _get(result.data(), SIZE); return result; } inline Data RandomGenerator::get(size_t size) { Data result(size); _get(result.data(), size); return result; } } #endif src/cpp-utils/random/RandomGeneratorThread.cpp000066400000000000000000000024741347701267100220070ustar00rootroot00000000000000#include "cpp-utils/crypto/cryptopp_byte.h" #include "RandomGeneratorThread.h" namespace cpputils { RandomGeneratorThread::RandomGeneratorThread(ThreadsafeRandomDataBuffer *buffer, size_t minSize, size_t maxSize) : _randomGenerator(), _buffer(buffer), _minSize(minSize), _maxSize(maxSize), _thread(std::bind(&RandomGeneratorThread::_loopIteration, this), "RandomGeneratorThread") { ASSERT(_maxSize >= _minSize, "Invalid parameters"); } void RandomGeneratorThread::start() { return _thread.start(); } bool RandomGeneratorThread::_loopIteration() { _buffer->waitUntilSizeIsLessThan(_minSize); size_t neededRandomDataSize = _maxSize - _buffer->size(); ASSERT(_maxSize > _buffer->size(), "This could theoretically fail if another thread refilled the buffer. But we should be the only refilling thread."); Data randomData = _generateRandomData(neededRandomDataSize); _buffer->add(randomData); return true; // Run another iteration (don't terminate thread) } Data RandomGeneratorThread::_generateRandomData(size_t size) { Data newRandom(size); _randomGenerator.GenerateBlock(static_cast(newRandom.data()), size); return newRandom; } } src/cpp-utils/random/RandomGeneratorThread.h000066400000000000000000000017271347701267100214540ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_RANDOM_RANDOMGENERATORTHREAD_H #define MESSMER_CPPUTILS_RANDOM_RANDOMGENERATORTHREAD_H #include "../thread/LoopThread.h" #include "ThreadsafeRandomDataBuffer.h" #include namespace cpputils { //TODO Test class RandomGeneratorThread final { public: RandomGeneratorThread(ThreadsafeRandomDataBuffer *buffer, size_t minSize, size_t maxSize); void start(); private: bool _loopIteration(); Data _generateRandomData(size_t size); CryptoPP::AutoSeededRandomPool _randomGenerator; ThreadsafeRandomDataBuffer *_buffer; size_t _minSize; size_t _maxSize; //This has to be the last member, because it has to be destructed first - otherwise the thread could still be //running while the RandomGeneratorThread object is invalid. LoopThread _thread; DISALLOW_COPY_AND_ASSIGN(RandomGeneratorThread); }; } #endif src/cpp-utils/random/ThreadsafeRandomDataBuffer.h000066400000000000000000000052541347701267100223670ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_RANDOM_THREADSAFERANDOMDATABUFFER_H #define MESSMER_CPPUTILS_RANDOM_THREADSAFERANDOMDATABUFFER_H #include "../data/Data.h" #include "../assert/assert.h" #include "RandomDataBuffer.h" #include namespace cpputils { //TODO Test class ThreadsafeRandomDataBuffer final { public: ThreadsafeRandomDataBuffer(); size_t size() const; void get(void *target, size_t numBytes); void add(const Data& data); void waitUntilSizeIsLessThan(size_t numBytes); private: size_t _get(void *target, size_t bytes); RandomDataBuffer _buffer; mutable boost::mutex _mutex; boost::condition_variable _dataAddedCv; // _dataGottenCv needs to be boost::condition_variable and not std::condition_variable, because the // RandomGeneratorThread calling ThreadsafeRandomDataBuffer::waitUntilSizeIsLessThan() needs the waiting to be // interruptible to stop the thread in RandomGeneratorThread::stop() or in the RandomGeneratorThread destructor. boost::condition_variable _dataGottenCv; DISALLOW_COPY_AND_ASSIGN(ThreadsafeRandomDataBuffer); }; inline ThreadsafeRandomDataBuffer::ThreadsafeRandomDataBuffer(): _buffer(), _mutex(), _dataAddedCv(), _dataGottenCv() { } inline size_t ThreadsafeRandomDataBuffer::size() const { boost::unique_lock lock(_mutex); return _buffer.size(); } inline void ThreadsafeRandomDataBuffer::get(void *target, size_t numBytes) { size_t alreadyGotten = 0; while (alreadyGotten < numBytes) { size_t got = _get(static_cast(target)+alreadyGotten, numBytes); alreadyGotten += got; ASSERT(alreadyGotten <= numBytes, "Got too many bytes"); } } inline size_t ThreadsafeRandomDataBuffer::_get(void *target, size_t numBytes) { boost::unique_lock lock(_mutex); _dataAddedCv.wait(lock, [this] { return _buffer.size() > 0; }); size_t gettableBytes = (std::min)(_buffer.size(), numBytes); _buffer.get(target, gettableBytes); _dataGottenCv.notify_all(); return gettableBytes; } inline void ThreadsafeRandomDataBuffer::add(const Data& data) { boost::unique_lock lock(_mutex); _buffer.add(data); _dataAddedCv.notify_all(); } inline void ThreadsafeRandomDataBuffer::waitUntilSizeIsLessThan(size_t numBytes) { boost::unique_lock lock(_mutex); _dataGottenCv.wait(lock, [this, numBytes] { return _buffer.size() < numBytes; }); } } #endif src/cpp-utils/system/000077500000000000000000000000001347701267100151215ustar00rootroot00000000000000src/cpp-utils/system/diskspace.cpp000066400000000000000000000014771347701267100176040ustar00rootroot00000000000000#include "diskspace.h" namespace bf = boost::filesystem; #if !defined(_MSC_VER) #include #include namespace cpputils { uint64_t free_disk_space_in_bytes(const bf::path& location) { struct statvfs stat {}; int result = ::statvfs(location.string().c_str(), &stat); if (0 != result) { throw std::runtime_error("Error calling statvfs(). Errno: " + std::to_string(errno)); } return stat.f_frsize*stat.f_bavail; } } #else #include namespace cpputils { uint64_t free_disk_space_in_bytes(const bf::path& location) { ULARGE_INTEGER freeBytes; if (!GetDiskFreeSpaceEx(location.string().c_str(), &freeBytes, nullptr, nullptr)) { throw std::runtime_error("Error calling GetDiskFreeSpaceEx(). Error code: " + std::to_string(GetLastError())); } return freeBytes.QuadPart; } } #endif src/cpp-utils/system/diskspace.h000066400000000000000000000004321347701267100172370ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_SYSTEM_DISKSPACE_H #define MESSMER_CPPUTILS_SYSTEM_DISKSPACE_H #include #include namespace cpputils { // TODO Test uint64_t free_disk_space_in_bytes(const boost::filesystem::path& location); } #endif src/cpp-utils/system/env.cpp000066400000000000000000000017051347701267100164200ustar00rootroot00000000000000#include "env.h" #include #include #include #if !defined(_MSC_VER) #include namespace cpputils { void setenv(const char* key, const char* value) { int retval = ::setenv(key, value, 1); if (0 != retval) { throw std::runtime_error("Error setting environment variable. Errno: " + std::to_string(errno)); } } void unsetenv(const char* key) { int retval = ::unsetenv(key); if (0 != retval) { throw std::runtime_error("Error unsetting environment variable. Errno: " + std::to_string(errno)); } } } #else #include #include namespace cpputils { void setenv(const char* key, const char* value) { std::ostringstream command; command << key << "=" << value; int retval = _putenv(command.str().c_str()); if (0 != retval) { throw std::runtime_error("Error setting environment variable. Errno: " + std::to_string(errno)); } } void unsetenv(const char* key) { setenv(key, ""); } } #endif src/cpp-utils/system/env.h000066400000000000000000000003121347701267100160560ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_SYSTEM_ENV_H #define MESSMER_CPPUTILS_SYSTEM_ENV_H namespace cpputils { void setenv(const char* key, const char* value); void unsetenv(const char* key); } #endif src/cpp-utils/system/filetime.h000066400000000000000000000004561347701267100170750ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_SYSTEM_FILETIME_H #include namespace cpputils { int set_filetime(const char *filepath, timespec lastAccessTime, timespec lastModificationTime); int get_filetime(const char *filepath, timespec* lastAccessTime, timespec* lastModificationTime); } #endif src/cpp-utils/system/filetime_nonwindows.cpp000066400000000000000000000015721347701267100217150ustar00rootroot00000000000000#if !defined(_MSC_VER) #include "filetime.h" #include #include #include #include #include #include namespace cpputils { int set_filetime(const char *filepath, timespec lastAccessTime, timespec lastModificationTime) { std::array casted_times{}; TIMESPEC_TO_TIMEVAL(&casted_times[0], &lastAccessTime); TIMESPEC_TO_TIMEVAL(&casted_times[1], &lastModificationTime); int retval = ::utimes(filepath, casted_times.data()); if (0 == retval) { return 0; } else { return errno; } } int get_filetime(const char *filepath, timespec* lastAccessTime, timespec* lastModificationTime) { struct ::stat attrib{}; int retval = ::stat(filepath, &attrib); if (retval != 0) { return errno; } *lastAccessTime = attrib.st_atim; *lastModificationTime = attrib.st_mtim; return 0; } } #endif src/cpp-utils/system/filetime_windows.cpp000066400000000000000000000052511347701267100212000ustar00rootroot00000000000000#if defined(_MSC_VER) #include "filetime.h" #include #include namespace cpputils { namespace { FILETIME to_filetime(timespec value) { ULARGE_INTEGER ull; ull.QuadPart = (value.tv_sec * 10000000ULL) + 116444736000000000ULL + (value.tv_nsec / 100); FILETIME result; result.dwLowDateTime = ull.LowPart; result.dwHighDateTime = ull.HighPart; return result; } timespec to_timespec(FILETIME value) { // function taken from https://github.com/wishstudio/flinux/blob/afbb7d7509d4f1e0fdd62bf02124e7c4ce20ca6d/src/datetime.c under GPLv3 constexpr uint64_t NANOSECONDS_PER_TICK = 100ULL; constexpr uint64_t NANOSECONDS_PER_SECOND = 1000000000ULL; constexpr uint64_t TICKS_PER_SECOND = 10000000ULL; constexpr uint64_t SEC_TO_UNIX_EPOCH = 11644473600ULL; constexpr uint64_t TICKS_TO_UNIX_EPOCH = TICKS_PER_SECOND * SEC_TO_UNIX_EPOCH; uint64_t ticks = ((uint64_t)value.dwHighDateTime << 32ULL) + value.dwLowDateTime; uint64_t nsec = 0; if (ticks >= TICKS_TO_UNIX_EPOCH) { // otherwise out of range ticks -= TICKS_TO_UNIX_EPOCH; nsec = ticks * NANOSECONDS_PER_TICK; } timespec result; result.tv_sec = nsec / NANOSECONDS_PER_SECOND; result.tv_nsec = nsec % NANOSECONDS_PER_SECOND; return result; } struct OpenFileRAII final { HANDLE handle; OpenFileRAII(const char* filepath, DWORD access) :handle(CreateFileA(filepath, access, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)) { } BOOL close() { if (INVALID_HANDLE_VALUE == handle) { return 1; } BOOL success = CloseHandle(handle); handle = INVALID_HANDLE_VALUE; return success; } ~OpenFileRAII() { close(); } }; } int set_filetime(const char *filepath, timespec lastAccessTime, timespec lastModificationTime) { OpenFileRAII file(filepath, FILE_WRITE_ATTRIBUTES); if (INVALID_HANDLE_VALUE == file.handle) { return GetLastError(); } FILETIME atime = to_filetime(lastAccessTime); FILETIME mtime = to_filetime(lastModificationTime); BOOL success = SetFileTime(file.handle, nullptr, &atime, &mtime); if (!success) { return GetLastError(); } success = file.close(); if (!success) { return GetLastError(); } return 0; } int get_filetime(const char *filepath, timespec* lastAccessTime, timespec* lastModificationTime) { OpenFileRAII file(filepath, FILE_READ_ATTRIBUTES); if (INVALID_HANDLE_VALUE == file.handle) { return GetLastError(); } FILETIME atime; FILETIME mtime; BOOL success = GetFileTime(file.handle, nullptr, &atime, &mtime); if (!success) { return GetLastError(); } success = file.close(); if (!success) { return GetLastError(); } *lastAccessTime = to_timespec(atime); *lastModificationTime = to_timespec(mtime); return 0; } } #endif src/cpp-utils/system/get_total_memory.cpp000066400000000000000000000022021347701267100211730ustar00rootroot00000000000000#include "get_total_memory.h" #include #include #if defined(__APPLE__) #include #include namespace cpputils { namespace system { uint64_t get_total_memory() { uint64_t mem; size_t size = sizeof(mem); int result = sysctlbyname("hw.memsize", &mem, &size, nullptr, 0); if (0 != result) { throw std::runtime_error("sysctlbyname syscall failed"); } return mem; } } } #elif defined(__linux__) || defined(__FreeBSD__) #include namespace cpputils { namespace system { uint64_t get_total_memory() { long numRAMPages = sysconf(_SC_PHYS_PAGES); long pageSize = sysconf(_SC_PAGESIZE); return numRAMPages * pageSize; } } } #elif defined(_MSC_VER) #include namespace cpputils { namespace system { uint64_t get_total_memory() { MEMORYSTATUSEX status; status.dwLength = sizeof(status); if (!::GlobalMemoryStatusEx(&status)) { throw std::runtime_error("Couldn't get system memory information. Error code: " + std::to_string(GetLastError())); } return status.ullTotalPhys; } } } #else #error Unsupported platform #endif src/cpp-utils/system/get_total_memory.h000066400000000000000000000003451347701267100206460ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_SYSTEM_GETTOTALMEMORY_H #define MESSMER_CPPUTILS_SYSTEM_GETTOTALMEMORY_H #include namespace cpputils { namespace system { uint64_t get_total_memory(); } } #endif src/cpp-utils/system/homedir.cpp000066400000000000000000000054511347701267100172610ustar00rootroot00000000000000#include "homedir.h" #include namespace bf = boost::filesystem; using std::string; #if !defined(_MSC_VER) #include namespace { bf::path _get_home_directory() { struct passwd* pwd = getpwuid(getuid()); string homedir; if (pwd) { homedir = pwd->pw_dir; } else { // try the $HOME environment variable homedir = getenv("HOME"); } if (homedir == "") { throw std::runtime_error("Couldn't determine home directory for user"); } return homedir; } bf::path _get_appdata_directory() { const char* xdg_data_dir = std::getenv("XDG_DATA_HOME"); if (xdg_data_dir != nullptr) { return xdg_data_dir; } return _get_home_directory() / ".local" / "share"; } } #else #include namespace { struct PathBuffer final { PWSTR path = nullptr; ~PathBuffer() { CoTaskMemFree(path); } }; bf::path _get_known_path(KNOWNFOLDERID folderId) { PathBuffer path; HRESULT result_code = ::SHGetKnownFolderPath(folderId, 0, nullptr, &path.path); if (S_OK != result_code) { throw std::runtime_error("Failed getting user home directory. Hresult: " + std::to_string(result_code)); } bf::path result(path.path); return result; } bf::path _get_home_directory() { return _get_known_path(FOLDERID_Profile); } bf::path _get_appdata_directory() { return _get_known_path(FOLDERID_LocalAppData); } } #endif namespace bf = boost::filesystem; using std::string; namespace cpputils { namespace system { HomeDirectory::HomeDirectory() : _home_directory(::_get_home_directory()) , _appdata_directory(::_get_appdata_directory()) { } HomeDirectory &HomeDirectory::singleton() { static HomeDirectory _singleton; return _singleton; } const bf::path &HomeDirectory::get() { return singleton()._home_directory; } const bf::path &HomeDirectory::getXDGDataDir() { return singleton()._appdata_directory; } FakeHomeDirectoryRAII::FakeHomeDirectoryRAII(const bf::path& fakeHomeDirectory, const bf::path& fakeAppdataDirectory) : _oldHomeDirectory(HomeDirectory::singleton()._home_directory) , _oldAppdataDirectory(HomeDirectory::singleton()._appdata_directory) { HomeDirectory::singleton()._home_directory = fakeHomeDirectory; HomeDirectory::singleton()._appdata_directory = fakeAppdataDirectory; } FakeHomeDirectoryRAII::~FakeHomeDirectoryRAII() { // Reset to old (non-fake) value HomeDirectory::singleton()._home_directory = _oldHomeDirectory; HomeDirectory::singleton()._appdata_directory = _oldAppdataDirectory; } FakeTempHomeDirectoryRAII::FakeTempHomeDirectoryRAII() : _tempDir(), _fakeHome(_tempDir.path() / "home", _tempDir.path() / "appdata") {} } } src/cpp-utils/system/homedir.h000066400000000000000000000027131347701267100167240ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_SYSTEM_GETTOTALMEMORY_H #define MESSMER_CPPUTILS_SYSTEM_GETTOTALMEMORY_H #include #include "../macros.h" #include #include "../tempfile/TempDir.h" namespace cpputils { namespace system { class FakeHomeDirectoryRAII; class HomeDirectory final { public: static const boost::filesystem::path &get(); static const boost::filesystem::path &getXDGDataDir(); private: boost::filesystem::path _home_directory; boost::filesystem::path _appdata_directory; HomeDirectory(); static HomeDirectory &singleton(); friend class FakeHomeDirectoryRAII; DISALLOW_COPY_AND_ASSIGN(HomeDirectory); }; class FakeHomeDirectoryRAII final { public: FakeHomeDirectoryRAII(const boost::filesystem::path &fakeHomeDirectory, const boost::filesystem::path &fakeAppdataDirectory); ~FakeHomeDirectoryRAII(); private: boost::filesystem::path _oldHomeDirectory; boost::filesystem::path _oldAppdataDirectory; DISALLOW_COPY_AND_ASSIGN(FakeHomeDirectoryRAII); }; class FakeTempHomeDirectoryRAII final { public: FakeTempHomeDirectoryRAII(); private: TempDir _tempDir; FakeHomeDirectoryRAII _fakeHome; DISALLOW_COPY_AND_ASSIGN(FakeTempHomeDirectoryRAII); }; } } #endif src/cpp-utils/system/memory.h000066400000000000000000000011421347701267100166000ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_SYSTEM_MEMORY_H #define MESSMER_CPPUTILS_SYSTEM_MEMORY_H #include #include "../data/Data.h" namespace cpputils { /** * Allocator for security relevant memory like key data. * The operating system will be given a hint that this memory shouldn't be swapped out to disk * (which is, however, only a hint and might be ignored), * and we'll make sure the memory is zeroed-out when deallocated. */ class UnswappableAllocator final : public Allocator { public: void* allocate(size_t size) override; void free(void* data, size_t size) override; }; } #endif src/cpp-utils/system/memory_nonwindows.cpp000066400000000000000000000015011347701267100214170ustar00rootroot00000000000000#if !defined(_MSC_VER) #include "memory.h" #include #include #include #include using namespace cpputils::logging; namespace cpputils { void* UnswappableAllocator::allocate(size_t size) { void* data = DefaultAllocator().allocate(size); const int result = ::mlock(data, size); if (0 != result) { throw std::runtime_error("Error calling mlock. Errno: " + std::to_string(errno)); } return data; } void UnswappableAllocator::free(void* data, size_t size) { const int result = ::munlock(data, size); if (0 != result) { LOG(WARN, "Error calling munlock. Errno: {}", errno); } // overwrite the memory with zeroes before we free it std::memset(data, 0, size); DefaultAllocator().free(data, size); } } #endif src/cpp-utils/system/memory_windows.cpp000066400000000000000000000026211347701267100207100ustar00rootroot00000000000000#if defined(_MSC_VER) #include "memory.h" #include #include #include using namespace cpputils::logging; namespace cpputils { void* UnswappableAllocator::allocate(size_t size) { // VirtualAlloc allocates memory in full pages. This is needed, because VirtualUnlock unlocks full pages // and might otherwise unlock unrelated memory of other allocations. // allocate pages void* data = ::VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (nullptr == data) { throw std::runtime_error("Error calling VirtualAlloc. Errno: " + std::to_string(GetLastError())); } // lock allocated pages into RAM const BOOL success = ::VirtualLock(data, size); if (!success) { throw std::runtime_error("Error calling VirtualLock. Errno: " + std::to_string(GetLastError())); } return data; } void UnswappableAllocator::free(void* data, size_t size) { // overwrite the memory with zeroes before we free it std::memset(data, 0, size); // unlock allocated pages from RAM BOOL success = ::VirtualUnlock(data, size); if (!success) { throw std::runtime_error("Error calling VirtualUnlock. Errno: " + std::to_string(GetLastError())); } // free allocated pages success = ::VirtualFree(data, 0, MEM_RELEASE); if (!success) { throw std::runtime_error("Error calling VirtualFree. Errno: " + std::to_string(GetLastError())); } } } #endif src/cpp-utils/system/path.h000066400000000000000000000010001347701267100162150ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_SYSTEM_PATH_H #define MESSMER_CPPUTILS_SYSTEM_PATH_H #include #include namespace cpputils { #if defined(_MSC_VER) inline bool path_is_just_drive_letter(const boost::filesystem::path& path) { return path.has_root_path() && !path.has_root_directory() && !path.has_parent_path(); } #else inline constexpr bool path_is_just_drive_letter(const boost::filesystem::path& /*path*/) { return false; } #endif } #endif src/cpp-utils/system/stat.h000066400000000000000000000004631347701267100162500ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_SYSTEM_STAT_H #define MESSMER_CPPUTILS_SYSTEM_STAT_H /** * For platform independence: Apple doesn't have stat.st_atim, but stat.st_atimespec */ #ifdef __APPLE__ # define st_atim st_atimespec # define st_mtim st_mtimespec # define st_ctim st_ctimespec #endif #endif src/cpp-utils/system/time.cpp000066400000000000000000000007001347701267100165600ustar00rootroot00000000000000#include "time.h" #include using std::chrono::system_clock; using std::chrono::duration_cast; using std::chrono::seconds; using std::chrono::nanoseconds; namespace cpputils { namespace time { struct timespec now() { auto now = system_clock::now().time_since_epoch(); struct timespec spec{}; spec.tv_sec = duration_cast(now).count(); spec.tv_nsec = duration_cast(now).count() % 1000000000; return spec; } } } src/cpp-utils/system/time.h000066400000000000000000000016721347701267100162360ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_SYSTEM_TIME_H #define MESSMER_CPPUTILS_SYSTEM_TIME_H #include namespace cpputils { namespace time { timespec now(); } } inline bool operator==(const timespec &lhs, const timespec &rhs) { return lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec == rhs.tv_nsec; } inline bool operator<(const timespec &lhs, const timespec &rhs) { return lhs.tv_sec < rhs.tv_sec || (lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec < rhs.tv_nsec); } inline bool operator>(const timespec &lhs, const timespec &rhs) { return lhs.tv_sec > rhs.tv_sec || (lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec > rhs.tv_nsec); } inline bool operator!=(const timespec &lhs, const timespec &rhs) { return !operator==(lhs, rhs); } inline bool operator<=(const timespec &lhs, const timespec &rhs) { return !operator>(lhs, rhs); } inline bool operator>=(const timespec &lhs, const timespec &rhs) { return !operator<(lhs, rhs); } #endif src/cpp-utils/tempfile/000077500000000000000000000000001347701267100154025ustar00rootroot00000000000000src/cpp-utils/tempfile/TempDir.cpp000066400000000000000000000011101347701267100174430ustar00rootroot00000000000000#include "TempDir.h" #include "../logging/logging.h" namespace bf = boost::filesystem; using namespace cpputils::logging; namespace cpputils { TempDir::TempDir() : _path(bf::unique_path(bf::temp_directory_path() / "%%%%-%%%%-%%%%-%%%%")) { bf::create_directory(_path); } TempDir::~TempDir() { remove(); } void TempDir::remove() { try { if (bf::exists(_path)) { bf::remove_all(_path); } } catch (const boost::filesystem::filesystem_error &e) { LOG(ERR, "Could not delete tempfile."); } } const bf::path &TempDir::path() const { return _path; } } src/cpp-utils/tempfile/TempDir.h000066400000000000000000000006241347701267100171210ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_TEMPFILE_TEMPDIR_H_ #define MESSMER_CPPUTILS_TEMPFILE_TEMPDIR_H_ #include #include "../macros.h" namespace cpputils { class TempDir final { public: TempDir(); ~TempDir(); const boost::filesystem::path &path() const; void remove(); private: const boost::filesystem::path _path; DISALLOW_COPY_AND_ASSIGN(TempDir); }; } #endif src/cpp-utils/tempfile/TempFile.cpp000066400000000000000000000015341347701267100176160ustar00rootroot00000000000000#include "TempFile.h" #include "../logging/logging.h" #include namespace bf = boost::filesystem; using std::ofstream; using namespace cpputils::logging; namespace cpputils { TempFile::TempFile(const bf::path &path, bool create) : _path(path) { if (create) { ofstream file(_path.string().c_str()); if (!file.good()) { throw std::runtime_error("Could not create tempfile"); } } } TempFile::TempFile(bool create) : TempFile(bf::unique_path(bf::temp_directory_path() / "%%%%-%%%%-%%%%-%%%%"), create) { } TempFile::~TempFile() { try { if (exists()) { bf::remove(_path); } } catch (const boost::filesystem::filesystem_error &e) { LOG(ERR, "Could not delete tempfile."); } } bool TempFile::exists() const { return bf::exists(_path); } const bf::path &TempFile::path() const { return _path; } } src/cpp-utils/tempfile/TempFile.h000066400000000000000000000010401347701267100172530ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_TEMPFILE_TEMPFILE_H_ #define MESSMER_CPPUTILS_TEMPFILE_TEMPFILE_H_ #include #include "../macros.h" namespace cpputils { class TempFile final { public: explicit TempFile(const boost::filesystem::path &path, bool create = true); explicit TempFile(bool create = true); ~TempFile(); const boost::filesystem::path &path() const; //TODO Test exists() bool exists() const; private: const boost::filesystem::path _path; DISALLOW_COPY_AND_ASSIGN(TempFile); }; } #endif src/cpp-utils/testutils/000077500000000000000000000000001347701267100156355ustar00rootroot00000000000000src/cpp-utils/testutils/CaptureStderrRAII.h000066400000000000000000000016571347701267100212530ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CAPTURESTDERRRAII_H #define MESSMER_CPPUTILS_CAPTURESTDERRRAII_H #include #include #include #include namespace cpputils { class CaptureStderrRAII final { public: CaptureStderrRAII() { _oldBuffer = std::cerr.rdbuf(); // Capture stderr to _buffer std::cerr.rdbuf(_buffer.rdbuf()); } ~CaptureStderrRAII() { // reset std::cerr.rdbuf(_oldBuffer); } std::string get_stderr() const { return _buffer.str(); } void EXPECT_MATCHES(const std::string ®ex) { // TODO For some reason this doesn't work on MSVC // EXPECT_THAT(get_stderr(), testing::MatchesRegex(".*" + regex + ".*")); EXPECT_TRUE(std::regex_search(get_stderr(), std::regex(regex, std::regex::basic))); } private: std::stringstream _buffer; std::streambuf *_oldBuffer; DISALLOW_COPY_AND_ASSIGN(CaptureStderrRAII); }; } #endif src/cpp-utils/thread/000077500000000000000000000000001347701267100150445ustar00rootroot00000000000000src/cpp-utils/thread/LeftRight.cpp000066400000000000000000000000271347701267100174370ustar00rootroot00000000000000#include "LeftRight.h" src/cpp-utils/thread/LeftRight.h000066400000000000000000000130411347701267100171040ustar00rootroot00000000000000#include #include #include #include #include #include namespace cpputils { namespace detail { struct IncrementRAII final { public: explicit IncrementRAII(std::atomic *counter): _counter(counter) { ++(*_counter); } ~IncrementRAII() { --(*_counter); } private: std::atomic *_counter; DISALLOW_COPY_AND_ASSIGN(IncrementRAII); }; } // LeftRight wait-free readers synchronization primitive // https://hal.archives-ouvertes.fr/hal-01207881/document template class LeftRight final { public: ~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 lock(_writeMutex); } // wait until any potentially running readers are finished while (_counters[0].load() != 0 || _counters[1].load() != 0) { std::this_thread::yield(); } } template auto read(F&& readFunc) const { detail::IncrementRAII _increment_counter(&_counters[_foregroundCounterIndex.load()]); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index) if(_inDestruction.load()) { throw std::logic_error("Issued LeftRight::read() after the destructor started running"); } return readFunc(_data[_foregroundDataIndex.load()]); // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index) } // Throwing an exception in writeFunc is ok but causes the state to be either the old or the new state, // depending on if the first or the second call to writeFunc threw. template auto write(F&& writeFunc) { std::unique_lock lock(_writeMutex); if(_inDestruction.load()) { throw std::logic_error("Issued LeftRight::read() after the destructor started running"); } return _write(writeFunc); } private: template 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 return _callWriteFuncOnBackgroundInstance(writeFunc, localDataIndex); } template 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 _foregroundCounterIndex = {0}; std::atomic _foregroundDataIndex = {0}; mutable std::array, 2> _counters = {{{0}, {0}}}; std::array _data = {{{}, {}}}; std::atomic _inDestruction = {false}; }; } src/cpp-utils/thread/LoopThread.cpp000066400000000000000000000014261347701267100176140ustar00rootroot00000000000000#include "LoopThread.h" #include "../logging/logging.h" using std::function; using boost::none; namespace cpputils { LoopThread::LoopThread(function loopIteration, std::string threadName) : _loopIteration(std::move(loopIteration)), _runningHandle(none), _threadName(std::move(threadName)) { } LoopThread::~LoopThread() { if (_runningHandle != none) { stop(); } } void LoopThread::start() { _runningHandle = ThreadSystem::singleton().start(_loopIteration, _threadName); } void LoopThread::stop() { if (_runningHandle == none) { throw std::runtime_error("LoopThread is not running"); } ThreadSystem::singleton().stop(*_runningHandle); _runningHandle = none; } } src/cpp-utils/thread/LoopThread.h000066400000000000000000000020111347701267100172500ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_THREAD_LOOPTHREAD_H #define MESSMER_CPPUTILS_THREAD_LOOPTHREAD_H #include "ThreadSystem.h" #include namespace cpputils { //TODO Test //TODO Test that fork() doesn't destroy anything (e.g. no deadlock on stop() because thread is not running anymore) // Has to be final, because otherwise there could be a race condition where LoopThreadForkHandler calls a LoopThread // where the child class destructor already ran. class LoopThread final { public: // The loopIteration callback returns true, if more iterations should be run, and false, if the thread should be terminated. LoopThread(std::function loopIteration, std::string threadName); ~LoopThread(); void start(); void stop(); private: std::function _loopIteration; boost::optional _runningHandle; std::string _threadName; DISALLOW_COPY_AND_ASSIGN(LoopThread); }; } #endif src/cpp-utils/thread/ThreadSystem.cpp000066400000000000000000000110701347701267100201630ustar00rootroot00000000000000#include "ThreadSystem.h" #include "../logging/logging.h" #include "debugging.h" using std::function; using std::string; using namespace cpputils::logging; namespace cpputils { ThreadSystem &ThreadSystem::singleton() { static ThreadSystem system; return system; } ThreadSystem::ThreadSystem(): _runningThreads(), _mutex() { #if !defined(_MSC_VER) //Stopping the thread before fork() (and then also restarting it in the parent thread after fork()) is important, //because as a running thread it might hold locks or condition variables that won't play well when forked. pthread_atfork(&ThreadSystem::_onBeforeFork, &ThreadSystem::_onAfterFork, &ThreadSystem::_onAfterFork); #else // not needed on windows because we don't fork #endif } ThreadSystem::Handle ThreadSystem::start(function loopIteration, string threadName) { boost::unique_lock lock(_mutex); auto thread = _startThread(loopIteration, threadName); _runningThreads.push_back(RunningThread{std::move(threadName), std::move(loopIteration), std::move(thread)}); return std::prev(_runningThreads.end()); } void ThreadSystem::stop(Handle handle) { boost::unique_lock lock(_mutex); boost::thread thread = std::move(handle->thread); thread.interrupt(); _runningThreads.erase(handle); //It's fine if another thread gets the mutex while we still wait for the join. Joining doesn't change any internal state. lock.unlock(); thread.join(); } void ThreadSystem::_onBeforeFork() { singleton()._stopAllThreadsForRestart(); } void ThreadSystem::_onAfterFork() { singleton()._restartAllThreads(); } void ThreadSystem::_stopAllThreadsForRestart() { _mutex.lock(); // Is unlocked in the after-fork handler. This way, the whole fork() is protected. for (RunningThread &thread : _runningThreads) { if (boost::this_thread::get_id() == thread.thread.get_id()) { // This means fork was called from within one of our _runningThreads. // We cannot wait or ourselves to die. // Forking from within a thread is usually chaos since the forked process only gets a copy // of the calling thread as its new main thread. So we (hopefully) never should do this. // This is, however, a valid pattern when fork() is directly followed by an exec(). // So let's just ignore this situation and continue as if nothing happened, assuming an exec() // follows soon. continue; } thread.thread.interrupt(); } for (RunningThread &thread : _runningThreads) { if (boost::this_thread::get_id() == thread.thread.get_id()) { // This means fork was called from within one of our _runningThreads. See comment above. continue; } thread.thread.join(); } } void ThreadSystem::_restartAllThreads() { for (RunningThread &thread : _runningThreads) { if (thread.thread.joinable()) { // Because all non-self threads have been terminated in _stopAllThreadsForRestart, // this means fork was called from within one of our _runningThreads. See comment above. continue; } thread.thread = _startThread(thread.loopIteration, thread.threadName); } _mutex.unlock(); // Was locked in the before-fork handler } boost::thread ThreadSystem::_startThread(function loopIteration, const string& threadName) { return boost::thread([loopIteration = std::move(loopIteration), threadName] { cpputils::set_thread_name(threadName.c_str()); ThreadSystem::_runThread(loopIteration); }); } void ThreadSystem::_runThread(function loopIteration) { try { bool cont = true; while(cont) { boost::this_thread::interruption_point(); cont = loopIteration(); // This might also be interrupted. } //The thread is terminated gracefully. } catch (const boost::thread_interrupted &e) { //Do nothing, exit thread. } catch (const std::exception &e) { LOG(ERR, "LoopThread crashed: {}", e.what()); } catch (...) { LOG(ERR, "LoopThread crashed"); } //TODO We should remove the thread from _runningThreads here, not in stop(). } } src/cpp-utils/thread/ThreadSystem.h000066400000000000000000000030101347701267100176230ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_THREAD_THREADSYSTEM_H #define MESSMER_CPPUTILS_THREAD_THREADSYSTEM_H #include "../macros.h" #include #include #include namespace cpputils { //TODO Test class ThreadSystem final { private: struct RunningThread { std::string threadName; std::function loopIteration; // The loopIteration callback returns true, if more iterations should be run, and false, if the thread should be terminated. boost::thread thread; // boost::thread because we need it to be interruptible. }; public: using Handle = std::list::iterator; static ThreadSystem &singleton(); Handle start(std::function loopIteration, std::string threadName); void stop(Handle handle); private: ThreadSystem(); static void _runThread(std::function loopIteration); static void _onBeforeFork(); static void _onAfterFork(); //TODO Rename to _doOnBeforeFork and _doAfterFork or similar, because they also handle locking _mutex for fork(). void _stopAllThreadsForRestart(); void _restartAllThreads(); boost::thread _startThread(std::function loopIteration, const std::string& threadName); std::list _runningThreads; // std::list, because we give out iterators as handles boost::mutex _mutex; DISALLOW_COPY_AND_ASSIGN(ThreadSystem); }; } #endif src/cpp-utils/thread/debugging.h000066400000000000000000000006621347701267100171540ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_DEBUGGING_H #define MESSMER_CPPUTILS_DEBUGGING_H #include #include 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 } #endif src/cpp-utils/thread/debugging_nonwindows.cpp000066400000000000000000000055461347701267100220020ustar00rootroot00000000000000#if !defined(_MSC_VER) #include "debugging.h" #include #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 { namespace { constexpr size_t MAX_NAME_LEN = 16; // this length includes the terminating null character at the end } void set_thread_name(const char* name) { std::string name_(name); if (name_.size() > MAX_NAME_LEN - 1) { name_.resize(MAX_NAME_LEN - 1); } #if defined(__APPLE__) int result = pthread_setname_np(name_.c_str()); #else int result = pthread_setname_np(pthread_self(), name_.c_str()); #endif if (0 != result) { throw std::runtime_error("Error setting thread name with pthread_setname_np. Code: " + std::to_string(result)); } } 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) { std::array name{}; #if defined(__GLIBC__) || defined(__APPLE__) int result = pthread_getname_np(thread, name.data(), MAX_NAME_LEN); #else int result = pthread_getname_np_gcompat(thread, name.data(), MAX_NAME_LEN); #endif if (0 != result) { throw std::runtime_error("Error getting thread name with pthread_getname_np. Code: " + std::to_string(result)); } // pthread_getname_np returns a null terminated string with maximum 16 bytes. // but just to be safe against a buggy implementation, let's set the last byte to zero. name[MAX_NAME_LEN - 1] = '\0'; return name.data(); } } 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 } #endif src/cpp-utils/thread/debugging_windows.cpp000066400000000000000000000060441347701267100212610ustar00rootroot00000000000000#if defined(_MSC_VER) #include #include "debugging.h" #include #include using std::string; using std::wstring; using std::wstring_convert; namespace cpputils { namespace { struct NameData final { wchar_t *name = nullptr; ~NameData() { if (nullptr != LocalFree(name)) { throw std::runtime_error("Error releasing thread description memory. Error code: " + std::to_string(GetLastError())); } } }; struct ModuleHandle final { HMODULE module; ModuleHandle(const char* dll) { bool success = GetModuleHandleExA(0, dll, &module); if (!success) { throw std::runtime_error(string() + "Error loading dll: " + dll + ". Error code: " + std::to_string(GetLastError())); } } ~ModuleHandle() { bool success = FreeLibrary(module); if (!success) { throw std::runtime_error("Error unloading dll. Error code: " + std::to_string(GetLastError())); } } }; template class APIFunction final { private: ModuleHandle module_; Fn func_; public: APIFunction(const char* dll, const char* function) : module_(dll), func_(reinterpret_cast(GetProcAddress(module_.module, function))) { } bool valid() const { return func_ != nullptr; } Fn func() const { return func_; } }; std::string get_thread_name(HANDLE thread) { // The GetThreadDescription API was brought in version 1607 of Windows 10. typedef HRESULT(WINAPI* GetThreadDescriptionFn)(HANDLE hThread, PWSTR* ppszThreadDescription); static APIFunction get_thread_description_func("Kernel32.dll", "GetThreadDescription"); if (get_thread_description_func.valid()) { NameData name_data; HRESULT status = get_thread_description_func.func()(thread, &name_data.name); if (FAILED(status)) { throw std::runtime_error("Error getting thread description. Error code: " + std::to_string(status)); } return wstring_convert>().to_bytes(name_data.name); } else { // GetThreadDescription API is not available. return ""; } } } void set_thread_name(const char* name) { // The GetThreadDescription API was brought in version 1607 of Windows 10. typedef HRESULT(WINAPI* SetThreadDescriptionFn)(HANDLE hThread, PCWSTR lpThreadDescription); static APIFunction set_thread_description_func("Kernel32.dll", "SetThreadDescription"); if (set_thread_description_func.valid()) { wstring wname = wstring_convert>().from_bytes(name); HRESULT status = set_thread_description_func.func()(GetCurrentThread(), wname.c_str()); if (FAILED(status)) { throw std::runtime_error("Error setting thread description. Error code: " + std::to_string(status)); } } else { // intentionally empty. SetThreadDescription API is not available. } } std::string get_thread_name() { return get_thread_name(GetCurrentThread()); } std::string get_thread_name(std::thread* thread) { ASSERT(thread->joinable(), "Thread not running"); return get_thread_name(static_cast(thread->native_handle())); } } #endif src/cpp-utils/value_type/000077500000000000000000000000001347701267100157525ustar00rootroot00000000000000src/cpp-utils/value_type/ValueType.cpp000066400000000000000000000000271347701267100203730ustar00rootroot00000000000000#include "ValueType.h" src/cpp-utils/value_type/ValueType.h000066400000000000000000000250231347701267100200430ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_VALUETYPE_VALUETYPE_H_ #define MESSMER_CPPUTILS_VALUETYPE_VALUETYPE_H_ #include #include namespace cpputils { namespace value_type { /** * This template simplifies generation of simple classes that wrap an id * in a typesafe way. Namely, you can use it to create a very lightweight * type that only offers equality comparators and hashing. Example: * * struct MyIdType final : IdValueType { * constexpr explicit MyIdType(uint32_t id): IdValueType(id) {} * }; * * Then in the global top level namespace: * * DEFINE_HASH_FOR_VALUE_TYPE(MyIdType); * * That's it - equality operators and hash functions are automatically defined * for you, given the underlying type supports it. * * OrderedIdValueType: Use this instead of IdValueType if you need an ordering relation on your id type. * This will define the operators * - val < val * - val > val * - val <= val * - val >= val * * QuantityValueType: Use this if you want a full-blown value type with arithmetics. * Additionally to what OrderedIdValueType offers, this also defines: * - ++val, val++ * - --val, val-- * - val += val (returns val) * - val -= val (returns val) * - val *= scalar (returns val) * - val /= scalar (returns val) * - val %= scalar (returns val) * - val + val (returns val) * - val - val (returns val) * - val * scalar, scalar * val (returns val) * - val / scalar (returns val) * - val % scalar (returns val) * - val / val (returns scalar) * - val % val (returns scalar) * * FlagsValueType: Use this if you want a value type for bitfields (i.e. flags). * Additionally to what IdValueType offers, this also defines: * - val |= val (returns val) * - val &= val (returns val) * - val ^= val (returns val) * - val | val (returns val) * - val & val (returns val) * - val ^ val (returns val) */ template class IdValueType { public: using underlying_type = UnderlyingType; using concrete_type = ConcreteType; constexpr IdValueType(IdValueType&& rhs) noexcept(noexcept(UnderlyingType(std::move(std::declval())))) = default; constexpr IdValueType(const IdValueType& rhs) noexcept(noexcept(UnderlyingType(std::declval()))) = default; constexpr IdValueType& operator=(IdValueType&& rhs) noexcept(noexcept(*std::declval() = std::move(rhs.value_))) { value_ = std::move(rhs.value_); return *this; } constexpr IdValueType& operator=(const IdValueType& rhs) noexcept(noexcept(*std::declval() = rhs.value_)) { value_ = rhs.value_; return *this; } protected: constexpr explicit IdValueType(underlying_type value) noexcept(noexcept(UnderlyingType(value))) : value_(value) { static_assert(std::is_base_of, ConcreteType>::value, "CRTP violated. First template parameter of this class must be the concrete class."); } friend struct std::hash; friend constexpr bool operator==(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(std::declval() == std::declval())) { return lhs.value_ == rhs.value_; } friend constexpr bool operator!=(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(lhs == rhs)) { return !operator==(lhs, rhs); } underlying_type value_; }; #define DEFINE_HASH_FOR_VALUE_TYPE(ClassName) \ namespace std { \ template <> \ struct hash { \ size_t operator()(ClassName x) const noexcept(noexcept(std::hash()(x.value_))) { \ return std::hash()(x.value_); \ } \ }; \ } template class OrderedIdValueType : public IdValueType { protected: using IdValueType::IdValueType; friend constexpr bool operator<(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(lhs.value_ < rhs.value_)) { return lhs.value_ < rhs.value_; } friend constexpr bool operator>(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(lhs.value_ > rhs.value_)) { return lhs.value_ > rhs.value_; } friend constexpr bool operator>=(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(lhs < rhs)) { return !operator<(lhs, rhs); } friend constexpr bool operator<=(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(lhs > rhs)) { return !operator>(lhs, rhs); } }; template class QuantityValueType : public OrderedIdValueType { protected: using OrderedIdValueType::OrderedIdValueType; public: constexpr ConcreteType& operator++() noexcept(noexcept(++*std::declval())) { ++this->value_; return *static_cast(this); } constexpr const ConcreteType operator++(int) noexcept(noexcept(++std::declval())) { ConcreteType tmp = *static_cast(this); ++(*this); return tmp; } constexpr ConcreteType& operator--() noexcept(noexcept(--*std::declval())) { --this->value_; return *static_cast(this); } constexpr const ConcreteType operator--(int) noexcept(noexcept(--std::declval())) { ConcreteType tmp = *static_cast(this); --(*this); return tmp; } constexpr ConcreteType& operator+=(ConcreteType rhs) noexcept(noexcept(*std::declval() += std::declval())) { this->value_ += rhs.value_; return *static_cast(this); } constexpr ConcreteType& operator-=(ConcreteType rhs) noexcept(noexcept(*std::declval() -= std::declval())) { this->value_ -= rhs.value_; return *static_cast(this); } constexpr ConcreteType& operator*=(UnderlyingType rhs) noexcept(noexcept(*std::declval() *= std::declval())) { this->value_ *= rhs; return *static_cast(this); } constexpr ConcreteType& operator/=(UnderlyingType rhs) noexcept(noexcept(*std::declval() /= std::declval())) { this->value_ /= rhs; return *static_cast(this); } constexpr ConcreteType& operator%=(UnderlyingType rhs) noexcept(noexcept(*std::declval() %= std::declval())) { this->value_ %= rhs; return *static_cast(this); } private: friend constexpr ConcreteType operator+(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(std::declval() += std::declval())) { return lhs += rhs; } friend constexpr ConcreteType operator-(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(std::declval() -= std::declval())) { return lhs -= rhs; } friend constexpr ConcreteType operator*(ConcreteType lhs, UnderlyingType rhs) noexcept(noexcept(std::declval() *= std::declval())) { return lhs *= rhs; } friend constexpr ConcreteType operator*(UnderlyingType lhs, ConcreteType rhs) noexcept(noexcept(std::declval() * std::declval())) { return rhs * lhs; } friend constexpr ConcreteType operator/(ConcreteType lhs, UnderlyingType rhs) noexcept(noexcept(std::declval() /= std::declval())) { return lhs /= rhs; } friend constexpr UnderlyingType operator/(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(std::declval() / std::declval())) { return lhs.value_ / rhs.value_; } friend constexpr ConcreteType operator%(ConcreteType lhs, UnderlyingType rhs) noexcept(noexcept(std::declval() %= std::declval())) { return lhs %= rhs; } friend constexpr UnderlyingType operator%(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(std::declval() % std::declval())) { return lhs.value_ % rhs.value_; } }; template class FlagsValueType : public IdValueType { protected: using IdValueType::IdValueType; public: constexpr ConcreteType& operator&=(ConcreteType rhs) noexcept(noexcept(*std::declval() &= std::declval())) { this->value_ &= rhs.value_; return *static_cast(this); } constexpr ConcreteType& operator|=(ConcreteType rhs) noexcept(noexcept(*std::declval() |= std::declval())) { this->value_ |= rhs.value_; return *static_cast(this); } constexpr ConcreteType& operator^=(ConcreteType rhs) noexcept(noexcept(*std::declval() ^= std::declval())) { this->value_ ^= rhs.value_; return *static_cast(this); } constexpr ConcreteType operator~() noexcept(noexcept(~*std::declval())) { return ConcreteType(~this->value_); } friend constexpr ConcreteType operator&(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(std::declval() &= std::declval())) { return lhs &= rhs; } friend constexpr ConcreteType operator|(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(std::declval() |= std::declval())) { return lhs |= rhs; } friend constexpr ConcreteType operator^(ConcreteType lhs, ConcreteType rhs) noexcept(noexcept(std::declval() ^= std::declval())) { return lhs ^= rhs; } }; } } #endif src/cryfs-cli/000077500000000000000000000000001347701267100135505ustar00rootroot00000000000000src/cryfs-cli/CMakeLists.txt000066400000000000000000000020051347701267100163050ustar00rootroot00000000000000project (cryfs-cli) INCLUDE(GNUInstallDirs) set(SOURCES Cli.cpp VersionChecker.cpp CallAfterTimeout.cpp Environment.cpp program_options/utils.cpp program_options/ProgramOptions.cpp program_options/Parser.cpp ) add_library(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} PUBLIC cryfs cpp-utils gitversion fspp-fuse) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) if(NOT CRYFS_UPDATE_CHECKS) target_compile_definitions(${PROJECT_NAME} PRIVATE -DCRYFS_NO_UPDATE_CHECKS) endif(NOT CRYFS_UPDATE_CHECKS) add_executable(${PROJECT_NAME}_bin main.cpp) set_target_properties(${PROJECT_NAME}_bin PROPERTIES OUTPUT_NAME cryfs) target_link_libraries(${PROJECT_NAME}_bin PUBLIC ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}_bin) target_activate_cpp14(${PROJECT_NAME}_bin) install(TARGETS ${PROJECT_NAME}_bin CONFIGURATIONS Debug Release RelWithDebInfo DESTINATION ${CMAKE_INSTALL_BINDIR} ) src/cryfs-cli/CallAfterTimeout.cpp000066400000000000000000000000361347701267100174570ustar00rootroot00000000000000#include "CallAfterTimeout.h" src/cryfs-cli/CallAfterTimeout.h000066400000000000000000000041011347701267100171210ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFSCLI_CALLAFTERTIMEOUT_H #define MESSMER_CRYFSCLI_CALLAFTERTIMEOUT_H #include #include #include namespace cryfs_cli { class CallAfterTimeout final { public: CallAfterTimeout(boost::chrono::milliseconds timeout, std::function callback, const std::string& timeoutName); void resetTimer(); private: bool _checkTimeoutThreadIteration(); boost::chrono::time_point _targetTime(); bool _callCallbackIfTimeout(); std::function _callback; boost::chrono::milliseconds _timeout; boost::chrono::time_point _start; cpputils::LoopThread _checkTimeoutThread; std::mutex _mutex; DISALLOW_COPY_AND_ASSIGN(CallAfterTimeout); }; inline CallAfterTimeout::CallAfterTimeout(boost::chrono::milliseconds timeout, std::function callback, const std::string& timeoutName) :_callback(std::move(callback)), _timeout(timeout), _start(), _checkTimeoutThread(std::bind(&CallAfterTimeout::_checkTimeoutThreadIteration, this), "timeout_" + timeoutName) { resetTimer(); _checkTimeoutThread.start(); } inline void CallAfterTimeout::resetTimer() { std::unique_lock lock(_mutex); _start = boost::chrono::steady_clock::now(); } inline bool CallAfterTimeout::_checkTimeoutThreadIteration() { boost::this_thread::sleep_until(_targetTime()); return _callCallbackIfTimeout(); } inline boost::chrono::time_point CallAfterTimeout::_targetTime() { std::unique_lock lock(_mutex); return _start + _timeout; } inline bool CallAfterTimeout::_callCallbackIfTimeout() { std::unique_lock lock(_mutex); if (boost::chrono::steady_clock::now() >= _start + _timeout) { _callback(); return false; // Stop thread } return true; // Continue thread } } #endif src/cryfs-cli/Cli.cpp000066400000000000000000000466601347701267100147770ustar00rootroot00000000000000#include "Cli.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "program_options/Parser.h" #include #include #include #include "VersionChecker.h" #include #include #include #include #include "Environment.h" #include #include //TODO Many functions accessing the ProgramOptions object. Factor out into class that stores it as a member. //TODO Factor out class handling askPassword using namespace cryfs_cli; using namespace cryfs; namespace bf = boost::filesystem; using namespace cpputils::logging; using blockstore::ondisk::OnDiskBlockStore2; using program_options::ProgramOptions; using cpputils::make_unique_ref; using cpputils::NoninteractiveConsole; using cpputils::TempFile; using cpputils::RandomGenerator; using cpputils::unique_ref; using cpputils::SCrypt; using cpputils::SCryptSettings; using cpputils::Console; using cpputils::HttpClient; using std::cout; using std::string; using std::endl; using std::shared_ptr; using std::make_shared; using std::unique_ptr; using std::make_unique; using std::function; using boost::optional; using boost::none; using boost::chrono::minutes; using boost::chrono::milliseconds; using cpputils::dynamic_pointer_move; using gitversion::VersionCompare; //TODO Delete a large file in parallel possible? Takes a long time right now... //TODO Improve parallelity. //TODO Replace ASSERTs with other error handling when it is not a programming error but an environment influence (e.g. a block is missing) //TODO Can we improve performance by setting compiler parameter -maes for scrypt? namespace cryfs_cli { Cli::Cli(RandomGenerator &keyGenerator, const SCryptSettings &scryptSettings, shared_ptr console): _keyGenerator(keyGenerator), _scryptSettings(scryptSettings), _console(), _noninteractive(false), _idleUnmounter(none), _device(none) { _noninteractive = Environment::isNoninteractive(); if (_noninteractive) { _console = make_shared(console); } else { _console = console; } } void Cli::_showVersion(unique_ref httpClient) { cout << "CryFS Version " << gitversion::VersionString() << endl; if (gitversion::IsDevVersion()) { cout << "WARNING! This is a development version based on git commit " << gitversion::GitCommitId() << ". Please do not use in production!" << endl; } else if (!gitversion::IsStableVersion()) { cout << "WARNING! This is an experimental version. Please backup your data frequently!" << endl; } #ifndef NDEBUG cout << "WARNING! This is a debug build. Performance might be slow." << endl; #endif #ifndef CRYFS_NO_UPDATE_CHECKS if (Environment::noUpdateCheck()) { cout << "Automatic checking for security vulnerabilities and updates is disabled." << endl; } else if (Environment::isNoninteractive()) { cout << "Automatic checking for security vulnerabilities and updates is disabled in noninteractive mode." << endl; } else { _checkForUpdates(std::move(httpClient)); } #else # warning Update checks are disabled. The resulting executable will not go online to check for newer versions or known security vulnerabilities. #endif cout << endl; } void Cli::_checkForUpdates(unique_ref httpClient) { VersionChecker versionChecker(httpClient.get()); optional newestVersion = versionChecker.newestVersion(); if (newestVersion == none) { cout << "Could not check for updates." << endl; } else if (VersionCompare::isOlderThan(gitversion::VersionString(), *newestVersion)) { cout << "CryFS " << *newestVersion << " is released. Please update." << endl; } optional securityWarning = versionChecker.securityWarningFor(gitversion::VersionString()); if (securityWarning != none) { cout << *securityWarning << endl; } } bool Cli::_checkPassword(const string &password) { if (password == "") { std::cerr << "Empty password not allowed. Please try again." << std::endl; return false; } return true; } function Cli::_askPasswordForExistingFilesystem(std::shared_ptr console) { return [console] () { string password = console->askPassword("Password: "); while (!_checkPassword(password)) { password = console->askPassword("Password: "); } return password; }; }; function Cli::_askPasswordForNewFilesystem(std::shared_ptr console) { //TODO Ask confirmation if using insecure password (<8 characters) return [console] () { string password; bool again = false; do { password = console->askPassword("Password: "); if (!_checkPassword(password)) { again = true; continue; } if (!_confirmPassword(console.get(), password)) { again = true; continue; } again = false; } while (again); return password; }; } bool Cli::_confirmPassword(cpputils::Console* console, const string &password) { string confirmPassword = console->askPassword("Confirm Password: "); if (password != confirmPassword) { std::cout << "Passwords don't match" << std::endl; return false; } return true; } function Cli::_askPasswordNoninteractive(std::shared_ptr console) { //TODO Test return [console] () { string password = console->askPassword("Password: "); if (!_checkPassword(password)) { throw CryfsException("Invalid password. Password cannot be empty.", ErrorCode::EmptyPassword); } return password; }; } bf::path Cli::_determineConfigFile(const ProgramOptions &options) { auto configFile = options.configFile(); if (configFile == none) { return bf::path(options.baseDir()) / "cryfs.config"; } return *configFile; } void Cli::_checkConfigIntegrity(const bf::path& basedir, const LocalStateDir& localStateDir, const CryConfigFile& config, bool allowReplacedFilesystem) { auto basedirMetadata = BasedirMetadata::load(localStateDir); if (!allowReplacedFilesystem && !basedirMetadata.filesystemIdForBasedirIsCorrect(basedir, config.config()->FilesystemId())) { if (!_console->askYesNo("The filesystem id in the config file is different to the last time we loaded a filesystem from this basedir. This can be genuine if you replaced the filesystem with a different one. If you didn't do that, it is possible that an attacker did. Do you want to continue loading the file system?", false)) { throw CryfsException( "The filesystem id in the config file is different to the last time we loaded a filesystem from this basedir.", ErrorCode::FilesystemIdChanged); } } // Update local state (or create it if it didn't exist yet) basedirMetadata.updateFilesystemIdForBasedir(basedir, config.config()->FilesystemId()); basedirMetadata.save(); } CryConfigLoader::ConfigLoadResult Cli::_loadOrCreateConfig(const ProgramOptions &options, const LocalStateDir& localStateDir) { auto configFile = _determineConfigFile(options); auto config = _loadOrCreateConfigFile(std::move(configFile), localStateDir, options.cipher(), options.blocksizeBytes(), options.allowFilesystemUpgrade(), options.missingBlockIsIntegrityViolation(), options.allowReplacedFilesystem()); if (config == none) { throw CryfsException("Could not load config file. Did you enter the correct password?", ErrorCode::WrongPassword); } _checkConfigIntegrity(options.baseDir(), localStateDir, config->configFile, options.allowReplacedFilesystem()); return std::move(*config); } optional Cli::_loadOrCreateConfigFile(bf::path configFilePath, LocalStateDir localStateDir, const optional &cipher, const optional &blocksizeBytes, bool allowFilesystemUpgrade, const optional &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem) { // TODO Instead of passing in _askPasswordXXX functions to KeyProvider, only pass in console and move logic to the key provider, // for example by having a separate CryPasswordBasedKeyProvider / CryNoninteractivePasswordBasedKeyProvider. auto keyProvider = make_unique_ref( _console, _noninteractive ? Cli::_askPasswordNoninteractive(_console) : Cli::_askPasswordForExistingFilesystem(_console), _noninteractive ? Cli::_askPasswordNoninteractive(_console) : Cli::_askPasswordForNewFilesystem(_console), make_unique_ref(_scryptSettings) ); return CryConfigLoader(_console, _keyGenerator, std::move(keyProvider), std::move(localStateDir), cipher, blocksizeBytes, missingBlockIsIntegrityViolation).loadOrCreate(std::move(configFilePath), allowFilesystemUpgrade, allowReplacedFilesystem); } void Cli::_runFilesystem(const ProgramOptions &options, std::function onMounted) { try { LocalStateDir localStateDir(Environment::localStateDir()); auto blockStore = make_unique_ref(options.baseDir()); auto config = _loadOrCreateConfig(options, localStateDir); unique_ptr fuse = nullptr; bool stoppedBecauseOfIntegrityViolation = false; auto onIntegrityViolation = [&fuse, &stoppedBecauseOfIntegrityViolation] () { if (fuse.get() != nullptr) { LOG(ERR, "Integrity violation detected. Unmounting."); stoppedBecauseOfIntegrityViolation = true; fuse->stop(); } else { // Usually on an integrity violation, the file system is unmounted. // Here, the file system isn't initialized yet, i.e. we failed in the initial steps when // setting up _device before running initFilesystem. // We can't unmount a not-mounted file system, but we can make sure it doesn't get mounted. throw CryfsException("Integrity violation detected. Unmounting.", ErrorCode::IntegrityViolation); } }; const bool missingBlockIsIntegrityViolation = config.configFile.config()->missingBlockIsIntegrityViolation(); _device = optional>(make_unique_ref(std::move(config.configFile), std::move(blockStore), std::move(localStateDir), config.myClientId, options.allowIntegrityViolations(), missingBlockIsIntegrityViolation, std::move(onIntegrityViolation))); _sanityCheckFilesystem(_device->get()); auto initFilesystem = [&] (fspp::fuse::Fuse *fs){ ASSERT(_device != none, "File system not ready to be initialized. Was it already initialized before?"); //TODO Test auto unmounting after idle timeout const boost::optional idle_minutes = options.unmountAfterIdleMinutes(); _idleUnmounter = _createIdleCallback(idle_minutes, [fs, idle_minutes] { LOG(INFO, "Unmounting because file system was idle for {} minutes", *idle_minutes); fs->stop(); }); if (_idleUnmounter != none) { (*_device)->onFsAction(std::bind(&CallAfterTimeout::resetTimer, _idleUnmounter->get())); } return make_shared(std::move(*_device)); }; fuse = make_unique(initFilesystem, std::move(onMounted), "cryfs", "cryfs@" + options.baseDir().string()); _initLogfile(options); std::cout << "\nMounting filesystem. To unmount, call:\n$ cryfs-unmount " << options.mountDir() << "\n" << std::endl; fuse->run(options.mountDir(), options.fuseOptions()); if (stoppedBecauseOfIntegrityViolation) { throw CryfsException("Integrity violation detected. Unmounting.", ErrorCode::IntegrityViolation); } } catch (const CryfsException &e) { throw; // CryfsException is only thrown if setup goes wrong. Throw it through so that we get the correct process exit code. } catch (const std::exception &e) { LOG(ERR, "Crashed: {}", e.what()); } catch (...) { LOG(ERR, "Crashed"); } } void Cli::_sanityCheckFilesystem(CryDevice *device) { //Try to list contents of base directory auto _rootDir = device->Load("/"); // this might throw an exception if the root blob doesn't exist if (_rootDir == none) { throw CryfsException("Couldn't find root blob", ErrorCode::InvalidFilesystem); } auto rootDir = dynamic_pointer_move(*_rootDir); if (rootDir == none) { throw CryfsException("Base directory blob doesn't contain a directory", ErrorCode::InvalidFilesystem); } (*rootDir)->children(); // Load children } optional> Cli::_createIdleCallback(optional minutes, function callback) { if (minutes == none) { return none; } uint64_t millis = std::llround(60000 * (*minutes)); return make_unique_ref(milliseconds(millis), callback, "idlecallback"); } void Cli::_initLogfile(const ProgramOptions &options) { spdlog::drop("cryfs"); //TODO Test that --logfile parameter works. Should be: file if specified, otherwise stderr if foreground, else syslog. if (options.logFile() != none) { cpputils::logging::setLogger( spdlog::create>("cryfs", options.logFile()->string())); } else if (options.foreground()) { cpputils::logging::setLogger(spdlog::stderr_logger_mt("cryfs")); } else { cpputils::logging::setLogger(cpputils::logging::system_logger("cryfs")); } } void Cli::_sanityChecks(const ProgramOptions &options) { _checkDirAccessible(bf::absolute(options.baseDir()), "base directory", ErrorCode::InaccessibleBaseDir); if (!options.mountDirIsDriveLetter()) { _checkDirAccessible(options.mountDir(), "mount directory", ErrorCode::InaccessibleMountDir); _checkMountdirDoesntContainBasedir(options); } else { if (bf::exists(options.mountDir())) { throw CryfsException("Drive " + options.mountDir().string() + " already exists.", ErrorCode::InaccessibleMountDir); } } } void Cli::_checkDirAccessible(const bf::path &dir, const std::string &name, ErrorCode errorCode) { if (!bf::exists(dir)) { bool create = _console->askYesNo("Could not find " + name + ". Do you want to create it?", false); if (create) { if (!bf::create_directory(dir)) { throw CryfsException("Error creating "+name, errorCode); } } else { //std::cerr << "Exit code: " << exitCode(errorCode) << std::endl; throw CryfsException(name + " not found.", errorCode); } } if (!bf::is_directory(dir)) { throw CryfsException(name+" is not a directory.", errorCode); } auto file = _checkDirWriteable(dir, name, errorCode); _checkDirReadable(dir, file, name, errorCode); } shared_ptr Cli::_checkDirWriteable(const bf::path &dir, const std::string &name, ErrorCode errorCode) { auto path = dir / "tempfile"; try { return make_shared(path); } catch (const std::runtime_error &e) { throw CryfsException("Could not write to "+name+".", errorCode); } } void Cli::_checkDirReadable(const bf::path &dir, shared_ptr tempfile, const std::string &name, ErrorCode errorCode) { ASSERT(bf::equivalent(dir, tempfile->path().parent_path()), "This function should be called with a file inside the directory"); try { bool found = false; bf::directory_iterator end; for (auto iter = bf::directory_iterator(dir); iter != end; ++iter) { if (bf::equivalent(*iter, tempfile->path())) { found = true; } } if (!found) { //This should not happen. Can only happen if the written temp file got deleted inbetween or maybe was not written at all. throw std::runtime_error("Error accessing "+name+"."); } } catch (const boost::filesystem::filesystem_error &e) { throw CryfsException("Could not read from "+name+".", errorCode); } } void Cli::_checkMountdirDoesntContainBasedir(const ProgramOptions &options) { if (_pathContains(options.mountDir(), options.baseDir())) { throw CryfsException("base directory can't be inside the mount directory.", ErrorCode::BaseDirInsideMountDir); } } bool Cli::_pathContains(const bf::path &parent, const bf::path &child) { bf::path absParent = bf::canonical(parent); bf::path current = bf::canonical(child); if (absParent.empty() && current.empty()) { return true; } while(!current.empty()) { if (bf::equivalent(current, absParent)) { return true; } current = current.parent_path(); } return false; } int Cli::main(int argc, const char **argv, unique_ref httpClient, std::function onMounted) { cpputils::showBacktraceOnCrash(); cpputils::set_thread_name("cryfs"); try { _showVersion(std::move(httpClient)); ProgramOptions options = program_options::Parser(argc, argv).parse(CryCiphers::supportedCipherNames()); _sanityChecks(options); _runFilesystem(options, std::move(onMounted)); } catch (const CryfsException &e) { if (e.what() != string()) { std::cerr << "Error " << static_cast(e.errorCode()) << ": " << e.what() << std::endl; } return exitCode(e.errorCode()); } catch (const std::runtime_error &e) { std::cerr << "Error: " << e.what() << std::endl; return exitCode(ErrorCode::UnspecifiedError); } return exitCode(ErrorCode::Success); } } src/cryfs-cli/Cli.h000066400000000000000000000074531347701267100144410ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFSCLI_CLI_H #define MESSMER_CRYFSCLI_CLI_H #include "program_options/ProgramOptions.h" #include #include #include #include #include #include #include #include "CallAfterTimeout.h" #include #include namespace cryfs_cli { class Cli final { public: Cli(cpputils::RandomGenerator &keyGenerator, const cpputils::SCryptSettings& scryptSettings, std::shared_ptr console); int main(int argc, const char **argv, cpputils::unique_ref httpClient, std::function onMounted); private: void _checkForUpdates(cpputils::unique_ref httpClient); void _runFilesystem(const program_options::ProgramOptions &options, std::function onMounted); cryfs::CryConfigLoader::ConfigLoadResult _loadOrCreateConfig(const program_options::ProgramOptions &options, const cryfs::LocalStateDir& localStateDir); void _checkConfigIntegrity(const boost::filesystem::path& basedir, const cryfs::LocalStateDir& localStateDir, const cryfs::CryConfigFile& config, bool allowReplacedFilesystem); boost::optional _loadOrCreateConfigFile(boost::filesystem::path configFilePath, cryfs::LocalStateDir localStateDir, const boost::optional &cipher, const boost::optional &blocksizeBytes, bool allowFilesystemUpgrade, const boost::optional &missingBlockIsIntegrityViolation, bool allowReplacedFilesystem); boost::filesystem::path _determineConfigFile(const program_options::ProgramOptions &options); static std::function _askPasswordForExistingFilesystem(std::shared_ptr console); static std::function _askPasswordForNewFilesystem(std::shared_ptr console); static std::function _askPasswordNoninteractive(std::shared_ptr console); static bool _confirmPassword(cpputils::Console* console, const std::string &password); static bool _checkPassword(const std::string &password); void _showVersion(cpputils::unique_ref httpClient); void _initLogfile(const program_options::ProgramOptions &options); void _sanityChecks(const program_options::ProgramOptions &options); void _checkMountdirDoesntContainBasedir(const program_options::ProgramOptions &options); bool _pathContains(const boost::filesystem::path &parent, const boost::filesystem::path &child); void _checkDirAccessible(const boost::filesystem::path &dir, const std::string &name, cryfs::ErrorCode errorCode); std::shared_ptr _checkDirWriteable(const boost::filesystem::path &dir, const std::string &name, cryfs::ErrorCode errorCode); void _checkDirReadable(const boost::filesystem::path &dir, std::shared_ptr tempfile, const std::string &name, cryfs::ErrorCode errorCode); boost::optional> _createIdleCallback(boost::optional minutes, std::function callback); void _sanityCheckFilesystem(cryfs::CryDevice *device); cpputils::RandomGenerator &_keyGenerator; cpputils::SCryptSettings _scryptSettings; std::shared_ptr _console; bool _noninteractive; boost::optional> _idleUnmounter; boost::optional> _device; DISALLOW_COPY_AND_ASSIGN(Cli); }; } #endif src/cryfs-cli/Environment.cpp000066400000000000000000000023151347701267100165610ustar00rootroot00000000000000#include "Environment.h" #include #include #include using std::string; namespace bf = boost::filesystem; namespace cryfs_cli { const string Environment::FRONTEND_KEY = "CRYFS_FRONTEND"; const string Environment::FRONTEND_NONINTERACTIVE = "noninteractive"; const string Environment::NOUPDATECHECK_KEY = "CRYFS_NO_UPDATE_CHECK"; const string Environment::LOCALSTATEDIR_KEY = "CRYFS_LOCAL_STATE_DIR"; bool Environment::isNoninteractive() { char *frontend = std::getenv(FRONTEND_KEY.c_str()); return frontend != nullptr && frontend == FRONTEND_NONINTERACTIVE; } bool Environment::noUpdateCheck() { return nullptr != std::getenv(NOUPDATECHECK_KEY.c_str()); } const bf::path& Environment::defaultLocalStateDir() { static const bf::path value = cpputils::system::HomeDirectory::getXDGDataDir() / "cryfs"; return value; } bf::path Environment::localStateDir() { const char* localStateDir = std::getenv(LOCALSTATEDIR_KEY.c_str()); if (nullptr == localStateDir) { return defaultLocalStateDir(); } return bf::absolute(localStateDir); } } src/cryfs-cli/Environment.h000066400000000000000000000012551347701267100162300ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFSCLI_ENVIRONMENT_H #define MESSMER_CRYFSCLI_ENVIRONMENT_H #include #include namespace cryfs_cli { class Environment { public: static bool isNoninteractive(); static bool noUpdateCheck(); static boost::filesystem::path localStateDir(); static const boost::filesystem::path& defaultLocalStateDir(); static const std::string FRONTEND_KEY; static const std::string FRONTEND_NONINTERACTIVE; static const std::string NOUPDATECHECK_KEY; static const std::string LOCALSTATEDIR_KEY; private: Environment() = delete; }; } #endif src/cryfs-cli/VersionChecker.cpp000066400000000000000000000040661347701267100171740ustar00rootroot00000000000000#include "VersionChecker.h" #include #include #include #include #include using boost::optional; using boost::none; using std::string; using cpputils::HttpClient; using boost::property_tree::ptree; using boost::property_tree::json_parser_error; using namespace cpputils::logging; namespace cryfs_cli { VersionChecker::VersionChecker(HttpClient* httpClient) : _versionInfo(_getVersionInfo(httpClient)) {} optional VersionChecker::newestVersion() const { if (_versionInfo == none) { return none; } string version = _versionInfo->get("version_info.current", ""); if (version == "") { return none; } return version; } optional VersionChecker::securityWarningFor(const string &version) const { if (_versionInfo == none) { return none; } auto warnings = _versionInfo->get_child_optional("warnings"); if (warnings == none) { return none; } BOOST_FOREACH(const ptree::value_type &v, *warnings) { if(v.first == version) { return v.second.get_value(); } } return none; } optional VersionChecker::_getVersionInfo(HttpClient* httpClient) { long timeoutMsec = 2000; string response; try { response = httpClient->get("https://www.cryfs.org/version_info.json", timeoutMsec); } catch (const std::exception& e) { LOG(WARN, "HTTP Error: {}", e.what()); return none; } return _parseJson(response); } optional VersionChecker::_parseJson(const string &json) { try { ptree pt; std::istringstream input(json); read_json(input, pt); return pt; } catch (const json_parser_error &e) { LOG(WARN, "Error parsing version information json object"); return none; } } } src/cryfs-cli/VersionChecker.h000066400000000000000000000017221347701267100166350ustar00rootroot00000000000000#ifndef MESSMER_CRYFSCLI_VERSIONCHECKER_H #define MESSMER_CRYFSCLI_VERSIONCHECKER_H #include #include #include #include #include #include namespace cryfs_cli { class VersionChecker final { public: //TODO Write a cpputils::shared_ref and use it VersionChecker(cpputils::HttpClient* httpClient); boost::optional newestVersion() const; boost::optional securityWarningFor(const std::string &version) const; private: static boost::optional _getVersionInfo(cpputils::HttpClient* httpClient); static boost::optional _parseJson(const std::string &json); boost::optional _versionInfo; DISALLOW_COPY_AND_ASSIGN(VersionChecker); }; } #endif src/cryfs-cli/main.cpp000066400000000000000000000026211347701267100152010ustar00rootroot00000000000000#include "Cli.h" #include #include #include #if defined(_MSC_VER) #include #include #else #include #endif using namespace cryfs_cli; using cpputils::Random; using cpputils::SCrypt; using cpputils::IOStreamConsole; using cpputils::make_unique_ref; using std::make_shared; using std::cerr; int main(int argc, const char *argv[]) { #if defined(_MSC_VER) if (!IsWindows7SP1OrGreater()) { std::cerr << "CryFS is currently only supported on Windows 7 SP1 (or later)." << std::endl; exit(1); } #endif try { auto &keyGenerator = Random::OSRandom(); #if defined(_MSC_VER) auto httpClient = make_unique_ref(); #else auto httpClient = make_unique_ref(); #endif return Cli(keyGenerator, SCrypt::DefaultSettings, make_shared()) .main(argc, argv, std::move(httpClient), []{}); } catch (const cryfs::CryfsException &e) { if (e.what() != string()) { std::cerr << "Error: " << e.what() << std::endl; } return exitCode(e.errorCode()); } catch (const std::exception &e) { cerr << "Error: " << e.what(); return exitCode(cryfs::ErrorCode::UnspecifiedError); } } src/cryfs-cli/program_options/000077500000000000000000000000001347701267100167725ustar00rootroot00000000000000src/cryfs-cli/program_options/Parser.cpp000066400000000000000000000252221347701267100207350ustar00rootroot00000000000000#include "Parser.h" #include "utils.h" #include #include #include #include #include namespace po = boost::program_options; namespace bf = boost::filesystem; using namespace cryfs_cli::program_options; using cryfs::CryConfigConsole; using cryfs::CryfsException; using cryfs::ErrorCode; using std::vector; using std::cerr; using std::endl; using std::string; using boost::optional; using boost::none; using namespace cpputils::logging; Parser::Parser(int argc, const char **argv) :_options(_argsToVector(argc, argv)) { } vector Parser::_argsToVector(int argc, const char **argv) { vector result; for(int i = 0; i < argc; ++i) { result.push_back(argv[i]); } return result; } ProgramOptions Parser::parse(const vector &supportedCiphers) const { vector cryfsOptions; vector fuseOptions; std::tie(cryfsOptions, fuseOptions) = splitAtDoubleDash(_options); if (fuseOptions.size() != 0) { LOG(WARN, "Passing fuse mount options after a double dash '--' is deprecated. Please pass them directly (e.g. 'cryfs basedir mountdir -o allow_other'"); } po::variables_map vm = _parseOptionsOrShowHelp(cryfsOptions, supportedCiphers); if (!vm.count("base-dir")) { _showHelpAndExit("Please specify a base directory.", ErrorCode::InvalidArguments); } if (!vm.count("mount-dir")) { _showHelpAndExit("Please specify a mount directory.", ErrorCode::InvalidArguments); } bf::path baseDir = vm["base-dir"].as(); bf::path mountDir = vm["mount-dir"].as(); optional configfile = none; if (vm.count("config")) { configfile = bf::absolute(vm["config"].as()); } bool foreground = vm.count("foreground"); if (foreground) { fuseOptions.push_back(const_cast("-f")); } bool allowFilesystemUpgrade = vm.count("allow-filesystem-upgrade"); bool allowReplacedFilesystem = vm.count("allow-replaced-filesystem"); optional unmountAfterIdleMinutes = 0.0; // first setting to 0 and then to none is somehow needed to silence a GCC warning from -Wmaybe-uninitialized unmountAfterIdleMinutes = none; if (vm.count("unmount-idle")) { unmountAfterIdleMinutes = vm["unmount-idle"].as(); } optional logfile = none; if (vm.count("logfile")) { logfile = bf::absolute(vm["logfile"].as()); } optional cipher = none; if (vm.count("cipher")) { cipher = vm["cipher"].as(); _checkValidCipher(*cipher, supportedCiphers); } optional blocksizeBytes = none; if (vm.count("blocksize")) { blocksizeBytes = vm["blocksize"].as(); } bool allowIntegrityViolations = vm.count("allow-integrity-violations"); optional missingBlockIsIntegrityViolation = none; if (vm.count("missing-block-is-integrity-violation")) { missingBlockIsIntegrityViolation = vm["missing-block-is-integrity-violation"].as(); } if (vm.count("fuse-option")) { auto options = vm["fuse-option"].as>(); for (const auto& option: options) { if (option == "noatime" || option == "atime") { LOG(WARN, "CryFS currently doesn't support noatime/atime flags. Using relatime behavior."); } fuseOptions.push_back("-o"); fuseOptions.push_back(option); } } return ProgramOptions(std::move(baseDir), std::move(mountDir), std::move(configfile), foreground, allowFilesystemUpgrade, allowReplacedFilesystem, std::move(unmountAfterIdleMinutes), std::move(logfile), std::move(cipher), blocksizeBytes, allowIntegrityViolations, std::move(missingBlockIsIntegrityViolation), std::move(fuseOptions)); } void Parser::_checkValidCipher(const string &cipher, const vector &supportedCiphers) { if (std::find(supportedCiphers.begin(), supportedCiphers.end(), cipher) == supportedCiphers.end()) { throw CryfsException("Invalid cipher: " + cipher, ErrorCode::InvalidArguments); } } po::variables_map Parser::_parseOptionsOrShowHelp(const vector &options, const vector &supportedCiphers) { try { return _parseOptions(options, supportedCiphers); } catch (const CryfsException& e) { // If CryfsException is thrown, we already know what's wrong. // Show usage information and pass through the exception, don't catch it. if (e.errorCode() != ErrorCode::Success) { _showHelp(); } throw; } catch(const std::exception &e) { std::cerr << e.what() << std::endl; _showHelpAndExit("Invalid arguments", ErrorCode::InvalidArguments); } } po::variables_map Parser::_parseOptions(const vector &options, const vector &supportedCiphers) { po::options_description desc; po::positional_options_description positional_desc; _addAllowedOptions(&desc); _addPositionalOptionForBaseDir(&desc, &positional_desc); po::variables_map vm; vector _options = _to_const_char_vector(options); po::store(po::command_line_parser(_options.size(), _options.data()) .options(desc).positional(positional_desc).run(), vm); if (vm.count("help")) { _showHelpAndExit("", ErrorCode::Success); } if (vm.count("show-ciphers")) { _showCiphersAndExit(supportedCiphers); } if (vm.count("version")) { _showVersionAndExit(); } po::notify(vm); return vm; } vector Parser::_to_const_char_vector(const vector &options) { vector result; result.reserve(options.size()); for (const string &option : options) { result.push_back(option.c_str()); } return result; } void Parser::_addAllowedOptions(po::options_description *desc) { po::options_description options("Allowed options"); string cipher_description = "Cipher to use for encryption. See possible values by calling cryfs with --show-ciphers. Default: "; cipher_description += CryConfigConsole::DEFAULT_CIPHER; string blocksize_description = "The block size used when storing ciphertext blocks (in bytes). Default: "; blocksize_description += std::to_string(CryConfigConsole::DEFAULT_BLOCKSIZE_BYTES); options.add_options() ("help,h", "show help message") ("config,c", po::value(), "Configuration file") ("foreground,f", "Run CryFS in foreground.") ("fuse-option,o", po::value>(), "Add a fuse mount option. Example: atime or noatime.") ("cipher", po::value(), cipher_description.c_str()) ("blocksize", po::value(), blocksize_description.c_str()) ("missing-block-is-integrity-violation", po::value(), "Whether to treat a missing block as an integrity violation. This makes sure you notice if an attacker deleted some of your files, but only works in single-client mode. You will not be able to use the file system on other devices.") ("allow-integrity-violations", "Disable integrity checks. Integrity checks ensure that your file system was not manipulated or rolled back to an earlier version. Disabling them is needed if you want to load an old snapshot of your file system.") ("allow-filesystem-upgrade", "Allow upgrading the file system if it was created with an old CryFS version. After the upgrade, older CryFS versions might not be able to use the file system anymore.") ("allow-replaced-filesystem", "By default, CryFS remembers file systems it has seen in this base directory and checks that it didn't get replaced by an attacker with an entirely different file system since the last time it was loaded. However, if you do want to replace the file system with an entirely new one, you can pass in this option to disable the check.") ("show-ciphers", "Show list of supported ciphers.") ("unmount-idle", po::value(), "Automatically unmount after specified number of idle minutes.") ("logfile", po::value(), "Specify the file to write log messages to. If this is not specified, log messages will go to stdout, or syslog if CryFS is running in the background.") ("version", "Show CryFS version number") ; desc->add(options); } void Parser::_addPositionalOptionForBaseDir(po::options_description *desc, po::positional_options_description *positional) { positional->add("base-dir", 1); positional->add("mount-dir", 1); po::options_description hidden("Hidden options"); hidden.add_options() ("base-dir", po::value(), "Base directory") ("mount-dir", po::value(), "Mount directory") ; desc->add(hidden); } [[noreturn]] void Parser::_showCiphersAndExit(const vector &supportedCiphers) { for (const auto &cipher : supportedCiphers) { std::cerr << cipher << "\n"; } throw CryfsException("", ErrorCode::Success); } void Parser::_showHelp() { cerr << "Usage: cryfs [options] baseDir mountPoint [-- [FUSE Mount Options]]\n"; po::options_description desc; _addAllowedOptions(&desc); cerr << desc << endl; cerr << "Environment variables:\n" << " " << Environment::FRONTEND_KEY << "=" << Environment::FRONTEND_NONINTERACTIVE << "\n" << "\tWork better together with tools.\n" << "\tWith this option set, CryFS won't ask anything, but use default values\n" << "\tfor options you didn't specify on command line. Furthermore, it won't\n" << "\task you to enter a new password a second time (password confirmation).\n" << " " << Environment::NOUPDATECHECK_KEY << "=true\n" << "\tBy default, CryFS connects to the internet to check for known\n" << "\tsecurity vulnerabilities and new versions. This option disables this.\n" << " " << Environment::LOCALSTATEDIR_KEY << "=[path]\n" << "\tSets the directory cryfs uses to store local state. This local state\n" << "\tis used to recognize known file systems and run integrity checks,\n" << "\ti.e. check that they haven't been modified by an attacker.\n" << "\tDefault value: " << Environment::defaultLocalStateDir().string() << "\n" << endl; } [[noreturn]] void Parser::_showHelpAndExit(const std::string& message, ErrorCode errorCode) { _showHelp(); throw CryfsException(message, errorCode); } [[noreturn]] void Parser::_showVersionAndExit() { // no need to show version because it was already shown in the CryFS header before parsing program options throw CryfsException("", ErrorCode::Success); } src/cryfs-cli/program_options/Parser.h000066400000000000000000000035301347701267100204000ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFSCLI_PROGRAMOPTIONS_PARSER_H #define MESSMER_CRYFSCLI_PROGRAMOPTIONS_PARSER_H #include "ProgramOptions.h" #include #include namespace cryfs_cli { namespace program_options { class Parser final { public: Parser(int argc, const char **argv); ProgramOptions parse(const std::vector &supportedCiphers) const; private: static std::vector _argsToVector(int argc, const char **argv); static std::vector _to_const_char_vector(const std::vector &options); static void _addAllowedOptions(boost::program_options::options_description *desc); static void _addPositionalOptionForBaseDir(boost::program_options::options_description *desc, boost::program_options::positional_options_description *positional); static void _showHelp(); [[noreturn]] static void _showHelpAndExit(const std::string& message, cryfs::ErrorCode errorCode); [[noreturn]] static void _showCiphersAndExit(const std::vector &supportedCiphers); [[noreturn]] static void _showVersionAndExit(); static boost::program_options::variables_map _parseOptionsOrShowHelp(const std::vector &options, const std::vector &supportedCiphers); static boost::program_options::variables_map _parseOptions(const std::vector &options, const std::vector &supportedCiphers); static void _checkValidCipher(const std::string &cipher, const std::vector &supportedCiphers); std::vector _options; DISALLOW_COPY_AND_ASSIGN(Parser); }; } } #endif src/cryfs-cli/program_options/ProgramOptions.cpp000066400000000000000000000055261347701267100224710ustar00rootroot00000000000000#include "ProgramOptions.h" #include #include #include using namespace cryfs_cli::program_options; using std::string; using std::vector; using boost::optional; namespace bf = boost::filesystem; ProgramOptions::ProgramOptions(bf::path baseDir, bf::path mountDir, optional configFile, bool foreground, bool allowFilesystemUpgrade, bool allowReplacedFilesystem, optional unmountAfterIdleMinutes, optional logFile, optional cipher, optional blocksizeBytes, bool allowIntegrityViolations, boost::optional missingBlockIsIntegrityViolation, vector fuseOptions) : _configFile(std::move(configFile)), _baseDir(bf::absolute(std::move(baseDir))), _mountDir(std::move(mountDir)), _mountDirIsDriveLetter(cpputils::path_is_just_drive_letter(_mountDir)), _foreground(foreground), _allowFilesystemUpgrade(allowFilesystemUpgrade), _allowReplacedFilesystem(allowReplacedFilesystem), _allowIntegrityViolations(allowIntegrityViolations), _cipher(std::move(cipher)), _blocksizeBytes(std::move(blocksizeBytes)), _unmountAfterIdleMinutes(std::move(unmountAfterIdleMinutes)), _missingBlockIsIntegrityViolation(std::move(missingBlockIsIntegrityViolation)), _logFile(std::move(logFile)), _fuseOptions(std::move(fuseOptions)) { if (!_mountDirIsDriveLetter) { _mountDir = bf::absolute(std::move(_mountDir)); } } const bf::path &ProgramOptions::baseDir() const { return _baseDir; } const bf::path &ProgramOptions::mountDir() const { return _mountDir; } bool ProgramOptions::mountDirIsDriveLetter() const { return _mountDirIsDriveLetter; } const optional &ProgramOptions::configFile() const { return _configFile; } bool ProgramOptions::foreground() const { return _foreground; } bool ProgramOptions::allowFilesystemUpgrade() const { return _allowFilesystemUpgrade; } const optional &ProgramOptions::unmountAfterIdleMinutes() const { return _unmountAfterIdleMinutes; } const optional &ProgramOptions::logFile() const { return _logFile; } const optional &ProgramOptions::cipher() const { return _cipher; } const optional &ProgramOptions::blocksizeBytes() const { return _blocksizeBytes; } bool ProgramOptions::allowIntegrityViolations() const { return _allowIntegrityViolations; } bool ProgramOptions::allowReplacedFilesystem() const { return _allowReplacedFilesystem; } const optional &ProgramOptions::missingBlockIsIntegrityViolation() const { return _missingBlockIsIntegrityViolation; } const vector &ProgramOptions::fuseOptions() const { return _fuseOptions; } src/cryfs-cli/program_options/ProgramOptions.h000066400000000000000000000055171347701267100221360ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFSCLI_PROGRAMOPTIONS_PROGRAMOPTIONS_H #define MESSMER_CRYFSCLI_PROGRAMOPTIONS_PROGRAMOPTIONS_H #include #include #include #include #include namespace cryfs_cli { namespace program_options { class ProgramOptions final { public: ProgramOptions(boost::filesystem::path baseDir, boost::filesystem::path mountDir, boost::optional configFile, bool foreground, bool allowFilesystemUpgrade, bool allowReplacedFilesystem, boost::optional unmountAfterIdleMinutes, boost::optional logFile, boost::optional cipher, boost::optional blocksizeBytes, bool allowIntegrityViolations, boost::optional missingBlockIsIntegrityViolation, std::vector fuseOptions); ProgramOptions(ProgramOptions &&rhs) = default; const boost::filesystem::path &baseDir() const; const boost::filesystem::path &mountDir() const; bool mountDirIsDriveLetter() const; const boost::optional &configFile() const; bool foreground() const; bool allowFilesystemUpgrade() const; bool allowReplacedFilesystem() const; const boost::optional &cipher() const; const boost::optional &blocksizeBytes() const; const boost::optional &unmountAfterIdleMinutes() const; bool allowIntegrityViolations() const; const boost::optional &missingBlockIsIntegrityViolation() const; const boost::optional &logFile() const; const std::vector &fuseOptions() const; private: boost::optional _configFile; boost::filesystem::path _baseDir; // this is always absolute boost::filesystem::path _mountDir; // this is absolute iff !_mountDirIsDriveLetter bool _mountDirIsDriveLetter; bool _foreground; bool _allowFilesystemUpgrade; bool _allowReplacedFilesystem; bool _allowIntegrityViolations; boost::optional _cipher; boost::optional _blocksizeBytes; boost::optional _unmountAfterIdleMinutes; boost::optional _missingBlockIsIntegrityViolation; boost::optional _logFile; std::vector _fuseOptions; DISALLOW_COPY_AND_ASSIGN(ProgramOptions); }; } } #endif src/cryfs-cli/program_options/utils.cpp000066400000000000000000000016641347701267100206450ustar00rootroot00000000000000#include "utils.h" #include #include using std::pair; using std::make_pair; using std::vector; using std::string; namespace cryfs_cli { namespace program_options { pair, vector> splitAtDoubleDash(const vector &options) { auto doubleDashIterator = std::find(options.begin(), options.end(), string("--")); vector beforeDoubleDash(options.begin(), doubleDashIterator); vector afterDoubleDash; if (doubleDashIterator != options.end() && doubleDashIterator + 1 != options.end()) { afterDoubleDash.reserve(options.size() - beforeDoubleDash.size() - 1); std::copy(doubleDashIterator + 1, options.end(), std::back_inserter(afterDoubleDash)); } return make_pair( beforeDoubleDash, afterDoubleDash ); } } } src/cryfs-cli/program_options/utils.h000066400000000000000000000007741347701267100203130ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFSCLI_PROGRAMOPTIONS_UTILS_H #define MESSMER_CRYFSCLI_PROGRAMOPTIONS_UTILS_H #include #include #include namespace cryfs_cli { namespace program_options { /** * Splits an array of program options into two arrays of program options, split at a double dash '--' option. */ std::pair, std::vector> splitAtDoubleDash(const std::vector &options); } } #endif src/cryfs-unmount/000077500000000000000000000000001347701267100145065ustar00rootroot00000000000000src/cryfs-unmount/CMakeLists.txt000066400000000000000000000013561347701267100172530ustar00rootroot00000000000000project (cryfs-unmount) INCLUDE(GNUInstallDirs) set(SOURCES program_options/ProgramOptions.cpp program_options/Parser.cpp Cli.cpp ) add_library(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} PUBLIC cpp-utils cryfs fspp-fuse) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) add_executable(${PROJECT_NAME}_bin main_unmount.cpp) set_target_properties(${PROJECT_NAME}_bin PROPERTIES OUTPUT_NAME cryfs-unmount) target_link_libraries(${PROJECT_NAME}_bin PUBLIC ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}_bin) target_activate_cpp14(${PROJECT_NAME}_bin) install(TARGETS ${PROJECT_NAME}_bin CONFIGURATIONS Debug Release RelWithDebInfo DESTINATION ${CMAKE_INSTALL_BINDIR} ) src/cryfs-unmount/Cli.cpp000066400000000000000000000021151347701267100157200ustar00rootroot00000000000000#include "Cli.h" #include #include #include #include #include using fspp::fuse::Fuse; using cryfs_unmount::program_options::Parser; using cryfs_unmount::program_options::ProgramOptions; namespace cryfs_unmount { namespace { void _showVersion() { std::cout << "CryFS Version " << gitversion::VersionString() << std::endl; } } void Cli::main(int argc, const char **argv) { _showVersion(); ProgramOptions options = Parser(argc, argv).parse(); if (!boost::filesystem::exists(options.mountDir())) { throw cryfs::CryfsException("Given mountdir doesn't exist", cryfs::ErrorCode::InaccessibleMountDir); } // TODO This doesn't seem to work with relative paths std::cout << "Unmounting CryFS filesystem at " << options.mountDir() << "." << std::endl; Fuse::unmount(options.mountDir()); // TODO Wait until it is actually unmounted and then show a better success message? std::cout << "Filesystem is unmounting now." << std::endl; } } src/cryfs-unmount/Cli.h000066400000000000000000000003031347701267100153620ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFSUNMOUNT_CLI_H #define MESSMER_CRYFSUNMOUNT_CLI_H namespace cryfs_unmount { class Cli final { public: void main(int argc, const char **argv); }; } #endif src/cryfs-unmount/main_unmount.cpp000066400000000000000000000016361347701267100177310ustar00rootroot00000000000000#if defined(_MSC_VER) #include #include #endif #include #include #include #include "Cli.h" using std::cerr; using cryfs::ErrorCode; int main(int argc, const char *argv[]) { #if defined(_MSC_VER) if (!IsWindows7SP1OrGreater()) { std::cerr << "CryFS is currently only supported on Windows 7 SP1 (or later)." << std::endl; exit(1); } #endif cpputils::showBacktraceOnCrash(); try { cryfs_unmount::Cli().main(argc, argv); } catch (const cryfs::CryfsException &e) { if (e.what() != std::string()) { std::cerr << "Error " << static_cast(e.errorCode()) << ": " << e.what() << std::endl; } return exitCode(e.errorCode()); } catch (const std::runtime_error &e) { std::cerr << "Error: " << e.what() << std::endl; return exitCode(ErrorCode::UnspecifiedError); } return exitCode(ErrorCode::Success); } src/cryfs-unmount/program_options/000077500000000000000000000000001347701267100177305ustar00rootroot00000000000000src/cryfs-unmount/program_options/Parser.cpp000066400000000000000000000075311347701267100216760ustar00rootroot00000000000000#include "Parser.h" #include #include #include #include #include namespace po = boost::program_options; namespace bf = boost::filesystem; using namespace cryfs_unmount::program_options; using cryfs::CryConfigConsole; using cryfs::CryfsException; using cryfs::ErrorCode; using std::vector; using std::cerr; using std::endl; using std::string; using namespace cpputils::logging; Parser::Parser(int argc, const char **argv) :_options(_argsToVector(argc, argv)) { } vector Parser::_argsToVector(int argc, const char **argv) { vector result; for (int i = 0; i < argc; ++i) { result.push_back(argv[i]); } return result; } ProgramOptions Parser::parse() const { po::variables_map vm = _parseOptionsOrShowHelp(_options); if (!vm.count("mount-dir")) { _showHelpAndExit("Please specify a mount directory.", ErrorCode::InvalidArguments); } bf::path mountDir = vm["mount-dir"].as(); return ProgramOptions(std::move(mountDir)); } po::variables_map Parser::_parseOptionsOrShowHelp(const vector &options) { try { return _parseOptions(options); } catch (const CryfsException& e) { // If CryfsException is thrown, we already know what's wrong. // Show usage information and pass through the exception, don't catch it. if (e.errorCode() != ErrorCode::Success) { _showHelp(); } throw; } catch (const std::exception &e) { std::cerr << e.what() << std::endl; _showHelpAndExit("Invalid arguments", ErrorCode::InvalidArguments); } } po::variables_map Parser::_parseOptions(const vector &options) { po::options_description desc; po::positional_options_description positional_desc; _addAllowedOptions(&desc); _addPositionalOptionForBaseDir(&desc, &positional_desc); po::variables_map vm; vector _options = _to_const_char_vector(options); po::store(po::command_line_parser(_options.size(), _options.data()) .options(desc).positional(positional_desc).run(), vm); if (vm.count("help")) { _showHelpAndExit("", ErrorCode::Success); } if (vm.count("version")) { _showVersionAndExit(); } po::notify(vm); return vm; } vector Parser::_to_const_char_vector(const vector &options) { vector result; result.reserve(options.size()); for (const string &option : options) { result.push_back(option.c_str()); } return result; } void Parser::_addAllowedOptions(po::options_description *desc) { po::options_description options("Allowed options"); string cipher_description = "Cipher to use for encryption. See possible values by calling cryfs with --show-ciphers. Default: "; cipher_description += CryConfigConsole::DEFAULT_CIPHER; string blocksize_description = "The block size used when storing ciphertext blocks (in bytes). Default: "; blocksize_description += std::to_string(CryConfigConsole::DEFAULT_BLOCKSIZE_BYTES); options.add_options() ("help,h", "show help message") ("version", "Show CryFS version number") ; desc->add(options); } void Parser::_addPositionalOptionForBaseDir(po::options_description *desc, po::positional_options_description *positional) { positional->add("mount-dir", 1); po::options_description hidden("Hidden options"); hidden.add_options() ("mount-dir", po::value(), "Mount directory") ; desc->add(hidden); } void Parser::_showHelp() { cerr << "Usage: cryfs-unmount [mountPoint]\n"; po::options_description desc; _addAllowedOptions(&desc); cerr << desc << endl; } [[noreturn]] void Parser::_showHelpAndExit(const std::string& message, ErrorCode errorCode) { _showHelp(); throw CryfsException(message, errorCode); } [[noreturn]] void Parser::_showVersionAndExit() { // no need to show version because it was already shown in the CryFS header before parsing program options throw CryfsException("", ErrorCode::Success); } src/cryfs-unmount/program_options/Parser.h000066400000000000000000000026111347701267100213350ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFSUNMOUNT_PROGRAMOPTIONS_PARSER_H #define MESSMER_CRYFSUNMOUNT_PROGRAMOPTIONS_PARSER_H #include "ProgramOptions.h" #include #include namespace cryfs_unmount { namespace program_options { class Parser final { public: Parser(int argc, const char **argv); ProgramOptions parse() const; private: static std::vector _argsToVector(int argc, const char **argv); static std::vector _to_const_char_vector(const std::vector &options); static void _addAllowedOptions(boost::program_options::options_description *desc); static void _addPositionalOptionForBaseDir(boost::program_options::options_description *desc, boost::program_options::positional_options_description *positional); static void _showHelp(); [[noreturn]] static void _showHelpAndExit(const std::string& message, cryfs::ErrorCode errorCode); [[noreturn]] static void _showCiphersAndExit(const std::vector &supportedCiphers); [[noreturn]] static void _showVersionAndExit(); static boost::program_options::variables_map _parseOptionsOrShowHelp(const std::vector &options); static boost::program_options::variables_map _parseOptions(const std::vector &options); std::vector _options; DISALLOW_COPY_AND_ASSIGN(Parser); }; } } #endif src/cryfs-unmount/program_options/ProgramOptions.cpp000066400000000000000000000011651347701267100234220ustar00rootroot00000000000000#include "ProgramOptions.h" #include #include #include using namespace cryfs_unmount::program_options; using std::string; namespace bf = boost::filesystem; ProgramOptions::ProgramOptions(bf::path mountDir) :_mountDir(std::move(mountDir)), _mountDirIsDriveLetter(cpputils::path_is_just_drive_letter(_mountDir)) { if (!_mountDirIsDriveLetter) { _mountDir = bf::absolute(std::move(_mountDir)); } } const bf::path &ProgramOptions::mountDir() const { return _mountDir; } bool ProgramOptions::mountDirIsDriveLetter() const { return _mountDirIsDriveLetter; } src/cryfs-unmount/program_options/ProgramOptions.h000066400000000000000000000012771347701267100230730ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFSUNMOUNT_PROGRAMOPTIONS_PROGRAMOPTIONS_H #define MESSMER_CRYFSUNMOUNT_PROGRAMOPTIONS_PROGRAMOPTIONS_H #include #include #include #include #include namespace cryfs_unmount { namespace program_options { class ProgramOptions final { public: ProgramOptions(boost::filesystem::path mountDir); ProgramOptions(ProgramOptions &&rhs) = default; const boost::filesystem::path &mountDir() const; bool mountDirIsDriveLetter() const; private: boost::filesystem::path _mountDir; bool _mountDirIsDriveLetter; DISALLOW_COPY_AND_ASSIGN(ProgramOptions); }; } } #endif src/cryfs/000077500000000000000000000000001347701267100130035ustar00rootroot00000000000000src/cryfs/CMakeLists.txt000066400000000000000000000050171347701267100155460ustar00rootroot00000000000000project (cryfs) set(LIB_SOURCES # cryfs.cpp CryfsException.cpp config/crypto/outer/OuterConfig.cpp config/crypto/outer/OuterEncryptor.cpp config/crypto/CryConfigEncryptorFactory.cpp config/crypto/inner/ConcreteInnerEncryptor.cpp config/crypto/inner/InnerConfig.cpp config/crypto/inner/InnerEncryptor.cpp config/crypto/CryConfigEncryptor.cpp config/CryConfigConsole.cpp config/CryConfigLoader.cpp config/CryConfig.cpp config/CryConfigFile.cpp config/CryCipher.cpp config/CryConfigCreator.cpp config/CryKeyProvider.cpp config/CryPasswordBasedKeyProvider.cpp config/CryPresetPasswordBasedKeyProvider.cpp filesystem/CryOpenFile.cpp filesystem/fsblobstore/utils/DirEntry.cpp filesystem/fsblobstore/utils/DirEntryList.cpp filesystem/fsblobstore/FsBlobStore.cpp filesystem/fsblobstore/FsBlobView.cpp filesystem/fsblobstore/FileBlob.cpp filesystem/fsblobstore/FsBlob.cpp filesystem/fsblobstore/SymlinkBlob.cpp filesystem/fsblobstore/DirBlob.cpp filesystem/CryNode.cpp filesystem/parallelaccessfsblobstore/DirBlobRef.cpp filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStore.cpp filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStoreAdapter.cpp filesystem/parallelaccessfsblobstore/FsBlobRef.cpp filesystem/parallelaccessfsblobstore/FileBlobRef.cpp filesystem/parallelaccessfsblobstore/SymlinkBlobRef.cpp filesystem/CrySymlink.cpp filesystem/CryDir.cpp filesystem/cachingfsblobstore/DirBlobRef.cpp filesystem/cachingfsblobstore/CachingFsBlobStore.cpp filesystem/cachingfsblobstore/FsBlobRef.cpp filesystem/cachingfsblobstore/FileBlobRef.cpp filesystem/cachingfsblobstore/SymlinkBlobRef.cpp filesystem/CryFile.cpp filesystem/CryDevice.cpp localstate/LocalStateDir.cpp localstate/LocalStateMetadata.cpp localstate/BasedirMetadata.cpp ) add_library(${PROJECT_NAME} STATIC ${LIB_SOURCES}) target_link_libraries(${PROJECT_NAME} PUBLIC cpp-utils fspp-interface blockstore blobstore gitversion) target_add_boost(${PROJECT_NAME} program_options chrono) # TODO Check that dependent projects don't get boost added (use PRIVATE here) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) #install(TARGETS ${PROJECT_NAME} # DESTINATION lib # CONFIGURATIONS Release #) src/cryfs/CryfsException.cpp000066400000000000000000000000341347701267100164510ustar00rootroot00000000000000#include "CryfsException.h" src/cryfs/CryfsException.h000066400000000000000000000007401347701267100161220ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_CRYFSEXCEPTION_H #define MESSMER_CRYFS_CRYFSEXCEPTION_H #include "ErrorCodes.h" #include #include namespace cryfs { class CryfsException final : public std::runtime_error { public: CryfsException(std::string message, ErrorCode errorCode) : std::runtime_error(std::move(message)), _errorCode(errorCode) {} ErrorCode errorCode() const { return _errorCode; } private: ErrorCode _errorCode; }; } #endif src/cryfs/ErrorCodes.h000066400000000000000000000050741347701267100152310ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFSCLI_EXITCODES_H #define MESSMER_CRYFSCLI_EXITCODES_H namespace cryfs { enum class ErrorCode : int { Success = 0, // An error happened that doesn't have an error code associated with it UnspecifiedError = 1, // The command line arguments are invalid. InvalidArguments = 10, // Couldn't load config file. Probably the password is wrong WrongPassword = 11, // Password cannot be empty EmptyPassword = 12, // The file system format is too new for this CryFS version. Please update your CryFS version. TooNewFilesystemFormat = 13, // The file system format is too old for this CryFS version. Run with --allow-filesystem-upgrade to upgrade it. TooOldFilesystemFormat = 14, // The file system uses a different cipher than the one specified on the command line using the --cipher argument. WrongCipher = 15, // Base directory doesn't exist or is inaccessible (i.e. not read or writable or not a directory) InaccessibleBaseDir = 16, // Mount directory doesn't exist or is inaccessible (i.e. not read or writable or not a directory) InaccessibleMountDir = 17, // Base directory can't be a subdirectory of the mount directory BaseDirInsideMountDir = 18, // Something's wrong with the file system. InvalidFilesystem = 19, // The filesystem id in the config file is different to the last time we loaded a filesystem from this basedir. This could mean an attacker replaced the file system with a different one. You can pass the --allow-replaced-filesystem option to allow this. FilesystemIdChanged = 20, // The filesystem encryption key differs from the last time we loaded this filesystem. This could mean an attacker replaced the file system with a different one. You can pass the --allow-replaced-filesystem option to allow this. EncryptionKeyChanged = 21, // The command line options and the file system disagree on whether missing blocks should be treated as integrity violations. FilesystemHasDifferentIntegritySetup = 22, // File system is in single-client mode and can only be used from the client that created it. SingleClientFileSystem = 23, // A previous run of the file system detected an integrity violation. Preventing access to make sure the user notices. The file system will be accessible again after the user deletes the integrity state file. IntegrityViolationOnPreviousRun = 24, // An integrity violation was detected and the file system unmounted to make sure the user notices. IntegrityViolation = 25 }; inline int exitCode(ErrorCode code) { return static_cast(code); } } #endif src/cryfs/config/000077500000000000000000000000001347701267100142505ustar00rootroot00000000000000src/cryfs/config/CryCipher.cpp000066400000000000000000000103311347701267100166420ustar00rootroot00000000000000#include "CryCipher.h" #include #include #include "crypto/inner/ConcreteInnerEncryptor.h" using std::vector; using std::string; using cpputils::unique_ref; using cpputils::make_unique_ref; using blockstore::BlockStore2; using std::shared_ptr; using std::make_shared; using boost::optional; using boost::none; using blockstore::encrypted::EncryptedBlockStore2; using namespace cryfs; using namespace cpputils; constexpr size_t CryCiphers::MAX_KEY_SIZE; template class CryCipherInstance: public CryCipher { public: BOOST_CONCEPT_ASSERT((CipherConcept)); static_assert(Cipher::KEYSIZE <= CryCiphers::MAX_KEY_SIZE, "The key size for this cipher is too large. Please modify CryCiphers::MAX_KEY_SIZE"); CryCipherInstance(const optional warning = none): _warning(warning) { } string cipherName() const override { return Cipher::NAME; } const optional &warning() const override { return _warning; } unique_ref createEncryptedBlockstore(unique_ref baseBlockStore, const string &encKey) const override { return make_unique_ref>(std::move(baseBlockStore), Cipher::EncryptionKey::FromString(encKey)); } string createKey(cpputils::RandomGenerator &randomGenerator) const override { return Cipher::EncryptionKey::CreateKey(randomGenerator, Cipher::KEYSIZE).ToString(); } unique_ref createInnerConfigEncryptor(const EncryptionKey& key) const override { ASSERT(key.binaryLength() == CryCiphers::MAX_KEY_SIZE, "Wrong key size"); return make_unique_ref>(key.take(Cipher::KEYSIZE)); } private: optional _warning; }; const string CryCiphers::INTEGRITY_WARNING = "This cipher does not ensure integrity."; //We have to use shared_ptr instead of unique_ref, because c++ initializer_list needs copyable values const vector> CryCiphers::SUPPORTED_CIPHERS = { make_shared>(), make_shared>(INTEGRITY_WARNING), make_shared>(), make_shared>(INTEGRITY_WARNING), make_shared>(), make_shared>(INTEGRITY_WARNING), make_shared>(), make_shared>(INTEGRITY_WARNING), make_shared>(), make_shared>(INTEGRITY_WARNING), make_shared>(), make_shared>(INTEGRITY_WARNING), make_shared>(), make_shared>(INTEGRITY_WARNING), #if CRYPTOPP_VERSION != 564 make_shared>(), make_shared>(INTEGRITY_WARNING), #endif make_shared>(), make_shared>(INTEGRITY_WARNING), make_shared>(), make_shared>(INTEGRITY_WARNING) }; const CryCipher& CryCiphers::find(const string &cipherName) { auto found = std::find_if(CryCiphers::SUPPORTED_CIPHERS.begin(), CryCiphers::SUPPORTED_CIPHERS.end(), [cipherName] (const std::shared_ptr& element) { return element->cipherName() == cipherName; }); ASSERT(found != CryCiphers::SUPPORTED_CIPHERS.end(), "Unknown Cipher: "+cipherName); return **found; } vector CryCiphers::_buildSupportedCipherNames() { vector result; for (const auto& cipher : CryCiphers::SUPPORTED_CIPHERS) { result.push_back(cipher->cipherName()); } return result; } const vector& CryCiphers::supportedCipherNames() { static vector supportedCipherNames = _buildSupportedCipherNames(); return supportedCipherNames; } src/cryfs/config/CryCipher.h000066400000000000000000000031231347701267100163100ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYCIPHER_H #define MESSMER_CRYFS_SRC_CONFIG_CRYCIPHER_H #include #include #include #include #include #include "crypto/inner/InnerEncryptor.h" #include namespace cryfs { class CryCipher; class CryCiphers final { public: static const std::vector& supportedCipherNames(); //A static_assert in CryCipherInstance ensures that there is no cipher with a key size larger than specified here. //TODO Calculate this from SUPPORTED_CIPHERS instead of setting it manually static constexpr size_t MAX_KEY_SIZE = 56; // in bytes static const CryCipher& find(const std::string &cipherName); private: static const std::string INTEGRITY_WARNING; static const std::vector> SUPPORTED_CIPHERS; static std::vector _buildSupportedCipherNames(); }; class CryCipher { public: virtual ~CryCipher() {} virtual std::string cipherName() const = 0; virtual const boost::optional &warning() const = 0; virtual cpputils::unique_ref createEncryptedBlockstore(cpputils::unique_ref baseBlockStore, const std::string &encKey) const = 0; virtual std::string createKey(cpputils::RandomGenerator &randomGenerator) const = 0; virtual cpputils::unique_ref createInnerConfigEncryptor(const cpputils::EncryptionKey &key) const = 0; }; } #endif src/cryfs/config/CryConfig.cpp000066400000000000000000000122261347701267100166420ustar00rootroot00000000000000#include "CryConfig.h" #include #include #include #include #include #include using boost::property_tree::ptree; using boost::optional; using boost::none; using std::string; using std::stringstream; using cpputils::Data; using cpputils::Random; namespace cryfs { constexpr const char* CryConfig::FilesystemFormatVersion; CryConfig::CryConfig() : _rootBlob("") , _encKey("") , _cipher("") , _version("") , _createdWithVersion("") , _lastOpenedWithVersion("") , _blocksizeBytes(0) , _filesystemId(FilesystemID::Null()) , _exclusiveClientId(none) #ifndef CRYFS_NO_COMPATIBILITY , _hasVersionNumbers(true) , _hasParentPointers(true) #endif { } CryConfig CryConfig::load(const Data &data) { stringstream stream; data.StoreToStream(stream); ptree pt; read_json(stream, pt); CryConfig cfg; cfg._rootBlob = pt.get("cryfs.rootblob"); cfg._encKey = pt.get("cryfs.key"); cfg._cipher = pt.get("cryfs.cipher"); cfg._version = pt.get("cryfs.version", "0.8"); // CryFS 0.8 didn't specify this field, so if the field doesn't exist, it's 0.8. cfg._createdWithVersion = pt.get("cryfs.createdWithVersion", cfg._version); // In CryFS <= 0.9.2, we didn't have this field, but also didn't update cryfs.version, so we can use this field instead. cfg._lastOpenedWithVersion = pt.get("cryfs.lastOpenedWithVersion", cfg._version); // In CryFS <= 0.9.8, we didn't have this field, but used the cryfs.version field for this purpose. cfg._blocksizeBytes = pt.get("cryfs.blocksizeBytes", 32832); // CryFS <= 0.9.2 used a 32KB block size which was this physical block size. cfg._exclusiveClientId = pt.get_optional("cryfs.exclusiveClientId"); #ifndef CRYFS_NO_COMPATIBILITY cfg._hasVersionNumbers = pt.get("cryfs.migrations.hasVersionNumbers", false); cfg._hasParentPointers = pt.get("cryfs.migrations.hasParentPointers", false); #endif optional filesystemIdOpt = pt.get_optional("cryfs.filesystemId"); if (filesystemIdOpt == none) { cfg._filesystemId = Random::PseudoRandom().getFixedSize(); } else { cfg._filesystemId = FilesystemID::FromString(*filesystemIdOpt); } return cfg; } Data CryConfig::save() const { ptree pt; pt.put("cryfs.rootblob", _rootBlob); pt.put("cryfs.key", _encKey); pt.put("cryfs.cipher", _cipher); pt.put("cryfs.version", _version); pt.put("cryfs.createdWithVersion", _createdWithVersion); pt.put("cryfs.lastOpenedWithVersion", _lastOpenedWithVersion); pt.put("cryfs.blocksizeBytes", _blocksizeBytes); pt.put("cryfs.filesystemId", _filesystemId.ToString()); if (_exclusiveClientId != none) { pt.put("cryfs.exclusiveClientId", *_exclusiveClientId); } #ifndef CRYFS_NO_COMPATIBILITY pt.put("cryfs.migrations.hasVersionNumbers", _hasVersionNumbers); pt.put("cryfs.migrations.hasParentPointers", _hasParentPointers); #endif stringstream stream; write_json(stream, pt); return Data::LoadFromStream(stream); } const std::string &CryConfig::RootBlob() const { return _rootBlob; } void CryConfig::SetRootBlob(std::string value) { _rootBlob = std::move(value); } const string &CryConfig::EncryptionKey() const { return _encKey; } void CryConfig::SetEncryptionKey(std::string value) { _encKey = std::move(value); } const std::string &CryConfig::Cipher() const { return _cipher; }; void CryConfig::SetCipher(std::string value) { _cipher = std::move(value); } const std::string &CryConfig::Version() const { return _version; } void CryConfig::SetVersion(std::string value) { _version = std::move(value); } const std::string &CryConfig::LastOpenedWithVersion() const { return _lastOpenedWithVersion; } const std::string &CryConfig::CreatedWithVersion() const { return _createdWithVersion; } void CryConfig::SetCreatedWithVersion(std::string value) { _createdWithVersion = std::move(value); } void CryConfig::SetLastOpenedWithVersion(const std::string &value) { _lastOpenedWithVersion = value; } uint64_t CryConfig::BlocksizeBytes() const { return _blocksizeBytes; } void CryConfig::SetBlocksizeBytes(uint64_t value) { _blocksizeBytes = value; } const CryConfig::FilesystemID &CryConfig::FilesystemId() const { return _filesystemId; } void CryConfig::SetFilesystemId(FilesystemID value) { _filesystemId = value; } optional CryConfig::ExclusiveClientId() const { return _exclusiveClientId; } void CryConfig::SetExclusiveClientId(optional value) { _exclusiveClientId = value; } bool CryConfig::missingBlockIsIntegrityViolation() const { return _exclusiveClientId != boost::none; } #ifndef CRYFS_NO_COMPATIBILITY bool CryConfig::HasVersionNumbers() const { return _hasVersionNumbers; } void CryConfig::SetHasVersionNumbers(bool value) { _hasVersionNumbers = value; } bool CryConfig::HasParentPointers() const { return _hasParentPointers; } void CryConfig::SetHasParentPointers(bool value) { _hasParentPointers = value; } #endif } src/cryfs/config/CryConfig.h000066400000000000000000000054041347701267100163070ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYCONFIG_H_ #define MESSMER_CRYFS_SRC_CONFIG_CRYCONFIG_H_ #include #include #include #include namespace cryfs { class CryConfig final { public: static constexpr const char* FilesystemFormatVersion = "0.10"; //TODO No default constructor, pass in config values instead! CryConfig(); CryConfig(CryConfig &&rhs) = default; CryConfig(const CryConfig &rhs) = default; const std::string &RootBlob() const; void SetRootBlob(std::string value); const std::string &EncryptionKey() const; void SetEncryptionKey(std::string value); const std::string &Cipher() const; void SetCipher(std::string value); const std::string &Version() const; void SetVersion(std::string value); const std::string &CreatedWithVersion() const; void SetCreatedWithVersion(std::string value); const std::string &LastOpenedWithVersion() const; void SetLastOpenedWithVersion(const std::string &value); uint64_t BlocksizeBytes() const; void SetBlocksizeBytes(uint64_t value); using FilesystemID = cpputils::FixedSizeData<16>; const FilesystemID &FilesystemId() const; void SetFilesystemId(FilesystemID value); // If the exclusive client Id is set, then additional integrity measures (i.e. treating missing blocks as integrity violations) are enabled. // Because this only works in a single-client setting, only this one client Id is allowed to access the file system. boost::optional ExclusiveClientId() const; void SetExclusiveClientId(boost::optional value); bool missingBlockIsIntegrityViolation() const; #ifndef CRYFS_NO_COMPATIBILITY // 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 HasVersionNumbers() const; 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 static CryConfig load(const cpputils::Data &data); cpputils::Data save() const; private: std::string _rootBlob; std::string _encKey; std::string _cipher; std::string _version; std::string _createdWithVersion; std::string _lastOpenedWithVersion; uint64_t _blocksizeBytes; FilesystemID _filesystemId; boost::optional _exclusiveClientId; #ifndef CRYFS_NO_COMPATIBILITY bool _hasVersionNumbers; bool _hasParentPointers; #endif CryConfig &operator=(const CryConfig &rhs) = delete; }; } #endif src/cryfs/config/CryConfigConsole.cpp000066400000000000000000000070001347701267100201570ustar00rootroot00000000000000#include "CryConfigConsole.h" #include "CryCipher.h" using cpputils::Console; using boost::none; using std::string; using std::vector; using std::shared_ptr; namespace cryfs { constexpr const char *CryConfigConsole::DEFAULT_CIPHER; constexpr uint32_t CryConfigConsole::DEFAULT_BLOCKSIZE_BYTES; CryConfigConsole::CryConfigConsole(shared_ptr console) : _console(std::move(console)), _useDefaultSettings(none) { } string CryConfigConsole::askCipher() { if (_checkUseDefaultSettings()) { return DEFAULT_CIPHER; } else { return _askCipher(); } } string CryConfigConsole::_askCipher() const { vector ciphers = CryCiphers::supportedCipherNames(); string cipherName = ""; bool askAgain = true; while(askAgain) { _console->print("\n"); unsigned int cipherIndex = _console->ask("Which block cipher do you want to use?", ciphers); cipherName = ciphers[cipherIndex]; askAgain = !_showWarningForCipherAndReturnIfOk(cipherName); }; return cipherName; } bool CryConfigConsole::_showWarningForCipherAndReturnIfOk(const string &cipherName) const { auto warning = CryCiphers::find(cipherName).warning(); if (warning == none) { return true; } return _console->askYesNo(string() + (*warning) + " Do you want to take this cipher nevertheless?", true); } uint32_t CryConfigConsole::askBlocksizeBytes() { if (_checkUseDefaultSettings()) { return DEFAULT_BLOCKSIZE_BYTES; } else { return _askBlocksizeBytes(); } } uint32_t CryConfigConsole::_askBlocksizeBytes() const { vector sizes = {"4KB", "8KB", "16KB", "32KB", "64KB", "512KB", "1MB", "4MB"}; unsigned int index = _console->ask("Which block size do you want to use?", sizes); switch(index) { case 0: return 4*1024; case 1: return 8*1024; case 2: return 16*1024; case 3: return 32*1024; case 4: return 64*1024; case 5: return 512*1024; case 6: return 1024*1024; case 7: return 4*1024*1024; default: ASSERT(false, "Unhandled case"); } } bool CryConfigConsole::askMissingBlockIsIntegrityViolation() { if (_checkUseDefaultSettings()) { return DEFAULT_MISSINGBLOCKISINTEGRITYVIOLATION; } else { return _askMissingBlockIsIntegrityViolation(); } } bool CryConfigConsole::_askMissingBlockIsIntegrityViolation() const { return _console->askYesNo("\nMost integrity checks are enabled by default. However, by default CryFS does not treat missing blocks as integrity violations.\nThat is, if CryFS finds a block missing, it will assume that this is due to a synchronization delay and not because an attacker deleted the block.\nIf you are in a single-client setting, you can let it treat missing blocks as integrity violations, which will ensure that you notice if an attacker deletes one of your files.\nHowever, in this case, you will not be able to use the file system with other devices anymore.\nDo you want to treat missing blocks as integrity violations?", false); } bool CryConfigConsole::_checkUseDefaultSettings() { if (_useDefaultSettings == none) { _useDefaultSettings = _console->askYesNo("Use default settings?", true); } return *_useDefaultSettings; } } src/cryfs/config/CryConfigConsole.h000066400000000000000000000023271347701267100176330ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGCONSOLE_H #define MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGCONSOLE_H #include #include #include namespace cryfs { class CryConfigConsole final { public: CryConfigConsole(std::shared_ptr console); CryConfigConsole(CryConfigConsole &&rhs) = default; std::string askCipher(); uint32_t askBlocksizeBytes(); bool askMissingBlockIsIntegrityViolation(); static constexpr const char *DEFAULT_CIPHER = "aes-256-gcm"; static constexpr uint32_t DEFAULT_BLOCKSIZE_BYTES = 16 * 1024; // 16KB static constexpr uint32_t DEFAULT_MISSINGBLOCKISINTEGRITYVIOLATION = false; private: bool _checkUseDefaultSettings(); std::string _askCipher() const; bool _showWarningForCipherAndReturnIfOk(const std::string &cipherName) const; uint32_t _askBlocksizeBytes() const; bool _askMissingBlockIsIntegrityViolation() const; std::shared_ptr _console; boost::optional _useDefaultSettings; DISALLOW_COPY_AND_ASSIGN(CryConfigConsole); }; } #endif src/cryfs/config/CryConfigCreator.cpp000066400000000000000000000103241347701267100201570ustar00rootroot00000000000000#include "CryConfigCreator.h" #include "CryCipher.h" #include #include #include #include using cpputils::Console; using cpputils::RandomGenerator; using cpputils::Random; using std::string; using std::shared_ptr; using boost::optional; using boost::none; namespace cryfs { CryConfigCreator::CryConfigCreator(shared_ptr console, RandomGenerator &encryptionKeyGenerator, LocalStateDir localStateDir) :_console(console), _configConsole(console), _encryptionKeyGenerator(encryptionKeyGenerator), _localStateDir(std::move(localStateDir)) { } CryConfigCreator::ConfigCreateResult CryConfigCreator::create(const optional &cipherFromCommandLine, const optional &blocksizeBytesFromCommandLine, const optional &missingBlockIsIntegrityViolationFromCommandLine, bool allowReplacedFilesystem) { CryConfig config; config.SetCipher(_generateCipher(cipherFromCommandLine)); config.SetVersion(CryConfig::FilesystemFormatVersion); config.SetCreatedWithVersion(gitversion::VersionString()); config.SetLastOpenedWithVersion(gitversion::VersionString()); config.SetBlocksizeBytes(_generateBlocksizeBytes(blocksizeBytesFromCommandLine)); config.SetRootBlob(_generateRootBlobId()); config.SetFilesystemId(_generateFilesystemID()); auto encryptionKey = _generateEncKey(config.Cipher()); auto localState = LocalStateMetadata::loadOrGenerate(_localStateDir.forFilesystemId(config.FilesystemId()), cpputils::Data::FromString(encryptionKey), allowReplacedFilesystem); uint32_t myClientId = localState.myClientId(); config.SetEncryptionKey(std::move(encryptionKey)); config.SetExclusiveClientId(_generateExclusiveClientId(missingBlockIsIntegrityViolationFromCommandLine, myClientId)); #ifndef CRYFS_NO_COMPATIBILITY config.SetHasVersionNumbers(true); #endif return ConfigCreateResult {std::move(config), myClientId}; } uint32_t CryConfigCreator::_generateBlocksizeBytes(const optional &blocksizeBytesFromCommandLine) { if (blocksizeBytesFromCommandLine != none) { // TODO Check block size is valid (i.e. large enough) return *blocksizeBytesFromCommandLine; } else { return _configConsole.askBlocksizeBytes(); } } string CryConfigCreator::_generateCipher(const optional &cipherFromCommandLine) { if (cipherFromCommandLine != none) { ASSERT(std::find(CryCiphers::supportedCipherNames().begin(), CryCiphers::supportedCipherNames().end(), *cipherFromCommandLine) != CryCiphers::supportedCipherNames().end(), "Invalid cipher"); return *cipherFromCommandLine; } else { return _configConsole.askCipher(); } } optional CryConfigCreator::_generateExclusiveClientId(const optional &missingBlockIsIntegrityViolationFromCommandLine, uint32_t myClientId) { if (!_generateMissingBlockIsIntegrityViolation(missingBlockIsIntegrityViolationFromCommandLine)) { return none; } return myClientId; } bool CryConfigCreator::_generateMissingBlockIsIntegrityViolation(const optional &missingBlockIsIntegrityViolationFromCommandLine) { if (missingBlockIsIntegrityViolationFromCommandLine != none) { return *missingBlockIsIntegrityViolationFromCommandLine; } else { return _configConsole.askMissingBlockIsIntegrityViolation(); } } string CryConfigCreator::_generateEncKey(const std::string &cipher) { _console->print("\nGenerating secure encryption key. This can take some time..."); auto key = CryCiphers::find(cipher).createKey(_encryptionKeyGenerator); _console->print("done\n"); return key; } string CryConfigCreator::_generateRootBlobId() { //An empty root blob entry will tell CryDevice to create a new root blob return ""; } CryConfig::FilesystemID CryConfigCreator::_generateFilesystemID() { return Random::PseudoRandom().getFixedSize(); } } src/cryfs/config/CryConfigCreator.h000066400000000000000000000036101347701267100176240ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGCREATOR_H #define MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGCREATOR_H #include #include #include #include #include "CryConfig.h" #include "CryConfigConsole.h" namespace cryfs { class CryConfigCreator final { public: CryConfigCreator(std::shared_ptr console, cpputils::RandomGenerator &encryptionKeyGenerator, LocalStateDir localStateDir); CryConfigCreator(CryConfigCreator &&rhs) = default; struct ConfigCreateResult { CryConfig config; uint32_t myClientId; }; ConfigCreateResult create(const boost::optional &cipherFromCommandLine, const boost::optional &blocksizeBytesFromCommandLine, const boost::optional &missingBlockIsIntegrityViolationFromCommandLine, bool allowReplacedFilesystem); private: std::string _generateCipher(const boost::optional &cipherFromCommandLine); std::string _generateEncKey(const std::string &cipher); std::string _generateRootBlobId(); uint32_t _generateBlocksizeBytes(const boost::optional &blocksizeBytesFromCommandLine); CryConfig::FilesystemID _generateFilesystemID(); boost::optional _generateExclusiveClientId(const boost::optional &missingBlockIsIntegrityViolationFromCommandLine, uint32_t myClientId); bool _generateMissingBlockIsIntegrityViolation(const boost::optional &missingBlockIsIntegrityViolationFromCommandLine); std::shared_ptr _console; CryConfigConsole _configConsole; cpputils::RandomGenerator &_encryptionKeyGenerator; LocalStateDir _localStateDir; DISALLOW_COPY_AND_ASSIGN(CryConfigCreator); }; } #endif src/cryfs/config/CryConfigFile.cpp000066400000000000000000000051251347701267100174420ustar00rootroot00000000000000#include "CryConfigFile.h" #include #include #include #include using boost::optional; using boost::none; using std::ifstream; using std::ofstream; using std::string; using std::istringstream; using std::ostringstream; using std::stringstream; using std::istream; using cpputils::Data; using cpputils::unique_ref; namespace bf = boost::filesystem; using namespace cpputils::logging; namespace cryfs { CryConfigFile::~CryConfigFile() { //We do not call save() here, because we do not want the config file to be re-encrypted on each filesystem run } optional CryConfigFile::load(bf::path path, CryKeyProvider* keyProvider) { auto encryptedConfigData = Data::LoadFromFile(path); if (encryptedConfigData == none) { LOG(ERR, "Config file not found"); return none; } auto encryptor = CryConfigEncryptorFactory::loadExistingKey(*encryptedConfigData, keyProvider); if (encryptor == none) { return none; } auto decrypted = (*encryptor)->decrypt(*encryptedConfigData); if (decrypted == none) { return none; } CryConfig config = CryConfig::load(decrypted->data); if (config.Cipher() != decrypted->cipherName) { LOG(ERR, "Inner cipher algorithm used to encrypt config file doesn't match config value"); return none; } auto configFile = CryConfigFile(std::move(path), std::move(config), std::move(*encryptor)); if (decrypted->wasInDeprecatedConfigFormat) { // Migrate it to new format configFile.save(); } //TODO For newer compilers, this works without std::move return configFile; } CryConfigFile CryConfigFile::create(bf::path path, CryConfig config, CryKeyProvider* keyProvider) { if (bf::exists(path)) { throw std::runtime_error("Config file exists already."); } auto result = CryConfigFile(std::move(path), std::move(config), CryConfigEncryptorFactory::deriveNewKey(keyProvider)); result.save(); return result; } CryConfigFile::CryConfigFile(bf::path path, CryConfig config, unique_ref encryptor) : _path(std::move(path)), _config(std::move(config)), _encryptor(std::move(encryptor)) { } void CryConfigFile::save() const { Data configData = _config.save(); auto encrypted = _encryptor->encrypt(configData, _config.Cipher()); encrypted.StoreToFile(_path); } CryConfig *CryConfigFile::config() { return const_cast(const_cast(this)->config()); } const CryConfig *CryConfigFile::config() const { return &_config; } } src/cryfs/config/CryConfigFile.h000066400000000000000000000021021347701267100170770ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGFILE_H #define MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGFILE_H #include #include #include "CryConfig.h" #include #include "crypto/CryConfigEncryptorFactory.h" namespace cryfs { class CryConfigFile final { public: CryConfigFile(CryConfigFile &&rhs) = default; ~CryConfigFile(); static CryConfigFile create(boost::filesystem::path path, CryConfig config, CryKeyProvider* keyProvider); static boost::optional load(boost::filesystem::path path, CryKeyProvider* keyProvider); void save() const; CryConfig *config(); const CryConfig *config() const; private: CryConfigFile(boost::filesystem::path path, CryConfig config, cpputils::unique_ref encryptor); boost::filesystem::path _path; CryConfig _config; cpputils::unique_ref _encryptor; DISALLOW_COPY_AND_ASSIGN(CryConfigFile); }; } #endif src/cryfs/config/CryConfigLoader.cpp000066400000000000000000000200151347701267100177640ustar00rootroot00000000000000#include "CryConfigLoader.h" #include "CryConfigFile.h" #include #include #include #include #include #include #include "../localstate/LocalStateDir.h" #include "../localstate/LocalStateMetadata.h" #include "../CryfsException.h" namespace bf = boost::filesystem; using cpputils::Console; using cpputils::RandomGenerator; using cpputils::unique_ref; using boost::optional; using boost::none; using std::shared_ptr; using std::string; using std::shared_ptr; using gitversion::VersionCompare; using namespace cpputils::logging; namespace cryfs { CryConfigLoader::CryConfigLoader(shared_ptr console, RandomGenerator &keyGenerator, unique_ref keyProvider, LocalStateDir localStateDir, const optional &cipherFromCommandLine, const boost::optional &blocksizeBytesFromCommandLine, const boost::optional &missingBlockIsIntegrityViolationFromCommandLine) : _console(console), _creator(std::move(console), keyGenerator, localStateDir), _keyProvider(std::move(keyProvider)), _cipherFromCommandLine(cipherFromCommandLine), _blocksizeBytesFromCommandLine(blocksizeBytesFromCommandLine), _missingBlockIsIntegrityViolationFromCommandLine(missingBlockIsIntegrityViolationFromCommandLine), _localStateDir(std::move(localStateDir)) { } optional CryConfigLoader::_loadConfig(bf::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem) { auto config = CryConfigFile::load(std::move(filename), _keyProvider.get()); if (config == none) { return none; } #ifndef CRYFS_NO_COMPATIBILITY //Since 0.9.7 and 0.9.8 set their own version to cryfs.version instead of the filesystem format version (which is 0.9.6), overwrite it if (config->config()->Version() == "0.9.7" || config->config()->Version() == "0.9.8") { config->config()->SetVersion("0.9.6"); } #endif _checkVersion(*config->config(), allowFilesystemUpgrade); if (config->config()->Version() != CryConfig::FilesystemFormatVersion) { config->config()->SetVersion(CryConfig::FilesystemFormatVersion); config->save(); } if (config->config()->LastOpenedWithVersion() != gitversion::VersionString()) { config->config()->SetLastOpenedWithVersion(gitversion::VersionString()); config->save(); } _checkCipher(*config->config()); auto localState = LocalStateMetadata::loadOrGenerate(_localStateDir.forFilesystemId(config->config()->FilesystemId()), cpputils::Data::FromString(config->config()->EncryptionKey()), allowReplacedFilesystem); uint32_t myClientId = localState.myClientId(); _checkMissingBlocksAreIntegrityViolations(&*config, myClientId); return ConfigLoadResult {std::move(*config), myClientId}; } void CryConfigLoader::_checkVersion(const CryConfig &config, bool allowFilesystemUpgrade) { if (gitversion::VersionCompare::isOlderThan(config.Version(), "0.9.4")) { throw CryfsException("This filesystem is for CryFS " + config.Version() + ". This format is not supported anymore. Please migrate the file system to a supported version first by opening it with CryFS 0.9.x (x>=4).", ErrorCode::TooOldFilesystemFormat); } if (gitversion::VersionCompare::isOlderThan(CryConfig::FilesystemFormatVersion, config.Version())) { if (!_console->askYesNo("This filesystem is for CryFS " + config.Version() + " or later and should not be opened with older versions. It is strongly recommended to update your CryFS version. However, if you have backed up your base directory and know what you're doing, you can continue trying to load it. Do you want to continue?", false)) { throw CryfsException("This filesystem is for CryFS " + config.Version() + " or later. Please update your CryFS version.", ErrorCode::TooNewFilesystemFormat); } } if (!allowFilesystemUpgrade && gitversion::VersionCompare::isOlderThan(config.Version(), CryConfig::FilesystemFormatVersion)) { if (!_console->askYesNo("This filesystem is for CryFS " + config.Version() + " (or a later version with the same storage format). You're running a CryFS version using storage format " + CryConfig::FilesystemFormatVersion + ". It is recommended to create a new filesystem with CryFS 0.10 and copy your files into it. If you don't want to do that, we can also attempt to migrate the existing filesystem, but that can take a long time, you won't be getting some of the performance advantages of the 0.10 release series, and if the migration fails, your data may be lost. If you decide to continue, please make sure you have a backup of your data. Do you want to attempt a migration now?", false)) { throw CryfsException("This filesystem is for CryFS " + config.Version() + " (or a later version with the same storage format). It has to be migrated.", ErrorCode::TooOldFilesystemFormat); } } } void CryConfigLoader::_checkCipher(const CryConfig &config) const { if (_cipherFromCommandLine != none && config.Cipher() != *_cipherFromCommandLine) { throw CryfsException(string() + "Filesystem uses " + config.Cipher() + " cipher and not " + *_cipherFromCommandLine + " as specified.", ErrorCode::WrongCipher); } } void CryConfigLoader::_checkMissingBlocksAreIntegrityViolations(CryConfigFile *configFile, uint32_t myClientId) { if (_missingBlockIsIntegrityViolationFromCommandLine == optional(true) && configFile->config()->ExclusiveClientId() == none) { throw CryfsException("You specified on the command line to treat missing blocks as integrity violations, but the file system is not setup to do that.", ErrorCode::FilesystemHasDifferentIntegritySetup); } if (_missingBlockIsIntegrityViolationFromCommandLine == optional(false) && configFile->config()->ExclusiveClientId() != none) { throw CryfsException("You specified on the command line to not treat missing blocks as integrity violations, but the file system is setup to do that.", ErrorCode::FilesystemHasDifferentIntegritySetup); } // If the file system is set up to treat missing blocks as integrity violations, but we're accessing from a different client, ask whether they want to disable the feature. auto exclusiveClientId = configFile->config()->ExclusiveClientId(); if (exclusiveClientId != none && *exclusiveClientId != myClientId) { if (!_console->askYesNo("\nThis filesystem is setup to treat missing blocks as integrity violations and therefore only works in single-client mode. You are trying to access it from a different client.\nDo you want to disable this integrity feature and stop treating missing blocks as integrity violations?\nChoosing yes will not affect the confidentiality of your data, but in future you might not notice if an attacker deletes one of your files.", false)) { throw CryfsException("File system is in single-client mode and can only be used from the client that created it.", ErrorCode::SingleClientFileSystem); } configFile->config()->SetExclusiveClientId(none); configFile->save(); } } optional CryConfigLoader::load(bf::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem) { return _loadConfig(std::move(filename), allowFilesystemUpgrade, allowReplacedFilesystem); } optional CryConfigLoader::loadOrCreate(bf::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem) { if (bf::exists(filename)) { return _loadConfig(std::move(filename), allowFilesystemUpgrade, allowReplacedFilesystem); } else { return _createConfig(std::move(filename), allowReplacedFilesystem); } } CryConfigLoader::ConfigLoadResult CryConfigLoader::_createConfig(bf::path filename, bool allowReplacedFilesystem) { auto config = _creator.create(_cipherFromCommandLine, _blocksizeBytesFromCommandLine, _missingBlockIsIntegrityViolationFromCommandLine, allowReplacedFilesystem); auto result = CryConfigFile::create(std::move(filename), std::move(config.config), _keyProvider.get()); return ConfigLoadResult {std::move(result), config.myClientId}; } } src/cryfs/config/CryConfigLoader.h000066400000000000000000000042651347701267100174420ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGLOADER_H_ #define MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGLOADER_H_ #include #include #include "CryConfigFile.h" #include "CryCipher.h" #include "CryConfigCreator.h" #include "CryKeyProvider.h" namespace cryfs { class CryConfigLoader final { public: // note: keyGenerator generates the inner (i.e. file system) key. keyProvider asks for the password and generates the outer (i.e. config file) key. CryConfigLoader(std::shared_ptr console, cpputils::RandomGenerator &keyGenerator, cpputils::unique_ref keyProvider, LocalStateDir localStateDir, const boost::optional &cipherFromCommandLine, const boost::optional &blocksizeBytesFromCommandLine, const boost::optional &missingBlockIsIntegrityViolationFromCommandLine); CryConfigLoader(CryConfigLoader &&rhs) = default; struct ConfigLoadResult { CryConfigFile configFile; uint32_t myClientId; }; boost::optional loadOrCreate(boost::filesystem::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem); boost::optional load(boost::filesystem::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem); private: boost::optional _loadConfig(boost::filesystem::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem); ConfigLoadResult _createConfig(boost::filesystem::path filename, bool allowReplacedFilesystem); void _checkVersion(const CryConfig &config, bool allowFilesystemUpgrade); void _checkCipher(const CryConfig &config) const; void _checkMissingBlocksAreIntegrityViolations(CryConfigFile *configFile, uint32_t myClientId); std::shared_ptr _console; CryConfigCreator _creator; cpputils::unique_ref _keyProvider; boost::optional _cipherFromCommandLine; boost::optional _blocksizeBytesFromCommandLine; boost::optional _missingBlockIsIntegrityViolationFromCommandLine; LocalStateDir _localStateDir; DISALLOW_COPY_AND_ASSIGN(CryConfigLoader); }; } #endif src/cryfs/config/CryKeyProvider.cpp000066400000000000000000000000341347701267100176720ustar00rootroot00000000000000#include "CryKeyProvider.h" src/cryfs/config/CryKeyProvider.h000066400000000000000000000010171347701267100173410ustar00rootroot00000000000000#pragma once #ifndef CRYFS_CRYKEYPROVIDER_H #define CRYFS_CRYKEYPROVIDER_H #include namespace cryfs { class CryKeyProvider { public: virtual ~CryKeyProvider() = default; struct KeyResult final { cpputils::EncryptionKey key; cpputils::Data kdfParameters; }; virtual cpputils::EncryptionKey requestKeyForExistingFilesystem(size_t keySize, const cpputils::Data& kdfParameters) = 0; virtual KeyResult requestKeyForNewFilesystem(size_t keySize) = 0; }; } #endif src/cryfs/config/CryPasswordBasedKeyProvider.cpp000066400000000000000000000026531347701267100223650ustar00rootroot00000000000000#include "CryPasswordBasedKeyProvider.h" using std::shared_ptr; using cpputils::Console; using cpputils::unique_ref; using cpputils::EncryptionKey; using cpputils::unique_ref; using cpputils::PasswordBasedKDF; namespace cryfs { CryPasswordBasedKeyProvider::CryPasswordBasedKeyProvider(shared_ptr console, std::function askPasswordForExistingFilesystem, std::function askPasswordForNewFilesystem, unique_ref kdf) : _console(std::move(console)), _askPasswordForExistingFilesystem(std::move(askPasswordForExistingFilesystem)), _askPasswordForNewFilesystem(std::move(askPasswordForNewFilesystem)), _kdf(std::move(kdf)) {} EncryptionKey CryPasswordBasedKeyProvider::requestKeyForExistingFilesystem(size_t keySize, const cpputils::Data& kdfParameters) { auto password = _askPasswordForExistingFilesystem(); _console->print("Deriving encryption key (this can take some time)..."); auto key = _kdf->deriveExistingKey(keySize, password, kdfParameters); _console->print("done\n"); return key; } CryKeyProvider::KeyResult CryPasswordBasedKeyProvider::requestKeyForNewFilesystem(size_t keySize) { auto password = _askPasswordForNewFilesystem(); _console->print("Deriving encryption key (this can take some time)..."); auto keyResult = _kdf->deriveNewKey(keySize, password); _console->print("done\n"); return {std::move(keyResult.key), std::move(keyResult.kdfParameters)}; } } src/cryfs/config/CryPasswordBasedKeyProvider.h000066400000000000000000000022201347701267100220200ustar00rootroot00000000000000#pragma once #ifndef CRYFS_CRYPASSWORDFROMCONSOLEKEYPROVIDER_H #define CRYFS_CRYPASSWORDFROMCONSOLEKEYPROVIDER_H #include "CryKeyProvider.h" #include #include #include namespace cryfs { // TODO Remove duplication with CryPresetPasswordBasedKeyProvider class CryPasswordBasedKeyProvider final : public CryKeyProvider { public: explicit CryPasswordBasedKeyProvider(std::shared_ptr console, std::function askPasswordForExistingFilesystem, std::function askPasswordForNewFilesystem, cpputils::unique_ref kdf); cpputils::EncryptionKey requestKeyForExistingFilesystem(size_t keySize, const cpputils::Data& kdfParameters) override; KeyResult requestKeyForNewFilesystem(size_t keySize) override; private: std::shared_ptr _console; std::function _askPasswordForExistingFilesystem; std::function _askPasswordForNewFilesystem; cpputils::unique_ref _kdf; DISALLOW_COPY_AND_ASSIGN(CryPasswordBasedKeyProvider); }; } #endif src/cryfs/config/CryPresetPasswordBasedKeyProvider.cpp000066400000000000000000000015351347701267100235460ustar00rootroot00000000000000#include "CryPresetPasswordBasedKeyProvider.h" using cpputils::unique_ref; using cpputils::EncryptionKey; using cpputils::unique_ref; using cpputils::PasswordBasedKDF; using cpputils::Data; namespace cryfs { CryPresetPasswordBasedKeyProvider::CryPresetPasswordBasedKeyProvider(std::string password, unique_ref kdf) : _password(std::move(password)), _kdf(std::move(kdf)) {} EncryptionKey CryPresetPasswordBasedKeyProvider::requestKeyForExistingFilesystem(size_t keySize, const Data& kdfParameters) { return _kdf->deriveExistingKey(keySize, _password, kdfParameters); } CryPresetPasswordBasedKeyProvider::KeyResult CryPresetPasswordBasedKeyProvider::requestKeyForNewFilesystem(size_t keySize) { auto keyResult = _kdf->deriveNewKey(keySize, _password); return {std::move(keyResult.key), std::move(keyResult.kdfParameters)}; } } src/cryfs/config/CryPresetPasswordBasedKeyProvider.h000066400000000000000000000015321347701267100232100ustar00rootroot00000000000000#pragma once #ifndef CRYFS_CRYPRESETPASSWORDFROMCONSOLEKEYPROVIDER_H #define CRYFS_CRYPRESETPASSWORDFROMCONSOLEKEYPROVIDER_H #include "CryKeyProvider.h" #include #include namespace cryfs { class CryPresetPasswordBasedKeyProvider final : public CryKeyProvider { public: explicit CryPresetPasswordBasedKeyProvider(std::string password, cpputils::unique_ref kdf); cpputils::EncryptionKey requestKeyForExistingFilesystem(size_t keySize, const cpputils::Data& kdfParameters) override; KeyResult requestKeyForNewFilesystem(size_t keySize) override; private: std::string _password; cpputils::unique_ref _kdf; DISALLOW_COPY_AND_ASSIGN(CryPresetPasswordBasedKeyProvider); }; } #endif src/cryfs/config/crypto/000077500000000000000000000000001347701267100155705ustar00rootroot00000000000000src/cryfs/config/crypto/CryConfigEncryptor.cpp000066400000000000000000000044611347701267100220720ustar00rootroot00000000000000#include "CryConfigEncryptor.h" #include using std::string; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::Data; using boost::optional; using boost::none; using namespace cpputils::logging; namespace cryfs { constexpr size_t CryConfigEncryptor::OuterKeySize; constexpr size_t CryConfigEncryptor::MaxTotalKeySize; CryConfigEncryptor::CryConfigEncryptor(cpputils::EncryptionKey derivedKey, cpputils::Data kdfParameters) : _derivedKey(std::move(derivedKey)), _kdfParameters(std::move(kdfParameters)) { ASSERT(_derivedKey.binaryLength() == MaxTotalKeySize, "Wrong key size"); } Data CryConfigEncryptor::encrypt(const Data &plaintext, const string &cipherName) const { InnerConfig innerConfig = _innerEncryptor(cipherName)->encrypt(plaintext); Data serializedInnerConfig = innerConfig.serialize(); OuterConfig outerConfig = _outerEncryptor()->encrypt(serializedInnerConfig); return outerConfig.serialize(); } optional CryConfigEncryptor::decrypt(const Data &data) const { auto outerConfig = OuterConfig::deserialize(data); if (outerConfig == none) { return none; } auto serializedInnerConfig = _outerEncryptor()->decrypt(*outerConfig); if(serializedInnerConfig == none) { return none; } auto innerConfig = InnerConfig::deserialize(*serializedInnerConfig); if (innerConfig == none) { return none; } auto plaintext = _innerEncryptor(innerConfig->cipherName)->decrypt(*innerConfig); if (plaintext == none) { return none; } return Decrypted{std::move(*plaintext), innerConfig->cipherName, outerConfig->wasInDeprecatedConfigFormat}; } unique_ref CryConfigEncryptor::_outerEncryptor() const { auto outerKey = _derivedKey.take(OuterKeySize); return make_unique_ref(std::move(outerKey), _kdfParameters.copy()); } unique_ref CryConfigEncryptor::_innerEncryptor(const string &cipherName) const { auto innerKey = _derivedKey.drop(OuterKeySize); return CryCiphers::find(cipherName).createInnerConfigEncryptor(std::move(innerKey)); } } src/cryfs/config/crypto/CryConfigEncryptor.h000066400000000000000000000027521347701267100215400ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYPTO_CRYCONFIGENCRYPTOR_H #define MESSMER_CRYFS_SRC_CONFIG_CRYPTO_CRYCONFIGENCRYPTOR_H #include #include #include #include #include "inner/InnerEncryptor.h" #include "outer/OuterEncryptor.h" #include "../CryCipher.h" namespace cryfs { //TODO Use own exception for cpputils::Serializer/cpputils::Deserializer errors and only catch them class CryConfigEncryptor final { public: static constexpr size_t OuterKeySize = OuterEncryptor::Cipher::KEYSIZE; static constexpr size_t MaxTotalKeySize = OuterKeySize + CryCiphers::MAX_KEY_SIZE; struct Decrypted { cpputils::Data data; std::string cipherName; bool wasInDeprecatedConfigFormat; }; CryConfigEncryptor(cpputils::EncryptionKey derivedKey, cpputils::Data _kdfParameters); cpputils::Data encrypt(const cpputils::Data &plaintext, const std::string &cipherName) const; boost::optional decrypt(const cpputils::Data &data) const; private: cpputils::unique_ref _outerEncryptor() const; cpputils::unique_ref _innerEncryptor(const std::string &cipherName) const; cpputils::EncryptionKey _derivedKey; cpputils::Data _kdfParameters; DISALLOW_COPY_AND_ASSIGN(CryConfigEncryptor); }; } #endif src/cryfs/config/crypto/CryConfigEncryptorFactory.cpp000066400000000000000000000033451347701267100234220ustar00rootroot00000000000000#include "CryConfigEncryptorFactory.h" #include #include "outer/OuterConfig.h" #include "cryfs/config/CryKeyProvider.h" using namespace cpputils::logging; using boost::optional; using boost::none; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::Data; using std::string; //TODO It would be better, not to generate a MaxTotalKeySize key here, but to generate the outer key first, and then // (once we know which inner cipher was used) only generate as many key bytes as we need for the inner cipher. // This would need a change in the scrypt interface though, because right now we can't continue past key computations. //TODO I might be able to know the actual key size here (at runtime) and switch the SCrypt deriveKey() interface to getting a dynamic size. namespace cryfs { optional> CryConfigEncryptorFactory::loadExistingKey(const Data &data, CryKeyProvider *keyProvider) { auto outerConfig = OuterConfig::deserialize(data); if (outerConfig == none) { return none; } auto key = keyProvider->requestKeyForExistingFilesystem(CryConfigEncryptor::MaxTotalKeySize, outerConfig->kdfParameters); return make_unique_ref(key, std::move(outerConfig->kdfParameters)); } unique_ref CryConfigEncryptorFactory::deriveNewKey(CryKeyProvider *keyProvider) { auto keyResult = keyProvider->requestKeyForNewFilesystem(CryConfigEncryptor::MaxTotalKeySize); return make_unique_ref(std::move(keyResult.key), std::move(keyResult.kdfParameters)); } } src/cryfs/config/crypto/CryConfigEncryptorFactory.h000066400000000000000000000014321347701267100230620ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYPTO_CRYCONFIGENCRYPTORFACTORY_H #define MESSMER_CRYFS_SRC_CONFIG_CRYPTO_CRYCONFIGENCRYPTORFACTORY_H #include "inner/ConcreteInnerEncryptor.h" #include "CryConfigEncryptor.h" #include #include #include "../CryCipher.h" namespace cryfs { class CryKeyProvider; class CryConfigEncryptorFactory final { public: static cpputils::unique_ref deriveNewKey(CryKeyProvider *keyProvider); static boost::optional> loadExistingKey(const cpputils::Data &ciphertext, CryKeyProvider *keyProvider); }; } #endif src/cryfs/config/crypto/inner/000077500000000000000000000000001347701267100167035ustar00rootroot00000000000000src/cryfs/config/crypto/inner/ConcreteInnerEncryptor.cpp000066400000000000000000000000441347701267100240510ustar00rootroot00000000000000#include "ConcreteInnerEncryptor.h" src/cryfs/config/crypto/inner/ConcreteInnerEncryptor.h000066400000000000000000000044241347701267100235240ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYPTO_INNER_CONCRETECRYCONFIGENCRYPTOR_H #define MESSMER_CRYFS_SRC_CONFIG_CRYPTO_INNER_CONCRETECRYCONFIGENCRYPTOR_H #include #include "InnerEncryptor.h" #include "InnerConfig.h" namespace cryfs { template class ConcreteInnerEncryptor final: public InnerEncryptor { public: static constexpr size_t CONFIG_SIZE = 900; // Inner config data is grown to this size before encryption to hide its actual size ConcreteInnerEncryptor(const typename Cipher::EncryptionKey& key); InnerConfig encrypt(const cpputils::Data &config) const override; boost::optional decrypt(const InnerConfig &innerConfig) const override; private: typename Cipher::EncryptionKey _key; DISALLOW_COPY_AND_ASSIGN(ConcreteInnerEncryptor); }; template ConcreteInnerEncryptor::ConcreteInnerEncryptor(const typename Cipher::EncryptionKey& key) : _key(key) { } template boost::optional ConcreteInnerEncryptor::decrypt(const InnerConfig &innerConfig) const { if (innerConfig.cipherName != Cipher::NAME) { cpputils::logging::LOG(cpputils::logging::ERR, "Initialized ConcreteInnerEncryptor with wrong cipher"); return boost::none; } auto decrypted = Cipher::decrypt(static_cast(innerConfig.encryptedConfig.data()), innerConfig.encryptedConfig.size(), _key); if (decrypted == boost::none) { cpputils::logging::LOG(cpputils::logging::ERR, "Failed decrypting configuration file"); return boost::none; } auto configData = cpputils::RandomPadding::remove(*decrypted); if (configData == boost::none) { return boost::none; } return std::move(*configData); } template InnerConfig ConcreteInnerEncryptor::encrypt(const cpputils::Data &config) const { auto padded = cpputils::RandomPadding::add(config, CONFIG_SIZE); auto encrypted = Cipher::encrypt(static_cast(padded.data()), padded.size(), _key); return InnerConfig{Cipher::NAME, std::move(encrypted)}; } } #endif src/cryfs/config/crypto/inner/InnerConfig.cpp000066400000000000000000000040131347701267100216060ustar00rootroot00000000000000#include "InnerConfig.h" #include using std::string; using std::exception; using cpputils::Deserializer; using cpputils::Serializer; using cpputils::Data; using boost::optional; using boost::none; using namespace cpputils::logging; namespace cryfs { const string InnerConfig::HEADER = "cryfs.config.inner;0"; Data InnerConfig::serialize() const { try { Serializer serializer(Serializer::StringSize(HEADER) + Serializer::StringSize(cipherName) + encryptedConfig.size()); serializer.writeString(HEADER); serializer.writeString(cipherName); serializer.writeTailData(encryptedConfig); return serializer.finished(); } catch (const exception &e) { LOG(ERR, "Error serializing inner configuration: {}", e.what()); throw; // This is a programming logic error, pass through exception. } } optional InnerConfig::deserialize(const Data &data) { Deserializer deserializer(&data); try { _checkHeader(&deserializer); string cipherName = deserializer.readString(); auto result = deserializer.readTailData(); deserializer.finished(); return InnerConfig {cipherName, std::move(result)}; } catch (const exception &e) { LOG(ERR, "Error deserializing inner configuration: {}", e.what()); return none; // This can be caused by invalid input data and does not have to be a programming error. Don't throw exception. } } void InnerConfig::_checkHeader(Deserializer *deserializer) { string header = deserializer->readString(); if (header != HEADER) { throw std::runtime_error("Invalid header. Maybe this filesystem was created with a different version of CryFS?"); } } void InnerConfig::_writeHeader(Serializer *serializer) { serializer->writeString(HEADER); } } src/cryfs/config/crypto/inner/InnerConfig.h000066400000000000000000000013061347701267100212550ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYPTO_INNER_INNERCONFIG_H #define MESSMER_CRYFS_SRC_CONFIG_CRYPTO_INNER_INNERCONFIG_H #include #include #include namespace cryfs { struct InnerConfig final { std::string cipherName; cpputils::Data encryptedConfig; cpputils::Data serialize() const; static boost::optional deserialize(const cpputils::Data &data); private: static void _checkHeader(cpputils::Deserializer *deserializer); static void _writeHeader(cpputils::Serializer *serializer); static const std::string HEADER; }; } #endif src/cryfs/config/crypto/inner/InnerEncryptor.cpp000066400000000000000000000000341347701267100223650ustar00rootroot00000000000000#include "InnerEncryptor.h" src/cryfs/config/crypto/inner/InnerEncryptor.h000066400000000000000000000012011347701267100220270ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYPTO_INNER_INNERENCRYPTOR_H #define MESSMER_CRYFS_SRC_CONFIG_CRYPTO_INNER_INNERENCRYPTOR_H #include #include #include #include #include #include "InnerConfig.h" namespace cryfs { class InnerEncryptor { public: virtual ~InnerEncryptor() {} virtual InnerConfig encrypt(const cpputils::Data &plaintext) const = 0; virtual boost::optional decrypt(const InnerConfig &innerConfig) const = 0; }; } #endif src/cryfs/config/crypto/outer/000077500000000000000000000000001347701267100167265ustar00rootroot00000000000000src/cryfs/config/crypto/outer/OuterConfig.cpp000066400000000000000000000062061347701267100216620ustar00rootroot00000000000000#include "OuterConfig.h" #include using std::string; using std::exception; using cpputils::Data; using cpputils::Serializer; using cpputils::Deserializer; using cpputils::SCryptParameters; using boost::optional; using boost::none; using namespace cpputils::logging; namespace cryfs { #ifndef CRYFS_NO_COMPATIBILITY const string OuterConfig::OLD_HEADER = "cryfs.config;0;scrypt"; #endif const string OuterConfig::HEADER = "cryfs.config;1;scrypt"; void OuterConfig::_checkHeader(Deserializer *deserializer) { string header = deserializer->readString(); if (header != HEADER) { throw std::runtime_error("Invalid header"); } } void OuterConfig::_writeHeader(Serializer *serializer) { serializer->writeString(HEADER); } Data OuterConfig::serialize() const { try { Serializer serializer(Serializer::StringSize(HEADER) + Serializer::DataSize(kdfParameters) + encryptedInnerConfig.size()); _writeHeader(&serializer); serializer.writeData(kdfParameters); serializer.writeTailData(encryptedInnerConfig); return serializer.finished(); } catch (const exception &e) { LOG(ERR, "Error serializing CryConfigEncryptor: {}", e.what()); throw; // This is a programming logic error. Pass through exception. } } optional OuterConfig::deserialize(const Data &data) { Deserializer deserializer(&data); try { #ifndef CRYFS_NO_COMPATIBILITY string header = deserializer.readString(); if (header == OLD_HEADER) { return _deserializeOldFormat(&deserializer); } else if (header == HEADER) { return _deserializeNewFormat(&deserializer); } else { throw std::runtime_error("Invalid header"); } #else _checkHeader(&deserializer); return _deserializeNewFormat(&deserializer); #endif } catch (const exception &e) { LOG(ERR, "Error deserializing outer configuration: {}", e.what()); return none; // This can be caused by invalid input data and does not have to be a programming error. Don't throw exception. } } #ifndef CRYFS_NO_COMPATIBILITY OuterConfig OuterConfig::_deserializeOldFormat(Deserializer *deserializer) { auto kdfParameters = SCryptParameters::deserializeOldFormat(deserializer); auto kdfParametersSerialized = kdfParameters.serialize(); auto encryptedInnerConfig = deserializer->readTailData(); deserializer->finished(); return OuterConfig {std::move(kdfParametersSerialized), std::move(encryptedInnerConfig), true}; } #endif OuterConfig OuterConfig::_deserializeNewFormat(Deserializer *deserializer) { auto kdfParameters = deserializer->readData(); auto encryptedInnerConfig = deserializer->readTailData(); deserializer->finished(); return OuterConfig {std::move(kdfParameters), std::move(encryptedInnerConfig), false}; } } src/cryfs/config/crypto/outer/OuterConfig.h000066400000000000000000000017761347701267100213360ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYPTO_OUTER_OUTERCONFIG_H #define MESSMER_CRYFS_SRC_CONFIG_CRYPTO_OUTER_OUTERCONFIG_H #include #include #include namespace cryfs { struct OuterConfig final { cpputils::Data kdfParameters; cpputils::Data encryptedInnerConfig; bool wasInDeprecatedConfigFormat; cpputils::Data serialize() const; static boost::optional deserialize(const cpputils::Data &data); private: static void _checkHeader(cpputils::Deserializer *deserializer); static void _writeHeader(cpputils::Serializer *serializer); static OuterConfig _deserializeNewFormat(cpputils::Deserializer *deserializer); static const std::string HEADER; #ifndef CRYFS_NO_COMPATIBILITY static const std::string OLD_HEADER; static OuterConfig _deserializeOldFormat(cpputils::Deserializer *deserializer); #endif }; } #endif src/cryfs/config/crypto/outer/OuterEncryptor.cpp000066400000000000000000000023411347701267100224360ustar00rootroot00000000000000#include "OuterEncryptor.h" #include #include "OuterConfig.h" using std::string; using cpputils::Data; using cpputils::RandomPadding; using boost::optional; using boost::none; using namespace cpputils::logging; namespace cryfs { OuterEncryptor::OuterEncryptor(Cipher::EncryptionKey key, cpputils::Data kdfParameters) : _key(std::move(key)), _kdfParameters(std::move(kdfParameters)) { } OuterConfig OuterEncryptor::encrypt(const Data &plaintext) const { auto padded = RandomPadding::add(plaintext, CONFIG_SIZE); auto ciphertext = Cipher::encrypt(static_cast(padded.data()), padded.size(), _key); return OuterConfig{_kdfParameters.copy(), std::move(ciphertext), false}; } optional OuterEncryptor::decrypt(const OuterConfig &outerConfig) const { ASSERT(outerConfig.kdfParameters == _kdfParameters, "OuterEncryptor was initialized with wrong key config"); auto inner = Cipher::decrypt(static_cast(outerConfig.encryptedInnerConfig.data()), outerConfig.encryptedInnerConfig.size(), _key); if(inner == none) { return none; } return RandomPadding::remove(*inner); } } src/cryfs/config/crypto/outer/OuterEncryptor.h000066400000000000000000000017271347701267100221120ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYPTO_OUTER_OUTERENCRYPTOR_H #define MESSMER_CRYFS_SRC_CONFIG_CRYPTO_OUTER_OUTERENCRYPTOR_H #include #include #include #include #include "OuterConfig.h" namespace cryfs { class OuterEncryptor final { public: using Cipher = cpputils::AES256_GCM; static constexpr size_t CONFIG_SIZE = 1024; // Config data is grown to this size before encryption to hide its actual size OuterEncryptor(Cipher::EncryptionKey key, cpputils::Data kdfParameters); OuterConfig encrypt(const cpputils::Data &encryptedInnerConfig) const; boost::optional decrypt(const OuterConfig &outerConfig) const; private: Cipher::EncryptionKey _key; cpputils::Data _kdfParameters; DISALLOW_COPY_AND_ASSIGN(OuterEncryptor); }; } #endif src/cryfs/cryfs.cpp000066400000000000000000000006121347701267100146340ustar00rootroot00000000000000#include "cryfs.h" class cryfs_load_handle { public: cryfs_load_handle(const char *value_): value(value_) {} const char *value; }; cryfs_load_handle *cryfs_load_init() { return new cryfs_load_handle("Hello Library World!"); } void cryfs_load_free(cryfs_load_handle *handle) { delete handle; } const char *cryfs_test(cryfs_load_handle *handle) { return handle->value; } src/cryfs/cryfs.h000066400000000000000000000004741347701267100143070ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_CRYFS_H #define MESSMER_CRYFS_CRYFS_H #ifdef __cplusplus extern "C" { #endif struct cryfs_load_handle; cryfs_load_handle *cryfs_load_init(); void cryfs_load_free(cryfs_load_handle *handle); const char *cryfs_test(cryfs_load_handle *handle); #ifdef __cplusplus } #endif #endif src/cryfs/filesystem/000077500000000000000000000000001347701267100151675ustar00rootroot00000000000000src/cryfs/filesystem/CryDevice.cpp000066400000000000000000000360751347701267100175630ustar00rootroot00000000000000#include #include #include "parallelaccessfsblobstore/DirBlobRef.h" #include "CryDevice.h" #include "CryDir.h" #include "CryFile.h" #include "CrySymlink.h" #include #include #include #include #include #include #include "parallelaccessfsblobstore/ParallelAccessFsBlobStore.h" #include "cachingfsblobstore/CachingFsBlobStore.h" #include "../config/CryCipher.h" #include #include #include #include "cryfs/localstate/LocalStateDir.h" #include using std::string; //TODO Get rid of this in favor of exception hierarchy using fspp::fuse::FuseErrnoException; using blockstore::BlockStore2; using blockstore::BlockId; using blobstore::BlobStore; using blockstore::lowtohighlevel::LowToHighLevelBlockStore; using blobstore::onblocks::BlobStoreOnBlocks; using blockstore::caching::CachingBlockStore2; using blockstore::integrity::IntegrityBlockStore2; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::dynamic_pointer_move; using boost::optional; using boost::none; using cryfs::fsblobstore::FsBlobStore; using cryfs::cachingfsblobstore::CachingFsBlobStore; using cryfs::parallelaccessfsblobstore::ParallelAccessFsBlobStore; using cryfs::parallelaccessfsblobstore::FileBlobRef; using cryfs::parallelaccessfsblobstore::DirBlobRef; using cryfs::parallelaccessfsblobstore::SymlinkBlobRef; using cryfs::parallelaccessfsblobstore::FsBlobRef; using namespace cpputils::logging; namespace bf = boost::filesystem; namespace cryfs { CryDevice::CryDevice(CryConfigFile configFile, unique_ref blockStore, const LocalStateDir& localStateDir, uint32_t myClientId, bool allowIntegrityViolations, bool missingBlockIsIntegrityViolation, std::function onIntegrityViolation) : _fsBlobStore(CreateFsBlobStore(std::move(blockStore), &configFile, localStateDir, myClientId, allowIntegrityViolations, missingBlockIsIntegrityViolation, std::move(onIntegrityViolation))), _rootBlobId(GetOrCreateRootBlobId(&configFile)), _onFsAction() { } unique_ref CryDevice::CreateFsBlobStore(unique_ref blockStore, CryConfigFile *configFile, const LocalStateDir& localStateDir, uint32_t myClientId, bool allowIntegrityViolations, bool missingBlockIsIntegrityViolation, std::function onIntegrityViolation) { auto blobStore = CreateBlobStore(std::move(blockStore), localStateDir, configFile, myClientId, allowIntegrityViolations, missingBlockIsIntegrityViolation, std::move(onIntegrityViolation)); #ifndef CRYFS_NO_COMPATIBILITY auto fsBlobStore = MigrateOrCreateFsBlobStore(std::move(blobStore), configFile); #else auto fsBlobStore = make_unique_ref(std::move(blobStore)); #endif return make_unique_ref( make_unique_ref( std::move(fsBlobStore) ) ); } #ifndef CRYFS_NO_COMPATIBILITY unique_ref CryDevice::MigrateOrCreateFsBlobStore(unique_ref blobStore, CryConfigFile *configFile) { string rootBlobId = configFile->config()->RootBlob(); if ("" == rootBlobId) { return make_unique_ref(std::move(blobStore)); } 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(std::move(blobStore)); } #endif unique_ref CryDevice::CreateBlobStore(unique_ref blockStore, const LocalStateDir& localStateDir, CryConfigFile *configFile, uint32_t myClientId, bool allowIntegrityViolations, bool missingBlockIsIntegrityViolation, std::function onIntegrityViolation) { auto integrityEncryptedBlockStore = CreateIntegrityEncryptedBlockStore(std::move(blockStore), localStateDir, configFile, myClientId, allowIntegrityViolations, missingBlockIsIntegrityViolation, std::move(onIntegrityViolation)); // Create integrityEncryptedBlockStore not in the same line as BlobStoreOnBlocks, because it can modify BlocksizeBytes // in the configFile and therefore has to be run before the second parameter to the BlobStoreOnBlocks parameter is evaluated. return make_unique_ref( make_unique_ref( make_unique_ref( std::move(integrityEncryptedBlockStore) ) ), configFile->config()->BlocksizeBytes()); } unique_ref CryDevice::CreateIntegrityEncryptedBlockStore(unique_ref blockStore, const LocalStateDir& localStateDir, CryConfigFile *configFile, uint32_t myClientId, bool allowIntegrityViolations, bool missingBlockIsIntegrityViolation, std::function onIntegrityViolation) { auto encryptedBlockStore = CreateEncryptedBlockStore(*configFile->config(), std::move(blockStore)); auto statePath = localStateDir.forFilesystemId(configFile->config()->FilesystemId()); auto integrityFilePath = statePath / "integritydata"; #ifndef CRYFS_NO_COMPATIBILITY if (!configFile->config()->HasVersionNumbers()) { 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) // Don't migrate again if it was successful configFile->config()->SetHasVersionNumbers(true); configFile->save(); } #endif try { return make_unique_ref(std::move(encryptedBlockStore), integrityFilePath, myClientId, allowIntegrityViolations, missingBlockIsIntegrityViolation, std::move(onIntegrityViolation)); } catch (const blockstore::integrity::IntegrityViolationOnPreviousRun& e) { throw CryfsException(string() + "There was an integrity violation detected. Preventing any further access to the file system. " + "This can either happen if an attacker changed your files or rolled back the file system to a previous state, " + "but it can also happen if you rolled back the file system yourself, for example restored a backup. " + "If you want to reset the integrity data (i.e. accept changes made by a potential attacker), " + "please delete the following file before re-mounting it: " + e.stateFile().string(), ErrorCode::IntegrityViolationOnPreviousRun); } } BlockId CryDevice::CreateRootBlobAndReturnId() { auto rootBlob = _fsBlobStore->createDirBlob(blockstore::BlockId::Null()); rootBlob->flush(); // Don't cache, but directly write the root blob (this causes it to fail early if the base directory is not accessible) return rootBlob->blockId(); } optional> CryDevice::LoadFile(const bf::path &path) { auto loaded = Load(path); if (loaded == none) { return none; } auto file = cpputils::dynamic_pointer_move(*loaded); if (file == none) { throw fspp::fuse::FuseErrnoException(EISDIR); // TODO Also EISDIR if it is a symlink? } return std::move(*file); } optional> CryDevice::LoadDir(const bf::path &path) { auto loaded = Load(path); if (loaded == none) { return none; } auto dir = cpputils::dynamic_pointer_move(*loaded); if (dir == none) { throw fspp::fuse::FuseErrnoException(ENOTDIR); } return std::move(*dir); } optional> CryDevice::LoadSymlink(const bf::path &path) { auto loaded = Load(path); if (loaded == none) { return none; } auto lnk = cpputils::dynamic_pointer_move(*loaded); if (lnk == none) { throw fspp::fuse::FuseErrnoException(ENOTDIR); // TODO ENOTDIR although it is a symlink? } return std::move(*lnk); } optional> CryDevice::Load(const bf::path &path) { // TODO Is it faster to not let CryFile/CryDir/CryDevice inherit from CryNode and loading CryNode without having to know what it is? // TODO Split into smaller functions ASSERT(path.has_root_directory() && !path.has_root_name(), "Must be an absolute path (but on windows without device specifier): " + path.string()); callFsActionCallbacks(); if (path.parent_path().empty()) { //We are asked to load the base directory '/'. return optional>(make_unique_ref(this, none, none, _rootBlobId)); } auto parentWithGrandparent = LoadDirBlobWithParent(path.parent_path()); auto parent = std::move(parentWithGrandparent.blob); auto grandparent = std::move(parentWithGrandparent.parent); auto optEntry = parent->GetChild(path.filename().string()); if (optEntry == boost::none) { return boost::none; } const auto &entry = *optEntry; switch(entry.type()) { case fspp::Dir::EntryType::DIR: return optional>(make_unique_ref(this, std::move(parent), std::move(grandparent), entry.blockId())); case fspp::Dir::EntryType::FILE: return optional>(make_unique_ref(this, std::move(parent), std::move(grandparent), entry.blockId())); case fspp::Dir::EntryType::SYMLINK: return optional>(make_unique_ref(this, std::move(parent), std::move(grandparent), entry.blockId())); } ASSERT(false, "Switch/case not exhaustive"); } CryDevice::DirBlobWithParent CryDevice::LoadDirBlobWithParent(const bf::path &path) { auto blob = LoadBlobWithParent(path); auto dir = dynamic_pointer_move(blob.blob); if (dir == none) { throw FuseErrnoException(ENOTDIR); // Loaded blob is not a directory } return DirBlobWithParent{std::move(*dir), std::move(blob.parent)}; } CryDevice::BlobWithParent CryDevice::LoadBlobWithParent(const bf::path &path) { optional> parentBlob = none; optional> currentBlobOpt = _fsBlobStore->load(_rootBlobId); if (currentBlobOpt == none) { LOG(ERR, "Could not load root blob. Is the base directory accessible?"); throw FuseErrnoException(EIO); } unique_ref currentBlob = std::move(*currentBlobOpt); ASSERT(currentBlob->parentPointer() == BlockId::Null(), "Root Blob should have a nullptr as parent"); for (const bf::path &component : path.relative_path()) { auto currentDir = dynamic_pointer_move(currentBlob); if (currentDir == none) { throw FuseErrnoException(ENOTDIR); // Path component is not a dir } auto childOpt = (*currentDir)->GetChild(component.string()); if (childOpt == boost::none) { throw FuseErrnoException(ENOENT); // Child entry in directory not found } BlockId childId = childOpt->blockId(); auto nextBlob = _fsBlobStore->load(childId); if (nextBlob == none) { throw FuseErrnoException(ENOENT); // Blob for directory entry not found } parentBlob = std::move(*currentDir); currentBlob = std::move(*nextBlob); ASSERT(currentBlob->parentPointer() == (*parentBlob)->blockId(), "Blob has wrong parent pointer"); } return BlobWithParent{std::move(currentBlob), std::move(parentBlob)}; //TODO (I think this is resolved, but I should test it) // Running the python script, waiting for "Create files in sequential order...", then going into dir ~/tmp/cryfs-mount-.../Bonnie.../ and calling "ls" // crashes cryfs with a sigsegv. // Possible reason: Many parallel changes to a directory blob are a race condition. Need something like ParallelAccessStore! } CryDevice::statvfs CryDevice::statfs() { callFsActionCallbacks(); uint64_t numUsedBlocks = _fsBlobStore->numBlocks(); uint64_t numFreeBlocks = _fsBlobStore->estimateSpaceForNumBlocksLeft(); statvfs result; result.max_filename_length = 255; // We theoretically support unlimited file name length, but this is default for many Linux file systems, so probably also makes sense for CryFS. result.blocksize = _fsBlobStore->virtualBlocksizeBytes(); result.num_total_blocks = numUsedBlocks + numFreeBlocks; result.num_free_blocks = numFreeBlocks; result.num_available_blocks = numFreeBlocks; result.num_total_inodes = numUsedBlocks + numFreeBlocks; result.num_free_inodes = numFreeBlocks; result.num_available_inodes = numFreeBlocks; return result; } unique_ref CryDevice::CreateFileBlob(const blockstore::BlockId &parent) { return _fsBlobStore->createFileBlob(parent); } unique_ref CryDevice::CreateDirBlob(const blockstore::BlockId &parent) { return _fsBlobStore->createDirBlob(parent); } unique_ref CryDevice::CreateSymlinkBlob(const bf::path &target, const blockstore::BlockId &parent) { return _fsBlobStore->createSymlinkBlob(target, parent); } unique_ref CryDevice::LoadBlob(const blockstore::BlockId &blockId) { auto blob = _fsBlobStore->load(blockId); if (blob == none) { LOG(ERR, "Could not load blob {}. Is the base directory accessible?", blockId.ToString()); throw FuseErrnoException(EIO); } return std::move(*blob); } void CryDevice::RemoveBlob(const blockstore::BlockId &blockId) { auto blob = _fsBlobStore->load(blockId); if (blob == none) { LOG(ERR, "Could not load blob {}. Is the base directory accessible?", blockId.ToString()); throw FuseErrnoException(EIO); } _fsBlobStore->remove(std::move(*blob)); } BlockId CryDevice::GetOrCreateRootBlobId(CryConfigFile *configFile) { string root_blockId = configFile->config()->RootBlob(); if (root_blockId == "") { // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) auto new_blockId = CreateRootBlobAndReturnId(); configFile->config()->SetRootBlob(new_blockId.ToString()); configFile->save(); return new_blockId; } return BlockId::FromString(root_blockId); } cpputils::unique_ref CryDevice::CreateEncryptedBlockStore(const CryConfig &config, unique_ref baseBlockStore) { //TODO Test that CryFS is using the specified cipher return CryCiphers::find(config.Cipher()).createEncryptedBlockstore(std::move(baseBlockStore), config.EncryptionKey()); } void CryDevice::onFsAction(std::function callback) { _onFsAction.push_back(callback); } void CryDevice::callFsActionCallbacks() const { for (const auto &callback : _onFsAction) { callback(); } } uint64_t CryDevice::numBlocks() const { return _fsBlobStore->numBlocks(); } } src/cryfs/filesystem/CryDevice.h000066400000000000000000000103771347701267100172250ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYDEVICE_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYDEVICE_H_ #include #include #include "../config/CryConfigFile.h" #include #include #include #include "parallelaccessfsblobstore/ParallelAccessFsBlobStore.h" #include "parallelaccessfsblobstore/DirBlobRef.h" #include "parallelaccessfsblobstore/FileBlobRef.h" #include "parallelaccessfsblobstore/SymlinkBlobRef.h" namespace cryfs { class CryDevice final: public fspp::Device { public: CryDevice(CryConfigFile config, cpputils::unique_ref blockStore, const LocalStateDir& localStateDir, uint32_t myClientId, bool allowIntegrityViolations, bool missingBlockIsIntegrityViolation, std::function onIntegrityViolation); statvfs statfs() override; cpputils::unique_ref CreateFileBlob(const blockstore::BlockId &parent); cpputils::unique_ref CreateDirBlob(const blockstore::BlockId &parent); cpputils::unique_ref CreateSymlinkBlob(const boost::filesystem::path &target, const blockstore::BlockId &parent); cpputils::unique_ref LoadBlob(const blockstore::BlockId &blockId); struct DirBlobWithParent { cpputils::unique_ref blob; boost::optional> parent; }; DirBlobWithParent LoadDirBlobWithParent(const boost::filesystem::path &path); void RemoveBlob(const blockstore::BlockId &blockId); void onFsAction(std::function callback); boost::optional> Load(const boost::filesystem::path &path) override; boost::optional> LoadFile(const boost::filesystem::path &path) override; boost::optional> LoadDir(const boost::filesystem::path &path) override; boost::optional> LoadSymlink(const boost::filesystem::path &path) override; void callFsActionCallbacks() const; uint64_t numBlocks() const; private: cpputils::unique_ref _fsBlobStore; blockstore::BlockId _rootBlobId; std::vector> _onFsAction; blockstore::BlockId GetOrCreateRootBlobId(CryConfigFile *config); blockstore::BlockId CreateRootBlobAndReturnId(); static cpputils::unique_ref CreateFsBlobStore(cpputils::unique_ref blockStore, CryConfigFile *configFile, const LocalStateDir& localStateDir, uint32_t myClientId, bool allowIntegrityViolations, bool missingBlockIsIntegrityViolation, std::function onIntegrityViolation); #ifndef CRYFS_NO_COMPATIBILITY static cpputils::unique_ref MigrateOrCreateFsBlobStore(cpputils::unique_ref blobStore, CryConfigFile *configFile); #endif static cpputils::unique_ref CreateBlobStore(cpputils::unique_ref blockStore, const LocalStateDir& localStateDir, CryConfigFile *configFile, uint32_t myClientId, bool allowIntegrityViolations, bool missingBlockIsIntegrityViolation, std::function onIntegrityViolation); static cpputils::unique_ref CreateIntegrityEncryptedBlockStore(cpputils::unique_ref blockStore, const LocalStateDir& localStateDir, CryConfigFile *configFile, uint32_t myClientId, bool allowIntegrityViolations, bool missingBlockIsIntegrityViolation, std::function onIntegrityViolation); static cpputils::unique_ref CreateEncryptedBlockStore(const CryConfig &config, cpputils::unique_ref baseBlockStore); struct BlobWithParent { cpputils::unique_ref blob; boost::optional> parent; }; BlobWithParent LoadBlobWithParent(const boost::filesystem::path &path); DISALLOW_COPY_AND_ASSIGN(CryDevice); }; } #endif src/cryfs/filesystem/CryDir.cpp000066400000000000000000000105071347701267100170720ustar00rootroot00000000000000#include "CryDir.h" #include #include #include #include #include "CryDevice.h" #include "CryFile.h" #include "CryOpenFile.h" #include #include "fsblobstore/utils/TimestampUpdateBehavior.h" //TODO Get rid of this in favor of exception hierarchy using fspp::fuse::FuseErrnoException; namespace bf = boost::filesystem; using std::string; using std::vector; using blockstore::BlockId; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::dynamic_pointer_move; using boost::optional; using boost::none; using cryfs::parallelaccessfsblobstore::DirBlobRef; namespace cryfs { CryDir::CryDir(CryDevice *device, optional> parent, optional> grandparent, const BlockId &blockId) : CryNode(device, std::move(parent), std::move(grandparent), blockId) { } CryDir::~CryDir() { } unique_ref CryDir::createAndOpenFile(const string &name, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid) { device()->callFsActionCallbacks(); if (!isRootDir()) { //TODO Instead of doing nothing when we're the root directory, handle timestamps in the root dir correctly (and delete isRootDir() function) parent()->updateModificationTimestampForChild(blockId()); } auto child = device()->CreateFileBlob(blockId()); auto now = cpputils::time::now(); auto dirBlob = LoadBlob(); dirBlob->AddChildFile(name, child->blockId(), mode, uid, gid, now, now); return make_unique_ref(device(), std::move(dirBlob), std::move(child)); } void CryDir::createDir(const string &name, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid) { device()->callFsActionCallbacks(); if (!isRootDir()) { //TODO Instead of doing nothing when we're the root directory, handle timestamps in the root dir correctly (and delete isRootDir() function) parent()->updateModificationTimestampForChild(blockId()); } auto blob = LoadBlob(); auto child = device()->CreateDirBlob(blockId()); auto now = cpputils::time::now(); blob->AddChildDir(name, child->blockId(), mode, uid, gid, now, now); } unique_ref CryDir::LoadBlob() const { auto blob = CryNode::LoadBlob(); auto dir_blob = dynamic_pointer_move(blob); ASSERT(dir_blob != none, "Blob does not store a directory"); return std::move(*dir_blob); } unique_ref> CryDir::children() { device()->callFsActionCallbacks(); if (!isRootDir()) { // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) //TODO Instead of doing nothing when we're the root directory, handle timestamps in the root dir correctly (and delete isRootDir() function) parent()->updateAccessTimestampForChild(blockId(), fsblobstore::TimestampUpdateBehavior::RELATIME); } auto children = make_unique_ref>(); children->push_back(fspp::Dir::Entry(fspp::Dir::EntryType::DIR, ".")); children->push_back(fspp::Dir::Entry(fspp::Dir::EntryType::DIR, "..")); auto blob = LoadBlob(); blob->AppendChildrenTo(children.get()); return children; } fspp::Dir::EntryType CryDir::getType() const { device()->callFsActionCallbacks(); return fspp::Dir::EntryType::DIR; } void CryDir::createSymlink(const string &name, const bf::path &target, fspp::uid_t uid, fspp::gid_t gid) { device()->callFsActionCallbacks(); if (!isRootDir()) { //TODO Instead of doing nothing when we're the root directory, handle timestamps in the root dir correctly (and delete isRootDir() function) parent()->updateModificationTimestampForChild(blockId()); } auto blob = LoadBlob(); auto child = device()->CreateSymlinkBlob(target, blockId()); auto now = cpputils::time::now(); blob->AddChildSymlink(name, child->blockId(), uid, gid, now, now); } void CryDir::remove() { device()->callFsActionCallbacks(); if (grandparent() != none) { //TODO Instead of doing nothing when we're in the root directory, handle timestamps in the root dir correctly (*grandparent())->updateModificationTimestampForChild(parent()->blockId()); } { auto blob = LoadBlob(); if (0 != blob->NumChildren()) { throw FuseErrnoException(ENOTEMPTY); } } //TODO removeNode() calls CryDevice::RemoveBlob, which loads the blob again. So we're loading it twice. Should be optimized. removeNode(); } } src/cryfs/filesystem/CryDir.h000066400000000000000000000025211347701267100165340ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYDIR_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYDIR_H_ #include #include "CryNode.h" #include "parallelaccessfsblobstore/DirBlobRef.h" namespace cryfs { class CryDir final: public fspp::Dir, public CryNode { public: CryDir(CryDevice *device, boost::optional> parent, boost::optional> grandparent, const blockstore::BlockId &blockId); ~CryDir(); //TODO return type variance to CryFile/CryDir? cpputils::unique_ref createAndOpenFile(const std::string &name, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid) override; void createDir(const std::string &name, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid) override; void createSymlink(const std::string &name, const boost::filesystem::path &target, fspp::uid_t uid, fspp::gid_t gid) override; //TODO Make Entry a public class instead of hidden in DirBlob (which is not publicly visible) cpputils::unique_ref> children() override; fspp::Dir::EntryType getType() const override; void remove() override; private: cpputils::unique_ref LoadBlob() const; DISALLOW_COPY_AND_ASSIGN(CryDir); }; } #endif src/cryfs/filesystem/CryFile.cpp000066400000000000000000000036361347701267100172400ustar00rootroot00000000000000#include "CryFile.h" #include "CryDevice.h" #include "CryOpenFile.h" #include //TODO Get rid of this in favor of exception hierarchy using blockstore::BlockId; using boost::none; using boost::optional; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::dynamic_pointer_move; using cryfs::parallelaccessfsblobstore::DirBlobRef; using cryfs::parallelaccessfsblobstore::FileBlobRef; namespace cryfs { CryFile::CryFile(CryDevice *device, unique_ref parent, optional> grandparent, const BlockId &blockId) : CryNode(device, std::move(parent), std::move(grandparent), blockId) { } CryFile::~CryFile() { } unique_ref CryFile::LoadBlob() const { auto blob = CryNode::LoadBlob(); auto file_blob = dynamic_pointer_move(blob); ASSERT(file_blob != none, "Blob does not store a file"); return std::move(*file_blob); } unique_ref CryFile::open(fspp::openflags_t flags) { // TODO Should we honor open flags? UNUSED(flags); device()->callFsActionCallbacks(); auto blob = LoadBlob(); return make_unique_ref(device(), parent(), std::move(blob)); } void CryFile::truncate(fspp::num_bytes_t size) { device()->callFsActionCallbacks(); auto blob = LoadBlob(); // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) blob->resize(size); parent()->updateModificationTimestampForChild(blockId()); } fspp::Dir::EntryType CryFile::getType() const { device()->callFsActionCallbacks(); return fspp::Dir::EntryType::FILE; } void CryFile::remove() { device()->callFsActionCallbacks(); if (grandparent() != none) { //TODO Instead of doing nothing when we're in the root directory, handle timestamps in the root dir correctly (*grandparent())->updateModificationTimestampForChild(parent()->blockId()); } removeNode(); } } src/cryfs/filesystem/CryFile.h000066400000000000000000000016431347701267100167010ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYFILE_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYFILE_H_ #include "parallelaccessfsblobstore/FileBlobRef.h" #include "parallelaccessfsblobstore/DirBlobRef.h" #include #include "CryNode.h" namespace cryfs { class CryFile final: public fspp::File, public CryNode { public: CryFile(CryDevice *device, cpputils::unique_ref parent, boost::optional> grandparent, const blockstore::BlockId &blockId); ~CryFile(); cpputils::unique_ref open(fspp::openflags_t flags) override; void truncate(fspp::num_bytes_t size) override; fspp::Dir::EntryType getType() const override; void remove() override; private: cpputils::unique_ref LoadBlob() const; DISALLOW_COPY_AND_ASSIGN(CryFile); }; } #endif src/cryfs/filesystem/CryNode.cpp000066400000000000000000000154361347701267100172470ustar00rootroot00000000000000#include "CryNode.h" #include "CryDevice.h" #include "CryDir.h" #include "CryFile.h" #include #include #include #include #include namespace bf = boost::filesystem; using blockstore::BlockId; using cpputils::unique_ref; using boost::optional; using boost::none; using std::shared_ptr; using cryfs::parallelaccessfsblobstore::FsBlobRef; using cryfs::parallelaccessfsblobstore::DirBlobRef; using namespace cpputils::logging; //TODO Get rid of this in favor of an exception hierarchy using fspp::fuse::FuseErrnoException; namespace cryfs { CryNode::CryNode(CryDevice *device, optional> parent, optional> grandparent, const BlockId &blockId) : _device(device), _parent(none), _grandparent(none), _blockId(blockId) { ASSERT(parent != none || grandparent == none, "Grandparent can only be set when parent is not none"); if (parent != none) { _parent = std::move(*parent); } _grandparent = std::move(grandparent); } CryNode::~CryNode() { } void CryNode::access(int mask) const { // TODO Should we implement access()? UNUSED(mask); device()->callFsActionCallbacks(); return; } bool CryNode::isRootDir() const { return _parent == none; } shared_ptr CryNode::parent() const { ASSERT(_parent != none, "We are the root directory and can't get the parent of the root directory"); return *_parent; } shared_ptr CryNode::parent() { ASSERT(_parent != none, "We are the root directory and can't get the parent of the root directory"); return *_parent; } optional CryNode::grandparent() { if (_grandparent == none) { return none; } return _grandparent->get(); } void CryNode::rename(const bf::path &to) { device()->callFsActionCallbacks(); if (_parent == none) { //We are the root direcory. throw FuseErrnoException(EBUSY); } auto targetDirWithParent = _device->LoadDirBlobWithParent(to.parent_path()); auto targetDir = std::move(targetDirWithParent.blob); auto targetDirParent = std::move(targetDirWithParent.parent); auto old = (*_parent)->GetChild(_blockId); if (old == boost::none) { throw FuseErrnoException(EIO); } fsblobstore::DirEntry oldEntry = *old; // Copying this (instead of only keeping the reference) is necessary, because the operations below (i.e. RenameChild()) might make a reference invalid. auto onOverwritten = [this] (const blockstore::BlockId &blockId) { device()->RemoveBlob(blockId); }; _updateParentModificationTimestamp(); if (targetDir->blockId() == (*_parent)->blockId()) { targetDir->RenameChild(oldEntry.blockId(), to.filename().string(), onOverwritten); } else { _updateTargetDirModificationTimestamp(*targetDir, std::move(targetDirParent)); targetDir->AddOrOverwriteChild(to.filename().string(), oldEntry.blockId(), oldEntry.type(), oldEntry.mode(), oldEntry.uid(), oldEntry.gid(), oldEntry.lastAccessTime(), oldEntry.lastModificationTime(), onOverwritten); (*_parent)->RemoveChild(oldEntry.name()); // targetDir is now the new parent for this node. Adapt to it, so we can call further operations on this node object. LoadBlob()->setParentPointer(targetDir->blockId()); _parent = std::move(targetDir); } } void CryNode::_updateParentModificationTimestamp() { if (_grandparent != none) { // TODO Handle timestamps of the root directory (_grandparent == none) correctly. ASSERT(_parent != none, "Grandparent is set, so also parent has to be set"); (*_grandparent)->updateModificationTimestampForChild((*_parent)->blockId()); } } void CryNode::_updateTargetDirModificationTimestamp(const DirBlobRef &targetDir, optional> targetDirParent) { if (targetDirParent != none) { // TODO Handle timestamps of the root directory (targetDirParent == none) correctly. (*targetDirParent)->updateModificationTimestampForChild(targetDir.blockId()); } } void CryNode::utimens(timespec lastAccessTime, timespec lastModificationTime) { // LOG(WARN, "---utimens called---"); device()->callFsActionCallbacks(); if (_parent == none) { //We are the root direcory. //TODO What should we do? return; } (*_parent)->utimensChild(_blockId, lastAccessTime, lastModificationTime); } void CryNode::removeNode() { //TODO Instead of all these if-else and having _parent being an optional, we could also introduce a CryRootDir which inherits from fspp::Dir. if (_parent == none) { //We are the root direcory. //TODO What should we do? throw FuseErrnoException(EIO); } (*_parent)->RemoveChild(_blockId); _device->RemoveBlob(_blockId); } CryDevice *CryNode::device() { return _device; } const CryDevice *CryNode::device() const { return _device; } unique_ref CryNode::LoadBlob() const { auto blob = _device->LoadBlob(_blockId); ASSERT(_parent == none || blob->parentPointer() == (*_parent)->blockId(), "Blob has wrong parent pointer."); return blob; // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) } const blockstore::BlockId &CryNode::blockId() const { return _blockId; } CryNode::stat_info CryNode::stat() const { device()->callFsActionCallbacks(); if(_parent == none) { stat_info result; //We are the root directory. //TODO What should we do? #if defined(_MSC_VER) // TODO And what to do on Windows? result.uid = fspp::uid_t(1000); result.gid = fspp::gid_t(1000); #else result.uid = fspp::uid_t(getuid()); result.gid = fspp::gid_t(getgid()); #endif result.mode = fspp::mode_t().addDirFlag().addUserReadFlag().addUserWriteFlag().addUserExecFlag(); result.size = fsblobstore::DirBlob::DIR_LSTAT_SIZE; //TODO If possible without performance loss, then for a directory, st_nlink should return number of dir entries (including "." and "..") result.nlink = 1; struct timespec now = cpputils::time::now(); result.atime = now; result.mtime = now; result.ctime = now; return result; } else { return (*_parent)->statChild(_blockId); } } void CryNode::chmod(fspp::mode_t mode) { device()->callFsActionCallbacks(); if (_parent == none) { //We are the root direcory. //TODO What should we do? return; } (*_parent)->chmodChild(_blockId, mode); } void CryNode::chown(fspp::uid_t uid, fspp::gid_t gid) { device()->callFsActionCallbacks(); if (_parent == none) { //We are the root direcory. //TODO What should we do? return; } (*_parent)->chownChild(_blockId, uid, gid); } bool CryNode::checkParentPointer() { auto parentPointer = LoadBlob()->parentPointer(); if (_parent == none) { return parentPointer == BlockId::Null(); } else { return parentPointer == (*_parent)->blockId(); } } } src/cryfs/filesystem/CryNode.h000066400000000000000000000043111347701267100167020ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYNODE_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYNODE_H_ #include #include #include #include "parallelaccessfsblobstore/DirBlobRef.h" #include "CryDevice.h" namespace cryfs { class CryNode: public fspp::Node { public: virtual ~CryNode(); // TODO grandparent is only needed to set the timestamps of the parent directory on rename and remove. Delete grandparent parameter once we store timestamps in the blob itself instead of in the directory listing. CryNode(CryDevice *device, boost::optional> parent, boost::optional> grandparent, const blockstore::BlockId &blockId); void access(int mask) const override; stat_info stat() const override; void chmod(fspp::mode_t mode) override; void chown(fspp::uid_t uid, fspp::gid_t gid) override; void rename(const boost::filesystem::path &to) override; void utimens(timespec lastAccessTime, timespec lastModificationTime) override; // used in test cases bool checkParentPointer(); protected: CryNode(); CryDevice *device(); const CryDevice *device() const; const blockstore::BlockId &blockId() const; cpputils::unique_ref LoadBlob() const; bool isRootDir() const; std::shared_ptr parent() const; std::shared_ptr parent(); boost::optional grandparent(); virtual fspp::Dir::EntryType getType() const = 0; void removeNode(); private: void _updateParentModificationTimestamp(); void _updateTargetDirModificationTimestamp(const parallelaccessfsblobstore::DirBlobRef &targetDir, boost::optional> targetDirParent); CryDevice *_device; boost::optional> _parent; boost::optional> _grandparent; blockstore::BlockId _blockId; DISALLOW_COPY_AND_ASSIGN(CryNode); }; } #endif src/cryfs/filesystem/CryOpenFile.cpp000066400000000000000000000035641347701267100200620ustar00rootroot00000000000000#include "CryOpenFile.h" #include #include #include "CryDevice.h" #include using std::shared_ptr; using cpputils::unique_ref; using cryfs::parallelaccessfsblobstore::FileBlobRef; using cryfs::parallelaccessfsblobstore::DirBlobRef; //TODO Get rid of this in favor of a exception hierarchy namespace cryfs { CryOpenFile::CryOpenFile(const CryDevice *device, shared_ptr parent, unique_ref fileBlob) : _device(device), _parent(parent), _fileBlob(std::move(fileBlob)) { } CryOpenFile::~CryOpenFile() { //TODO } // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) void CryOpenFile::flush() { _device->callFsActionCallbacks(); _fileBlob->flush(); _parent->flush(); } fspp::Node::stat_info CryOpenFile::stat() const { _device->callFsActionCallbacks(); return _parent->statChildWithKnownSize(_fileBlob->blockId(), _fileBlob->size()); } void CryOpenFile::truncate(fspp::num_bytes_t size) const { _device->callFsActionCallbacks(); _fileBlob->resize(size); _parent->updateModificationTimestampForChild(_fileBlob->blockId()); } fspp::num_bytes_t CryOpenFile::read(void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) const { _device->callFsActionCallbacks(); _parent->updateAccessTimestampForChild(_fileBlob->blockId(), fsblobstore::TimestampUpdateBehavior::RELATIME); return _fileBlob->read(buf, offset, count); } void CryOpenFile::write(const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) { _device->callFsActionCallbacks(); _parent->updateModificationTimestampForChild(_fileBlob->blockId()); _fileBlob->write(buf, offset, count); } void CryOpenFile::fsync() { _device->callFsActionCallbacks(); _fileBlob->flush(); _parent->flush(); } void CryOpenFile::fdatasync() { _device->callFsActionCallbacks(); _fileBlob->flush(); } } src/cryfs/filesystem/CryOpenFile.h000066400000000000000000000021711347701267100175200ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYOPENFILE_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYOPENFILE_H_ #include #include "parallelaccessfsblobstore/FileBlobRef.h" #include "parallelaccessfsblobstore/DirBlobRef.h" namespace cryfs { class CryDevice; class CryOpenFile final: public fspp::OpenFile { public: explicit CryOpenFile(const CryDevice *device, std::shared_ptr parent, cpputils::unique_ref fileBlob); ~CryOpenFile(); stat_info stat() const override; void truncate(fspp::num_bytes_t size) const override; fspp::num_bytes_t read(void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) const override; void write(const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) override; void flush() override; void fsync() override; void fdatasync() override; private: const CryDevice *_device; std::shared_ptr _parent; cpputils::unique_ref _fileBlob; DISALLOW_COPY_AND_ASSIGN(CryOpenFile); }; } #endif src/cryfs/filesystem/CrySymlink.cpp000066400000000000000000000035071347701267100200040ustar00rootroot00000000000000#include "CrySymlink.h" #include #include "CryDevice.h" #include "CrySymlink.h" #include "parallelaccessfsblobstore/SymlinkBlobRef.h" #include "fsblobstore/utils/TimestampUpdateBehavior.h" //TODO Get rid of this in favor of exception hierarchy namespace bf = boost::filesystem; using std::string; using blockstore::BlockId; using boost::none; using boost::optional; using cpputils::unique_ref; using cpputils::dynamic_pointer_move; using cryfs::parallelaccessfsblobstore::SymlinkBlobRef; using cryfs::parallelaccessfsblobstore::DirBlobRef; namespace cryfs { CrySymlink::CrySymlink(CryDevice *device, unique_ref parent, optional> grandparent, const BlockId &blockId) : CryNode(device, std::move(parent), std::move(grandparent), blockId) { } CrySymlink::~CrySymlink() { } unique_ref CrySymlink::LoadBlob() const { auto blob = CryNode::LoadBlob(); auto symlink_blob = dynamic_pointer_move(blob); ASSERT(symlink_blob != none, "Blob does not store a symlink"); return std::move(*symlink_blob); } fspp::Dir::EntryType CrySymlink::getType() const { device()->callFsActionCallbacks(); return fspp::Dir::EntryType::SYMLINK; } bf::path CrySymlink::target() { device()->callFsActionCallbacks(); parent()->updateAccessTimestampForChild(blockId(), fsblobstore::TimestampUpdateBehavior::RELATIME); auto blob = LoadBlob(); // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) return blob->target(); } void CrySymlink::remove() { device()->callFsActionCallbacks(); if (grandparent() != none) { //TODO Instead of doing nothing when we're in the root directory, handle timestamps in the root dir correctly (*grandparent())->updateModificationTimestampForChild(parent()->blockId()); } removeNode(); } } src/cryfs/filesystem/CrySymlink.h000066400000000000000000000015571347701267100174540ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYSYMLINK_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYSYMLINK_H_ #include #include "CryNode.h" #include "parallelaccessfsblobstore/SymlinkBlobRef.h" #include "parallelaccessfsblobstore/DirBlobRef.h" namespace cryfs { class CrySymlink final: public fspp::Symlink, public CryNode { public: CrySymlink(CryDevice *device, cpputils::unique_ref parent, boost::optional> grandparent, const blockstore::BlockId &blockId); ~CrySymlink(); boost::filesystem::path target() override; fspp::Dir::EntryType getType() const override; void remove() override; private: cpputils::unique_ref LoadBlob() const; DISALLOW_COPY_AND_ASSIGN(CrySymlink); }; } #endif src/cryfs/filesystem/cachingfsblobstore/000077500000000000000000000000001347701267100210305ustar00rootroot00000000000000src/cryfs/filesystem/cachingfsblobstore/CachingFsBlobStore.cpp000066400000000000000000000031241347701267100251750ustar00rootroot00000000000000#include "CachingFsBlobStore.h" #include "../fsblobstore/FsBlobStore.h" using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::dynamic_pointer_move; using blockstore::BlockId; using boost::optional; using boost::none; using cryfs::fsblobstore::FsBlob; using cryfs::fsblobstore::FileBlob; using cryfs::fsblobstore::DirBlob; using cryfs::fsblobstore::SymlinkBlob; namespace cryfs { namespace cachingfsblobstore { constexpr double CachingFsBlobStore::MAX_LIFETIME_SEC; optional> CachingFsBlobStore::load(const BlockId &blockId) { auto fromCache = _cache.pop(blockId); if (fromCache != none) { return _makeRef(std::move(*fromCache)); } auto fromBaseStore = _baseBlobStore->load(blockId); if (fromBaseStore != none) { return _makeRef(std::move(*fromBaseStore)); } return none; } unique_ref CachingFsBlobStore::_makeRef(unique_ref baseBlob) { auto fileBlob = dynamic_pointer_move(baseBlob); if (fileBlob != none) { return make_unique_ref(std::move(*fileBlob), this); } auto dirBlob = dynamic_pointer_move(baseBlob); if (dirBlob != none) { return make_unique_ref(std::move(*dirBlob), this); } auto symlinkBlob = dynamic_pointer_move(baseBlob); if (symlinkBlob != none) { return make_unique_ref(std::move(*symlinkBlob), this); } ASSERT(false, "Unknown blob type"); } } } src/cryfs/filesystem/cachingfsblobstore/CachingFsBlobStore.h000066400000000000000000000121671347701267100246510ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_CACHINGFSBLOBSTORE_H #define MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_CACHINGFSBLOBSTORE_H #include #include "../fsblobstore/FsBlobStore.h" #include #include "FileBlobRef.h" #include "DirBlobRef.h" #include "SymlinkBlobRef.h" namespace cryfs { namespace cachingfsblobstore { //TODO Test classes in cachingfsblobstore //TODO Inherit from same interface as FsBlobStore? class CachingFsBlobStore final { public: CachingFsBlobStore(cpputils::unique_ref baseBlobStore); ~CachingFsBlobStore(); cpputils::unique_ref createFileBlob(const blockstore::BlockId &parent); cpputils::unique_ref createDirBlob(const blockstore::BlockId &parent); cpputils::unique_ref createSymlinkBlob(const boost::filesystem::path &target, const blockstore::BlockId &parent); boost::optional> load(const blockstore::BlockId &blockId); void remove(cpputils::unique_ref blob); void remove(const blockstore::BlockId &blockId); uint64_t virtualBlocksizeBytes() const; uint64_t numBlocks() const; uint64_t estimateSpaceForNumBlocksLeft() const; void releaseForCache(cpputils::unique_ref baseBlob); private: cpputils::unique_ref _makeRef(cpputils::unique_ref baseBlob); cpputils::unique_ref _baseBlobStore; //TODO Move Cache to some common location, not in blockstore //TODO Use other cache config (i.e. smaller max number of entries) here than in blockstore blockstore::caching::Cache, 50> _cache; public: static constexpr double MAX_LIFETIME_SEC = decltype(_cache)::MAX_LIFETIME_SEC; private: DISALLOW_COPY_AND_ASSIGN(CachingFsBlobStore); }; inline CachingFsBlobStore::CachingFsBlobStore(cpputils::unique_ref baseBlobStore) : _baseBlobStore(std::move(baseBlobStore)), _cache("fsblobstore") { } inline CachingFsBlobStore::~CachingFsBlobStore() { } inline cpputils::unique_ref CachingFsBlobStore::createFileBlob(const blockstore::BlockId &parent) { // This already creates the file blob in the underlying blobstore. // We could also cache this operation, but that is more complicated (blockstore::CachingBlockStore does it) // and probably not worth it here. return cpputils::make_unique_ref(_baseBlobStore->createFileBlob(parent), this); } inline cpputils::unique_ref CachingFsBlobStore::createDirBlob(const blockstore::BlockId &parent) { // This already creates the file blob in the underlying blobstore. // We could also cache this operation, but that is more complicated (blockstore::CachingBlockStore does it) // and probably not worth it here. return cpputils::make_unique_ref(_baseBlobStore->createDirBlob(parent), this); } inline cpputils::unique_ref CachingFsBlobStore::createSymlinkBlob(const boost::filesystem::path &target, const blockstore::BlockId &parent) { // This already creates the file blob in the underlying blobstore. // We could also cache this operation, but that is more complicated (blockstore::CachingBlockStore does it) // and probably not worth it here. return cpputils::make_unique_ref(_baseBlobStore->createSymlinkBlob(target, parent), this); } inline void CachingFsBlobStore::remove(cpputils::unique_ref blob) { auto baseBlob = blob->releaseBaseBlob(); return _baseBlobStore->remove(std::move(baseBlob)); } inline void CachingFsBlobStore::remove(const blockstore::BlockId &blockId) { auto fromCache = _cache.pop(blockId); if (fromCache != boost::none) { remove(_makeRef(std::move(*fromCache))); } else { _baseBlobStore->remove(blockId); } } inline void CachingFsBlobStore::releaseForCache(cpputils::unique_ref baseBlob) { blockstore::BlockId blockId = baseBlob->blockId(); _cache.push(blockId, std::move(baseBlob)); } inline uint64_t CachingFsBlobStore::virtualBlocksizeBytes() const { return _baseBlobStore->virtualBlocksizeBytes(); } inline uint64_t CachingFsBlobStore::numBlocks() const { return _baseBlobStore->numBlocks(); } inline uint64_t CachingFsBlobStore::estimateSpaceForNumBlocksLeft() const { return _baseBlobStore->estimateSpaceForNumBlocksLeft(); } } } #endif src/cryfs/filesystem/cachingfsblobstore/DirBlobRef.cpp000066400000000000000000000000301347701267100234770ustar00rootroot00000000000000#include "DirBlobRef.h" src/cryfs/filesystem/cachingfsblobstore/DirBlobRef.h000066400000000000000000000112161347701267100231540ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_DIRBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_DIRBLOBREF_H #include "FsBlobRef.h" #include "../fsblobstore/DirBlob.h" #include "../fsblobstore/utils/TimestampUpdateBehavior.h" #include namespace cryfs { namespace cachingfsblobstore { class DirBlobRef final: public FsBlobRef { public: DirBlobRef(cpputils::unique_ref base, CachingFsBlobStore *fsBlobStore): FsBlobRef(std::move(base), fsBlobStore), _base(dynamic_cast(baseBlob())) { ASSERT(_base != nullptr, "We just initialized this with a pointer to DirBlob. Can't be something else now."); } using Entry = fsblobstore::DirEntry; boost::optional GetChild(const std::string &name) const { return _base->GetChild(name); } boost::optional GetChild(const blockstore::BlockId &blockId) const { return _base->GetChild(blockId); } size_t NumChildren() const { return _base->NumChildren(); } void RemoveChild(const blockstore::BlockId &blockId) { return _base->RemoveChild(blockId); } void RemoveChild(const std::string &name) { return _base->RemoveChild(name); } void flush() { return _base->flush(); } void AddOrOverwriteChild(const std::string &name, const blockstore::BlockId &blobId, fspp::Dir::EntryType type, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime, std::function onOverwritten) { return _base->AddOrOverwriteChild(name, blobId, type, mode, uid, gid, lastAccessTime, lastModificationTime, onOverwritten); } void RenameChild(const blockstore::BlockId &blockId, const std::string &newName, std::function onOverwritten) { return _base->RenameChild(blockId, newName, onOverwritten); } fspp::Node::stat_info statChild(const blockstore::BlockId &blockId) const { return _base->statChild(blockId); } fspp::Node::stat_info statChildWithKnownSize(const blockstore::BlockId &blockId, fspp::num_bytes_t size) const { return _base->statChildWithKnownSize(blockId, size); } void updateAccessTimestampForChild(const blockstore::BlockId &blockId, fsblobstore::TimestampUpdateBehavior timestampUpdateBehavior) { return _base->updateAccessTimestampForChild(blockId, timestampUpdateBehavior); } void updateModificationTimestampForChild(const blockstore::BlockId &blockId) { return _base->updateModificationTimestampForChild(blockId); } void chmodChild(const blockstore::BlockId &blockId, fspp::mode_t mode) { return _base->chmodChild(blockId, mode); } void chownChild(const blockstore::BlockId &blockId, fspp::uid_t uid, fspp::gid_t gid) { return _base->chownChild(blockId, uid, gid); } void utimensChild(const blockstore::BlockId &blockId, timespec lastAccessTime, timespec lastModificationTime) { return _base->utimensChild(blockId, lastAccessTime, lastModificationTime); } void AddChildDir(const std::string &name, const blockstore::BlockId &blobId, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { return _base->AddChildDir(name, blobId, mode, uid, gid, lastAccessTime, lastModificationTime); } void AddChildFile(const std::string &name, const blockstore::BlockId &blobId, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { return _base->AddChildFile(name, blobId, mode, uid, gid, lastAccessTime, lastModificationTime); } void AddChildSymlink(const std::string &name, const blockstore::BlockId &blobId, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { return _base->AddChildSymlink(name, blobId, uid, gid, lastAccessTime, lastModificationTime); } void AppendChildrenTo(std::vector *result) const { return _base->AppendChildrenTo(result); } const blockstore::BlockId &blockId() const { return _base->blockId(); } fspp::num_bytes_t lstat_size() const { return _base->lstat_size(); } void setLstatSizeGetter(std::function getLstatSize) { return _base->setLstatSizeGetter(getLstatSize); } private: fsblobstore::DirBlob *_base; DISALLOW_COPY_AND_ASSIGN(DirBlobRef); }; } } #endif src/cryfs/filesystem/cachingfsblobstore/FileBlobRef.cpp000066400000000000000000000000311347701267100236410ustar00rootroot00000000000000#include "FileBlobRef.h" src/cryfs/filesystem/cachingfsblobstore/FileBlobRef.h000066400000000000000000000026621347701267100233220ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_FILEBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_FILEBLOBREF_H #include "FsBlobRef.h" #include "../fsblobstore/FileBlob.h" namespace cryfs { namespace cachingfsblobstore { class FileBlobRef final: public FsBlobRef { public: FileBlobRef(cpputils::unique_ref base, CachingFsBlobStore *fsBlobStore) :FsBlobRef(std::move(base), fsBlobStore), _base(dynamic_cast(baseBlob())) { ASSERT(_base != nullptr, "We just initialized this with a pointer to FileBlob. Can't be something else now."); } void resize(fspp::num_bytes_t size) { return _base->resize(size); } fspp::num_bytes_t size() const { return _base->size(); } fspp::num_bytes_t read(void *target, fspp::num_bytes_t offset, fspp::num_bytes_t count) const { return _base->read(target, offset, count); } void write(const void *source, fspp::num_bytes_t offset, fspp::num_bytes_t count) { return _base->write(source, offset, count); } void flush() { return _base->flush(); } const blockstore::BlockId &blockId() const { return _base->blockId(); } fspp::num_bytes_t lstat_size() const { return _base->lstat_size(); } private: fsblobstore::FileBlob *_base; DISALLOW_COPY_AND_ASSIGN(FileBlobRef); }; } } #endif src/cryfs/filesystem/cachingfsblobstore/FsBlobRef.cpp000066400000000000000000000003561347701267100233440ustar00rootroot00000000000000#include "FsBlobRef.h" #include "CachingFsBlobStore.h" namespace cryfs { namespace cachingfsblobstore { FsBlobRef::~FsBlobRef() { if (_baseBlob.is_valid()) { _fsBlobStore->releaseForCache(std::move(_baseBlob)); } } } } src/cryfs/filesystem/cachingfsblobstore/FsBlobRef.h000066400000000000000000000024411347701267100230060ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_FSBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_FSBLOBREF_H #include "../fsblobstore/FsBlob.h" namespace cryfs { namespace cachingfsblobstore { class CachingFsBlobStore; //TODO Rename to CachedFsBlob, CachedFileBlob, CachedDirBlob to avoid confusion with parallelaccessfsblobstore class FsBlobRef { public: virtual ~FsBlobRef(); virtual const blockstore::BlockId &blockId() const = 0; virtual fspp::num_bytes_t lstat_size() const = 0; const blockstore::BlockId &parentPointer() const { return _baseBlob->parentPointer(); } void setParentPointer(const blockstore::BlockId &parentBlobId) { return _baseBlob->setParentPointer(parentBlobId); } cpputils::unique_ref releaseBaseBlob() { return std::move(_baseBlob); } protected: FsBlobRef(cpputils::unique_ref baseBlob, cachingfsblobstore::CachingFsBlobStore *fsBlobStore): _fsBlobStore(fsBlobStore), _baseBlob(std::move(baseBlob)) {} fsblobstore::FsBlob *baseBlob() { return _baseBlob.get(); } private: CachingFsBlobStore *_fsBlobStore; cpputils::unique_ref _baseBlob; DISALLOW_COPY_AND_ASSIGN(FsBlobRef); }; } } #endif src/cryfs/filesystem/cachingfsblobstore/SymlinkBlobRef.cpp000066400000000000000000000000341347701267100244130ustar00rootroot00000000000000#include "SymlinkBlobRef.h" src/cryfs/filesystem/cachingfsblobstore/SymlinkBlobRef.h000066400000000000000000000020321347701267100240600ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_SYMLINKBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_SYMLINKBLOBREF_H #include "FsBlobRef.h" #include "../fsblobstore/SymlinkBlob.h" namespace cryfs { namespace cachingfsblobstore { class SymlinkBlobRef final: public FsBlobRef { public: SymlinkBlobRef(cpputils::unique_ref base, CachingFsBlobStore *fsBlobStore) :FsBlobRef(std::move(base), fsBlobStore), _base(dynamic_cast(baseBlob())) { ASSERT(_base != nullptr, "We just initialized this with a pointer to SymlinkBlob. Can't be something else now."); } const boost::filesystem::path &target() const { return _base->target(); } const blockstore::BlockId &blockId() const { return _base->blockId(); } fspp::num_bytes_t lstat_size() const { return _base->lstat_size(); } private: fsblobstore::SymlinkBlob *_base; DISALLOW_COPY_AND_ASSIGN(SymlinkBlobRef); }; } } #endif src/cryfs/filesystem/fsblobstore/000077500000000000000000000000001347701267100175135ustar00rootroot00000000000000src/cryfs/filesystem/fsblobstore/DirBlob.cpp000066400000000000000000000212741347701267100215420ustar00rootroot00000000000000#include "DirBlob.h" #include //TODO Remove and replace with exception hierarchy #include #include #include #include "../CryDevice.h" #include "FileBlob.h" #include "SymlinkBlob.h" #include using std::vector; using std::string; using blobstore::Blob; using blockstore::BlockId; using cpputils::Data; using cpputils::unique_ref; using cpputils::make_unique_ref; using boost::none; namespace cryfs { namespace fsblobstore { constexpr fspp::num_bytes_t DirBlob::DIR_LSTAT_SIZE; DirBlob::DirBlob(unique_ref blob, std::function getLstatSize) : FsBlob(std::move(blob)), _getLstatSize(getLstatSize), _getLstatSizeMutex(), _entries(), _entriesAndChangedMutex(), _changed(false) { ASSERT(baseBlob().blobType() == FsBlobView::BlobType::DIR, "Loaded blob is not a directory"); _readEntriesFromBlob(); } DirBlob::~DirBlob() { std::unique_lock lock(_entriesAndChangedMutex); _writeEntriesToBlob(); } void DirBlob::flush() { std::unique_lock lock(_entriesAndChangedMutex); _writeEntriesToBlob(); baseBlob().flush(); } unique_ref DirBlob::InitializeEmptyDir(unique_ref blob, const blockstore::BlockId &parent, std::function getLstatSize) { InitializeBlob(blob.get(), FsBlobView::BlobType::DIR, parent); return make_unique_ref(std::move(blob), getLstatSize); } void DirBlob::_writeEntriesToBlob() { if (_changed) { Data serialized = _entries.serialize(); baseBlob().resize(serialized.size()); baseBlob().write(serialized.data(), 0, serialized.size()); _changed = false; } } void DirBlob::_readEntriesFromBlob() { //No lock needed, because this is only called from the constructor. Data data = baseBlob().readAll(); _entries.deserializeFrom(static_cast(data.data()), data.size()); } void DirBlob::AddChildDir(const std::string &name, const BlockId &blobId, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { std::unique_lock lock(_entriesAndChangedMutex); _addChild(name, blobId, fspp::Dir::EntryType::DIR, mode, uid, gid, lastAccessTime, lastModificationTime); } void DirBlob::AddChildFile(const std::string &name, const BlockId &blobId, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { std::unique_lock lock(_entriesAndChangedMutex); _addChild(name, blobId, fspp::Dir::EntryType::FILE, mode, uid, gid, lastAccessTime, lastModificationTime); } void DirBlob::AddChildSymlink(const std::string &name, const blockstore::BlockId &blobId, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { std::unique_lock lock(_entriesAndChangedMutex); auto mode = fspp::mode_t().addSymlinkFlag() .addUserReadFlag().addUserWriteFlag().addUserExecFlag() .addGroupReadFlag().addGroupWriteFlag().addGroupExecFlag() .addOtherReadFlag().addOtherWriteFlag().addOtherExecFlag(); _addChild(name, blobId, fspp::Dir::EntryType::SYMLINK, mode, uid, gid, lastAccessTime, lastModificationTime); } void DirBlob::_addChild(const std::string &name, const BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { _entries.add(name, blobId, entryType, mode, uid, gid, lastAccessTime, lastModificationTime); _changed = true; } void DirBlob::AddOrOverwriteChild(const std::string &name, const BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime, std::function onOverwritten) { std::unique_lock lock(_entriesAndChangedMutex); _entries.addOrOverwrite(name, blobId, entryType, mode, uid, gid, lastAccessTime, lastModificationTime, onOverwritten); _changed = true; } void DirBlob::RenameChild(const blockstore::BlockId &blockId, const std::string &newName, std::function onOverwritten) { std::unique_lock lock(_entriesAndChangedMutex); _entries.rename(blockId, newName, onOverwritten); _changed = true; } boost::optional DirBlob::GetChild(const string &name) const { std::unique_lock lock(_entriesAndChangedMutex); return _entries.get(name); } boost::optional DirBlob::GetChild(const BlockId &blockId) const { std::unique_lock lock(_entriesAndChangedMutex); return _entries.get(blockId); } void DirBlob::RemoveChild(const string &name) { std::unique_lock lock(_entriesAndChangedMutex); _entries.remove(name); _changed = true; } void DirBlob::RemoveChild(const BlockId &blockId) { std::unique_lock lock(_entriesAndChangedMutex); _entries.remove(blockId); _changed = true; } void DirBlob::AppendChildrenTo(vector *result) const { std::unique_lock lock(_entriesAndChangedMutex); result->reserve(result->size() + _entries.size()); for (const auto &entry : _entries) { result->emplace_back(entry.type(), entry.name()); } } fspp::num_bytes_t DirBlob::lstat_size() const { return DIR_LSTAT_SIZE; } fspp::Node::stat_info DirBlob::statChild(const BlockId &blockId) const { std::unique_lock lock(_getLstatSizeMutex); auto lstatSizeGetter = _getLstatSize; // The following unlock is important to avoid deadlock. // ParallelAccessFsBlobStore::load() causes a call to DirBlob::setLstatSizeGetter, // so their lock ordering first locks the ParallelAccessStore::_mutex, then the DirBlob::_getLstatSizeMutex. // this requires us to free DirBlob::_getLstatSizeMutex before calling into lstatSizeGetter(), because // lstatSizeGetter can call ParallelAccessFsBlobStore::load(). lock.unlock(); auto lstatSize = lstatSizeGetter(blockId); return statChildWithKnownSize(blockId, lstatSize); } fspp::Node::stat_info DirBlob::statChildWithKnownSize(const BlockId &blockId, fspp::num_bytes_t size) const { fspp::Node::stat_info result; auto childOpt = GetChild(blockId); if (childOpt == boost::none) { throw fspp::fuse::FuseErrnoException(ENOENT); } const auto &child = *childOpt; result.mode = child.mode(); result.uid = child.uid(); result.gid = child.gid(); //TODO If possible without performance loss, then for a directory, st_nlink should return number of dir entries (including "." and "..") result.nlink = 1; result.size = size; result.atime = child.lastAccessTime(); result.mtime = child.lastModificationTime(); result.ctime = child.lastMetadataChangeTime(); //TODO Move ceilDivision to general utils which can be used by cryfs as well result.blocks = blobstore::onblocks::utils::ceilDivision(size.value(), static_cast(512)); return result; } void DirBlob::updateAccessTimestampForChild(const BlockId &blockId, TimestampUpdateBehavior timestampUpdateBehavior) { std::unique_lock lock(_entriesAndChangedMutex); if (_entries.updateAccessTimestampForChild(blockId, timestampUpdateBehavior)) { _changed = true; } } void DirBlob::updateModificationTimestampForChild(const BlockId &blockId) { std::unique_lock lock(_entriesAndChangedMutex); _entries.updateModificationTimestampForChild(blockId); _changed = true; } void DirBlob::chmodChild(const BlockId &blockId, fspp::mode_t mode) { std::unique_lock lock(_entriesAndChangedMutex); _entries.setMode(blockId, mode); _changed = true; } void DirBlob::chownChild(const BlockId &blockId, fspp::uid_t uid, fspp::gid_t gid) { std::unique_lock lock(_entriesAndChangedMutex); if(_entries.setUidGid(blockId, uid, gid)) { _changed = true; } } void DirBlob::utimensChild(const BlockId &blockId, timespec lastAccessTime, timespec lastModificationTime) { std::unique_lock lock(_entriesAndChangedMutex); _entries.setAccessTimes(blockId, lastAccessTime, lastModificationTime); _changed = true; } void DirBlob::setLstatSizeGetter(std::function getLstatSize) { std::lock_guard lock(_getLstatSizeMutex); _getLstatSize = std::move(getLstatSize); } cpputils::unique_ref DirBlob::releaseBaseBlob() { std::unique_lock lock(_entriesAndChangedMutex); _writeEntriesToBlob(); return FsBlob::releaseBaseBlob(); } size_t DirBlob::NumChildren() const { return _entries.size(); } } } src/cryfs/filesystem/fsblobstore/DirBlob.h000066400000000000000000000105251347701267100212040ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_DIRBLOB_H_ #define MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_DIRBLOB_H_ #include #include #include #include #include "FsBlob.h" #include "utils/DirEntryList.h" #include namespace cryfs { namespace fsblobstore { class FsBlobStore; class DirBlob final : public FsBlob { public: constexpr static fspp::num_bytes_t DIR_LSTAT_SIZE = fspp::num_bytes_t(4096); static cpputils::unique_ref InitializeEmptyDir(cpputils::unique_ref blob, const blockstore::BlockId &parent, std::function getLstatSize); DirBlob(cpputils::unique_ref blob, std::function getLstatSize); ~DirBlob(); fspp::num_bytes_t lstat_size() const override; void AppendChildrenTo(std::vector *result) const; //TODO Test NumChildren() size_t NumChildren() const; boost::optional GetChild(const std::string &name) const; boost::optional GetChild(const blockstore::BlockId &blobId) const; void AddChildDir(const std::string &name, const blockstore::BlockId &blobId, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime); void AddChildFile(const std::string &name, const blockstore::BlockId &blobId, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime); void AddChildSymlink(const std::string &name, const blockstore::BlockId &blobId, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime); void AddOrOverwriteChild(const std::string &name, const blockstore::BlockId &blobId, fspp::Dir::EntryType type, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime, std::function onOverwritten); void RenameChild(const blockstore::BlockId &blockId, const std::string &newName, std::function onOverwritten); void RemoveChild(const std::string &name); void RemoveChild(const blockstore::BlockId &blockId); void flush(); fspp::Node::stat_info statChild(const blockstore::BlockId &blockId) const; fspp::Node::stat_info statChildWithKnownSize(const blockstore::BlockId &blockId, fspp::num_bytes_t size) const; void updateAccessTimestampForChild(const blockstore::BlockId &blockId, TimestampUpdateBehavior timestampUpdateBehavior); void updateModificationTimestampForChild(const blockstore::BlockId &blockId); void chmodChild(const blockstore::BlockId &blockId, fspp::mode_t mode); void chownChild(const blockstore::BlockId &blockId, fspp::uid_t uid, fspp::gid_t gid); void utimensChild(const blockstore::BlockId &blockId, timespec lastAccessTime, timespec lastModificationTime); void setLstatSizeGetter(std::function getLstatSize); private: void _addChild(const std::string &name, const blockstore::BlockId &blobId, fspp::Dir::EntryType type, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime); void _readEntriesFromBlob(); void _writeEntriesToBlob(); cpputils::unique_ref releaseBaseBlob() override; std::function _getLstatSize; mutable std::mutex _getLstatSizeMutex; DirEntryList _entries; mutable std::mutex _entriesAndChangedMutex; bool _changed; DISALLOW_COPY_AND_ASSIGN(DirBlob); }; } } #endif src/cryfs/filesystem/fsblobstore/FileBlob.cpp000066400000000000000000000024111347701267100216730ustar00rootroot00000000000000#include "FileBlob.h" #include #include using blobstore::Blob; using cpputils::unique_ref; using cpputils::make_unique_ref; using blockstore::BlockId; namespace cryfs { namespace fsblobstore { FileBlob::FileBlob(unique_ref blob) : FsBlob(std::move(blob)) { ASSERT(baseBlob().blobType() == FsBlobView::BlobType::FILE, "Loaded blob is not a file"); } unique_ref FileBlob::InitializeEmptyFile(unique_ref blob, const blockstore::BlockId &parent) { InitializeBlob(blob.get(), FsBlobView::BlobType::FILE, parent); return make_unique_ref(std::move(blob)); } fspp::num_bytes_t FileBlob::read(void *target, fspp::num_bytes_t offset, fspp::num_bytes_t count) const { return fspp::num_bytes_t(baseBlob().tryRead(target, offset.value(), count.value())); } void FileBlob::write(const void *source, fspp::num_bytes_t offset, fspp::num_bytes_t count) { baseBlob().write(source, offset.value(), count.value()); } void FileBlob::flush() { baseBlob().flush(); } void FileBlob::resize(fspp::num_bytes_t size) { baseBlob().resize(size.value()); } fspp::num_bytes_t FileBlob::lstat_size() const { return size(); } fspp::num_bytes_t FileBlob::size() const { return fspp::num_bytes_t(baseBlob().size()); } } } src/cryfs/filesystem/fsblobstore/FileBlob.h000066400000000000000000000016711347701267100213470ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_FILEBLOB_H_ #define MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_FILEBLOB_H_ #include "FsBlob.h" namespace cryfs { namespace fsblobstore { class FileBlob final: public FsBlob { public: static cpputils::unique_ref InitializeEmptyFile(cpputils::unique_ref blob, const blockstore::BlockId &parent); FileBlob(cpputils::unique_ref blob); fspp::num_bytes_t read(void *target, fspp::num_bytes_t offset, fspp::num_bytes_t count) const; void write(const void *source, fspp::num_bytes_t offset, fspp::num_bytes_t count); void flush(); void resize(fspp::num_bytes_t size); fspp::num_bytes_t lstat_size() const override; fspp::num_bytes_t size() const; private: DISALLOW_COPY_AND_ASSIGN(FileBlob); }; } } #endif src/cryfs/filesystem/fsblobstore/FsBlob.cpp000066400000000000000000000000241347701267100213620ustar00rootroot00000000000000#include "FsBlob.h" src/cryfs/filesystem/fsblobstore/FsBlob.h000066400000000000000000000046021347701267100210350ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_FSBLOB_H #define MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_FSBLOB_H #include #include #include "FsBlobView.h" #include namespace cryfs { namespace fsblobstore { class FsBlob { public: virtual ~FsBlob(); virtual fspp::num_bytes_t lstat_size() const = 0; const blockstore::BlockId &blockId() const; const blockstore::BlockId &parentPointer() const; void setParentPointer(const blockstore::BlockId &parentId); protected: FsBlob(cpputils::unique_ref baseBlob); FsBlobView &baseBlob(); const FsBlobView &baseBlob() const; static void InitializeBlob(blobstore::Blob *blob, FsBlobView::BlobType magicNumber, const blockstore::BlockId &parent); friend class FsBlobStore; virtual cpputils::unique_ref releaseBaseBlob(); private: FsBlobView _baseBlob; DISALLOW_COPY_AND_ASSIGN(FsBlob); }; // --------------------------- // Inline function definitions // --------------------------- inline FsBlob::FsBlob(cpputils::unique_ref baseBlob) : _baseBlob(std::move(baseBlob)) { } inline FsBlob::~FsBlob() { } inline const blockstore::BlockId &FsBlob::blockId() const { return _baseBlob.blockId(); } inline const FsBlobView &FsBlob::baseBlob() const { return _baseBlob; } inline FsBlobView &FsBlob::baseBlob() { return _baseBlob; } inline void FsBlob::InitializeBlob(blobstore::Blob *blob, FsBlobView::BlobType magicNumber, const blockstore::BlockId &parent) { FsBlobView::InitializeBlob(blob, magicNumber, parent); } inline cpputils::unique_ref FsBlob::releaseBaseBlob() { return _baseBlob.releaseBaseBlob(); } inline const blockstore::BlockId &FsBlob::parentPointer() const { return _baseBlob.parentPointer(); } inline void FsBlob::setParentPointer(const blockstore::BlockId &parentId) { return _baseBlob.setParentPointer(parentId); } } } #endif src/cryfs/filesystem/fsblobstore/FsBlobStore.cpp000066400000000000000000000063461347701267100224140ustar00rootroot00000000000000#include "FsBlobStore.h" #include "FileBlob.h" #include "DirBlob.h" #include "SymlinkBlob.h" #include #include #include using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::SignalCatcher; using blobstore::BlobStore; using blockstore::BlockId; using boost::none; using std::vector; namespace cryfs { namespace fsblobstore { boost::optional> FsBlobStore::load(const blockstore::BlockId &blockId) { auto blob = _baseBlobStore->load(blockId); if (blob == none) { return none; } FsBlobView::BlobType blobType = FsBlobView::blobType(**blob); if (blobType == FsBlobView::BlobType::FILE) { return unique_ref(make_unique_ref(std::move(*blob))); } else if (blobType == FsBlobView::BlobType::DIR) { return unique_ref(make_unique_ref(std::move(*blob), _getLstatSize())); } else if (blobType == FsBlobView::BlobType::SYMLINK) { return unique_ref(make_unique_ref(std::move(*blob))); } else { ASSERT(false, "Unknown magic number"); } } #ifndef CRYFS_NO_COMPATIBILITY unique_ref FsBlobStore::migrate(unique_ref blobStore, const blockstore::BlockId &rootBlobId) { SignalCatcher signalCatcher; auto rootBlob = blobStore->load(rootBlobId); ASSERT(rootBlob != none, "Could not load root blob"); auto fsBlobStore = make_unique_ref(std::move(blobStore)); uint64_t migratedBlocks = 0; 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(), &signalCatcher, [&] (uint32_t numNodes) { migratedBlocks += numNodes; progressbar.update(migratedBlocks); }); return fsBlobStore; } void FsBlobStore::_migrate(unique_ref node, const blockstore::BlockId &parentId, SignalCatcher* signalCatcher, std::function perBlobCallback) { FsBlobView::migrate(node.get(), parentId); perBlobCallback(node->numNodes()); if (FsBlobView::blobType(*node) == FsBlobView::BlobType::DIR) { DirBlob dir(std::move(node), _getLstatSize()); vector children; dir.AppendChildrenTo(&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); ASSERT(childEntry != none, "Couldn't load child, although it was returned as a child in the list."); auto childBlob = _baseBlobStore->load(childEntry->blockId()); ASSERT(childBlob != none, "Couldn't load child blob"); _migrate(std::move(*childBlob), dir.blockId(), signalCatcher, perBlobCallback); } } } #endif } } src/cryfs/filesystem/fsblobstore/FsBlobStore.h000066400000000000000000000077211347701267100220570ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_FSBLOBSTORE_H #define MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_FSBLOBSTORE_H #include #include #include #include "FileBlob.h" #include "DirBlob.h" #include "SymlinkBlob.h" #ifndef CRYFS_NO_COMPATIBILITY #include #endif namespace cryfs { namespace fsblobstore { //TODO Test classes in fsblobstore class FsBlobStore final { public: FsBlobStore(cpputils::unique_ref baseBlobStore); cpputils::unique_ref createFileBlob(const blockstore::BlockId &parent); cpputils::unique_ref createDirBlob(const blockstore::BlockId &parent); cpputils::unique_ref createSymlinkBlob(const boost::filesystem::path &target, const blockstore::BlockId &parent); boost::optional> load(const blockstore::BlockId &blockId); void remove(cpputils::unique_ref blob); void remove(const blockstore::BlockId &blockId); uint64_t numBlocks() const; uint64_t estimateSpaceForNumBlocksLeft() const; uint64_t virtualBlocksizeBytes() const; #ifndef CRYFS_NO_COMPATIBILITY static cpputils::unique_ref migrate(cpputils::unique_ref blobStore, const blockstore::BlockId &blockId); #endif private: #ifndef CRYFS_NO_COMPATIBILITY void _migrate(cpputils::unique_ref node, const blockstore::BlockId &parentId, cpputils::SignalCatcher* signalCatcher, std::function perBlobCallback); #endif std::function _getLstatSize(); cpputils::unique_ref _baseBlobStore; DISALLOW_COPY_AND_ASSIGN(FsBlobStore); }; inline FsBlobStore::FsBlobStore(cpputils::unique_ref baseBlobStore) : _baseBlobStore(std::move(baseBlobStore)) { } inline cpputils::unique_ref FsBlobStore::createFileBlob(const blockstore::BlockId &parent) { auto blob = _baseBlobStore->create(); return FileBlob::InitializeEmptyFile(std::move(blob), parent); } inline cpputils::unique_ref FsBlobStore::createDirBlob(const blockstore::BlockId &parent) { auto blob = _baseBlobStore->create(); return DirBlob::InitializeEmptyDir(std::move(blob), parent, _getLstatSize()); } inline cpputils::unique_ref FsBlobStore::createSymlinkBlob(const boost::filesystem::path &target, const blockstore::BlockId &parent) { auto blob = _baseBlobStore->create(); return SymlinkBlob::InitializeSymlink(std::move(blob), target, parent); } inline uint64_t FsBlobStore::numBlocks() const { return _baseBlobStore->numBlocks(); } inline uint64_t FsBlobStore::estimateSpaceForNumBlocksLeft() const { return _baseBlobStore->estimateSpaceForNumBlocksLeft(); } inline void FsBlobStore::remove(cpputils::unique_ref blob) { _baseBlobStore->remove(blob->releaseBaseBlob()); } inline void FsBlobStore::remove(const blockstore::BlockId &blockId) { _baseBlobStore->remove(blockId); } inline std::function FsBlobStore::_getLstatSize() { return [this] (const blockstore::BlockId &blockId) { auto blob = load(blockId); ASSERT(blob != boost::none, "Blob not found"); return (*blob)->lstat_size(); }; } inline uint64_t FsBlobStore::virtualBlocksizeBytes() const { return _baseBlobStore->virtualBlocksizeBytes(); } } } #endif src/cryfs/filesystem/fsblobstore/FsBlobView.cpp000066400000000000000000000020701347701267100222200ustar00rootroot00000000000000#include "FsBlobView.h" using cpputils::Data; namespace cryfs { constexpr uint16_t FsBlobView::FORMAT_VERSION_HEADER; constexpr unsigned int FsBlobView::HEADER_SIZE; #ifndef CRYFS_NO_COMPATIBILITY void FsBlobView::migrate(blobstore::Blob *blob, const blockstore::BlockId &parentId) { constexpr unsigned int OLD_HEADER_SIZE = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint8_t); if(FsBlobView::getFormatVersionHeader(*blob) != 0) { // blob already migrated return; } // Resize blob and move data back cpputils::Data data = blob->readAll(); blob->resize(blob->size() + blockstore::BlockId::BINARY_LENGTH); blob->write(data.dataOffset(OLD_HEADER_SIZE), HEADER_SIZE, data.size() - OLD_HEADER_SIZE); // Write parent pointer blob->write(parentId.data().data(), sizeof(FORMAT_VERSION_HEADER) + sizeof(uint8_t), blockstore::BlockId::BINARY_LENGTH); // Update format version number blob->write(&FORMAT_VERSION_HEADER, 0, sizeof(FORMAT_VERSION_HEADER)); } #endif } src/cryfs/filesystem/fsblobstore/FsBlobView.h000066400000000000000000000127111347701267100216700ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_FSBLOBVIEW_H #define MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_FSBLOBVIEW_H #include #include namespace cryfs { //TODO Test class FsBlobView final : public blobstore::Blob { public: //TODO Rename to "Type" or similar enum class BlobType : uint8_t { DIR = 0x00, FILE = 0x01, SYMLINK = 0x02 }; FsBlobView(cpputils::unique_ref baseBlob): _baseBlob(std::move(baseBlob)), _parentPointer(blockstore::BlockId::Null()) { _checkHeader(*_baseBlob); _loadParentPointer(); } static void InitializeBlob(blobstore::Blob *baseBlob, BlobType blobType, const blockstore::BlockId &parent) { baseBlob->resize(HEADER_SIZE); baseBlob->write(&FORMAT_VERSION_HEADER, 0, sizeof(FORMAT_VERSION_HEADER)); uint8_t blobTypeInt = static_cast(blobType); baseBlob->write(&blobTypeInt, sizeof(FORMAT_VERSION_HEADER), sizeof(uint8_t)); baseBlob->write(parent.data().data(), sizeof(FORMAT_VERSION_HEADER) + sizeof(uint8_t), blockstore::BlockId::BINARY_LENGTH); static_assert(HEADER_SIZE == sizeof(FORMAT_VERSION_HEADER) + sizeof(uint8_t) + blockstore::BlockId::BINARY_LENGTH, "If this fails, the header is not initialized correctly in this function."); } static BlobType blobType(const blobstore::Blob &blob) { _checkHeader(blob); return _blobType(blob); } BlobType blobType() const { return _blobType(*_baseBlob); } const blockstore::BlockId &parentPointer() const { return _parentPointer; } void setParentPointer(const blockstore::BlockId &parentId) { _parentPointer = parentId; _storeParentPointer(); } const blockstore::BlockId &blockId() const override { return _baseBlob->blockId(); } uint64_t size() const override { return _baseBlob->size() - HEADER_SIZE; } void resize(uint64_t numBytes) override { return _baseBlob->resize(numBytes + HEADER_SIZE); } cpputils::Data readAll() const override { cpputils::Data data = _baseBlob->readAll(); cpputils::Data dataWithoutHeader(data.size() - HEADER_SIZE); //Can we avoid this memcpy? Maybe by having Data::subdata() that returns a reference to the same memory region? Should we? std::memcpy(dataWithoutHeader.data(), data.dataOffset(HEADER_SIZE), dataWithoutHeader.size()); return dataWithoutHeader; } void read(void *target, uint64_t offset, uint64_t size) const override { return _baseBlob->read(target, offset + HEADER_SIZE, size); } uint64_t tryRead(void *target, uint64_t offset, uint64_t size) const override { return _baseBlob->tryRead(target, offset + HEADER_SIZE, size); } void write(const void *source, uint64_t offset, uint64_t size) override { return _baseBlob->write(source, offset + HEADER_SIZE, size); } void flush() override { return _baseBlob->flush(); } uint32_t numNodes() const override { return _baseBlob->numNodes(); } cpputils::unique_ref releaseBaseBlob() { return std::move(_baseBlob); } static uint16_t getFormatVersionHeader(const blobstore::Blob &blob) { static_assert(sizeof(uint16_t) == sizeof(FORMAT_VERSION_HEADER), "Wrong type used to read format version header"); uint16_t actualFormatVersion; blob.read(&actualFormatVersion, 0, sizeof(FORMAT_VERSION_HEADER)); return actualFormatVersion; } #ifndef CRYFS_NO_COMPATIBILITY static void migrate(blobstore::Blob *blob, const blockstore::BlockId &parentId); #endif private: static constexpr uint16_t FORMAT_VERSION_HEADER = 1; static constexpr unsigned int HEADER_SIZE = sizeof(FORMAT_VERSION_HEADER) + sizeof(uint8_t) + blockstore::BlockId::BINARY_LENGTH; static void _checkHeader(const blobstore::Blob &blob) { uint16_t actualFormatVersion = getFormatVersionHeader(blob); if (FORMAT_VERSION_HEADER != actualFormatVersion) { throw std::runtime_error("This file system entity has the wrong format. Was it created with a newer version of CryFS?"); } } static BlobType _blobType(const blobstore::Blob &blob) { uint8_t result; blob.read(&result, sizeof(FORMAT_VERSION_HEADER), sizeof(uint8_t)); return static_cast(result); } void _loadParentPointer() { auto idData = cpputils::FixedSizeData::Null(); _baseBlob->read(idData.data(), sizeof(FORMAT_VERSION_HEADER) + sizeof(uint8_t), blockstore::BlockId::BINARY_LENGTH); _parentPointer = blockstore::BlockId(idData); } void _storeParentPointer() { _baseBlob->write(_parentPointer.data().data(), sizeof(FORMAT_VERSION_HEADER) + sizeof(uint8_t), blockstore::BlockId::BINARY_LENGTH); } cpputils::unique_ref _baseBlob; blockstore::BlockId _parentPointer; DISALLOW_COPY_AND_ASSIGN(FsBlobView); }; } #endif src/cryfs/filesystem/fsblobstore/SymlinkBlob.cpp000066400000000000000000000026721347701267100224530ustar00rootroot00000000000000#include "SymlinkBlob.h" #include #include using std::string; using blobstore::Blob; using cpputils::unique_ref; using cpputils::make_unique_ref; namespace bf = boost::filesystem; namespace cryfs { namespace fsblobstore { SymlinkBlob::SymlinkBlob(unique_ref blob) : FsBlob(std::move(blob)), _target(_readTargetFromBlob(baseBlob())) { ASSERT(baseBlob().blobType() == FsBlobView::BlobType::SYMLINK, "Loaded blob is not a symlink"); } unique_ref SymlinkBlob::InitializeSymlink(unique_ref blob, const bf::path &target, const blockstore::BlockId &parent) { InitializeBlob(blob.get(), FsBlobView::BlobType::SYMLINK, parent); FsBlobView symlinkBlobView(std::move(blob)); string targetStr = target.string(); symlinkBlobView.resize(targetStr.size()); symlinkBlobView.write(targetStr.c_str(), 0, targetStr.size()); return make_unique_ref(symlinkBlobView.releaseBaseBlob()); } bf::path SymlinkBlob::_readTargetFromBlob(const FsBlobView &blob) { // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) auto targetStr = std::make_unique(blob.size() + 1); // +1 because of the nullbyte blob.read(targetStr.get(), 0, blob.size()); targetStr[blob.size()] = '\0'; return targetStr.get(); } const bf::path &SymlinkBlob::target() const { return _target; } fspp::num_bytes_t SymlinkBlob::lstat_size() const { return fspp::num_bytes_t(target().string().size()); } } } src/cryfs/filesystem/fsblobstore/SymlinkBlob.h000066400000000000000000000017321347701267100221140ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_SYMLINKBLOB_H_ #define MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_SYMLINKBLOB_H_ #include #include "FsBlob.h" namespace cryfs { namespace fsblobstore { class SymlinkBlob final: public FsBlob { public: static cpputils::unique_ref InitializeSymlink(cpputils::unique_ref blob, const boost::filesystem::path &target, const blockstore::BlockId &parent); SymlinkBlob(cpputils::unique_ref blob); const boost::filesystem::path &target() const; fspp::num_bytes_t lstat_size() const override; private: boost::filesystem::path _target; static boost::filesystem::path _readTargetFromBlob(const FsBlobView &blob); DISALLOW_COPY_AND_ASSIGN(SymlinkBlob); }; } } #endif src/cryfs/filesystem/fsblobstore/utils/000077500000000000000000000000001347701267100206535ustar00rootroot00000000000000src/cryfs/filesystem/fsblobstore/utils/DirEntry.cpp000066400000000000000000000117321347701267100231230ustar00rootroot00000000000000#include #include "DirEntry.h" #include using std::vector; using std::string; using blockstore::BlockId; namespace cryfs { namespace fsblobstore { namespace { template size_t _serialize(void* dst, const DataType& obj) { cpputils::serialize(dst, obj); return sizeof(DataType); } template DataType _deserialize(const char** src) { DataType result = cpputils::deserialize(*src); *src += sizeof(DataType); return result; } constexpr size_t _serializedTimeValueSize() { return sizeof(uint64_t) + sizeof(uint32_t); } unsigned int _serializeTimeValue(uint8_t *dest, timespec value) { unsigned int offset = 0; offset += _serialize(dest + offset, value.tv_sec); offset += _serialize(dest + offset, value.tv_nsec); ASSERT(offset == _serializedTimeValueSize(), "serialized to wrong size"); return offset; } timespec _deserializeTimeValue(const char **pos) { timespec value{}; value.tv_sec = _deserialize(pos); value.tv_nsec = _deserialize(pos); return value; } unsigned int _serializeString(uint8_t *dest, const string &value) { std::memcpy(dest, value.c_str(), value.size()+1); return value.size() + 1; } string _deserializeString(const char **pos) { size_t length = strlen(*pos); string value(*pos, length); *pos += length + 1; return value; } unsigned int _serializeBlockId(uint8_t *dest, const BlockId &blockId) { blockId.ToBinary(dest); return blockId.BINARY_LENGTH; } BlockId _deserializeBlockId(const char **pos) { BlockId blockId = BlockId::FromBinary(*pos); *pos += BlockId::BINARY_LENGTH; return blockId; } } void DirEntry::serialize(uint8_t *dest) const { ASSERT( ((_type == fspp::Dir::EntryType::FILE) && _mode.hasFileFlag() && !_mode.hasDirFlag() && !_mode.hasSymlinkFlag()) || ((_type == fspp::Dir::EntryType::DIR) && !_mode.hasFileFlag() && _mode.hasDirFlag() && !_mode.hasSymlinkFlag()) || ((_type == fspp::Dir::EntryType::SYMLINK) && !_mode.hasFileFlag() && !_mode.hasDirFlag() && _mode.hasSymlinkFlag()) , "Wrong mode bit set for this type: " + std::to_string(_mode.hasFileFlag()) + ", " + std::to_string( _mode.hasDirFlag()) + ", " + std::to_string(_mode.hasSymlinkFlag()) + ", " + std::to_string(static_cast(_type)) ); unsigned int offset = 0; offset += _serialize(dest + offset, static_cast(_type)); offset += _serialize(dest + offset, _mode.value()); offset += _serialize(dest + offset, _uid.value()); offset += _serialize(dest + offset, _gid.value()); offset += _serializeTimeValue(dest + offset, _lastAccessTime); offset += _serializeTimeValue(dest + offset, _lastModificationTime); offset += _serializeTimeValue(dest + offset, _lastMetadataChangeTime); offset += _serializeString(dest + offset, _name); offset += _serializeBlockId(dest + offset, _blockId); ASSERT(offset == serializedSize(), "Didn't write correct number of elements"); } const char *DirEntry::deserializeAndAddToVector(const char *pos, vector *result) { fspp::Dir::EntryType type = static_cast(_deserialize(&pos)); fspp::mode_t mode = fspp::mode_t(_deserialize(&pos)); fspp::uid_t uid = fspp::uid_t(_deserialize(&pos)); fspp::gid_t gid = fspp::gid_t(_deserialize(&pos)); timespec lastAccessTime = _deserializeTimeValue(&pos); timespec lastModificationTime = _deserializeTimeValue(&pos); timespec lastMetadataChangeTime = _deserializeTimeValue(&pos); string name = _deserializeString(&pos); BlockId blockId = _deserializeBlockId(&pos); result->emplace_back(type, name, blockId, mode, uid, gid, lastAccessTime, lastModificationTime, lastMetadataChangeTime); return pos; } size_t DirEntry::serializedSize() const { return 1 + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t) + 3*_serializedTimeValueSize() + ( _name.size() + 1) + _blockId.BINARY_LENGTH; } } } src/cryfs/filesystem/fsblobstore/utils/DirEntry.h000066400000000000000000000123611347701267100225670ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_UTILS_DIRENTRY_H #define MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_UTILS_DIRENTRY_H #include #include #include #include #include namespace cryfs { namespace fsblobstore { class DirEntry final { public: DirEntry(fspp::Dir::EntryType type, const std::string &name, const blockstore::BlockId &blockId, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime, timespec lastMetadataChangeTime); void serialize(uint8_t* dest) const; size_t serializedSize() const; static const char *deserializeAndAddToVector(const char *pos, std::vector *result); fspp::Dir::EntryType type() const; void setType(fspp::Dir::EntryType value); const std::string &name() const; void setName(const std::string &value); const blockstore::BlockId &blockId() const; fspp::mode_t mode() const; void setMode(fspp::mode_t value); fspp::uid_t uid() const; void setUid(fspp::uid_t value); fspp::gid_t gid() const; void setGid(fspp::gid_t value); timespec lastAccessTime() const; void setLastAccessTime(timespec value); timespec lastModificationTime() const; void setLastModificationTime(timespec value); timespec lastMetadataChangeTime() const; private: void _updateLastMetadataChangeTime(); fspp::Dir::EntryType _type; std::string _name; blockstore::BlockId _blockId; fspp::mode_t _mode; fspp::uid_t _uid; fspp::gid_t _gid; timespec _lastAccessTime; timespec _lastModificationTime; timespec _lastMetadataChangeTime; }; inline DirEntry::DirEntry(fspp::Dir::EntryType type, const std::string &name, const blockstore::BlockId &blockId, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime, timespec lastMetadataChangeTime) : _type(type), _name(name), _blockId(blockId), _mode(mode), _uid(uid), _gid(gid), _lastAccessTime(lastAccessTime), _lastModificationTime(lastModificationTime), _lastMetadataChangeTime(lastMetadataChangeTime) { switch (_type) { case fspp::Dir::EntryType::FILE: _mode.addFileFlag(); break; case fspp::Dir::EntryType::DIR: _mode.addDirFlag(); break; case fspp::Dir::EntryType::SYMLINK: _mode.addSymlinkFlag(); break; } ASSERT((_mode.hasFileFlag() && _type == fspp::Dir::EntryType::FILE) || (_mode.hasDirFlag() && _type == fspp::Dir::EntryType::DIR) || (_mode.hasSymlinkFlag() && _type == fspp::Dir::EntryType::SYMLINK), "Unknown mode in entry"); } inline fspp::Dir::EntryType DirEntry::type() const { return _type; } inline const std::string &DirEntry::name() const { return _name; } inline const blockstore::BlockId &DirEntry::blockId() const { return _blockId; } inline fspp::mode_t DirEntry::mode() const { return _mode; } inline fspp::uid_t DirEntry::uid() const { return _uid; } inline fspp::gid_t DirEntry::gid() const { return _gid; } inline timespec DirEntry::lastAccessTime() const { return _lastAccessTime; } inline timespec DirEntry::lastModificationTime() const { return _lastModificationTime; } inline timespec DirEntry::lastMetadataChangeTime() const { return _lastMetadataChangeTime; } inline void DirEntry::setType(fspp::Dir::EntryType value) { _type = value; _updateLastMetadataChangeTime(); } inline void DirEntry::setName(const std::string &value) { _name = value; _updateLastMetadataChangeTime(); } inline void DirEntry::setMode(fspp::mode_t value) { _mode = value; _updateLastMetadataChangeTime(); } inline void DirEntry::setUid(fspp::uid_t value) { _uid = value; _updateLastMetadataChangeTime(); } inline void DirEntry::setGid(fspp::gid_t value) { _gid = value; _updateLastMetadataChangeTime(); } inline void DirEntry::setLastAccessTime(timespec value) { _lastAccessTime = value; } inline void DirEntry::setLastModificationTime(timespec value) { _lastModificationTime = value; _updateLastMetadataChangeTime(); } inline void DirEntry::_updateLastMetadataChangeTime() { _lastMetadataChangeTime = cpputils::time::now(); } } } #endif src/cryfs/filesystem/fsblobstore/utils/DirEntryList.cpp000066400000000000000000000236621347701267100237640ustar00rootroot00000000000000#include "DirEntryList.h" #include #include //TODO Get rid of that in favor of better error handling #include using cpputils::Data; using std::string; using std::vector; using blockstore::BlockId; namespace cryfs { namespace fsblobstore { DirEntryList::DirEntryList() : _entries() { } Data DirEntryList::serialize() const { Data serialized(_serializedSize()); unsigned int offset = 0; for (auto iter = _entries.begin(); iter != _entries.end(); ++iter) { ASSERT(iter == _entries.begin() || std::less()((iter-1)->blockId(), iter->blockId()), "Invariant hurt: Directory entries should be ordered by blockId and not have duplicate blockIds."); iter->serialize(static_cast(serialized.dataOffset(offset))); offset += iter->serializedSize(); } return serialized; } uint64_t DirEntryList::_serializedSize() const { uint64_t serializedSize = 0; for (const auto &entry : _entries) { serializedSize += entry.serializedSize(); } return serializedSize; } void DirEntryList::deserializeFrom(const void *data, uint64_t size) { _entries.clear(); const char *pos = static_cast(data); while (pos < static_cast(data) + size) { pos = DirEntry::deserializeAndAddToVector(pos, &_entries); ASSERT(_entries.size() == 1 || std::less()(_entries[_entries.size()-2].blockId(), _entries[_entries.size()-1].blockId()), "Invariant hurt: Directory entries should be ordered by blockId and not have duplicate blockIds."); } } bool DirEntryList::_hasChild(const string &name) const { return _entries.end() != _findByName(name); } void DirEntryList::add(const string &name, const BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { if (_hasChild(name)) { throw fspp::fuse::FuseErrnoException(EEXIST); } _add(name, blobId, entryType, mode, uid, gid, lastAccessTime, lastModificationTime); } void DirEntryList::_add(const string &name, const BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { auto insert_pos = _findUpperBound(blobId); _entries.emplace(insert_pos, entryType, name, blobId, mode, uid, gid, lastAccessTime, lastModificationTime, cpputils::time::now()); } void DirEntryList::addOrOverwrite(const string &name, const BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime, std::function onOverwritten) { auto found = _findByName(name); if (found != _entries.end()) { onOverwritten(found->blockId()); _overwrite(found, name, blobId, entryType, mode, uid, gid, lastAccessTime, lastModificationTime); } else { _add(name, blobId, entryType, mode, uid, gid, lastAccessTime, lastModificationTime); } } void DirEntryList::rename(const blockstore::BlockId &blockId, const std::string &name, std::function onOverwritten) { auto foundSameName = _findByName(name); if (foundSameName != _entries.end() && foundSameName->blockId() != blockId) { _checkAllowedOverwrite(foundSameName->type(), _findById(blockId)->type()); onOverwritten(foundSameName->blockId()); _entries.erase(foundSameName); } _findById(blockId)->setName(name); } void DirEntryList::_checkAllowedOverwrite(fspp::Dir::EntryType oldType, fspp::Dir::EntryType newType) { if (oldType != newType) { if (oldType == fspp::Dir::EntryType::DIR) { // new path is an existing directory, but old path is not a directory throw fspp::fuse::FuseErrnoException(EISDIR); } if (newType == fspp::Dir::EntryType::DIR) { // oldpath is a directory, and newpath exists but is not a directory. throw fspp::fuse::FuseErrnoException(ENOTDIR); } } } void DirEntryList::_overwrite(vector::iterator entry, const string &name, const BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { _checkAllowedOverwrite(entry->type(), entryType); // The new entry has possibly a different blockId, so it has to be in a different list position (list is ordered by blockIds). // That's why we remove-and-add instead of just modifying the existing entry. _entries.erase(entry); _add(name, blobId, entryType, mode, uid, gid, lastAccessTime, lastModificationTime); } boost::optional DirEntryList::get(const string &name) const { auto found = _findByName(name); if (found == _entries.end()) { return boost::none; } return *found; } boost::optional DirEntryList::get(const BlockId &blockId) const { auto found = _findById(blockId); if (found == _entries.end()) { return boost::none; } return *found; } void DirEntryList::remove(const string &name) { auto found = _findByName(name); if (found == _entries.end()) { throw fspp::fuse::FuseErrnoException(ENOENT); } _entries.erase(found); } void DirEntryList::remove(const BlockId &blockId) { auto lowerBound = _findLowerBound(blockId); auto upperBound = std::find_if(lowerBound, _entries.end(), [&blockId] (const DirEntry &entry) { return entry.blockId() != blockId; }); _entries.erase(lowerBound, upperBound); } vector::iterator DirEntryList::_findByName(const string &name) { return std::find_if(_entries.begin(), _entries.end(), [&name] (const DirEntry &entry) { return entry.name() == name; }); } vector::const_iterator DirEntryList::_findByName(const string &name) const { return const_cast(this)->_findByName(name); } vector::iterator DirEntryList::_findById(const BlockId &blockId) { auto found = _findLowerBound(blockId); if (found == _entries.end() || found->blockId() != blockId) { throw fspp::fuse::FuseErrnoException(ENOENT); } return found; } vector::iterator DirEntryList::_findLowerBound(const BlockId &blockId) { return _findFirst(blockId, [&blockId] (const DirEntry &entry) { return !std::less()(entry.blockId(), blockId); }); } vector::iterator DirEntryList::_findUpperBound(const BlockId &blockId) { return _findFirst(blockId, [&blockId] (const DirEntry &entry) { return std::less()(blockId, entry.blockId()); }); } vector::iterator DirEntryList::_findFirst(const BlockId &hint, std::function pred) { //TODO Factor out a datastructure that keeps a sorted std::vector and allows these _findLowerBound()/_findUpperBound operations using this hinted linear search if (_entries.size() == 0) { return _entries.end(); } double startpos_percent = static_cast(*static_cast(hint.data().data())) / std::numeric_limits::max(); auto iter = _entries.begin() + static_cast(startpos_percent * static_cast(_entries.size()-1)); ASSERT(iter >= _entries.begin() && iter < _entries.end(), "Startpos out of range"); while(iter != _entries.begin() && pred(*iter)) { --iter; } while(iter != _entries.end() && !pred(*iter)) { ++iter; } return iter; } vector::const_iterator DirEntryList::_findById(const BlockId &blockId) const { return const_cast(this)->_findById(blockId); } size_t DirEntryList::size() const { return _entries.size(); } DirEntryList::const_iterator DirEntryList::begin() const { return _entries.begin(); } DirEntryList::const_iterator DirEntryList::end() const { return _entries.end(); } void DirEntryList::setMode(const BlockId &blockId, fspp::mode_t mode) { auto found = _findById(blockId); ASSERT ((mode.hasFileFlag() && found->mode().hasFileFlag()) || (mode.hasDirFlag() && found->mode().hasDirFlag()) || (mode.hasSymlinkFlag()), "Unknown mode in entry"); found->setMode(mode); } bool DirEntryList::setUidGid(const BlockId &blockId, fspp::uid_t uid, fspp::gid_t gid) { auto found = _findById(blockId); bool changed = false; if (uid != fspp::uid_t(-1)) { found->setUid(uid); changed = true; } if (gid != fspp::gid_t(-1)) { found->setGid(gid); changed = true; } return changed; } void DirEntryList::setAccessTimes(const blockstore::BlockId &blockId, timespec lastAccessTime, timespec lastModificationTime) { auto found = _findById(blockId); found->setLastAccessTime(lastAccessTime); found->setLastModificationTime(lastModificationTime); } bool DirEntryList::updateAccessTimestampForChild(const blockstore::BlockId &blockId, TimestampUpdateBehavior timestampUpdateBehavior) { ASSERT(timestampUpdateBehavior == TimestampUpdateBehavior::RELATIME, "Currently only relatime supported"); auto found = _findById(blockId); const timespec lastAccessTime = found->lastAccessTime(); const timespec lastModificationTime = found->lastModificationTime(); const timespec now = cpputils::time::now(); const timespec yesterday { /*.tv_sec = */ now.tv_sec - 60*60*24, /*.tv_nsec = */ now.tv_nsec }; bool changed = false; if (lastAccessTime < lastModificationTime || lastAccessTime < yesterday) { found->setLastAccessTime(now); changed = true; } return changed; } void DirEntryList::updateModificationTimestampForChild(const blockstore::BlockId &blockId) { auto found = _findById(blockId); found->setLastModificationTime(cpputils::time::now()); } } } src/cryfs/filesystem/fsblobstore/utils/DirEntryList.h000066400000000000000000000076341347701267100234320ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_UTILS_DIRENTRYLIST_H #define MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_UTILS_DIRENTRYLIST_H #include #include "DirEntry.h" #include #include #include "TimestampUpdateBehavior.h" //TODO Address elements by name instead of by blockId when accessing them. Who knows whether there is two hard links for the same blob. namespace cryfs { namespace fsblobstore { class DirEntryList final { public: using const_iterator = std::vector::const_iterator; DirEntryList(); cpputils::Data serialize() const; void deserializeFrom(const void *data, uint64_t size); void add(const std::string &name, const blockstore::BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime); void addOrOverwrite(const std::string &name, const blockstore::BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime, std::function onOverwritten); void rename(const blockstore::BlockId &blockId, const std::string &name, std::function onOverwritten); boost::optional get(const std::string &name) const; boost::optional get(const blockstore::BlockId &blockId) const; void remove(const std::string &name); void remove(const blockstore::BlockId &blockId); size_t size() const; const_iterator begin() const; const_iterator end() const; void setMode(const blockstore::BlockId &blockId, fspp::mode_t mode); bool setUidGid(const blockstore::BlockId &blockId, fspp::uid_t uid, fspp::gid_t gid); void setAccessTimes(const blockstore::BlockId &blockId, timespec lastAccessTime, timespec lastModificationTime); bool updateAccessTimestampForChild(const blockstore::BlockId &blockId, TimestampUpdateBehavior timestampUpdateBehavior); void updateModificationTimestampForChild(const blockstore::BlockId &blockId); private: uint64_t _serializedSize() const; bool _hasChild(const std::string &name) const; std::vector::iterator _findByName(const std::string &name); std::vector::const_iterator _findByName(const std::string &name) const; std::vector::iterator _findById(const blockstore::BlockId &blockId); std::vector::const_iterator _findById(const blockstore::BlockId &blockId) const; std::vector::iterator _findUpperBound(const blockstore::BlockId &blockId); std::vector::iterator _findLowerBound(const blockstore::BlockId &blockId); std::vector::iterator _findFirst(const blockstore::BlockId &hint, std::function pred); void _add(const std::string &name, const blockstore::BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime); void _overwrite(std::vector::iterator entry, const std::string &name, const blockstore::BlockId &blobId, fspp::Dir::EntryType entryType, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime); static void _checkAllowedOverwrite(fspp::Dir::EntryType oldType, fspp::Dir::EntryType newType); std::vector _entries; DISALLOW_COPY_AND_ASSIGN(DirEntryList); }; } } #endif src/cryfs/filesystem/fsblobstore/utils/TimestampUpdateBehavior.h000066400000000000000000000003771347701267100256210ustar00rootroot00000000000000#pragma once #ifndef CRYFS_TIMESTAMPUPDATEBEHAVIOR_H #define CRYFS_TIMESTAMPUPDATEBEHAVIOR_H namespace cryfs { namespace fsblobstore { enum class TimestampUpdateBehavior : uint8_t { // currently only relatime supported RELATIME }; } } #endif src/cryfs/filesystem/parallelaccessfsblobstore/000077500000000000000000000000001347701267100224125ustar00rootroot00000000000000src/cryfs/filesystem/parallelaccessfsblobstore/DirBlobRef.cpp000066400000000000000000000000301347701267100250610ustar00rootroot00000000000000#include "DirBlobRef.h" src/cryfs/filesystem/parallelaccessfsblobstore/DirBlobRef.h000066400000000000000000000107761347701267100245500ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_DIRBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_DIRBLOBREF_H #include "FsBlobRef.h" #include "../cachingfsblobstore/DirBlobRef.h" #include "../fsblobstore/utils/TimestampUpdateBehavior.h" #include namespace cryfs { namespace parallelaccessfsblobstore { class DirBlobRef final: public FsBlobRef { public: DirBlobRef(cachingfsblobstore::DirBlobRef *base): _base(base) {} using Entry = fsblobstore::DirEntry; boost::optional GetChild(const std::string &name) const { return _base->GetChild(name); } boost::optional GetChild(const blockstore::BlockId &blockId) const { return _base->GetChild(blockId); } size_t NumChildren() const { return _base->NumChildren(); } void RemoveChild(const blockstore::BlockId &blockId) { return _base->RemoveChild(blockId); } void RemoveChild(const std::string &name) { return _base->RemoveChild(name); } void flush() { return _base->flush(); } void AddOrOverwriteChild(const std::string &name, const blockstore::BlockId &blobId, fspp::Dir::EntryType type, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime, std::function onOverwritten) { return _base->AddOrOverwriteChild(name, blobId, type, mode, uid, gid, lastAccessTime, lastModificationTime, onOverwritten); } void RenameChild(const blockstore::BlockId &blockId, const std::string &newName, std::function onOverwritten) { return _base->RenameChild(blockId, newName, onOverwritten); } fspp::Node::stat_info statChild(const blockstore::BlockId &blockId) const { return _base->statChild(blockId); } fspp::Node::stat_info statChildWithKnownSize(const blockstore::BlockId &blockId, fspp::num_bytes_t size) const { return _base->statChildWithKnownSize(blockId, size); } void updateAccessTimestampForChild(const blockstore::BlockId &blockId, fsblobstore::TimestampUpdateBehavior timestampUpdateBehavior) { return _base->updateAccessTimestampForChild(blockId, timestampUpdateBehavior); } void updateModificationTimestampForChild(const blockstore::BlockId &blockId) { return _base->updateModificationTimestampForChild(blockId); } void chmodChild(const blockstore::BlockId &blockId, fspp::mode_t mode) { return _base->chmodChild(blockId, mode); } void chownChild(const blockstore::BlockId &blockId, fspp::uid_t uid, fspp::gid_t gid) { return _base->chownChild(blockId, uid, gid); } void utimensChild(const blockstore::BlockId &blockId, timespec lastAccessTime, timespec lastModificationTime) { return _base->utimensChild(blockId, lastAccessTime, lastModificationTime); } void AddChildDir(const std::string &name, const blockstore::BlockId &blobId, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { return _base->AddChildDir(name, blobId, mode, uid, gid, lastAccessTime, lastModificationTime); } void AddChildFile(const std::string &name, const blockstore::BlockId &blobId, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { return _base->AddChildFile(name, blobId, mode, uid, gid, lastAccessTime, lastModificationTime); } void AddChildSymlink(const std::string &name, const blockstore::BlockId &blobId, fspp::uid_t uid, fspp::gid_t gid, timespec lastAccessTime, timespec lastModificationTime) { return _base->AddChildSymlink(name, blobId, uid, gid, lastAccessTime, lastModificationTime); } void AppendChildrenTo(std::vector *result) const { return _base->AppendChildrenTo(result); } const blockstore::BlockId &blockId() const override { return _base->blockId(); } fspp::num_bytes_t lstat_size() const override { return _base->lstat_size(); } const blockstore::BlockId &parentPointer() const override { return _base->parentPointer(); } void setParentPointer(const blockstore::BlockId &parentId) override { return _base->setParentPointer(parentId); } private: cachingfsblobstore::DirBlobRef *_base; DISALLOW_COPY_AND_ASSIGN(DirBlobRef); }; } } #endif src/cryfs/filesystem/parallelaccessfsblobstore/FileBlobRef.cpp000066400000000000000000000000311347701267100252230ustar00rootroot00000000000000#include "FileBlobRef.h" src/cryfs/filesystem/parallelaccessfsblobstore/FileBlobRef.h000066400000000000000000000027111347701267100246770ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_FILEBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_FILEBLOBREF_H #include "FsBlobRef.h" #include "../cachingfsblobstore/FileBlobRef.h" namespace cryfs { namespace parallelaccessfsblobstore { class FileBlobRef final: public FsBlobRef { public: FileBlobRef(cachingfsblobstore::FileBlobRef *base) : _base(base) {} void resize(fspp::num_bytes_t size) { return _base->resize(size); } fspp::num_bytes_t size() const { return _base->size(); } fspp::num_bytes_t read(void *target, fspp::num_bytes_t offset, fspp::num_bytes_t count) const { return _base->read(target, offset, count); } void write(const void *source, fspp::num_bytes_t offset, fspp::num_bytes_t count) { return _base->write(source, offset, count); } void flush() { return _base->flush(); } const blockstore::BlockId &blockId() const override { return _base->blockId(); } fspp::num_bytes_t lstat_size() const override { return _base->lstat_size(); } const blockstore::BlockId &parentPointer() const override { return _base->parentPointer(); } void setParentPointer(const blockstore::BlockId &parentId) override { return _base->setParentPointer(parentId); } private: cachingfsblobstore::FileBlobRef *_base; DISALLOW_COPY_AND_ASSIGN(FileBlobRef); }; } } #endif src/cryfs/filesystem/parallelaccessfsblobstore/FsBlobRef.cpp000066400000000000000000000000271347701267100247210ustar00rootroot00000000000000#include "FsBlobRef.h" src/cryfs/filesystem/parallelaccessfsblobstore/FsBlobRef.h000066400000000000000000000015211347701267100243660ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_FSBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_FSBLOBREF_H #include #include "../cachingfsblobstore/FsBlobRef.h" namespace cryfs { namespace parallelaccessfsblobstore { class FsBlobRef: public parallelaccessstore::ParallelAccessStore::ResourceRefBase { public: virtual ~FsBlobRef() {} virtual const blockstore::BlockId &blockId() const = 0; virtual fspp::num_bytes_t lstat_size() const = 0; virtual const blockstore::BlockId &parentPointer() const = 0; virtual void setParentPointer(const blockstore::BlockId &parentId) = 0; protected: FsBlobRef() {} private: DISALLOW_COPY_AND_ASSIGN(FsBlobRef); }; } } #endif src/cryfs/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStore.cpp000066400000000000000000000060421347701267100301030ustar00rootroot00000000000000#include "ParallelAccessFsBlobStore.h" #include "ParallelAccessFsBlobStoreAdapter.h" #include "../fsblobstore/FsBlobStore.h" namespace bf = boost::filesystem; using cpputils::unique_ref; using cpputils::make_unique_ref; using boost::optional; using blockstore::BlockId; namespace cryfs { namespace parallelaccessfsblobstore { optional> ParallelAccessFsBlobStore::load(const BlockId &blockId) { return _parallelAccessStore.load(blockId, [this] (cachingfsblobstore::FsBlobRef *blob) { // NOLINT (workaround https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82481 ) cachingfsblobstore::FileBlobRef *fileBlob = dynamic_cast(blob); if (fileBlob != nullptr) { return unique_ref(make_unique_ref(fileBlob)); } cachingfsblobstore::DirBlobRef *dirBlob = dynamic_cast(blob); if (dirBlob != nullptr) { dirBlob->setLstatSizeGetter(_getLstatSize()); return unique_ref(make_unique_ref(dirBlob)); } cachingfsblobstore::SymlinkBlobRef *symlinkBlob = dynamic_cast(blob); if (symlinkBlob != nullptr) { return unique_ref(make_unique_ref(symlinkBlob)); } ASSERT(false, "Unknown blob type loaded"); }); } unique_ref ParallelAccessFsBlobStore::createDirBlob(const blockstore::BlockId &parent) { auto blob = _baseBlobStore->createDirBlob(parent); blob->setLstatSizeGetter(_getLstatSize()); BlockId blockId = blob->blockId(); return _parallelAccessStore.add(blockId, std::move(blob), [] (cachingfsblobstore::FsBlobRef *resource) { auto dirBlob = dynamic_cast(resource); ASSERT(dirBlob != nullptr, "Wrong resource given"); return make_unique_ref(dirBlob); }); } unique_ref ParallelAccessFsBlobStore::createFileBlob(const blockstore::BlockId &parent) { auto blob = _baseBlobStore->createFileBlob(parent); BlockId blockId = blob->blockId(); return _parallelAccessStore.add(blockId, std::move(blob), [] (cachingfsblobstore::FsBlobRef *resource) { auto fileBlob = dynamic_cast(resource); ASSERT(fileBlob != nullptr, "Wrong resource given"); return make_unique_ref(fileBlob); }); } unique_ref ParallelAccessFsBlobStore::createSymlinkBlob(const bf::path &target, const blockstore::BlockId &parent) { auto blob = _baseBlobStore->createSymlinkBlob(target, parent); BlockId blockId = blob->blockId(); return _parallelAccessStore.add(blockId, std::move(blob), [] (cachingfsblobstore::FsBlobRef *resource) { auto symlinkBlob = dynamic_cast(resource); ASSERT(symlinkBlob != nullptr, "Wrong resource given"); return make_unique_ref(symlinkBlob); }); } } } src/cryfs/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStore.h000066400000000000000000000066571347701267100275640ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_PARALLELACCESSFSBLOBSTORE_H #define MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_PARALLELACCESSFSBLOBSTORE_H #include #include "FileBlobRef.h" #include "DirBlobRef.h" #include "SymlinkBlobRef.h" #include "../cachingfsblobstore/CachingFsBlobStore.h" #include "ParallelAccessFsBlobStoreAdapter.h" namespace cryfs { namespace parallelaccessfsblobstore { //TODO Test classes in parallelaccessfsblobstore //TODO Race condition: Thread 1 destructs CachingFsBlobStore element from ParallelAccessFsBlobStore, but // it didn't get written into cache yet, when Thread 2 requests it. // Same race condition in Caching/ParallelAccessBlockStore? class ParallelAccessFsBlobStore final { public: ParallelAccessFsBlobStore(cpputils::unique_ref baseBlobStore); cpputils::unique_ref createFileBlob(const blockstore::BlockId &parent); cpputils::unique_ref createDirBlob(const blockstore::BlockId &parent); cpputils::unique_ref createSymlinkBlob(const boost::filesystem::path &target, const blockstore::BlockId &parent); boost::optional> load(const blockstore::BlockId &blockId); void remove(cpputils::unique_ref blob); uint64_t virtualBlocksizeBytes() const; uint64_t numBlocks() const; uint64_t estimateSpaceForNumBlocksLeft() const; private: cpputils::unique_ref _baseBlobStore; parallelaccessstore::ParallelAccessStore _parallelAccessStore; std::function _getLstatSize(); DISALLOW_COPY_AND_ASSIGN(ParallelAccessFsBlobStore); }; inline ParallelAccessFsBlobStore::ParallelAccessFsBlobStore(cpputils::unique_ref baseBlobStore) : _baseBlobStore(std::move(baseBlobStore)), _parallelAccessStore(cpputils::make_unique_ref(_baseBlobStore.get())) { } inline void ParallelAccessFsBlobStore::remove(cpputils::unique_ref blob) { blockstore::BlockId blockId = blob->blockId(); return _parallelAccessStore.remove(blockId, std::move(blob)); } inline std::function ParallelAccessFsBlobStore::_getLstatSize() { return [this] (const blockstore::BlockId &blockId) { auto blob = load(blockId); ASSERT(blob != boost::none, "Blob not found"); return (*blob)->lstat_size(); }; } inline uint64_t ParallelAccessFsBlobStore::virtualBlocksizeBytes() const { return _baseBlobStore->virtualBlocksizeBytes(); } inline uint64_t ParallelAccessFsBlobStore::numBlocks() const { return _baseBlobStore->numBlocks(); } inline uint64_t ParallelAccessFsBlobStore::estimateSpaceForNumBlocksLeft() const { return _baseBlobStore->estimateSpaceForNumBlocksLeft(); } } } #endif src/cryfs/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStoreAdapter.cpp000066400000000000000000000000561347701267100314030ustar00rootroot00000000000000#include "ParallelAccessFsBlobStoreAdapter.h" src/cryfs/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStoreAdapter.h000066400000000000000000000024221347701267100310470ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_PARALLELACCESSFSBLOBSTOREADAPTER_H_ #define MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_PARALLELACCESSFSBLOBSTOREADAPTER_H_ #include #include #include "../cachingfsblobstore/CachingFsBlobStore.h" namespace cryfs { namespace parallelaccessfsblobstore { class ParallelAccessFsBlobStoreAdapter final: public parallelaccessstore::ParallelAccessBaseStore { public: explicit ParallelAccessFsBlobStoreAdapter(cachingfsblobstore::CachingFsBlobStore *baseBlobStore) :_baseBlobStore(baseBlobStore) { } boost::optional> loadFromBaseStore(const blockstore::BlockId &blockId) override { return _baseBlobStore->load(blockId); } void removeFromBaseStore(cpputils::unique_ref block) override { return _baseBlobStore->remove(std::move(block)); } void removeFromBaseStore(const blockstore::BlockId &blockId) override { return _baseBlobStore->remove(blockId); } private: cachingfsblobstore::CachingFsBlobStore *_baseBlobStore; DISALLOW_COPY_AND_ASSIGN(ParallelAccessFsBlobStoreAdapter); }; } } #endif src/cryfs/filesystem/parallelaccessfsblobstore/SymlinkBlobRef.cpp000066400000000000000000000000341347701267100257750ustar00rootroot00000000000000#include "SymlinkBlobRef.h" src/cryfs/filesystem/parallelaccessfsblobstore/SymlinkBlobRef.h000066400000000000000000000020631347701267100254460ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_SYMLINKBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_SYMLINKBLOBREF_H #include "FsBlobRef.h" #include "../cachingfsblobstore/SymlinkBlobRef.h" namespace cryfs { namespace parallelaccessfsblobstore { class SymlinkBlobRef final: public FsBlobRef { public: SymlinkBlobRef(cachingfsblobstore::SymlinkBlobRef *base) : _base(base) {} const boost::filesystem::path &target() const { return _base->target(); } const blockstore::BlockId &blockId() const override { return _base->blockId(); } fspp::num_bytes_t lstat_size() const override { return _base->lstat_size(); } const blockstore::BlockId &parentPointer() const override { return _base->parentPointer(); } void setParentPointer(const blockstore::BlockId &parentId) override { return _base->setParentPointer(parentId); } private: cachingfsblobstore::SymlinkBlobRef *_base; DISALLOW_COPY_AND_ASSIGN(SymlinkBlobRef); }; } } #endif src/cryfs/localstate/000077500000000000000000000000001347701267100151365ustar00rootroot00000000000000src/cryfs/localstate/BasedirMetadata.cpp000066400000000000000000000042411347701267100206550ustar00rootroot00000000000000#include "BasedirMetadata.h" #include #include #include #include #include "LocalStateDir.h" #include namespace bf = boost::filesystem; using boost::property_tree::ptree; using boost::property_tree::write_json; using boost::property_tree::read_json; using boost::none; using std::ostream; using std::istream; using std::ifstream; using std::ofstream; using std::string; using namespace cpputils::logging; namespace cryfs { namespace { ptree _load(const bf::path &metadataFilePath) { try { ptree result; ifstream file(metadataFilePath.string()); if (file.good()) { read_json(file, result); } return result; } catch (...) { LOG(ERR, "Error loading BasedirMetadata"); throw; } } void _save(const bf::path &metadataFilePath, const ptree& data) { ofstream file(metadataFilePath.string(), std::ios::trunc); write_json(file, data); } string jsonPathForBasedir(const bf::path &basedir) { return bf::canonical(basedir).string() + ".filesystemId"; } } BasedirMetadata::BasedirMetadata(ptree data, bf::path filename) :_filename(std::move(filename)), _data(std::move(data)) {} BasedirMetadata BasedirMetadata::load(const LocalStateDir& localStateDir) { auto filename = localStateDir.forBasedirMetadata(); auto loaded = _load(filename); return BasedirMetadata(std::move(loaded), std::move(filename)); } void BasedirMetadata::save() { _save(_filename, _data); } bool BasedirMetadata::filesystemIdForBasedirIsCorrect(const bf::path &basedir, const CryConfig::FilesystemID &filesystemId) const { auto entry = _data.get_optional(jsonPathForBasedir(basedir)); if (entry == boost::none) { return true; // Basedir not known in local state yet. } auto filesystemIdFromState = CryConfig::FilesystemID::FromString(*entry); return filesystemIdFromState == filesystemId; } BasedirMetadata& BasedirMetadata::updateFilesystemIdForBasedir(const bf::path &basedir, const CryConfig::FilesystemID &filesystemId) { _data.put(jsonPathForBasedir(basedir), filesystemId.ToString()); return *this; } } src/cryfs/localstate/BasedirMetadata.h000066400000000000000000000020571347701267100203250ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_LOCALSTATE_BASEDIRMETADATA_H_ #define MESSMER_CRYFS_LOCALSTATE_BASEDIRMETADATA_H_ #include #include #include "../config/CryConfig.h" #include "LocalStateDir.h" namespace cryfs { class BasedirMetadata final { public: static BasedirMetadata load(const LocalStateDir& localStateDir); void save(); BasedirMetadata(const BasedirMetadata&) = delete; BasedirMetadata& operator=(const BasedirMetadata&) = delete; BasedirMetadata(BasedirMetadata&&) = default; BasedirMetadata& operator=(BasedirMetadata&&) = default; bool filesystemIdForBasedirIsCorrect(const boost::filesystem::path &basedir, const CryConfig::FilesystemID &filesystemId) const; BasedirMetadata& updateFilesystemIdForBasedir(const boost::filesystem::path &basedir, const CryConfig::FilesystemID &filesystemId); private: BasedirMetadata(boost::property_tree::ptree data, boost::filesystem::path filename); boost::filesystem::path _filename; boost::property_tree::ptree _data; }; } #endif src/cryfs/localstate/LocalStateDir.cpp000066400000000000000000000016111347701267100203330ustar00rootroot00000000000000#include "LocalStateDir.h" #include namespace bf = boost::filesystem; namespace cryfs { LocalStateDir::LocalStateDir(bf::path appDir): _appDir(std::move(appDir)) {} bf::path LocalStateDir::forFilesystemId(const CryConfig::FilesystemID &filesystemId) const { _createDirIfNotExists(_appDir); bf::path filesystems_dir = _appDir / "filesystems"; _createDirIfNotExists(filesystems_dir); bf::path this_filesystem_dir = filesystems_dir / filesystemId.ToString(); _createDirIfNotExists(this_filesystem_dir); return this_filesystem_dir; } bf::path LocalStateDir::forBasedirMetadata() const { _createDirIfNotExists(_appDir); return _appDir / "basedirs"; } void LocalStateDir::_createDirIfNotExists(const bf::path &path) { if (!bf::exists(path)) { bf::create_directories(path); } } } src/cryfs/localstate/LocalStateDir.h000066400000000000000000000012101347701267100177730ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_LOCALSTATE_LOCALSTATEDIR_H_ #define MESSMER_CRYFS_LOCALSTATE_LOCALSTATEDIR_H_ #include #include #include "../config/CryConfig.h" namespace cryfs { class LocalStateDir final { public: LocalStateDir(boost::filesystem::path appDir); boost::filesystem::path forFilesystemId(const CryConfig::FilesystemID &filesystemId) const; boost::filesystem::path forBasedirMetadata() const; private: boost::filesystem::path _appDir; static void _createDirIfNotExists(const boost::filesystem::path &path); }; } #endif src/cryfs/localstate/LocalStateMetadata.cpp000066400000000000000000000102531347701267100213370ustar00rootroot00000000000000#include "LocalStateMetadata.h" #include #include #include #include #include #include using boost::optional; using boost::none; using boost::property_tree::ptree; using boost::property_tree::write_json; using boost::property_tree::read_json; using std::ifstream; using std::ofstream; using std::istream; using std::ostream; using std::string; using blockstore::integrity::KnownBlockVersions; using cpputils::hash::Hash; using cpputils::Data; using cpputils::Random; namespace bf = boost::filesystem; using namespace cpputils::logging; namespace cryfs { LocalStateMetadata::LocalStateMetadata(uint32_t myClientId, Hash encryptionKeyHash) : _myClientId(myClientId), _encryptionKeyHash(encryptionKeyHash) {} LocalStateMetadata LocalStateMetadata::loadOrGenerate(const bf::path &statePath, const Data& encryptionKey, bool allowReplacedFilesystem) { auto metadataFile = statePath / "metadata"; auto loaded = _load(metadataFile); if (loaded == none) { // If it couldn't be loaded, generate a new client id. return _generate(metadataFile, encryptionKey); } if (!allowReplacedFilesystem && loaded->_encryptionKeyHash.digest != cpputils::hash::hash(encryptionKey, loaded->_encryptionKeyHash.salt).digest) { throw CryfsException("The filesystem encryption key differs from the last time we loaded this filesystem. Did an attacker replace the file system?", ErrorCode::EncryptionKeyChanged); } return *loaded; } optional LocalStateMetadata::_load(const bf::path &metadataFilePath) { ifstream file(metadataFilePath.string()); if (!file.good()) { // State file doesn't exist return none; } return _deserialize(file); } void LocalStateMetadata::_save(const bf::path &metadataFilePath) const { ofstream file(metadataFilePath.string(), std::ios::trunc); _serialize(file); } namespace { uint32_t _generateClientId() { uint32_t result; do { result = cpputils::deserialize(Random::PseudoRandom().getFixedSize().data()); } while(result == KnownBlockVersions::CLIENT_ID_FOR_DELETED_BLOCK); // Safety check - CLIENT_ID_FOR_DELETED_BLOCK shouldn't be used by any valid client. return result; } #ifndef CRYFS_NO_COMPATIBILITY optional _tryLoadClientIdFromLegacyFile(const bf::path &metadataFilePath) { auto myClientIdFile = metadataFilePath.parent_path() / "myClientId"; ifstream file(myClientIdFile.string()); if (!file.good()) { return none; } uint32_t value; file >> value; file.close(); bf::remove(myClientIdFile); return value; } #endif } LocalStateMetadata LocalStateMetadata::_generate(const bf::path &metadataFilePath, const Data& encryptionKey) { uint32_t myClientId = _generateClientId(); #ifndef CRYFS_NO_COMPATIBILITY // In the old format, this was stored in a "myClientId" file. If that file exists, load it from there. optional legacy = _tryLoadClientIdFromLegacyFile(metadataFilePath); if (legacy != none) { myClientId = *legacy; } #endif LocalStateMetadata result(myClientId, cpputils::hash::hash(encryptionKey, cpputils::hash::generateSalt())); result._save(metadataFilePath); return result; } void LocalStateMetadata::_serialize(ostream& stream) const { ptree pt; pt.put("myClientId", myClientId()); pt.put("encryptionKey.salt", _encryptionKeyHash.salt.ToString()); pt.put("encryptionKey.hash", _encryptionKeyHash.digest.ToString()); write_json(stream, pt); } LocalStateMetadata LocalStateMetadata::_deserialize(istream& stream) { try { ptree pt; read_json(stream, pt); uint32_t myClientId = pt.get("myClientId"); string encryptionKeySalt = pt.get("encryptionKey.salt"); string encryptionKeyDigest = pt.get("encryptionKey.hash"); return LocalStateMetadata(myClientId, Hash{ /*.digest = */ cpputils::hash::Digest::FromString(encryptionKeyDigest), /*.salt = */ cpputils::hash::Salt::FromString(encryptionKeySalt) }); } catch (...) { LOG(ERR, "Error loading LocalStateMetadata"); throw; } } } src/cryfs/localstate/LocalStateMetadata.h000066400000000000000000000022221347701267100210010ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_LOCALSTATE_LOCALSTATEMETADATA_H_ #define MESSMER_CRYFS_LOCALSTATE_LOCALSTATEMETADATA_H_ #include #include #include #include namespace cryfs { class LocalStateMetadata final { public: static LocalStateMetadata loadOrGenerate(const boost::filesystem::path &statePath, const cpputils::Data& encryptionKey, bool allowReplacedFilesystem); uint32_t myClientId() const; private: const uint32_t _myClientId; const cpputils::hash::Hash _encryptionKeyHash; static boost::optional _load(const boost::filesystem::path &metadataFilePath); static LocalStateMetadata _deserialize(std::istream& stream); static LocalStateMetadata _generate(const boost::filesystem::path &metadataFilePath, const cpputils::Data& encryptionKey); void _save(const boost::filesystem::path &metadataFilePath) const; void _serialize(std::ostream& stream) const; LocalStateMetadata(uint32_t myClientId, cpputils::hash::Hash encryptionKey); }; inline uint32_t LocalStateMetadata::myClientId() const { return _myClientId; } } #endif src/fspp/000077500000000000000000000000001347701267100126255ustar00rootroot00000000000000src/fspp/CMakeLists.txt000066400000000000000000000000661347701267100153670ustar00rootroot00000000000000add_subdirectory(fs_interface) add_subdirectory(fuse) src/fspp/fs_interface/000077500000000000000000000000001347701267100152555ustar00rootroot00000000000000src/fspp/fs_interface/CMakeLists.txt000066400000000000000000000005601347701267100200160ustar00rootroot00000000000000project (fspp-interface) set(SOURCES Device.cpp Dir.cpp File.cpp Node.cpp OpenFile.cpp Symlink.cpp Types.cpp) add_library(${PROJECT_NAME} STATIC ${SOURCES}) target_link_libraries(${PROJECT_NAME} PUBLIC cpp-utils) target_add_boost(${PROJECT_NAME} filesystem system) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) src/fspp/fs_interface/Device.cpp000066400000000000000000000000241347701267100171540ustar00rootroot00000000000000#include "Device.h" src/fspp/fs_interface/Device.h000066400000000000000000000020431347701267100166240ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSINTERFACE_DEVICE_H_ #define MESSMER_FSPP_FSINTERFACE_DEVICE_H_ #include #include #include "Types.h" namespace fspp { class Node; class File; class Dir; class Symlink; class Device { public: virtual ~Device() {} using statvfs = fspp::statvfs; virtual statvfs statfs() = 0; virtual boost::optional> Load(const boost::filesystem::path &path) = 0; //TODO Test default implementation (Device.cpp) //TODO Test client implementation (fstest) //TODO When it exists but is wrong node type, don't throw exception, but return this somehow (or at least throw specific exception, not just FuseErrnoException) virtual boost::optional> LoadFile(const boost::filesystem::path &path) = 0; virtual boost::optional> LoadDir(const boost::filesystem::path &path) = 0; virtual boost::optional> LoadSymlink(const boost::filesystem::path &path) = 0; }; } #endif src/fspp/fs_interface/Dir.cpp000066400000000000000000000000211347701267100164700ustar00rootroot00000000000000#include "Dir.h" src/fspp/fs_interface/Dir.h000066400000000000000000000022301347701267100161410ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSINTERFACE_DIR_H_ #define MESSMER_FSPP_FSINTERFACE_DIR_H_ #include #include #include #include "Types.h" namespace fspp { class Device; class OpenFile; class Dir { public: virtual ~Dir() {} enum class EntryType: uint8_t { DIR = 0x00, FILE = 0x01, SYMLINK = 0x02 }; struct Entry { Entry(EntryType type_, const std::string &name_): type(type_), name(name_) {} EntryType type; std::string name; }; virtual cpputils::unique_ref createAndOpenFile(const std::string &name, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid) = 0; virtual void createDir(const std::string &name, fspp::mode_t mode, fspp::uid_t uid, fspp::gid_t gid) = 0; virtual void createSymlink(const std::string &name, const boost::filesystem::path &target, fspp::uid_t uid, fspp::gid_t gid) = 0; //TODO Allow alternative implementation returning only children names without more information //virtual std::unique_ptr> children() const = 0; virtual cpputils::unique_ref> children() = 0; }; } #endif src/fspp/fs_interface/File.cpp000066400000000000000000000000221347701267100166320ustar00rootroot00000000000000#include "File.h" src/fspp/fs_interface/File.h000066400000000000000000000006101347701267100163020ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSINTERFACE_FILE_H_ #define MESSMER_FSPP_FSINTERFACE_FILE_H_ #include #include "Types.h" namespace fspp { class Device; class OpenFile; class File { public: virtual ~File() {} virtual cpputils::unique_ref open(fspp::openflags_t flags) = 0; virtual void truncate(fspp::num_bytes_t size) = 0; }; } #endif src/fspp/fs_interface/FuseErrnoException.cpp000066400000000000000000000000371347701267100215500ustar00rootroot00000000000000#include "FuseErrnoException.h"src/fspp/fs_interface/FuseErrnoException.h000066400000000000000000000014141347701267100212150ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FUSE_FUSEERRNOEXCEPTION_H_ #define MESSMER_FSPP_FUSE_FUSEERRNOEXCEPTION_H_ #include #include #include // TODO Need a portable way to report errors namespace fspp { namespace fuse{ class FuseErrnoException final: public std::runtime_error { public: explicit FuseErrnoException(int errno_); int getErrno() const; private: int _errno; }; inline void CHECK_RETVAL(int retval) { if (retval < 0) { throw FuseErrnoException(errno); } } inline FuseErrnoException::FuseErrnoException(int errno_) :runtime_error(strerror(errno_)), _errno(errno_) { ASSERT(_errno != 0, "Errno shouldn't be zero"); } inline int FuseErrnoException::getErrno() const { return _errno; } } } #endif src/fspp/fs_interface/Node.cpp000066400000000000000000000000211347701267100166370ustar00rootroot00000000000000#include "Node.h"src/fspp/fs_interface/Node.h000066400000000000000000000013501347701267100163120ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSINTERFACE_NODE_H_ #define MESSMER_FSPP_FSINTERFACE_NODE_H_ #include #include "Types.h" namespace fspp { class Node { public: virtual ~Node() {} using stat_info = fspp::stat_info; virtual stat_info stat() const = 0; virtual void chmod(fspp::mode_t mode) = 0; virtual void chown(fspp::uid_t uid, fspp::gid_t gid) = 0; virtual void access(int mask) const = 0; virtual void rename(const boost::filesystem::path &to) = 0; // 'to' will always be an absolute path (but on Windows without the device specifier, i.e. starting with '/') virtual void utimens(const timespec lastAccessTime, const timespec lastModificationTime) = 0; virtual void remove() = 0; }; } #endif src/fspp/fs_interface/OpenFile.cpp000066400000000000000000000000261347701267100174600ustar00rootroot00000000000000#include "OpenFile.h" src/fspp/fs_interface/OpenFile.h000066400000000000000000000012441347701267100171300ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSINTERFACE_OPENFILE_H_ #define MESSMER_FSPP_FSINTERFACE_OPENFILE_H_ #include #include "Types.h" namespace fspp { class Device; class OpenFile { public: virtual ~OpenFile() {} using stat_info = fspp::stat_info; virtual stat_info stat() const = 0; virtual void truncate(fspp::num_bytes_t size) const = 0; virtual fspp::num_bytes_t read(void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) const = 0; virtual void write(const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) = 0; virtual void flush() = 0; virtual void fsync() = 0; virtual void fdatasync() = 0; }; } #endif src/fspp/fs_interface/Symlink.cpp000066400000000000000000000000251347701267100174040ustar00rootroot00000000000000#include "Symlink.h" src/fspp/fs_interface/Symlink.h000066400000000000000000000005271347701267100170600ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSINTERFACE_SYMLINK_H_ #define MESSMER_FSPP_FSINTERFACE_SYMLINK_H_ #include #include #include namespace fspp { class Device; class Symlink { public: virtual ~Symlink() {} virtual boost::filesystem::path target() = 0; }; } #endif src/fspp/fs_interface/Types.cpp000066400000000000000000000000231347701267100170600ustar00rootroot00000000000000#include "Types.h" src/fspp/fs_interface/Types.h000066400000000000000000000120471347701267100165360ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSINTERFACE_TYPES_H_ #define MESSMER_FSPP_FSINTERFACE_TYPES_H_ #include #include #include namespace fspp { struct uid_t final : cpputils::value_type::IdValueType { // TODO Remove default constructor constexpr uid_t() noexcept: IdValueType(0) {} constexpr explicit uid_t(uint32_t id) noexcept: IdValueType(id) {} constexpr uint32_t value() const noexcept { return value_; } }; struct gid_t final : cpputils::value_type::IdValueType { // TODO Remove default constructor constexpr gid_t() noexcept: IdValueType(0) {} constexpr explicit gid_t(uint32_t id) noexcept: IdValueType(id) {} constexpr uint32_t value() const noexcept { return value_; } }; struct mode_t final : cpputils::value_type::FlagsValueType { // TODO Remove default constructor constexpr mode_t() noexcept: FlagsValueType(0) {} constexpr explicit mode_t(uint32_t id) noexcept: FlagsValueType(id) {} constexpr uint32_t value() const noexcept { return value_; } private: static constexpr mode_t S_IFMT_() { return mode_t(0170000); }; static constexpr mode_t S_IFDIR_() { return mode_t(0040000); }; static constexpr mode_t S_IFREG_() { return mode_t(0100000); }; static constexpr mode_t S_IFLNK_() { return mode_t(0120000); }; static constexpr mode_t S_IRUSR_() { return mode_t(0000400); }; static constexpr mode_t S_IWUSR_() { return mode_t(0000200); }; static constexpr mode_t S_IXUSR_() { return mode_t(0000100); }; static constexpr mode_t S_IRGRP_() { return mode_t(0000040); }; static constexpr mode_t S_IWGRP_() { return mode_t(0000020); }; static constexpr mode_t S_IXGRP_() { return mode_t(0000010); }; static constexpr mode_t S_IROTH_() { return mode_t(0000004); }; static constexpr mode_t S_IWOTH_() { return mode_t(0000002); }; static constexpr mode_t S_IXOTH_() { return mode_t(0000001); }; static constexpr bool S_ISREG_(mode_t mode) { return (mode & S_IFMT_()) == S_IFREG_(); } static constexpr bool S_ISDIR_(mode_t mode) { return (mode & S_IFMT_()) == S_IFDIR_(); } static constexpr bool S_ISLNK_(mode_t mode) { return (mode & S_IFMT_()) == S_IFLNK_(); } public: constexpr mode_t& addFileFlag() noexcept { return *this |= S_IFREG_(); } constexpr mode_t& addDirFlag() noexcept { return *this |= S_IFDIR_(); } constexpr mode_t& addSymlinkFlag() noexcept { return *this |= S_IFLNK_(); } constexpr mode_t& addUserReadFlag() noexcept { return *this |= S_IRUSR_(); } constexpr mode_t& addUserWriteFlag() noexcept { return *this |= S_IWUSR_(); } constexpr mode_t& addUserExecFlag() noexcept { return *this |= S_IXUSR_(); } constexpr mode_t& addGroupReadFlag() noexcept { return *this |= S_IRGRP_(); } constexpr mode_t& addGroupWriteFlag() noexcept { return *this |= S_IWGRP_(); } constexpr mode_t& addGroupExecFlag() noexcept { return *this |= S_IXGRP_(); } constexpr mode_t& addOtherReadFlag() noexcept { return *this |= S_IROTH_(); } constexpr mode_t& addOtherWriteFlag() noexcept { return *this |= S_IWOTH_(); } constexpr mode_t& addOtherExecFlag() noexcept { return *this |= S_IXOTH_(); } constexpr bool hasFileFlag() const noexcept { return S_ISREG_(*this); } constexpr bool hasDirFlag() const noexcept { return S_ISDIR_(*this); } constexpr bool hasSymlinkFlag() const noexcept { return S_ISLNK_(*this); } }; struct openflags_t final : cpputils::value_type::FlagsValueType { // TODO Remove default constructor constexpr openflags_t() noexcept: FlagsValueType(0) {} constexpr explicit openflags_t(int id) noexcept : FlagsValueType(id) {} constexpr int value() const noexcept { return value_; } static constexpr openflags_t RDONLY() { return openflags_t(0x0000); }; static constexpr openflags_t WRONLY() { return openflags_t(0x0001); }; static constexpr openflags_t RDWR() { return openflags_t(0x0002); }; }; struct num_bytes_t final : cpputils::value_type::QuantityValueType { // TODO Remove default constructor constexpr num_bytes_t() noexcept: QuantityValueType(0) {} constexpr explicit num_bytes_t(int64_t id) noexcept: QuantityValueType(id) {} constexpr int64_t value() const noexcept { return value_; } }; struct stat_info final { uint32_t nlink{}; fspp::mode_t mode; fspp::uid_t uid; fspp::gid_t gid; fspp::num_bytes_t size; uint64_t blocks{}; struct timespec atime{}; struct timespec mtime{}; struct timespec ctime{}; }; struct statvfs final { uint32_t max_filename_length; uint32_t blocksize; uint64_t num_total_blocks; uint64_t num_free_blocks; uint64_t num_available_blocks; // free blocks for unprivileged users uint64_t num_total_inodes; uint64_t num_free_inodes; uint64_t num_available_inodes; // free inodes for unprivileged users }; } #endif src/fspp/fstest/000077500000000000000000000000001347701267100141355ustar00rootroot00000000000000src/fspp/fstest/FsTest.h000066400000000000000000000041731347701267100155230ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSTEST_H_ #define MESSMER_FSPP_FSTEST_FSTEST_H_ #include "testutils/FileSystemTest.h" #include "FsppDeviceTest.h" #include "FsppDirTest.h" #include "FsppFileTest.h" #include "FsppSymlinkTest.h" #include "FsppNodeTest_Rename.h" #include "FsppNodeTest_Stat.h" #include "FsppOpenFileTest.h" #include "FsppDeviceTest_Timestamps.h" #include "FsppNodeTest_Timestamps.h" #include "FsppDirTest_Timestamps.h" #include "FsppSymlinkTest_Timestamps.h" #include "FsppFileTest_Timestamps.h" #include "FsppOpenFileTest_Timestamps.h" #define FSPP_ADD_FILESYTEM_TESTS(FS_NAME, FIXTURE) \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppDeviceTest_One, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppDeviceTest_Two, FIXTURE); \ INSTANTIATE_NODE_TEST_CASE( FS_NAME, FsppDeviceTest_Timestamps, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppDirTest, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppDirTest_Timestamps, FIXTURE); \ INSTANTIATE_NODE_TEST_CASE( FS_NAME, FsppDirTest_Timestamps_Entries, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppFileTest, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppFileTest_Timestamps, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppSymlinkTest, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppSymlinkTest_Timestamps, FIXTURE); \ INSTANTIATE_NODE_TEST_CASE( FS_NAME, FsppNodeTest_Rename, FIXTURE); \ INSTANTIATE_NODE_TEST_CASE( FS_NAME, FsppNodeTest_Stat, FIXTURE); \ INSTANTIATE_NODE_TEST_CASE( FS_NAME, FsppNodeTest_Timestamps, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppNodeTest_Stat_FileOnly, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppNodeTest_Stat_DirOnly, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppNodeTest_Stat_SymlinkOnly, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppOpenFileTest, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, FsppOpenFileTest_Timestamps, FIXTURE); \ #endif src/fspp/fstest/FsppDeviceTest.h000066400000000000000000000356601347701267100172100ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPDEVICETEST_H_ #define MESSMER_FSPP_FSTEST_FSPPDEVICETEST_H_ #include "fspp/fs_interface/FuseErrnoException.h" template class FsppDeviceTest: public FileSystemTest { public: void InitDirStructure() { this->LoadDir("/")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/")->createSymlink("mysymlink", "/symlink/target", fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/")->createDir("myemptydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir")->createAndOpenFile("myfile2", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir")->createSymlink("mysymlink", "/symlink/target", fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir")->createDir("mysubdir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir/mysubdir")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir/mysubdir")->createSymlink("mysymlink", "/symlink/target", fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir/mysubdir")->createDir("mysubsubdir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); } }; //Unfortunately, googletest only allows 50 test cases per REGISTER_TYPED_TEST_CASE_P, so we have to split it. template class FsppDeviceTest_One: public FsppDeviceTest {}; template class FsppDeviceTest_Two: public FsppDeviceTest {}; TYPED_TEST_CASE_P(FsppDeviceTest_One); TYPED_TEST_CASE_P(FsppDeviceTest_Two); TYPED_TEST_P(FsppDeviceTest_One, InitFilesystem) { //fixture->createDevice() is called in the FileSystemTest constructor } TYPED_TEST_P(FsppDeviceTest_One, LoadRootDir_Load) { auto node = this->Load("/"); this->EXPECT_IS_DIR(node); } TYPED_TEST_P(FsppDeviceTest_One, LoadRootDir_LoadDir) { this->LoadDir("/"); } TYPED_TEST_P(FsppDeviceTest_One, LoadRootDir_LoadFile) { EXPECT_THROW( this->LoadFile("/"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadRootDir_LoadSymlink) { EXPECT_THROW( this->LoadSymlink("/"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadFileFromRootDir_Load) { this->InitDirStructure(); auto node = this->Load("/myfile"); this->EXPECT_IS_FILE(node); } TYPED_TEST_P(FsppDeviceTest_One, LoadFileFromRootDir_LoadFile) { this->InitDirStructure(); this->LoadFile("/myfile"); } TYPED_TEST_P(FsppDeviceTest_One, LoadFileFromRootDir_LoadDir) { this->InitDirStructure(); EXPECT_THROW( this->LoadDir("/myfile"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadFileFromRootDir_LoadSymlink) { this->InitDirStructure(); EXPECT_THROW( this->LoadSymlink("/myfile"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadDirFromRootDir_Load) { this->InitDirStructure(); auto node = this->Load("/mydir"); this->EXPECT_IS_DIR(node); } TYPED_TEST_P(FsppDeviceTest_One, LoadDirFromRootDir_LoadDir) { this->InitDirStructure(); this->LoadDir("/mydir"); } TYPED_TEST_P(FsppDeviceTest_One, LoadDirFromRootDir_LoadFile) { this->InitDirStructure(); EXPECT_THROW( this->LoadFile("/mydir"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadDirFromRootDir_LoadSymlink) { this->InitDirStructure(); EXPECT_THROW( this->LoadSymlink("/mydir"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadSymlinkFromRootDir_Load) { this->InitDirStructure(); auto node = this->Load("/mysymlink"); this->EXPECT_IS_SYMLINK(node); } TYPED_TEST_P(FsppDeviceTest_One, LoadSymlinkFromRootDir_LoadSymlink) { this->InitDirStructure(); this->LoadSymlink("/mysymlink"); } TYPED_TEST_P(FsppDeviceTest_One, LoadSymlinkFromRootDir_LoadFile) { this->InitDirStructure(); EXPECT_THROW( this->LoadFile("/mysymlink"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadSymlinkFromRootDir_LoadDir) { this->InitDirStructure(); EXPECT_THROW( this->LoadDir("/mysymlink"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromEmptyRootDir_Load) { EXPECT_EQ(boost::none, this->device->Load("/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromEmptyRootDir_LoadDir) { EXPECT_EQ(boost::none, this->device->LoadDir("/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromEmptyRootDir_LoadFile) { EXPECT_EQ(boost::none, this->device->LoadFile("/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromEmptyRootDir_LoadSymlink) { EXPECT_EQ(boost::none, this->device->LoadSymlink("/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromRootDir_Load) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->Load("/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromRootDir_LoadDir) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->LoadDir("/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromRootDir_LoadFile) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->LoadFile("/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromRootDir_LoadSymlink) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->LoadSymlink("/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromNonexistingDir_Load) { this->InitDirStructure(); //TODO Change as soon as we have a concept of how to handle filesystem errors in the interface EXPECT_ANY_THROW( this->device->Load("/nonexisting/nonexisting2") ); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromNonexistingDir_LoadDir) { this->InitDirStructure(); //TODO Change as soon as we have a concept of how to handle filesystem errors in the interface EXPECT_ANY_THROW( this->device->LoadDir("/nonexisting/nonexisting2") ); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromNonexistingDir_LoadFile) { this->InitDirStructure(); //TODO Change as soon as we have a concept of how to handle filesystem errors in the interface EXPECT_ANY_THROW( this->device->LoadFile("/nonexisting/nonexisting2") ); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromNonexistingDir_LoadSymlink) { this->InitDirStructure(); //TODO Change as soon as we have a concept of how to handle filesystem errors in the interface EXPECT_ANY_THROW( this->device->LoadSymlink("/nonexisting/nonexisting2") ); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromExistingDir_Load) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->Load("/mydir/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromExistingDir_LoadDir) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->LoadDir("/mydir/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromExistingDir_LoadFile) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->LoadFile("/mydir/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromExistingDir_LoadSymlink) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->LoadSymlink("/mydir/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromExistingEmptyDir_Load) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->Load("/myemptydir/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromExistingEmptyDir_LoadDir) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->LoadDir("/myemptydir/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromExistingEmptyDir_LoadFile) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->LoadFile("/myemptydir/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadNonexistingFromExistingEmptyDir_LoadSymlink) { this->InitDirStructure(); EXPECT_EQ(boost::none, this->device->LoadSymlink("/myemptydir/nonexisting")); } TYPED_TEST_P(FsppDeviceTest_One, LoadFileFromDir_Nesting1_Load) { this->InitDirStructure(); auto node = this->Load("/mydir/myfile"); this->EXPECT_IS_FILE(node); } TYPED_TEST_P(FsppDeviceTest_One, LoadFileFromDir_Nesting1_LoadFile) { this->InitDirStructure(); this->LoadFile("/mydir/myfile"); } TYPED_TEST_P(FsppDeviceTest_One, LoadFileFromDir_Nesting1_LoadDir) { this->InitDirStructure(); EXPECT_THROW( this->LoadDir("/mydir/myfile"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadFileFromDir_Nesting1_LoadSymlink) { this->InitDirStructure(); EXPECT_THROW( this->LoadSymlink("/mydir/myfile"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadDirFromDir_Nesting1_Load) { this->InitDirStructure(); auto node = this->Load("/mydir/mysubdir"); this->EXPECT_IS_DIR(node); } TYPED_TEST_P(FsppDeviceTest_One, LoadDirFromDir_Nesting1_LoadDir) { this->InitDirStructure(); this->LoadDir("/mydir/mysubdir"); } TYPED_TEST_P(FsppDeviceTest_One, LoadDirFromDir_Nesting1_LoadFile) { this->InitDirStructure(); EXPECT_THROW( this->LoadFile("/mydir/mysubdir"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadDirFromDir_Nesting1_LoadSymlink) { this->InitDirStructure(); EXPECT_THROW( this->LoadSymlink("/mydir/mysubdir"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadSymlinkFromDir_Nesting1_Load) { this->InitDirStructure(); auto node = this->Load("/mydir/mysymlink"); this->EXPECT_IS_SYMLINK(node); } TYPED_TEST_P(FsppDeviceTest_One, LoadSymlinkFromDir_Nesting1_LoadSymlink) { this->InitDirStructure(); this->LoadSymlink("/mydir/mysymlink"); } TYPED_TEST_P(FsppDeviceTest_One, LoadSymlinkFromDir_Nesting1_LoadFile) { this->InitDirStructure(); EXPECT_THROW( this->LoadFile("/mydir/mysymlink"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_One, LoadSymlinkFromDir_Nesting1_LoadDir) { this->InitDirStructure(); EXPECT_THROW( this->LoadDir("/mydir/mysymlink"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_Two, LoadFileFromDir_Nesting2_Load) { this->InitDirStructure(); auto node = this->Load("/mydir/mysubdir/myfile"); this->EXPECT_IS_FILE(node); } TYPED_TEST_P(FsppDeviceTest_Two, LoadFileFromDir_Nesting2_LoadFile) { this->InitDirStructure(); this->LoadFile("/mydir/mysubdir/myfile"); } TYPED_TEST_P(FsppDeviceTest_Two, LoadFileFromDir_Nesting2_LoadDir) { this->InitDirStructure(); EXPECT_THROW( this->LoadDir("/mydir/mysubdir/myfile"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_Two, LoadFileFromDir_Nesting2_LoadSymlink) { this->InitDirStructure(); EXPECT_THROW( this->LoadSymlink("/mydir/mysubdir/myfile"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_Two, LoadDirFromDir_Nesting2_Load) { this->InitDirStructure(); auto node = this->Load("/mydir/mysubdir/mysubsubdir"); this->EXPECT_IS_DIR(node); } TYPED_TEST_P(FsppDeviceTest_Two, LoadDirFromDir_Nesting2_LoadDir) { this->InitDirStructure(); this->LoadDir("/mydir/mysubdir/mysubsubdir"); } TYPED_TEST_P(FsppDeviceTest_Two, LoadDirFromDir_Nesting2_LoadFile) { this->InitDirStructure(); EXPECT_THROW( this->LoadFile("/mydir/mysubdir/mysubsubdir"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_Two, LoadDirFromDir_Nesting2_LoadSymlink) { this->InitDirStructure(); EXPECT_THROW( this->LoadSymlink("/mydir/mysubdir/mysubsubdir"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_Two, LoadSymlinkFromDir_Nesting2_Load) { this->InitDirStructure(); auto node = this->Load("/mydir/mysubdir/mysymlink"); this->EXPECT_IS_SYMLINK(node); } TYPED_TEST_P(FsppDeviceTest_Two, LoadSymlinkFromDir_Nesting2_LoadSymlink) { this->InitDirStructure(); this->LoadSymlink("/mydir/mysubdir/mysymlink"); } TYPED_TEST_P(FsppDeviceTest_Two, LoadSymlinkFromDir_Nesting2_LoadFile) { this->InitDirStructure(); EXPECT_THROW( this->LoadFile("/mydir/mysubdir/mysymlink"), fspp::fuse::FuseErrnoException ); } TYPED_TEST_P(FsppDeviceTest_Two, LoadSymlinkFromDir_Nesting2_LoadDir) { this->InitDirStructure(); EXPECT_THROW( this->LoadDir("/mydir/mysubdir/mysymlink"), fspp::fuse::FuseErrnoException ); } //TODO Test statfs REGISTER_TYPED_TEST_CASE_P(FsppDeviceTest_One, InitFilesystem, LoadRootDir_Load, LoadRootDir_LoadDir, LoadRootDir_LoadFile, LoadRootDir_LoadSymlink, LoadFileFromRootDir_Load, LoadFileFromRootDir_LoadFile, LoadFileFromRootDir_LoadDir, LoadFileFromRootDir_LoadSymlink, LoadDirFromRootDir_Load, LoadDirFromRootDir_LoadDir, LoadDirFromRootDir_LoadFile, LoadDirFromRootDir_LoadSymlink, LoadSymlinkFromRootDir_Load, LoadSymlinkFromRootDir_LoadSymlink, LoadSymlinkFromRootDir_LoadFile, LoadSymlinkFromRootDir_LoadDir, LoadNonexistingFromEmptyRootDir_Load, LoadNonexistingFromEmptyRootDir_LoadDir, LoadNonexistingFromEmptyRootDir_LoadFile, LoadNonexistingFromEmptyRootDir_LoadSymlink, LoadNonexistingFromRootDir_Load, LoadNonexistingFromRootDir_LoadDir, LoadNonexistingFromRootDir_LoadFile, LoadNonexistingFromRootDir_LoadSymlink, LoadNonexistingFromNonexistingDir_Load, LoadNonexistingFromNonexistingDir_LoadDir, LoadNonexistingFromNonexistingDir_LoadFile, LoadNonexistingFromNonexistingDir_LoadSymlink, LoadNonexistingFromExistingDir_Load, LoadNonexistingFromExistingDir_LoadDir, LoadNonexistingFromExistingDir_LoadFile, LoadNonexistingFromExistingDir_LoadSymlink, LoadNonexistingFromExistingEmptyDir_Load, LoadNonexistingFromExistingEmptyDir_LoadDir, LoadNonexistingFromExistingEmptyDir_LoadFile, LoadNonexistingFromExistingEmptyDir_LoadSymlink, LoadFileFromDir_Nesting1_Load, LoadFileFromDir_Nesting1_LoadFile, LoadFileFromDir_Nesting1_LoadDir, LoadFileFromDir_Nesting1_LoadSymlink, LoadDirFromDir_Nesting1_Load, LoadDirFromDir_Nesting1_LoadDir, LoadDirFromDir_Nesting1_LoadFile, LoadDirFromDir_Nesting1_LoadSymlink, LoadSymlinkFromDir_Nesting1_Load, LoadSymlinkFromDir_Nesting1_LoadSymlink, LoadSymlinkFromDir_Nesting1_LoadFile, LoadSymlinkFromDir_Nesting1_LoadDir ); REGISTER_TYPED_TEST_CASE_P(FsppDeviceTest_Two, LoadFileFromDir_Nesting2_Load, LoadFileFromDir_Nesting2_LoadFile, LoadFileFromDir_Nesting2_LoadDir, LoadFileFromDir_Nesting2_LoadSymlink, LoadDirFromDir_Nesting2_Load, LoadDirFromDir_Nesting2_LoadDir, LoadDirFromDir_Nesting2_LoadFile, LoadDirFromDir_Nesting2_LoadSymlink, LoadSymlinkFromDir_Nesting2_Load, LoadSymlinkFromDir_Nesting2_LoadSymlink, LoadSymlinkFromDir_Nesting2_LoadFile, LoadSymlinkFromDir_Nesting2_LoadDir ); //TODO Missing tests: LoadSymlink #endif src/fspp/fstest/FsppDeviceTest_Timestamps.h000066400000000000000000000024401347701267100214040ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPDEVICETEST_TIMESTAMPS_H_ #define MESSMER_FSPP_FSTEST_FSPPDEVICETEST_TIMESTAMPS_H_ #include "testutils/TimestampTestUtils.h" template class FsppDeviceTest_Timestamps: public FsppNodeTest, public TimestampTestUtils { public: void Test_Load_While_Loaded() { auto node = this->CreateNode("/mynode"); auto operation = [this] () { this->device->Load("/mynode"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, {this->ExpectDoesntUpdateAnyTimestamps}); } void Test_Load_While_Not_Loaded() { fspp::Node::stat_info oldStat{}; { auto node = this->CreateNode("/mynode"); oldStat = this->stat(*node); this->ensureNodeTimestampsAreOld(oldStat); } this->device->Load("/myfile"); auto node = this->device->Load("/mynode"); //Test that timestamps didn't change fspp::Node::stat_info newStat = this->stat(*node.value()); EXPECT_EQ(oldStat.atime, newStat.atime); EXPECT_EQ(oldStat.mtime, newStat.mtime); EXPECT_EQ(oldStat.ctime, newStat.ctime); } }; REGISTER_NODE_TEST_CASE(FsppDeviceTest_Timestamps, Load_While_Loaded, Load_While_Not_Loaded ); #endif src/fspp/fstest/FsppDirTest.h000066400000000000000000000257011347701267100165220ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPDIRTEST_H_ #define MESSMER_FSPP_FSTEST_FSPPDIRTEST_H_ template class FsppDirTest: public FileSystemTest { public: void InitDirStructure() { this->LoadDir("/")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/")->createDir("myemptydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir")->createAndOpenFile("myfile2", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir")->createDir("mysubdir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir/mysubdir")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir/mysubdir")->createDir("mysubsubdir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); } void EXPECT_CHILDREN_ARE(const boost::filesystem::path &path, const std::initializer_list expected) { EXPECT_CHILDREN_ARE(this->LoadDir(path).get(), expected); } void EXPECT_CHILDREN_ARE(fspp::Dir *dir, const std::initializer_list expected) { std::vector expectedChildren = expected; expectedChildren.push_back(fspp::Dir::Entry(fspp::Dir::EntryType::DIR, ".")); expectedChildren.push_back(fspp::Dir::Entry(fspp::Dir::EntryType::DIR, "..")); EXPECT_UNORDERED_EQ(expectedChildren, *dir->children()); } template void EXPECT_UNORDERED_EQ(const std::vector &expected, std::vector actual) { EXPECT_EQ(expected.size(), actual.size()); for (const Entry &expectedEntry : expected) { removeOne(&actual, expectedEntry); } } template void removeOne(std::vector *entries, const Entry &toRemove) { for (auto iter = entries->begin(); iter != entries->end(); ++iter) { if (iter->type == toRemove.type && iter->name == toRemove.name) { entries->erase(iter); return; } } EXPECT_TRUE(false); } }; TYPED_TEST_CASE_P(FsppDirTest); inline fspp::Dir::Entry DirEntry(const std::string &name) { return fspp::Dir::Entry(fspp::Dir::EntryType::DIR, name); } inline fspp::Dir::Entry FileEntry(const std::string &name) { return fspp::Dir::Entry(fspp::Dir::EntryType::FILE, name); } TYPED_TEST_P(FsppDirTest, Children_RootDir_Empty) { this->EXPECT_CHILDREN_ARE("/", {}); } TYPED_TEST_P(FsppDirTest, Children_RootDir_OneFile_Directly) { auto rootdir = this->LoadDir("/"); rootdir->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE(rootdir.get(), { FileEntry("myfile") }); } TYPED_TEST_P(FsppDirTest, Children_RootDir_OneFile_AfterReloadingDir) { this->LoadDir("/")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE("/", { FileEntry("myfile") }); } TYPED_TEST_P(FsppDirTest, Children_RootDir_OneDir_Directly) { auto rootdir = this->LoadDir("/"); rootdir->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE(rootdir.get(), { DirEntry("mydir") }); } TYPED_TEST_P(FsppDirTest, Children_RootDir_OneDir_AfterReloadingDir) { this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE("/", { DirEntry("mydir") }); } TYPED_TEST_P(FsppDirTest, Children_RootDir_LargerStructure) { this->InitDirStructure(); this->EXPECT_CHILDREN_ARE("/", { FileEntry("myfile"), DirEntry("mydir"), DirEntry("myemptydir") }); } TYPED_TEST_P(FsppDirTest, Children_Nested_Empty) { this->LoadDir("/")->createDir("myemptydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE("/myemptydir", {}); } TYPED_TEST_P(FsppDirTest, Children_Nested_OneFile_Directly) { this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); auto dir = this->LoadDir("/mydir"); dir->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE(dir.get(), { FileEntry("myfile") }); } TYPED_TEST_P(FsppDirTest, Children_Nested_OneFile_AfterReloadingDir) { this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE("/mydir", { FileEntry("myfile") }); } TYPED_TEST_P(FsppDirTest, Children_Nested_OneDir_Directly) { this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); auto dir = this->LoadDir("/mydir"); dir->createDir("mysubdir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE(dir.get(), { DirEntry("mysubdir") }); } TYPED_TEST_P(FsppDirTest, Children_Nested_OneDir_AfterReloadingDir) { this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir")->createDir("mysubdir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE("/mydir", { DirEntry("mysubdir") }); } TYPED_TEST_P(FsppDirTest, Children_Nested_LargerStructure_Empty) { this->InitDirStructure(); this->EXPECT_CHILDREN_ARE("/myemptydir", {}); } TYPED_TEST_P(FsppDirTest, Children_Nested_LargerStructure) { this->InitDirStructure(); this->EXPECT_CHILDREN_ARE("/mydir", { FileEntry("myfile"), FileEntry("myfile2"), DirEntry("mysubdir") }); } TYPED_TEST_P(FsppDirTest, Children_Nested2_LargerStructure) { this->InitDirStructure(); this->EXPECT_CHILDREN_ARE("/mydir/mysubdir", { FileEntry("myfile"), DirEntry("mysubsubdir") }); } TYPED_TEST_P(FsppDirTest, CreateAndOpenFile_InEmptyRoot) { this->LoadDir("/")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadFile("/myfile"); this->Load("/myfile"); // Test that we can also load the file node } TYPED_TEST_P(FsppDirTest, CreateAndOpenFile_InNonemptyRoot) { this->InitDirStructure(); this->LoadDir("/")->createAndOpenFile("mynewfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE("/", { FileEntry("myfile"), DirEntry("mydir"), DirEntry("myemptydir"), FileEntry("mynewfile") }); } TYPED_TEST_P(FsppDirTest, CreateAndOpenFile_InEmptyNestedDir) { this->InitDirStructure(); this->LoadDir("/myemptydir")->createAndOpenFile("mynewfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE("/myemptydir", { FileEntry("mynewfile") }); } TYPED_TEST_P(FsppDirTest, CreateAndOpenFile_InNonemptyNestedDir) { this->InitDirStructure(); this->LoadDir("/mydir")->createAndOpenFile("mynewfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE("/mydir", { FileEntry("myfile"), FileEntry("myfile2"), DirEntry("mysubdir"), FileEntry("mynewfile") }); } TYPED_TEST_P(FsppDirTest, CreateAndOpenFile_AlreadyExisting) { this->LoadDir("/")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); //TODO Change, once we know which way of error reporting we want for such errors EXPECT_ANY_THROW( this->LoadDir("/")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); ); } TYPED_TEST_P(FsppDirTest, CreateDir_InEmptyRoot) { this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir"); this->Load("/mydir"); // Test we can also load the dir node } TYPED_TEST_P(FsppDirTest, CreateDir_InNonemptyRoot) { this->InitDirStructure(); this->LoadDir("/")->createDir("mynewdir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE("/", { FileEntry("myfile"), DirEntry("mydir"), DirEntry("myemptydir"), DirEntry("mynewdir") }); } TYPED_TEST_P(FsppDirTest, CreateDir_InEmptyNestedDir) { this->InitDirStructure(); this->LoadDir("/myemptydir")->createDir("mynewdir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE("/myemptydir", { DirEntry("mynewdir") }); } TYPED_TEST_P(FsppDirTest, CreateDir_InNonemptyNestedDir) { this->InitDirStructure(); this->LoadDir("/mydir")->createDir("mynewdir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->EXPECT_CHILDREN_ARE("/mydir", { FileEntry("myfile"), FileEntry("myfile2"), DirEntry("mysubdir"), DirEntry("mynewdir") }); } TYPED_TEST_P(FsppDirTest, CreateDir_AlreadyExisting) { this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); //TODO Change, once we know which way of error reporting we want for such errors EXPECT_ANY_THROW( this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); ); } TYPED_TEST_P(FsppDirTest, Remove) { this->CreateDir("/mytestdir"); EXPECT_NE(boost::none, this->device->Load("/mytestdir")); EXPECT_NE(boost::none, this->device->LoadDir("/mytestdir")); this->Load("/mytestdir")->remove(); EXPECT_EQ(boost::none, this->device->Load("/mytestdir")); EXPECT_EQ(boost::none, this->device->LoadDir("/mytestdir")); } TYPED_TEST_P(FsppDirTest, Remove_Nested) { this->CreateDir("/mytestdir"); this->CreateDir("/mytestdir/mydir"); EXPECT_NE(boost::none, this->device->Load("/mytestdir/mydir")); EXPECT_NE(boost::none, this->device->LoadDir("/mytestdir/mydir")); this->Load("/mytestdir/mydir")->remove(); EXPECT_EQ(boost::none, this->device->Load("/mytestdir/mydir")); EXPECT_EQ(boost::none, this->device->LoadDir("/mytestdir/mydir")); } REGISTER_TYPED_TEST_CASE_P(FsppDirTest, Children_RootDir_Empty, Children_RootDir_OneFile_Directly, Children_RootDir_OneFile_AfterReloadingDir, Children_RootDir_OneDir_Directly, Children_RootDir_OneDir_AfterReloadingDir, Children_RootDir_LargerStructure, Children_Nested_Empty, Children_Nested_OneFile_Directly, Children_Nested_OneFile_AfterReloadingDir, Children_Nested_OneDir_Directly, Children_Nested_OneDir_AfterReloadingDir, Children_Nested_LargerStructure, Children_Nested_LargerStructure_Empty, Children_Nested2_LargerStructure, CreateAndOpenFile_InEmptyRoot, CreateAndOpenFile_InNonemptyRoot, CreateAndOpenFile_InEmptyNestedDir, CreateAndOpenFile_InNonemptyNestedDir, CreateAndOpenFile_AlreadyExisting, CreateDir_InEmptyRoot, CreateDir_InNonemptyRoot, CreateDir_InEmptyNestedDir, CreateDir_InNonemptyNestedDir, CreateDir_AlreadyExisting, Remove, Remove_Nested ); //TODO rmdir (also test that deleting a non-empty dir returns ENOTEMPTY, because otherwise there might not be any unlink syscalls for the entries issued) //TODO mkdir with uid/gid //TODO createAndOpenFile: all stat values correctly set (1. in the OpenFile instance returned from createAndOpenFile and 2. on an lstat on the file object afterwards) //TODO Test all operations do (or don't) affect dir timestamps correctly #endif src/fspp/fstest/FsppDirTest_Timestamps.h000066400000000000000000000266521347701267100207360ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPDIRTEST_TIMESTAMPS_H_ #define MESSMER_FSPP_FSTEST_FSPPDIRTEST_TIMESTAMPS_H_ #include "testutils/TimestampTestUtils.h" template class FsppDirTest_Timestamps: public TimestampTestUtils { public: }; TYPED_TEST_CASE_P(FsppDirTest_Timestamps); TYPED_TEST_P(FsppDirTest_Timestamps, createAndOpenFile) { auto dir = this->CreateDir("/mydir"); auto operation = [&dir] () { dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } /* TODO Re-enable this test once the root dir handles timestamps correctly TYPED_TEST_P(FsppDirTest_Timestamps, createAndOpenFile_inRootDir) { auto dir = this->LoadDir("/"); auto operation = [&dir] () { dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }*/ TYPED_TEST_P(FsppDirTest_Timestamps, createAndOpenFile_TimestampsOfCreatedFile) { auto dir = this->CreateDir("/mydir"); timespec lowerBound = now(); dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); timespec upperBound = now(); auto child = this->Load("/mydir/childname"); this->EXPECT_ACCESS_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *child); } TYPED_TEST_P(FsppDirTest_Timestamps, createDir) { auto dir = this->CreateDir("/mydir"); auto operation = [&dir] () { dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } /* TODO Re-enable this test once the root dir handles timestamps correctly TYPED_TEST_P(FsppDirTest_Timestamps, createDir_inRootDir) { auto dir = this->LoadDir("/"); auto operation = [&dir] () { dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }*/ TYPED_TEST_P(FsppDirTest_Timestamps, createDir_TimestampsOfCreatedDir) { auto dir = this->CreateDir("/mydir"); timespec lowerBound = now(); dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); timespec upperBound = now(); auto child = this->Load("/mydir/childname"); this->EXPECT_ACCESS_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *child); } TYPED_TEST_P(FsppDirTest_Timestamps, createSymlink) { auto dir = this->CreateDir("/mydir"); auto operation = [&dir] () { dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } /* TODO Re-enable this test once the root dir handles timestamps correctly TYPED_TEST_P(FsppDirTest_Timestamps, createSymlink_inRootDir) { auto dir = this->LoadDir("/"); auto operation = [&dir] () { dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }*/ TYPED_TEST_P(FsppDirTest_Timestamps, createSymlink_TimestampsOfCreatedSymlink) { auto dir = this->CreateDir("/mydir"); timespec lowerBound = now(); dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); timespec upperBound = now(); auto child = this->Load("/mydir/childname"); this->EXPECT_ACCESS_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN (lowerBound, upperBound, *child); this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *child); } TYPED_TEST_P(FsppDirTest_Timestamps, children_empty) { auto dir = this->CreateDir("/mydir"); this->setModificationTimestampLaterThanAccessTimestamp("/mydir"); // to make sure that even in relatime behavior, the read access below changes the access timestamp auto operation = [&dir] () { dir->children(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); } /* TODO Re-enable this test once the root dir handles timestamps correctly TYPED_TEST_P(FsppDirTest_Timestamps, children_empty_inRootDir) { auto dir = this->LoadDir("/"); auto operation = [&dir] () { dir->children(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }*/ TYPED_TEST_P(FsppDirTest_Timestamps, children_nonempty) { auto dir = this->CreateDir("/mydir"); dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); auto operation = [&dir] () { dir->children(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); } /* TODO Re-enable this test once the root dir handles timestamps correctly TYPED_TEST_P(FsppDirTest_Timestamps, children_nonempty_inRootDir) { auto dir = this->LoadDir("/"); dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); auto operation = [&dir] () { dir->children(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }*/ template class FsppDirTest_Timestamps_Entries: public FsppNodeTest, public TimestampTestUtils { public: void Test_deleteChild() { auto dir = this->CreateDir("/mydir"); auto child = this->CreateNode("/mydir/childname"); auto operation = [&child]() { child->remove(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } /* TODO Re-enable this test once the root dir handles timestamps correctly void Test_deleteChild_inRootDir() { auto dir = this->LoadDir("/"); auto child = this->CreateNode("/childname"); auto operation = [&child] () { child->remove(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }*/ void Test_renameChild() { auto dir = this->CreateDir("/mydir"); auto child = this->CreateNode("/mydir/childname"); auto operation = [&child]() { child->rename("/mydir/mychild"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } /* TODO Re-enable this test once the root dir handles timestamps correctly void Test_renameChild_inRootDir() { auto dir = this->LoadDir("/"); auto child = this->CreateNode("/childname"); auto operation = [&child] () { child->rename("/mydir/mychild"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }*/ void Test_moveChildIn() { auto sourceDir = this->CreateDir("/sourcedir"); auto child = this->CreateNode("/sourcedir/childname"); auto dir = this->CreateDir("/mydir"); auto operation = [&child]() { child->rename("/mydir/mychild"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } /* TODO Re-enable this test once the root dir handles timestamps correctly void Test_moveChildIn_inRootDir() { auto sourceDir = this->CreateDir("/sourcedir"); auto child = this->CreateNode("/sourcedir/childname"); auto dir = this->LoadDir("/"); auto operation = [&child] () { child->rename("/mychild"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }*/ void Test_moveChildOut() { auto dir = this->CreateDir("/mydir"); auto child = this->CreateNode("/mydir/childname"); this->CreateDir("/targetdir"); auto operation = [&child]() { child->rename("/targetdir/mychild"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } /* TODO Re-enable this test once the root dir handles timestamps correctly void Test_moveChildOut_inRootDir() { auto dir = this->LoadDir("/"); auto child = this->CreateNode("/childname"); this->CreateDir("/targetdir"); auto operation = [&child] () { child->rename("/targetdir/mychild"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }*/ }; REGISTER_TYPED_TEST_CASE_P(FsppDirTest_Timestamps, createAndOpenFile, createAndOpenFile_TimestampsOfCreatedFile, createDir, createDir_TimestampsOfCreatedDir, createSymlink, createSymlink_TimestampsOfCreatedSymlink, children_empty, children_nonempty ); REGISTER_NODE_TEST_CASE(FsppDirTest_Timestamps_Entries, deleteChild, renameChild, moveChildIn, moveChildOut ); #endif src/fspp/fstest/FsppFileTest.h000066400000000000000000000177601347701267100166710ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPFILETEST_H_ #define MESSMER_FSPP_FSTEST_FSPPFILETEST_H_ #include #include "testutils/FileTest.h" //TODO Restructure. FsppFileTest tests fspp::File interface. All tests for fspp::Node interface go to a FsppNodeTest. template class FsppFileTest: public FileTest { public: void Test_Open_RDONLY(fspp::File *file) { file->open(fspp::openflags_t::RDONLY()); } void Test_Open_WRONLY(fspp::File *file) { file->open(fspp::openflags_t::WRONLY()); } void Test_Open_RDWR(fspp::File *file) { file->open(fspp::openflags_t::RDONLY()); } void Test_Truncate_DontChange1(fspp::File *file, fspp::Node *node) { file->truncate(fspp::num_bytes_t(0)); this->EXPECT_SIZE(fspp::num_bytes_t(0), file, node); } void Test_Truncate_GrowTo1(fspp::File *file, fspp::Node *node) { file->truncate(fspp::num_bytes_t(1)); this->EXPECT_SIZE(fspp::num_bytes_t(1), file, node); } void Test_Truncate_Grow(fspp::File *file, fspp::Node *node) { file->truncate(fspp::num_bytes_t(10*1024*1024)); this->EXPECT_SIZE(fspp::num_bytes_t(10*1024*1024), file, node); } void Test_Truncate_DontChange2(fspp::File *file, fspp::Node *node) { file->truncate(fspp::num_bytes_t(10*1024*1024)); file->truncate(fspp::num_bytes_t(10*1024*1024)); this->EXPECT_SIZE(fspp::num_bytes_t(10*1024*1024), file, node); } void Test_Truncate_Shrink(fspp::File *file, fspp::Node *node) { file->truncate(fspp::num_bytes_t(10*1024*1024)); file->truncate(fspp::num_bytes_t(5*1024*1024)); this->EXPECT_SIZE(fspp::num_bytes_t(5*1024*1024), file, node); } void Test_Truncate_ShrinkTo0(fspp::File *file, fspp::Node *node) { file->truncate(fspp::num_bytes_t(10*1024*1024)); file->truncate(fspp::num_bytes_t(0)); this->EXPECT_SIZE(fspp::num_bytes_t(0), file, node); } void Test_Chown_Uid(fspp::File *file, fspp::Node *node) { node->chown(fspp::uid_t(100), fspp::gid_t(200)); this->IN_STAT(file, node, [] (const fspp::Node::stat_info& st){ EXPECT_EQ(fspp::uid_t(100u), st.uid); }); } void Test_Chown_Gid(fspp::File *file, fspp::Node *node) { node->chown(fspp::uid_t(100), fspp::gid_t(200)); this->IN_STAT(file, node, [] (const fspp::Node::stat_info& st){ EXPECT_EQ(fspp::gid_t(200u), st.gid); }); } void Test_Chmod(fspp::File *file, fspp::Node *node) { constexpr auto mode = fspp::mode_t().addFileFlag().addUserReadFlag().addOtherWriteFlag(); node->chmod(mode); this->IN_STAT(file, node, [mode] (const fspp::Node::stat_info& st){ EXPECT_EQ(mode, st.mode); }); } void Test_Utimens(fspp::File *file, fspp::Node *node) { struct timespec ATIME{}; ATIME.tv_sec = 1458086400; ATIME.tv_nsec = 34525; struct timespec MTIME{}; MTIME.tv_sec = 1458086300; MTIME.tv_nsec = 48293; node->utimens(ATIME, MTIME); this->IN_STAT(file, node, [this, ATIME, MTIME] (const fspp::Node::stat_info& st) { this->EXPECT_ATIME_EQ(ATIME, st); this->EXPECT_MTIME_EQ(MTIME, st); }); } }; TYPED_TEST_CASE_P(FsppFileTest); TYPED_TEST_P(FsppFileTest, Open_RDONLY) { this->Test_Open_RDONLY(this->file_root.get()); } TYPED_TEST_P(FsppFileTest, Open_RDONLY_Nested) { this->Test_Open_RDONLY(this->file_nested.get()); } TYPED_TEST_P(FsppFileTest, Open_WRONLY) { this->Test_Open_WRONLY(this->file_root.get()); } TYPED_TEST_P(FsppFileTest, Open_WRONLY_Nested) { this->Test_Open_WRONLY(this->file_nested.get()); } TYPED_TEST_P(FsppFileTest, Open_RDWR) { this->Test_Open_RDWR(this->file_root.get()); } TYPED_TEST_P(FsppFileTest, Open_RDWR_Nested) { this->Test_Open_RDWR(this->file_nested.get()); } TYPED_TEST_P(FsppFileTest, Truncate_DontChange1) { this->Test_Truncate_DontChange1(this->file_root.get(), this->file_root_node.get()); } TYPED_TEST_P(FsppFileTest, Truncate_DontChange1_Nested) { this->Test_Truncate_DontChange1(this->file_nested.get(), this->file_nested_node.get()); } TYPED_TEST_P(FsppFileTest, Truncate_GrowTo1) { this->Test_Truncate_GrowTo1(this->file_root.get(), this->file_root_node.get()); } TYPED_TEST_P(FsppFileTest, Truncate_GrowTo1_Nested) { this->Test_Truncate_GrowTo1(this->file_nested.get(), this->file_nested_node.get()); } TYPED_TEST_P(FsppFileTest, Truncate_Grow) { this->Test_Truncate_Grow(this->file_root.get(), this->file_root_node.get()); } TYPED_TEST_P(FsppFileTest, Truncate_Grow_Nested) { this->Test_Truncate_Grow(this->file_nested.get(), this->file_nested_node.get()); } TYPED_TEST_P(FsppFileTest, Truncate_DontChange2) { this->Test_Truncate_DontChange2(this->file_root.get(), this->file_root_node.get()); } TYPED_TEST_P(FsppFileTest, Truncate_DontChange2_Nested) { this->Test_Truncate_DontChange2(this->file_nested.get(), this->file_nested_node.get()); } TYPED_TEST_P(FsppFileTest, Truncate_Shrink) { this->Test_Truncate_Shrink(this->file_root.get(), this->file_root_node.get()); } TYPED_TEST_P(FsppFileTest, Truncate_Shrink_Nested) { this->Test_Truncate_Shrink(this->file_nested.get(), this->file_nested_node.get()); } TYPED_TEST_P(FsppFileTest, Truncate_ShrinkTo0) { this->Test_Truncate_ShrinkTo0(this->file_root.get(), this->file_root_node.get()); } TYPED_TEST_P(FsppFileTest, Truncate_ShrinkTo0_Nested) { this->Test_Truncate_ShrinkTo0(this->file_nested.get(), this->file_nested_node.get()); } TYPED_TEST_P(FsppFileTest, Chown_Uid) { this->Test_Chown_Uid(this->file_root.get(), this->file_root_node.get()); } TYPED_TEST_P(FsppFileTest, Chown_Uid_Nested) { this->Test_Chown_Uid(this->file_nested.get(), this->file_nested_node.get()); } TYPED_TEST_P(FsppFileTest, Chown_Gid) { this->Test_Chown_Gid(this->file_root.get(), this->file_root_node.get()); } TYPED_TEST_P(FsppFileTest, Chown_Gid_Nested) { this->Test_Chown_Gid(this->file_nested.get(), this->file_nested_node.get()); } TYPED_TEST_P(FsppFileTest, Chmod) { this->Test_Chmod(this->file_root.get(), this->file_root_node.get()); } TYPED_TEST_P(FsppFileTest, Chmod_Nested) { this->Test_Chmod(this->file_nested.get(), this->file_nested_node.get()); } TYPED_TEST_P(FsppFileTest, Utimens) { this->Test_Utimens(this->file_root.get(), this->file_root_node.get()); } TYPED_TEST_P(FsppFileTest, Utimens_Nested) { this->Test_Utimens(this->file_nested.get(), this->file_nested_node.get()); } TYPED_TEST_P(FsppFileTest, Remove) { this->CreateFile("/mytestfile"); EXPECT_NE(boost::none, this->device->Load("/mytestfile")); EXPECT_NE(boost::none, this->device->LoadFile("/mytestfile")); this->Load("/mytestfile")->remove(); EXPECT_EQ(boost::none, this->device->Load("/mytestfile")); EXPECT_EQ(boost::none, this->device->LoadFile("/mytestfile")); } TYPED_TEST_P(FsppFileTest, Remove_Nested) { this->CreateDir("/mytestdir"); this->CreateFile("/mytestdir/myfile"); EXPECT_NE(boost::none, this->device->Load("/mytestdir/myfile")); EXPECT_NE(boost::none, this->device->LoadFile("/mytestdir/myfile")); this->Load("/mytestdir/myfile")->remove(); EXPECT_EQ(boost::none, this->device->Load("/mytestdir/myfile")); EXPECT_EQ(boost::none, this->device->LoadFile("/mytestdir/myfile")); } REGISTER_TYPED_TEST_CASE_P(FsppFileTest, Open_RDONLY, Open_RDONLY_Nested, Open_WRONLY, Open_WRONLY_Nested, Open_RDWR, Open_RDWR_Nested, Truncate_DontChange1, Truncate_DontChange1_Nested, Truncate_GrowTo1, Truncate_GrowTo1_Nested, Truncate_Grow, Truncate_Grow_Nested, Truncate_DontChange2, Truncate_DontChange2_Nested, Truncate_Shrink, Truncate_Shrink_Nested, Truncate_ShrinkTo0, Truncate_ShrinkTo0_Nested, Chown_Uid, Chown_Uid_Nested, Chown_Gid, Chown_Gid_Nested, Chmod, Chmod_Nested, Utimens, Utimens_Nested, Remove, Remove_Nested ); //TODO access //TODO unlink //TODO Test all operations do (or don't) affect file timestamps correctly (including rename, which shouldn't modify access/modify time, but inode change time) //TODO Move applicable test cases to new instances of FsppNodeTest (like FsppNodeTest_Rename) (e.g. utimens, chmod, ...) //TODO access //TODO utimens //TODO chmod //TODO chown #endif src/fspp/fstest/FsppFileTest_Timestamps.h000066400000000000000000000103241347701267100210640ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPFILETEST_TIMESTAMPS_H_ #define MESSMER_FSPP_FSTEST_FSPPFILETEST_TIMESTAMPS_H_ #include "testutils/TimestampTestUtils.h" template class FsppFileTest_Timestamps: public TimestampTestUtils { public: cpputils::unique_ref CreateFileWithSize(const boost::filesystem::path &path, fspp::num_bytes_t size) { auto file = this->CreateFile(path); file->truncate(size); assert(this->stat(*this->Load(path)).size == size); return file; } }; TYPED_TEST_CASE_P(FsppFileTest_Timestamps); TYPED_TEST_P(FsppFileTest_Timestamps, open_nomode) { auto file = this->CreateFile("/myfile"); auto operation = [&file] () { file->open(fspp::openflags_t(0)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAnyTimestamps}); } TYPED_TEST_P(FsppFileTest_Timestamps, open_rdonly) { auto file = this->CreateFile("/myfile"); auto operation = [&file] () { file->open(fspp::openflags_t::RDONLY()); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAnyTimestamps}); } TYPED_TEST_P(FsppFileTest_Timestamps, open_wronly) { auto file = this->CreateFile("/myfile"); auto operation = [&file] () { file->open(fspp::openflags_t::WRONLY()); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAnyTimestamps}); } TYPED_TEST_P(FsppFileTest_Timestamps, open_rdwr) { auto file = this->CreateFile("/myfile"); auto operation = [&file] () { file->open(fspp::openflags_t::RDWR()); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAnyTimestamps}); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_empty_to_empty) { auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(0)); auto operation = [&file] () { file->truncate(fspp::num_bytes_t(0)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_empty_to_nonempty) { auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(0)); auto operation = [&file] () { file->truncate(fspp::num_bytes_t(10)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_nonempty_to_empty) { auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); auto operation = [&file] () { file->truncate(fspp::num_bytes_t(0)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_nonempty_to_nonempty_shrink) { auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); auto operation = [&file] () { file->truncate(fspp::num_bytes_t(5)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_nonempty_to_nonempty_grow) { auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); auto operation = [&file] () { file->truncate(fspp::num_bytes_t(20)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } REGISTER_TYPED_TEST_CASE_P(FsppFileTest_Timestamps, open_nomode, open_rdonly, open_wronly, open_rdwr, truncate_empty_to_empty, truncate_empty_to_nonempty, truncate_nonempty_to_empty, truncate_nonempty_to_nonempty_shrink, truncate_nonempty_to_nonempty_grow ); #endif src/fspp/fstest/FsppNodeTest_Rename.h000066400000000000000000000217601347701267100201610ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPNODETEST_RENAME_H_ #define MESSMER_FSPP_FSTEST_FSPPNODETEST_RENAME_H_ #include "testutils/FsppNodeTest.h" #include "../fs_interface/FuseErrnoException.h" template class FsppNodeTest_Rename: public FsppNodeTest { public: void Test_Error_TargetParentDirDoesntExist() { auto node = this->CreateNode("/oldname"); try { node->rename("/notexistingdir/newname"); EXPECT_TRUE(false); // Expect it throws an exception } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(ENOENT, e.getErrno()); } //Old node should still exist EXPECT_NE(boost::none, this->device->Load("/oldname")); } void Test_Error_TargetParentDirIsFile() { this->CreateNode("/oldname"); this->CreateFile("/somefile"); try { this->Load("/somefile")->rename("/somefile/newname"); EXPECT_TRUE(false); // Expect it throws an exception } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(ENOTDIR, e.getErrno()); } //Nodes should still exist EXPECT_NE(boost::none, this->device->Load("/oldname")); EXPECT_NE(boost::none, this->device->Load("/somefile")); } void Test_Error_RootDir() { auto rootDirNode = this->Load("/"); try { rootDirNode->rename("/newname"); EXPECT_TRUE(false); // expect throws } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(EBUSY, e.getErrno()); } } void Test_InRoot() { auto node = this->CreateNode("/oldname"); node->rename("/newname"); EXPECT_EQ(boost::none, this->device->Load("/oldname")); EXPECT_NE(boost::none, this->device->Load("/newname")); } void Test_InNested() { this->CreateDir("/mydir"); auto node = this->CreateNode("/mydir/oldname"); node->rename("/mydir/newname"); EXPECT_EQ(boost::none, this->device->Load("/mydir/oldname")); EXPECT_NE(boost::none, this->device->Load("/mydir/newname")); } void Test_RootToNested_SameName() { this->CreateDir("/mydir"); auto node = this->CreateNode("/oldname"); node->rename("/mydir/oldname"); EXPECT_EQ(boost::none, this->device->Load("/oldname")); EXPECT_NE(boost::none, this->device->Load("/mydir/oldname")); } void Test_RootToNested_NewName() { this->CreateDir("/mydir"); auto node = this->CreateNode("/oldname"); node->rename("/mydir/newname"); EXPECT_EQ(boost::none, this->device->Load("/oldname")); EXPECT_NE(boost::none, this->device->Load("/mydir/newname")); } void Test_NestedToRoot_SameName() { this->CreateDir("/mydir"); auto node = this->CreateNode("/mydir/oldname"); node->rename("/oldname"); EXPECT_EQ(boost::none, this->device->Load("/mydir/oldname")); EXPECT_NE(boost::none, this->device->Load("/oldname")); } void Test_NestedToRoot_NewName() { this->CreateDir("/mydir"); auto node = this->CreateNode("/mydir/oldname"); node->rename("/newname"); EXPECT_EQ(boost::none, this->device->Load("/mydir/oldname")); EXPECT_NE(boost::none, this->device->Load("/newname")); } void Test_NestedToNested_SameName() { this->CreateDir("/mydir"); this->CreateDir("/mydir2"); auto node = this->CreateNode("/mydir/oldname"); node->rename("/mydir2/oldname"); EXPECT_EQ(boost::none, this->device->Load("/mydir/oldname")); EXPECT_NE(boost::none, this->device->Load("/mydir2/oldname")); } void Test_NestedToNested_NewName() { this->CreateDir("/mydir"); this->CreateDir("/mydir2"); auto node = this->CreateNode("/mydir/oldname"); node->rename("/mydir2/newname"); EXPECT_EQ(boost::none, this->device->Load("/mydir/oldname")); EXPECT_NE(boost::none, this->device->Load("/mydir2/newname")); } void Test_ToItself() { auto node = this->CreateNode("/oldname"); node->rename("/oldname"); EXPECT_NE(boost::none, this->device->Load("/oldname")); } void Test_Overwrite_InSameDir() { auto node = this->CreateNode("/oldname"); this->CreateNode("/newname"); node->rename("/newname"); EXPECT_EQ(boost::none, this->device->Load("/oldname")); EXPECT_NE(boost::none, this->device->Load("/newname")); } void Test_Overwrite_InDifferentDir() { this->CreateDir("/parent1"); this->CreateDir("/parent2"); auto node = this->CreateNode("/parent1/oldname"); this->CreateNode("/parent2/newname"); node->rename("/parent2/newname"); EXPECT_EQ(boost::none, this->device->Load("/parent1/oldname")); EXPECT_NE(boost::none, this->device->Load("/parent2/newname")); } void Test_Overwrite_DoesntHaveSameEntryTwice() { auto node = this->CreateNode("/oldname"); this->CreateNode("/newname"); EXPECT_EQ(4u, this->LoadDir("/")->children()->size()); // 4, because of '.' and '..' node->rename("/newname"); EXPECT_EQ(3u, this->LoadDir("/")->children()->size()); // 3, because of '.' and '..' } void Test_Overwrite_Error_DirWithFile_InSameDir() { this->CreateFile("/oldname"); this->CreateDir("/newname"); try { this->Load("/oldname")->rename("/newname"); EXPECT_TRUE(false); // expect throw } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(EISDIR, e.getErrno()); } EXPECT_NE(boost::none, this->device->Load("/oldname")); EXPECT_NE(boost::none, this->device->Load("/newname")); } void Test_Overwrite_Error_DirWithFile_InDifferentDir() { this->CreateDir("/parent1"); this->CreateDir("/parent2"); this->CreateFile("/parent1/oldname"); this->CreateDir("/parent2/newname"); try { this->Load("/parent1/oldname")->rename("/parent2/newname"); EXPECT_TRUE(false); // expect throw } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(EISDIR, e.getErrno()); } EXPECT_NE(boost::none, this->device->Load("/parent1/oldname")); EXPECT_NE(boost::none, this->device->Load("/parent2/newname")); } void Test_Overwrite_Error_FileWithDir_InSameDir() { this->CreateDir("/oldname"); this->CreateFile("/newname"); try { this->Load("/oldname")->rename("/newname"); EXPECT_TRUE(false); // expect throw } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(ENOTDIR, e.getErrno()); } EXPECT_NE(boost::none, this->device->Load("/oldname")); EXPECT_NE(boost::none, this->device->Load("/newname")); } void Test_Overwrite_Error_FileWithDir_InDifferentDir() { this->CreateDir("/parent1"); this->CreateDir("/parent2"); this->CreateDir("/parent1/oldname"); this->CreateFile("/parent2/newname"); try { this->Load("/parent1/oldname")->rename("/parent2/newname"); EXPECT_TRUE(false); // expect throw } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(ENOTDIR, e.getErrno()); } EXPECT_NE(boost::none, this->device->Load("/parent1/oldname")); EXPECT_NE(boost::none, this->device->Load("/parent2/newname")); } void Test_CanRenameTwice() { // Test that the node object stays valid after a rename, even if it now points to an entry of a different parent directory. this->CreateDir("/mydir1"); this->CreateDir("/mydir2"); auto node = this->CreateNode("/oldname"); node->rename("/mydir1/newname"); node->rename("/mydir2/newname"); EXPECT_EQ(boost::none, this->device->Load("/oldname")); EXPECT_EQ(boost::none, this->device->Load("/mydir1/newname")); EXPECT_NE(boost::none, this->device->Load("/mydir2/newname")); } }; REGISTER_NODE_TEST_CASE(FsppNodeTest_Rename, Error_TargetParentDirDoesntExist, Error_TargetParentDirIsFile, Error_RootDir, InRoot, InNested, RootToNested_SameName, RootToNested_NewName, NestedToRoot_SameName, NestedToRoot_NewName, NestedToNested_SameName, NestedToNested_NewName, ToItself, Overwrite_InSameDir, Overwrite_InDifferentDir, Overwrite_DoesntHaveSameEntryTwice, Overwrite_Error_DirWithFile_InSameDir, Overwrite_Error_DirWithFile_InDifferentDir, Overwrite_Error_FileWithDir_InSameDir, Overwrite_Error_FileWithDir_InDifferentDir, CanRenameTwice ); #endif //TODO Test for rename (success AND error cases) that stat values stay unchanged (i.e. mode, uid, gid, access times, ...) //TODO Test for rename (success AND error cases) that contents stay unchanged (i.e. file contents, directory children, symlink target) //TODO (here and in other fstest operations): Test error paths src/fspp/fstest/FsppNodeTest_Stat.h000066400000000000000000000051021347701267100176550ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPNODETEST_STAT_H_ #define MESSMER_FSPP_FSTEST_FSPPNODETEST_STAT_H_ #include "testutils/FsppNodeTest.h" #include "../fs_interface/FuseErrnoException.h" template class FsppNodeTest_Stat: public FsppNodeTest { public: void Test_Nlink() { this->CreateNode("/mynode"); auto node = this->Load("/mynode"); this->IN_STAT(node.get(), [] (const fspp::Node::stat_info& st) { EXPECT_EQ(1u, st.nlink); }); } }; // Test cases only run for file nodes template class FsppNodeTest_Stat_FileOnly: public FileSystemTest, public FsppNodeTestHelper {}; TYPED_TEST_CASE_P(FsppNodeTest_Stat_FileOnly); TYPED_TEST_P(FsppNodeTest_Stat_FileOnly, CreatedFileIsEmpty) { this->CreateFile("/myfile"); auto node = this->Load("/myfile"); this->EXPECT_SIZE(fspp::num_bytes_t(0), node.get()); } TYPED_TEST_P(FsppNodeTest_Stat_FileOnly, FileIsFile) { this->CreateFile("/myfile"); auto node = this->Load("/myfile"); this->IN_STAT(node.get(), [] (const fspp::Node::stat_info& st) { EXPECT_TRUE(st.mode.hasFileFlag()); }); } // Test cases only run for dir nodes template class FsppNodeTest_Stat_DirOnly: public FileSystemTest, public FsppNodeTestHelper {}; TYPED_TEST_CASE_P(FsppNodeTest_Stat_DirOnly); TYPED_TEST_P(FsppNodeTest_Stat_DirOnly, DirIsDir) { this->CreateDir("/mydir"); auto node = this->Load("/mydir"); this->IN_STAT(node.get(), [] (const fspp::Node::stat_info& st) { EXPECT_TRUE(st.mode.hasDirFlag()); }); } // Test cases only run for symlink nodes template class FsppNodeTest_Stat_SymlinkOnly: public FileSystemTest, public FsppNodeTestHelper {}; TYPED_TEST_CASE_P(FsppNodeTest_Stat_SymlinkOnly); TYPED_TEST_P(FsppNodeTest_Stat_SymlinkOnly, SymlinkIsSymlink) { this->CreateSymlink("/mysymlink"); auto node = this->Load("/mysymlink"); this->IN_STAT(node.get(), [] (const fspp::Node::stat_info& st) { EXPECT_TRUE(st.mode.hasSymlinkFlag()); }); } REGISTER_NODE_TEST_CASE(FsppNodeTest_Stat, Nlink ); REGISTER_TYPED_TEST_CASE_P(FsppNodeTest_Stat_FileOnly, CreatedFileIsEmpty, FileIsFile ); REGISTER_TYPED_TEST_CASE_P(FsppNodeTest_Stat_DirOnly, DirIsDir ); REGISTER_TYPED_TEST_CASE_P(FsppNodeTest_Stat_SymlinkOnly, SymlinkIsSymlink ); #endif //TODO More test cases src/fspp/fstest/FsppNodeTest_Timestamps.h000066400000000000000000000335361347701267100211040ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPNODETEST_TIMESTAMPS_H_ #define MESSMER_FSPP_FSTEST_FSPPNODETEST_TIMESTAMPS_H_ #include "testutils/FsppNodeTest.h" #include "../fs_interface/FuseErrnoException.h" #include "testutils/TimestampTestUtils.h" #include using namespace cpputils::time; using std::function; template class FsppNodeTest_Timestamps: public FsppNodeTest, public TimestampTestUtils { public: void Test_Create() { timespec lowerBound = now(); auto node = this->CreateNode("/mynode"); timespec upperBound = now(); this->EXPECT_ACCESS_TIMESTAMP_BETWEEN (lowerBound, upperBound, *node); this->EXPECT_MODIFICATION_TIMESTAMP_BETWEEN (lowerBound, upperBound, *node); this->EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(lowerBound, upperBound, *node); } void Test_Stat() { auto node = this->CreateNode("/mynode"); auto operation = [&node] () { node->stat(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { this->ExpectDoesntUpdateAnyTimestamps }); } void Test_Chmod() { auto node = this->CreateNode("/mynode"); fspp::mode_t mode = this->stat(*node).mode; auto operation = [&node, mode] () { node->chmod(mode); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Chown() { auto node = this->CreateNode("/mynode"); fspp::uid_t uid = this->stat(*node).uid; fspp::gid_t gid = this->stat(*node).gid; auto operation = [&node, uid, gid] () { node->chown(uid, gid); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Access() { auto node = this->CreateNode("/mynode"); auto operation = [&node] () { node->access(0); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { this->ExpectDoesntUpdateAnyTimestamps }); } void Test_Rename_Error_TargetParentDirDoesntExist() { auto node = this->CreateNode("/oldname"); auto operation = [&node] () { try { node->rename("/notexistingdir/newname"); EXPECT_TRUE(false); // expect rename to fail } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(ENOENT, e.getErrno()); //Rename fails, everything is ok. } }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation, { this->ExpectDoesntUpdateAnyTimestamps }); } void Test_Rename_Error_TargetParentDirIsFile() { auto node = this->CreateNode("/oldname"); this->CreateFile("/somefile"); auto operation = [&node] () { try { node->rename("/somefile/newname"); EXPECT_TRUE(false); // expect rename to fail } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(ENOTDIR, e.getErrno()); //Rename fails, everything is ok. } }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation, { this->ExpectDoesntUpdateAnyTimestamps }); } void Test_Rename_Error_RootDir() { // TODO Re-enable this test once the root dir stores timestamps correctly /* auto root = this->Load("/"); auto operation = [&root] () { try { root->rename("/newname"); EXPECT_TRUE(false); // expect throws } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(EBUSY, e.getErrno()); //Rename fails, everything is ok. } }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { this->ExpectDoesntUpdateAnyTimestamps }); */ } void Test_Rename_InRoot() { auto node = this->CreateNode("/oldname"); auto operation = [&node] () { node->rename("/newname"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/newname", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Rename_InNested() { this->CreateDir("/mydir"); auto node = this->CreateNode("/mydir/oldname"); auto operation = [&node] () { node->rename("/mydir/newname"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/mydir/newname", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Rename_RootToNested_SameName() { this->CreateDir("/mydir"); auto node = this->CreateNode("/oldname"); auto operation = [&node] () { node->rename("/mydir/oldname"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/mydir/oldname", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Rename_RootToNested_NewName() { this->CreateDir("/mydir"); auto node = this->CreateNode("/oldname"); auto operation = [&node] () { node->rename("/mydir/newname"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/mydir/newname", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Rename_NestedToRoot_SameName() { this->CreateDir("/mydir"); auto node = this->CreateNode("/mydir/oldname"); auto operation = [&node] () { node->rename("/oldname"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/oldname", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Rename_NestedToRoot_NewName() { this->CreateDir("/mydir"); auto node = this->CreateNode("/mydir/oldname"); auto operation = [&node] () { node->rename("/newname"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/newname", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Rename_NestedToNested_SameName() { this->CreateDir("/mydir1"); this->CreateDir("/mydir2"); auto node = this->CreateNode("/mydir1/oldname"); auto operation = [&node] () { node->rename("/mydir2/oldname"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/oldname", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Rename_NestedToNested_NewName() { this->CreateDir("/mydir1"); this->CreateDir("/mydir2"); auto node = this->CreateNode("/mydir1/oldname"); auto operation = [&node] () { node->rename("/mydir2/newname"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/newname", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Rename_ToItself() { auto node = this->CreateNode("/oldname"); auto operation = [&node] () { node->rename("/oldname"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/oldname", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Rename_Overwrite_InSameDir() { auto node = this->CreateNode("/oldname"); this->CreateNode("/newname"); auto operation = [&node] () { node->rename("/newname"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/newname", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Rename_Overwrite_InDifferentDir() { this->CreateDir("/mydir1"); this->CreateDir("/mydir2"); this->CreateNode("/mydir2/newname"); auto node = this->CreateNode("/mydir1/oldname"); auto operation = [&node] () { node->rename("/mydir2/newname"); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/newname", operation, { this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp }); } void Test_Rename_Overwrite_Error_DirWithFile_InSameDir() { this->CreateFile("/oldname"); this->CreateDir("/newname"); auto node = this->Load("/oldname"); auto operation = [&node] () { try { node->rename("/newname"); EXPECT_TRUE(false); // expect rename to fail } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(EISDIR, e.getErrno()); //Rename fails, everything is ok. } }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation, { this->ExpectDoesntUpdateAnyTimestamps }); } void Test_Rename_Overwrite_Error_DirWithFile_InDifferentDir() { this->CreateDir("/mydir1"); this->CreateDir("/mydir2"); this->CreateFile("/mydir1/oldname"); this->CreateDir("/mydir2/newname"); auto node = this->Load("/mydir1/oldname"); auto operation = [&node] () { try { node->rename("/mydir2/newname"); EXPECT_TRUE(false); // expect rename to fail } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(EISDIR, e.getErrno());//Rename fails, everything is ok. } }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", operation, { this->ExpectDoesntUpdateAnyTimestamps }); } void Test_Rename_Overwrite_Error_FileWithDir_InSameDir() { this->CreateDir("/oldname"); this->CreateFile("/newname"); auto node = this->Load("/oldname"); auto operation = [&node] () { try { node->rename("/newname"); EXPECT_TRUE(false); // expect rename to fail } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(ENOTDIR, e.getErrno()); //Rename fails, everything is ok. } }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation, { this->ExpectDoesntUpdateAnyTimestamps }); } void Test_Rename_Overwrite_Error_FileWithDir_InDifferentDir() { this->CreateDir("/mydir1"); this->CreateDir("/mydir2"); this->CreateDir("/mydir1/oldname"); this->CreateFile("/mydir2/newname"); auto node = this->Load("/mydir1/oldname"); auto operation = [&node] () { try { node->rename("/mydir2/newname"); EXPECT_TRUE(false); // expect rename to fail } catch (const fspp::fuse::FuseErrnoException &e) { EXPECT_EQ(ENOTDIR, e.getErrno()); //Rename fails, everything is ok. } }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", operation, { this->ExpectDoesntUpdateAnyTimestamps }); } void Test_Utimens() { auto node = this->CreateNode("/mynode"); timespec atime = this->xSecondsAgo(100); timespec mtime = this->xSecondsAgo(200); auto operation = [&node, atime, mtime] () { node->utimens(atime, mtime); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation, { this->ExpectUpdatesMetadataTimestamp }); EXPECT_EQ(atime, this->stat(*node).atime); EXPECT_EQ(mtime, this->stat(*node).mtime); } }; REGISTER_NODE_TEST_CASE(FsppNodeTest_Timestamps, Create, Stat, Chmod, Chown, Access, Rename_Error_TargetParentDirDoesntExist, Rename_Error_TargetParentDirIsFile, Rename_Error_RootDir, Rename_InRoot, Rename_InNested, Rename_RootToNested_SameName, Rename_RootToNested_NewName, Rename_NestedToRoot_SameName, Rename_NestedToRoot_NewName, Rename_NestedToNested_SameName, Rename_NestedToNested_NewName, Rename_ToItself, Rename_Overwrite_InSameDir, Rename_Overwrite_InDifferentDir, Rename_Overwrite_Error_DirWithFile_InSameDir, Rename_Overwrite_Error_DirWithFile_InDifferentDir, Rename_Overwrite_Error_FileWithDir_InSameDir, Rename_Overwrite_Error_FileWithDir_InDifferentDir, Utimens ); #endif src/fspp/fstest/FsppOpenFileTest.h000066400000000000000000000042141347701267100175010ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPOPENFILETEST_H_ #define MESSMER_FSPP_FSTEST_FSPPOPENFILETEST_H_ #include "testutils/FileTest.h" template class FsppOpenFileTest: public FileSystemTest { public: void IN_STAT(fspp::OpenFile *openFile, std::function callback) { auto st = openFile->stat(); callback(st); } void EXPECT_SIZE(fspp::num_bytes_t expectedSize, fspp::OpenFile *openFile) { IN_STAT(openFile, [expectedSize] (const fspp::OpenFile::stat_info& st) { EXPECT_EQ(expectedSize, st.size); }); EXPECT_NUMBYTES_READABLE(expectedSize, openFile); } void EXPECT_NUMBYTES_READABLE(fspp::num_bytes_t expectedSize, fspp::OpenFile *openFile) { cpputils::Data data(expectedSize.value()); //Try to read one byte more than the expected size fspp::num_bytes_t readBytes = openFile->read(data.data(), expectedSize+fspp::num_bytes_t(1), fspp::num_bytes_t(0)); //and check that it only read the expected size (but also not less) EXPECT_EQ(expectedSize, readBytes); } }; TYPED_TEST_CASE_P(FsppOpenFileTest); TYPED_TEST_P(FsppOpenFileTest, CreatedFileIsEmpty) { auto file = this->CreateFile("/myfile"); auto openFile = this->LoadFile("/myfile")->open(fspp::openflags_t::RDONLY()); this->EXPECT_SIZE(fspp::num_bytes_t(0), openFile.get()); } TYPED_TEST_P(FsppOpenFileTest, FileIsFile) { auto file = this->CreateFile("/myfile"); auto openFile = this->LoadFile("/myfile")->open(fspp::openflags_t::RDONLY()); this->IN_STAT(openFile.get(), [] (const fspp::OpenFile::stat_info& st) { EXPECT_TRUE(st.mode.hasFileFlag()); }); } REGISTER_TYPED_TEST_CASE_P(FsppOpenFileTest, CreatedFileIsEmpty, FileIsFile ); //TODO Test stat //TODO Test truncate //TODO Test read //TODO Test write //TODO Test flush //TODO Test fsync //TODO Test fdatasync //TODO Test stat on file that was just created (i.e. the OpenFile instance returned by createAndOpenFile) //TODO Test all operations do (or don't) affect file timestamps correctly #endif src/fspp/fstest/FsppOpenFileTest_Timestamps.h000066400000000000000000000156441347701267100217200ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPOPENFILETEST_TIMESTAMPS_H_ #define MESSMER_FSPP_FSTEST_FSPPOPENFILETEST_TIMESTAMPS_H_ #include "testutils/TimestampTestUtils.h" template class FsppOpenFileTest_Timestamps: public TimestampTestUtils { public: cpputils::unique_ref CreateAndOpenFile(const boost::filesystem::path &path) { return this->CreateFile(path)->open(fspp::openflags_t::RDWR()); } cpputils::unique_ref CreateAndOpenFileWithSize(const boost::filesystem::path &path, fspp::num_bytes_t size) { auto file = this->CreateFile(path); file->truncate(size); auto openFile = file->open(fspp::openflags_t::RDWR()); assert(this->stat(*openFile).size == size); assert(this->stat(*this->Load(path)).size == size); return openFile; } }; TYPED_TEST_CASE_P(FsppOpenFileTest_Timestamps); TYPED_TEST_P(FsppOpenFileTest_Timestamps, stat) { auto openFile = this->CreateAndOpenFile("/mynode"); auto operation = [&openFile] () { openFile->stat(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAnyTimestamps}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_empty_to_empty) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); auto operation = [&openFile] () { openFile->truncate(fspp::num_bytes_t(0)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_empty_to_nonempty) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); auto operation = [&openFile] () { openFile->truncate(fspp::num_bytes_t(10)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_nonempty_to_empty) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); auto operation = [&openFile] () { openFile->truncate(fspp::num_bytes_t(0)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_nonempty_to_nonempty_shrink) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); auto operation = [&openFile] () { openFile->truncate(fspp::num_bytes_t(5)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_nonempty_to_nonempty_grow) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); auto operation = [&openFile] () { openFile->truncate(fspp::num_bytes_t(20)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, read_inbounds) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); auto operation = [&openFile] () { std::array buffer{}; openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(0)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, read_outofbounds) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); auto operation = [&openFile] () { std::array buffer{}; openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(2)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, write_inbounds) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); auto operation = [&openFile] () { openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, write_outofbounds) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); auto operation = [&openFile] () { openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(2)); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, flush) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); auto operation = [&openFile] () { openFile->flush(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAnyTimestamps}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, fsync) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); auto operation = [&openFile] () { openFile->fsync(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAnyTimestamps}); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, fdatasync) { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); auto operation = [&openFile] () { openFile->fdatasync(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation, {this->ExpectDoesntUpdateAnyTimestamps}); } REGISTER_TYPED_TEST_CASE_P(FsppOpenFileTest_Timestamps, stat, truncate_empty_to_empty, truncate_empty_to_nonempty, truncate_nonempty_to_empty, truncate_nonempty_to_nonempty_shrink, truncate_nonempty_to_nonempty_grow, read_inbounds, read_outofbounds, write_inbounds, write_outofbounds, flush, fsync, fdatasync ); #endif src/fspp/fstest/FsppSymlinkTest.h000066400000000000000000000040331347701267100174250ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPSYMLINKTEST_H_ #define MESSMER_FSPP_FSTEST_FSPPSYMLINKTEST_H_ #include #include "testutils/FileSystemTest.h" template class FsppSymlinkTest: public FileSystemTest { public: }; TYPED_TEST_CASE_P(FsppSymlinkTest); TYPED_TEST_P(FsppSymlinkTest, Create_AbsolutePath) { this->CreateSymlink("/mysymlink", "/my/symlink/target"); } TYPED_TEST_P(FsppSymlinkTest, Create_RelativePath) { this->CreateSymlink("/mysymlink", "../target"); } TYPED_TEST_P(FsppSymlinkTest, Read_AbsolutePath) { this->CreateSymlink("/mysymlink", "/my/symlink/target"); EXPECT_EQ("/my/symlink/target", this->LoadSymlink("/mysymlink")->target()); } TYPED_TEST_P(FsppSymlinkTest, Read_RelativePath) { this->CreateSymlink("/mysymlink", "../target"); EXPECT_EQ("../target", this->LoadSymlink("/mysymlink")->target()); } TYPED_TEST_P(FsppSymlinkTest, Remove) { this->CreateSymlink("/mysymlink", "/my/symlink/target"); EXPECT_NE(boost::none, this->device->Load("/mysymlink")); EXPECT_NE(boost::none, this->device->LoadSymlink("/mysymlink")); this->Load("/mysymlink")->remove(); EXPECT_EQ(boost::none, this->device->Load("/mysymlink")); EXPECT_EQ(boost::none, this->device->LoadSymlink("/mysymlink")); } TYPED_TEST_P(FsppSymlinkTest, Remove_Nested) { this->CreateDir("/mytestdir"); this->CreateSymlink("/mytestdir/mysymlink", "/my/symlink/target"); EXPECT_NE(boost::none, this->device->Load("/mytestdir/mysymlink")); EXPECT_NE(boost::none, this->device->LoadSymlink("/mytestdir/mysymlink")); this->Load("/mytestdir/mysymlink")->remove(); EXPECT_EQ(boost::none, this->device->Load("/mytestdir/mysymlink")); EXPECT_EQ(boost::none, this->device->LoadSymlink("/mytestdir/mysymlink")); } REGISTER_TYPED_TEST_CASE_P(FsppSymlinkTest, Create_AbsolutePath, Create_RelativePath, Read_AbsolutePath, Read_RelativePath, Remove, Remove_Nested ); //TODO Other tests? //TODO Test all operations do (or don't) affect timestamps correctly #endif src/fspp/fstest/FsppSymlinkTest_Timestamps.h000066400000000000000000000017521347701267100216400ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_FSPPSYMLINKTEST_TIMESTAMPS_H_ #define MESSMER_FSPP_FSTEST_FSPPSYMLINKTEST_TIMESTAMPS_H_ #include "testutils/TimestampTestUtils.h" template class FsppSymlinkTest_Timestamps: public TimestampTestUtils { public: }; TYPED_TEST_CASE_P(FsppSymlinkTest_Timestamps); TYPED_TEST_P(FsppSymlinkTest_Timestamps, target) { auto symlink = this->CreateSymlink("/mysymlink"); this->setModificationTimestampLaterThanAccessTimestamp("/mysymlink"); // to make sure that even in relatime behavior, the read access below changes the access timestamp auto operation = [&symlink] () { symlink->target(); }; this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation, {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); } REGISTER_TYPED_TEST_CASE_P(FsppSymlinkTest_Timestamps, target ); #endif src/fspp/fstest/testutils/000077500000000000000000000000001347701267100161755ustar00rootroot00000000000000src/fspp/fstest/testutils/FileSystemTest.h000066400000000000000000000075561347701267100213070ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_TESTUTILS_FILESYSTEMTEST_H_ #define MESSMER_FSPP_FSTEST_TESTUTILS_FILESYSTEMTEST_H_ #include #include #include #include #include #include #include "../../fs_interface/Device.h" #include "../../fs_interface/Node.h" #include "../../fs_interface/Dir.h" #include "../../fs_interface/File.h" #include "../../fs_interface/Symlink.h" #include "../../fs_interface/OpenFile.h" class FileSystemTestFixture { public: virtual ~FileSystemTestFixture() {} virtual cpputils::unique_ref createDevice() = 0; }; template class FileSystemTest: public ::testing::Test { public: BOOST_STATIC_ASSERT_MSG( (std::is_base_of::value), "Given test fixture for instantiating the (type parameterized) FileSystemTest must inherit from FileSystemTestFixture" ); FileSystemTest(): fixture(), device(fixture.createDevice()) {} ConcreteFileSystemTestFixture fixture; cpputils::unique_ref device; static constexpr fspp::mode_t MODE_PUBLIC = fspp::mode_t() .addUserReadFlag().addUserWriteFlag().addUserExecFlag() .addGroupReadFlag().addGroupWriteFlag().addGroupExecFlag() .addOtherReadFlag().addOtherWriteFlag().addOtherExecFlag(); cpputils::unique_ref Load(const boost::filesystem::path &path) { auto loaded = device->Load(path); EXPECT_NE(boost::none, loaded); return std::move(*loaded); } cpputils::unique_ref LoadDir(const boost::filesystem::path &path) { auto loaded = device->LoadDir(path); EXPECT_NE(boost::none, loaded); return std::move(*loaded); } cpputils::unique_ref LoadFile(const boost::filesystem::path &path) { auto loaded = device->LoadFile(path); EXPECT_NE(boost::none, loaded); return std::move(*loaded); } cpputils::unique_ref LoadSymlink(const boost::filesystem::path &path) { auto loaded = device->LoadSymlink(path); EXPECT_NE(boost::none, loaded); return std::move(*loaded); } cpputils::unique_ref CreateDir(const boost::filesystem::path &path) { this->LoadDir(path.parent_path())->createDir(path.filename().string(), this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); return this->LoadDir(path); } cpputils::unique_ref CreateFile(const boost::filesystem::path &path) { this->LoadDir(path.parent_path())->createAndOpenFile(path.filename().string(), this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); return this->LoadFile(path); } cpputils::unique_ref CreateSymlink(const boost::filesystem::path &path, const boost::filesystem::path &target = "/my/symlink/target") { this->LoadDir(path.parent_path())->createSymlink(path.filename().string(), target, fspp::uid_t(0), fspp::gid_t(0)); return this->LoadSymlink(path); } void EXPECT_IS_FILE(const cpputils::unique_ref &node) { EXPECT_NE(nullptr, dynamic_cast(node.get())); } void EXPECT_IS_DIR(const cpputils::unique_ref &node) { EXPECT_NE(nullptr, dynamic_cast(node.get())); } void EXPECT_IS_SYMLINK(const cpputils::unique_ref &node) { EXPECT_NE(nullptr, dynamic_cast(node.get())); } void setModificationTimestampLaterThanAccessTimestamp(const boost::filesystem::path& path) { auto node = device->Load(path).value(); auto st = node->stat(); st.mtime.tv_nsec = st.mtime.tv_nsec + 1; node->utimens( st.atime, st.mtime ); } }; template constexpr fspp::mode_t FileSystemTest::MODE_PUBLIC; #endif src/fspp/fstest/testutils/FileTest.h000066400000000000000000000050711347701267100200700ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_TESTUTILS_FILETEST_H_ #define MESSMER_FSPP_FSTEST_TESTUTILS_FILETEST_H_ #include "FileSystemTest.h" #include #include #include template class FileTest: public FileSystemTest { public: FileTest(): file_root(), file_nested() { this->LoadDir("/")->createAndOpenFile("myfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); file_root = this->LoadFile("/myfile"); file_root_node = this->Load("/myfile"); this->LoadDir("/")->createDir("mydir", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); this->LoadDir("/mydir")->createAndOpenFile("mynestedfile", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); file_nested = this->LoadFile("/mydir/mynestedfile"); file_nested_node = this->Load("/mydir/mynestedfile"); this->LoadDir("/")->createDir("mydir2", this->MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); } std::unique_ptr file_root; std::unique_ptr file_nested; std::unique_ptr file_root_node; std::unique_ptr file_nested_node; //TODO IN_STAT still needed after moving it to FsppNodeTest? void IN_STAT(fspp::File *file, fspp::Node *node, std::function callback) { auto st1 = node->stat(); callback(st1); auto st2 = file->open(fspp::openflags_t::RDONLY())->stat(); callback(st2); } void EXPECT_SIZE(fspp::num_bytes_t expectedSize, fspp::File *file, fspp::Node *node) { IN_STAT(file, node, [expectedSize] (const fspp::Node::stat_info& st) { EXPECT_EQ(expectedSize, st.size); }); EXPECT_NUMBYTES_READABLE(expectedSize, file); } void EXPECT_NUMBYTES_READABLE(fspp::num_bytes_t expectedSize, fspp::File *file) { auto openFile = file->open(fspp::openflags_t::RDONLY()); cpputils::Data data(expectedSize.value()); //Try to read one byte more than the expected size fspp::num_bytes_t readBytes = openFile->read(data.data(), expectedSize+fspp::num_bytes_t(1), fspp::num_bytes_t(0)); //and check that it only read the expected size (but also not less) EXPECT_EQ(expectedSize, readBytes); } void EXPECT_ATIME_EQ(struct timespec expected, const fspp::Node::stat_info& st) { EXPECT_EQ(expected.tv_sec, st.atime.tv_sec); EXPECT_EQ(expected.tv_nsec, st.atime.tv_nsec); } void EXPECT_MTIME_EQ(struct timespec expected, const fspp::Node::stat_info& st) { EXPECT_EQ(expected.tv_sec, st.mtime.tv_sec); EXPECT_EQ(expected.tv_nsec, st.mtime.tv_nsec); } }; #endif src/fspp/fstest/testutils/FsppNodeTest.h000066400000000000000000000162511347701267100207310ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_TESTUTILS_FSPPNODETEST_H_ #define MESSMER_FSPP_FSTEST_TESTUTILS_FSPPNODETEST_H_ #include "FileSystemTest.h" #include #include #include class FsppNodeTestHelper { public: void IN_STAT(fspp::Node *file, std::function callback) { auto st = file->stat(); callback(st); } void EXPECT_SIZE(fspp::num_bytes_t expectedSize, fspp::Node *node) { IN_STAT(node, [expectedSize] (const fspp::Node::stat_info& st) { EXPECT_EQ(expectedSize, st.size); }); } }; /** * Inherit your fixture from this class to write a test case that is run on nodes, i.e. files, directories and symlinks. * You can use this->CreateNode() to create a node and then call fspp::Node functions on it. * Add your test cases as void Test_xxx() functions to your fixture and register/instantiate them using * REGISTER_NODE_TEST_CASE and INSTANTIATE_NODE_TEST_CASE. * It will then automatically create a test case for each node type (file, directory, symlink). * See FsppNodeTest_Rename for an example. */ template class FsppNodeTest: public virtual FsppNodeTestHelper, public virtual FileSystemTest { public: virtual cpputils::unique_ref CreateNode(const boost::filesystem::path &path) = 0; }; #define _REGISTER_SINGLE_NODE_TEST_CASE(r, Class, Name) \ TYPED_TEST_P(Class, Name) { \ this->BOOST_PP_CAT(Test_,Name)(); \ } \ #define _REGISTER_NODE_TEST_CASES_FOR_CLASS(Class, ...) \ BOOST_PP_SEQ_FOR_EACH(_REGISTER_SINGLE_NODE_TEST_CASE, Class, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)); \ REGISTER_TYPED_TEST_CASE_P(Class, __VA_ARGS__); \ #define _REGISTER_FILE_TEST_CASE(Class, ...) \ template \ class Class##_FileNode: public Class, public virtual FsppNodeTestHelper { \ public: \ cpputils::unique_ref CreateNode(const boost::filesystem::path &path) override { \ this->CreateFile(path); \ return this->Load(path); \ } \ }; \ TYPED_TEST_CASE_P(Class##_FileNode); \ _REGISTER_NODE_TEST_CASES_FOR_CLASS(Class##_FileNode, __VA_ARGS__); \ #define _REGISTER_DIR_TEST_CASE(Class, ...) \ template \ class Class##_DirNode: public Class, public virtual FsppNodeTestHelper { \ public: \ cpputils::unique_ref CreateNode(const boost::filesystem::path &path) override { \ this->CreateDir(path); \ return this->Load(path); \ } \ }; \ TYPED_TEST_CASE_P(Class##_DirNode); \ _REGISTER_NODE_TEST_CASES_FOR_CLASS(Class##_DirNode, __VA_ARGS__); \ #define _REGISTER_SYMLINK_TEST_CASE(Class, ...) \ template \ class Class##_SymlinkNode: public Class, public virtual FsppNodeTestHelper { \ public: \ cpputils::unique_ref CreateNode(const boost::filesystem::path &path) override { \ this->CreateSymlink(path); \ return this->Load(path); \ } \ }; \ TYPED_TEST_CASE_P(Class##_SymlinkNode); \ _REGISTER_NODE_TEST_CASES_FOR_CLASS(Class##_SymlinkNode, __VA_ARGS__); \ #define REGISTER_NODE_TEST_CASE(Class, ...) \ _REGISTER_FILE_TEST_CASE(Class, __VA_ARGS__); \ _REGISTER_DIR_TEST_CASE(Class, __VA_ARGS__); \ _REGISTER_SYMLINK_TEST_CASE(Class, __VA_ARGS__); \ #define INSTANTIATE_NODE_TEST_CASE(FS_NAME, Class, FIXTURE) \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, Class##_FileNode, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, Class##_DirNode, FIXTURE); \ INSTANTIATE_TYPED_TEST_CASE_P(FS_NAME, Class##_SymlinkNode, FIXTURE); \ #endif src/fspp/fstest/testutils/TimestampTestUtils.h000066400000000000000000000205401347701267100221730ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSTEST_TESTUTILS_TIMESTAMPTESTUTILS_H_ #define MESSMER_FSPP_FSTEST_TESTUTILS_TIMESTAMPTESTUTILS_H_ #include #include #include "FileSystemTest.h" #include template class TimestampTestUtils : public virtual FileSystemTest { public: using TimestampUpdateBehavior = std::function; static TimestampUpdateBehavior ExpectUpdatesAccessTimestamp; static TimestampUpdateBehavior ExpectDoesntUpdateAccessTimestamp; static TimestampUpdateBehavior ExpectUpdatesModificationTimestamp; static TimestampUpdateBehavior ExpectDoesntUpdateModificationTimestamp; static TimestampUpdateBehavior ExpectUpdatesMetadataTimestamp; static TimestampUpdateBehavior ExpectDoesntUpdateMetadataTimestamp; static TimestampUpdateBehavior ExpectDoesntUpdateAnyTimestamps; void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(std::function statOld, std::function statNew, std::function operation, std::initializer_list behaviorChecks) { auto oldStat = statOld(); ensureNodeTimestampsAreOld(oldStat); timespec timeBeforeOperation = cpputils::time::now(); operation(); timespec timeAfterOperation = cpputils::time::now(); auto newStat = statNew(); for (auto behaviorCheck : behaviorChecks) { behaviorCheck(oldStat, newStat, timeBeforeOperation, timeAfterOperation); } } void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const fspp::OpenFile &node, std::function operation, std::initializer_list behaviorChecks) { EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS( [this, &node](){return this->stat(node);}, [this, &node](){return this->stat(node);}, operation, behaviorChecks ); } void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const boost::filesystem::path &oldPath, const boost::filesystem::path &newPath, std::function operation, std::initializer_list behaviorChecks) { EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS( [this, oldPath](){return this->stat(*this->Load(oldPath));}, [this, newPath](){return this->stat(*this->Load(newPath));}, operation, behaviorChecks ); } void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const boost::filesystem::path &path, std::function operation, std::initializer_list behaviorChecks) { EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(path, path, operation, behaviorChecks); } void EXPECT_ACCESS_TIMESTAMP_BETWEEN(timespec lowerBound, timespec upperBound, const fspp::Node &node) { EXPECT_LE(lowerBound, stat(node).atime); EXPECT_GE(upperBound, stat(node).atime); } void EXPECT_MODIFICATION_TIMESTAMP_BETWEEN(timespec lowerBound, timespec upperBound, const fspp::Node &node) { EXPECT_LE(lowerBound, stat(node).mtime); EXPECT_GE(upperBound, stat(node).mtime); } void EXPECT_METADATACHANGE_TIMESTAMP_BETWEEN(timespec lowerBound, timespec upperBound, const fspp::Node &node) { EXPECT_LE(lowerBound, stat(node).ctime); EXPECT_GE(upperBound, stat(node).ctime); } static fspp::Node::stat_info stat(const fspp::Node &node) { return node.stat(); } static fspp::Node::stat_info stat(const fspp::OpenFile &openFile) { return openFile.stat(); } timespec xSecondsAgo(int sec) { timespec result = cpputils::time::now(); result.tv_sec -= sec; return result; } void ensureNodeTimestampsAreOld(const fspp::Node::stat_info &nodeStat) { waitUntilClockProgresses(); EXPECT_LT(nodeStat.atime, cpputils::time::now()); EXPECT_LT(nodeStat.mtime, cpputils::time::now()); EXPECT_LT(nodeStat.ctime, cpputils::time::now()); } private: void waitUntilClockProgresses() { auto start = cpputils::time::now(); while (start == cpputils::time::now()) { // busy waiting is the fastest, we only have to wait for a nanosecond increment. } } }; template typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectUpdatesAccessTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(statBeforeOperation); UNUSED(timeBeforeOperation); UNUSED(timeAfterOperation); EXPECT_LE(timeBeforeOperation, statAfterOperation.atime); EXPECT_GE(timeAfterOperation, statAfterOperation.atime); }; template typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectDoesntUpdateAccessTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(timeBeforeOperation); UNUSED(timeAfterOperation); EXPECT_EQ(statBeforeOperation.atime, statAfterOperation.atime); }; template typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectUpdatesModificationTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(statBeforeOperation); EXPECT_LE(timeBeforeOperation, statAfterOperation.mtime); EXPECT_GE(timeAfterOperation, statAfterOperation.mtime); }; template typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectDoesntUpdateModificationTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(timeBeforeOperation); UNUSED(timeAfterOperation); EXPECT_EQ(statBeforeOperation.mtime, statAfterOperation.mtime); }; template typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectUpdatesMetadataTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(statBeforeOperation); EXPECT_LE(timeBeforeOperation, statAfterOperation.ctime); EXPECT_GE(timeAfterOperation, statAfterOperation.ctime); }; template typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectDoesntUpdateMetadataTimestamp = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { UNUSED(timeBeforeOperation); UNUSED(timeAfterOperation); EXPECT_EQ(statBeforeOperation.ctime, statAfterOperation.ctime); }; template typename TimestampTestUtils::TimestampUpdateBehavior TimestampTestUtils::ExpectDoesntUpdateAnyTimestamps = [] (const fspp::Node::stat_info& statBeforeOperation, const fspp::Node::stat_info& statAfterOperation, timespec timeBeforeOperation, timespec timeAfterOperation) { ExpectDoesntUpdateAccessTimestamp(statBeforeOperation, statAfterOperation, timeBeforeOperation, timeAfterOperation); ExpectDoesntUpdateModificationTimestamp(statBeforeOperation, statAfterOperation, timeBeforeOperation, timeAfterOperation); ExpectDoesntUpdateMetadataTimestamp(statBeforeOperation, statAfterOperation, timeBeforeOperation, timeAfterOperation); }; #endif src/fspp/fuse/000077500000000000000000000000001347701267100135675ustar00rootroot00000000000000src/fspp/fuse/CMakeLists.txt000066400000000000000000000031301347701267100163240ustar00rootroot00000000000000project (fspp-fuse) set(SOURCES ../impl/FilesystemImpl.cpp ../impl/Profiler.cpp ../fuse/Fuse.cpp ) add_library(${PROJECT_NAME} STATIC ${SOURCES}) target_compile_definitions(${PROJECT_NAME} PUBLIC _FILE_OFFSET_BITS=64) target_link_libraries(${PROJECT_NAME} PUBLIC cpp-utils fspp-interface) target_add_boost(${PROJECT_NAME} filesystem system thread chrono) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") get_target_architecture(TARGET_ARCH) message(STATUS "Linking to Dokan ${TARGET_ARCH}") if ("${TARGET_ARCH}" STREQUAL "x86_64") set(DOKAN_LIB_PATH "${DOKAN_PATH}") elseif("${TARGET_ARCH}" STREQUAL "i386") set(DOKAN_LIB_PATH "${DOKAN_PATH}/x86") else() message(FATAL_ERROR "Unsupported architecture: ${TARGET_ARCH}") endif() target_include_directories(${PROJECT_NAME} PUBLIC "${DOKAN_PATH}/include") target_link_libraries(${PROJECT_NAME} PUBLIC "${DOKAN_LIB_PATH}/lib/dokan1.lib") #target_link_libraries(${PROJECT_NAME} PUBLIC "${DOKAN_LIB_PATH}/lib/dokannp1.lib") target_link_libraries(${PROJECT_NAME} PUBLIC "${DOKAN_LIB_PATH}/lib/dokanfuse1.lib") install(FILES "${DOKAN_LIB_PATH}/dokan1.dll" "${DOKAN_LIB_PATH}/dokanfuse1.dll" DESTINATION "${CMAKE_INSTALL_BINDIR}" ) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set(CMAKE_FIND_FRAMEWORK LAST) find_library_with_path(FUSE "osxfuse" FUSE_LIB_PATH) target_link_libraries(${PROJECT_NAME} PUBLIC ${FUSE}) else() # Linux find_library_with_path(FUSE "fuse" FUSE_LIB_PATH) target_link_libraries(${PROJECT_NAME} PUBLIC ${FUSE}) endif() src/fspp/fuse/Filesystem.h000066400000000000000000000052101347701267100160620ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FUSE_FILESYSTEM_H_ #define MESSMER_FSPP_FUSE_FILESYSTEM_H_ #include #include #include #include "../fs_interface/Dir.h" #if defined(_MSC_VER) #include #else #include #endif #include "stat_compatibility.h" namespace fspp { namespace fuse { class Filesystem { public: virtual ~Filesystem() {} //TODO Test uid/gid parameters of createAndOpenFile virtual int createAndOpenFile(const boost::filesystem::path &path, ::mode_t mode, ::uid_t uid, ::gid_t gid) = 0; virtual int openFile(const boost::filesystem::path &path, int flags) = 0; virtual void flush(int descriptor) = 0; virtual void closeFile(int descriptor) = 0; virtual void lstat(const boost::filesystem::path &path, fspp::fuse::STAT *stbuf) = 0; virtual void fstat(int descriptor, fspp::fuse::STAT *stbuf) = 0; //TODO Test chmod virtual void chmod(const boost::filesystem::path &path, ::mode_t mode) = 0; //TODO Test chown virtual void chown(const boost::filesystem::path &path, ::uid_t uid, ::gid_t gid) = 0; virtual void truncate(const boost::filesystem::path &path, fspp::num_bytes_t size) = 0; virtual void ftruncate(int descriptor, fspp::num_bytes_t size) = 0; virtual fspp::num_bytes_t read(int descriptor, void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) = 0; virtual void write(int descriptor, const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) = 0; virtual void fsync(int descriptor) = 0; virtual void fdatasync(int descriptor) = 0; virtual void access(const boost::filesystem::path &path, int mask) = 0; //TODO Test uid/gid parameters of mkdir virtual void mkdir(const boost::filesystem::path &path, ::mode_t mode, ::uid_t uid, ::gid_t gid) = 0; virtual void rmdir(const boost::filesystem::path &path) = 0; virtual void unlink(const boost::filesystem::path &path) = 0; virtual void rename(const boost::filesystem::path &from, const boost::filesystem::path &to) = 0; virtual void utimens(const boost::filesystem::path &path, timespec lastAccessTime, timespec lastModificationTime) = 0; virtual void statfs(struct ::statvfs *fsstat) = 0; //TODO We shouldn't use Dir::Entry here, that's in another layer virtual cpputils::unique_ref> readDir(const boost::filesystem::path &path) = 0; //TODO Test createSymlink virtual void createSymlink(const boost::filesystem::path &to, const boost::filesystem::path &from, ::uid_t uid, ::gid_t gid) = 0; //TODO Test readSymlink virtual void readSymlink(const boost::filesystem::path &path, char *buf, fspp::num_bytes_t size) = 0; }; } } #endif src/fspp/fuse/Fuse.cpp000066400000000000000000001056361347701267100152100ustar00rootroot00000000000000#include "Fuse.h" #include #include #include "../fs_interface/FuseErrnoException.h" #include "Filesystem.h" #include #include #include #include #include #include #include "InvalidFilesystem.h" #include #if defined(_MSC_VER) #include #include #endif using std::vector; using std::string; namespace bf = boost::filesystem; using namespace cpputils::logging; using std::make_shared; using std::shared_ptr; using std::string; using namespace fspp::fuse; using cpputils::set_thread_name; namespace { bool is_valid_fspp_path(const bf::path& path) { // TODO In boost 1.63, we can use path.generic() or path.generic_path() instead of path.generic_string() return path.has_root_directory() // must be absolute path && !path.has_root_name() // on Windows, it shouldn't have a device specifier (i.e. no "C:") && (path.string() == path.generic_string()); // must use portable '/' as directory separator } class ThreadNameForDebugging final { public: ThreadNameForDebugging(const string& threadName) { std::string name = "fspp_" + threadName; set_thread_name(name.c_str()); } ~ThreadNameForDebugging() { set_thread_name("fspp_idle"); } }; } #define FUSE_OBJ (static_cast(fuse_get_context()->private_data)) // Remove the following line, if you don't want to output each fuse operation on the console //#define FSPP_LOG 1 namespace { int fusepp_getattr(const char *path, fspp::fuse::STAT *stbuf) { int rs = FUSE_OBJ->getattr(bf::path(path), stbuf); return rs; } int fusepp_fgetattr(const char *path, fspp::fuse::STAT *stbuf, fuse_file_info *fileinfo) { return FUSE_OBJ->fgetattr(bf::path(path), stbuf, fileinfo); } int fusepp_readlink(const char *path, char *buf, size_t size) { return FUSE_OBJ->readlink(bf::path(path), buf, size); } int fusepp_mknod(const char *path, ::mode_t mode, dev_t rdev) { return FUSE_OBJ->mknod(bf::path(path), mode, rdev); } int fusepp_mkdir(const char *path, ::mode_t mode) { return FUSE_OBJ->mkdir(bf::path(path), mode); } int fusepp_unlink(const char *path) { return FUSE_OBJ->unlink(bf::path(path)); } int fusepp_rmdir(const char *path) { return FUSE_OBJ->rmdir(bf::path(path)); } int fusepp_symlink(const char *to, const char *from) { return FUSE_OBJ->symlink(bf::path(to), bf::path(from)); } int fusepp_rename(const char *from, const char *to) { return FUSE_OBJ->rename(bf::path(from), bf::path(to)); } int fusepp_link(const char *from, const char *to) { return FUSE_OBJ->link(bf::path(from), bf::path(to)); } int fusepp_chmod(const char *path, ::mode_t mode) { return FUSE_OBJ->chmod(bf::path(path), mode); } int fusepp_chown(const char *path, ::uid_t uid, ::gid_t gid) { return FUSE_OBJ->chown(bf::path(path), uid, gid); } int fusepp_truncate(const char *path, int64_t size) { return FUSE_OBJ->truncate(bf::path(path), size); } int fusepp_ftruncate(const char *path, int64_t size, fuse_file_info *fileinfo) { return FUSE_OBJ->ftruncate(bf::path(path), size, fileinfo); } int fusepp_utimens(const char *path, const timespec times[2]) { // NOLINT(cppcoreguidelines-avoid-c-arrays) return FUSE_OBJ->utimens(bf::path(path), {times[0], times[1]}); } int fusepp_open(const char *path, fuse_file_info *fileinfo) { return FUSE_OBJ->open(bf::path(path), fileinfo); } int fusepp_release(const char *path, fuse_file_info *fileinfo) { return FUSE_OBJ->release(bf::path(path), fileinfo); } int fusepp_read(const char *path, char *buf, size_t size, int64_t offset, fuse_file_info *fileinfo) { return FUSE_OBJ->read(bf::path(path), buf, size, offset, fileinfo); } int fusepp_write(const char *path, const char *buf, size_t size, int64_t offset, fuse_file_info *fileinfo) { return FUSE_OBJ->write(bf::path(path), buf, size, offset, fileinfo); } int fusepp_statfs(const char *path, struct statvfs *fsstat) { return FUSE_OBJ->statfs(bf::path(path), fsstat); } int fusepp_flush(const char *path, fuse_file_info *fileinfo) { return FUSE_OBJ->flush(bf::path(path), fileinfo); } int fusepp_fsync(const char *path, int datasync, fuse_file_info *fileinfo) { return FUSE_OBJ->fsync(bf::path(path), datasync, fileinfo); } //int fusepp_setxattr(const char*, const char*, const char*, size_t, int) //int fusepp_getxattr(const char*, const char*, char*, size_t) //int fusepp_listxattr(const char*, char*, size_t) //int fusepp_removexattr(const char*, const char*) int fusepp_opendir(const char *path, fuse_file_info *fileinfo) { return FUSE_OBJ->opendir(bf::path(path), fileinfo); } int fusepp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, int64_t offset, fuse_file_info *fileinfo) { return FUSE_OBJ->readdir(bf::path(path), buf, filler, offset, fileinfo); } int fusepp_releasedir(const char *path, fuse_file_info *fileinfo) { return FUSE_OBJ->releasedir(bf::path(path), fileinfo); } int fusepp_fsyncdir(const char *path, int datasync, fuse_file_info *fileinfo) { return FUSE_OBJ->fsyncdir(bf::path(path), datasync, fileinfo); } void* fusepp_init(fuse_conn_info *conn) { auto f = FUSE_OBJ; f->init(conn); return f; } void fusepp_destroy(void *userdata) { auto f = FUSE_OBJ; ASSERT(userdata == f, "Wrong userdata set"); UNUSED(userdata); //In case the assert is disabled f->destroy(); } int fusepp_access(const char *path, int mask) { return FUSE_OBJ->access(bf::path(path), mask); } int fusepp_create(const char *path, ::mode_t mode, fuse_file_info *fileinfo) { return FUSE_OBJ->create(bf::path(path), mode, fileinfo); } /*int fusepp_lock(const char*, fuse_file_info*, int cmd, flock*) int fusepp_bmap(const char*, size_t blocksize, uint64_t *idx) int fusepp_ioctl(const char*, int cmd, void *arg, fuse_file_info*, unsigned int flags, void *data) int fusepp_poll(const char*, fuse_file_info*, fuse_pollhandle *ph, unsigned *reventsp) int fusepp_write_buf(const char*, fuse_bufvec *buf, int64_t off, fuse_file_info*) int fusepp_read_buf(const chas*, struct fuse_bufvec **bufp, size_t size, int64_t off, fuse_file_info*) int fusepp_flock(const char*, fuse_file_info*, int op) int fusepp_fallocate(const char*, int, int64_t, int64_t, fuse_file_info*)*/ fuse_operations *operations() { static std::unique_ptr singleton(nullptr); if (!singleton) { singleton = std::make_unique(); singleton->getattr = &fusepp_getattr; singleton->fgetattr = &fusepp_fgetattr; singleton->readlink = &fusepp_readlink; singleton->mknod = &fusepp_mknod; singleton->mkdir = &fusepp_mkdir; singleton->unlink = &fusepp_unlink; singleton->rmdir = &fusepp_rmdir; singleton->symlink = &fusepp_symlink; singleton->rename = &fusepp_rename; singleton->link = &fusepp_link; singleton->chmod = &fusepp_chmod; singleton->chown = &fusepp_chown; singleton->truncate = &fusepp_truncate; singleton->utimens = &fusepp_utimens; singleton->open = &fusepp_open; singleton->read = &fusepp_read; singleton->write = &fusepp_write; singleton->statfs = &fusepp_statfs; singleton->flush = &fusepp_flush; singleton->release = &fusepp_release; singleton->fsync = &fusepp_fsync; /*#ifdef HAVE_SYS_XATTR_H singleton->setxattr = &fusepp_setxattr; singleton->getxattr = &fusepp_getxattr; singleton->listxattr = &fusepp_listxattr; singleton->removexattr = &fusepp_removexattr; #endif*/ singleton->opendir = &fusepp_opendir; singleton->readdir = &fusepp_readdir; singleton->releasedir = &fusepp_releasedir; singleton->fsyncdir = &fusepp_fsyncdir; singleton->init = &fusepp_init; singleton->destroy = &fusepp_destroy; singleton->access = &fusepp_access; singleton->create = &fusepp_create; singleton->ftruncate = &fusepp_ftruncate; } return singleton.get(); } } Fuse::~Fuse() { for(char *arg : _argv) { delete[] arg; arg = nullptr; } _argv.clear(); } Fuse::Fuse(std::function (Fuse *fuse)> init, std::function onMounted, std::string fstype, boost::optional fsname) :_init(std::move(init)), _onMounted(std::move(onMounted)), _fs(make_shared()), _mountdir(), _running(false), _fstype(std::move(fstype)), _fsname(std::move(fsname)) { ASSERT(static_cast(_init), "Invalid init given"); ASSERT(static_cast(_onMounted), "Invalid onMounted given"); } void Fuse::_logException(const std::exception &e) { LOG(ERR, "Exception thrown: {}", e.what()); } void Fuse::_logUnknownException() { LOG(ERR, "Unknown exception thrown"); } void Fuse::run(const bf::path &mountdir, const vector &fuseOptions) { #if defined(__GLIBC__)|| defined(__APPLE__) || defined(_MSC_VER) // Avoid encoding errors for non-utf8 characters, see https://github.com/cryfs/cryfs/issues/247 // this is ifdef'd out for non-glibc linux, because musl doesn't handle this correctly. bf::path::imbue(std::locale(std::locale(), new std::codecvt_utf8_utf16())); #endif _mountdir = mountdir; ASSERT(_argv.size() == 0, "Filesystem already started"); _argv = _build_argv(mountdir, fuseOptions); fuse_main(_argv.size(), _argv.data(), operations(), this); } vector Fuse::_build_argv(const bf::path &mountdir, const vector &fuseOptions) { vector argv; argv.reserve(6 + fuseOptions.size()); // fuseOptions + executable name + mountdir + 2x fuse options (subtype, fsname), each taking 2 entries ("-o", "key=value"). argv.push_back(_create_c_string(_fstype)); // The first argument (executable name) is the file system type argv.push_back(_create_c_string(mountdir.string())); // The second argument is the mountdir for (const string &option : fuseOptions) { argv.push_back(_create_c_string(option)); } _add_fuse_option_if_not_exists(&argv, "subtype", _fstype); _add_fuse_option_if_not_exists(&argv, "fsname", _fsname.get_value_or(_fstype)); #ifdef __APPLE__ // Make volume name default to mountdir on macOS _add_fuse_option_if_not_exists(&argv, "volname", mountdir.filename().string()); #endif // TODO Also set read/write size for osxfuse. The options there are called differently. // large_read not necessary because reads are large anyhow. This option is only important for 2.4. //argv.push_back(_create_c_string("-o")); //argv.push_back(_create_c_string("large_read")); argv.push_back(_create_c_string("-o")); argv.push_back(_create_c_string("big_writes")); return argv; } void Fuse::_add_fuse_option_if_not_exists(vector *argv, const string &key, const string &value) { if(!_has_option(*argv, key)) { argv->push_back(_create_c_string("-o")); argv->push_back(_create_c_string(key + "=" + value)); } } bool Fuse::_has_option(const vector &vec, const string &key) { // The fuse option can either be present as "-okey=value" or as "-o key=value", we have to check both. return _has_entry_with_prefix(key + "=", vec) || _has_entry_with_prefix("-o" + key + "=", vec); } bool Fuse::_has_entry_with_prefix(const string &prefix, const vector &vec) { auto found = std::find_if(vec.begin(), vec.end(), [&prefix](const char *entry) { return 0 == std::strncmp(prefix.c_str(), entry, prefix.size()); }); return found != vec.end(); } char *Fuse::_create_c_string(const string &str) { // The memory allocated here is destroyed in the destructor of the Fuse class. char *c_str = new char[str.size()+1]; std::memcpy(c_str, str.c_str(), str.size()+1); return c_str; } bool Fuse::running() const { return _running; } void Fuse::stop() { unmount(_mountdir, false); } void Fuse::unmount(const bf::path& mountdir, bool force) { //TODO Find better way to unmount (i.e. don't use external fusermount). Unmounting by kill(getpid(), SIGINT) worked, but left the mount directory transport endpoint as not connected. #if defined(__APPLE__) UNUSED(force); int returncode = cpputils::Subprocess::call(std::string("umount ") + mountdir.string()).exitcode; #elif defined(_MSC_VER) UNUSED(force); std::wstring mountdir_ = std::wstring_convert>().from_bytes(mountdir.string()); BOOL success = DokanRemoveMountPoint(mountdir_.c_str()); int returncode = success ? 0 : -1; #else std::string command = force ? "fusermount -u" : "fusermount -z -u"; // "-z" takes care that if the filesystem can't be unmounted right now because something is opened, it will be unmounted as soon as it can be. int returncode = cpputils::Subprocess::call( command + " " + mountdir.string()).exitcode; #endif if (returncode != 0) { throw std::runtime_error("Could not unmount filesystem"); } } int Fuse::getattr(const bf::path &path, fspp::fuse::STAT *stbuf) { ThreadNameForDebugging _threadName("getattr"); #ifdef FSPP_LOG LOG(DEBUG, "getattr({}, _, _)", path); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); _fs->lstat(path, stbuf); #ifdef FSPP_LOG LOG(DEBUG, "getattr({}, _, _): success", path); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::getattr: {}", e.what()); return -EIO; } catch(const fspp::fuse::FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "getattr({}, _, _): failed with errno {}", path, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::fgetattr(const bf::path &path, fspp::fuse::STAT *stbuf, fuse_file_info *fileinfo) { ThreadNameForDebugging _threadName("fgetattr"); #ifdef FSPP_LOG LOG(DEBUG, "fgetattr({}, _, _)", path); #endif // On FreeBSD, trying to do anything with the mountpoint ends up // opening it, and then using the FD for an fgetattr. So in the // special case of a path of "/", I need to do a getattr on the // underlying base directory instead of doing the fgetattr(). // TODO Check if necessary if (path.string() == "/") { int result = getattr(path, stbuf); #ifdef FSPP_LOG LOG(DEBUG, "fgetattr({}, _, _): success", path); #endif return result; } try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); _fs->fstat(fileinfo->fh, stbuf); #ifdef FSPP_LOG LOG(DEBUG, "fgetattr({}, _, _): success", path); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::fgetattr: {}", e.what()); return -EIO; } catch(const fspp::fuse::FuseErrnoException &e) { #ifdef FSPP_LOG LOG(ERR, "fgetattr({}, _, _): error", path); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::readlink(const bf::path &path, char *buf, size_t size) { ThreadNameForDebugging _threadName("readlink"); #ifdef FSPP_LOG LOG(DEBUG, "readlink({}, _, {})", path, size); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); _fs->readSymlink(path, buf, fspp::num_bytes_t(size)); #ifdef FSPP_LOG LOG(DEBUG, "readlink({}, _, {}): success", path, size); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::readlink: {}", e.what()); return -EIO; } catch (fspp::fuse::FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "readlink({}, _, {}): failed with errno {}", path, size, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::mknod(const bf::path &path, ::mode_t mode, dev_t rdev) { UNUSED(rdev); UNUSED(mode); UNUSED(path); ThreadNameForDebugging _threadName("mknod"); LOG(WARN, "Called non-implemented mknod({}, {}, _)", path, mode); return ENOSYS; } int Fuse::mkdir(const bf::path &path, ::mode_t mode) { ThreadNameForDebugging _threadName("mkdir"); #ifdef FSPP_LOG LOG(DEBUG, "mkdir({}, {})", path, mode); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); // DokanY seems to call mkdir("/"). Ignore that if ("/" == path) { #ifdef FSPP_LOG LOG(DEBUG, "mkdir({}, {}): ignored", path, mode); #endif return 0; } auto context = fuse_get_context(); _fs->mkdir(path, mode, context->uid, context->gid); #ifdef FSPP_LOG LOG(DEBUG, "mkdir({}, {}): success", path, mode); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::mkdir: {}", e.what()); return -EIO; } catch(const fspp::fuse::FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "mkdir({}, {}): failed with errno {}", path, mode, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::unlink(const bf::path &path) { ThreadNameForDebugging _threadName("unlink"); #ifdef FSPP_LOG LOG(DEBUG, "unlink({})", path); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); _fs->unlink(path); #ifdef FSPP_LOG LOG(DEBUG, "unlink({}): success", path); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::unlink: {}", e.what()); return -EIO; } catch(const fspp::fuse::FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "unlink({}): failed with errno {}", path, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::rmdir(const bf::path &path) { ThreadNameForDebugging _threadName("rmdir"); #ifdef FSPP_LOG LOG(DEBUG, "rmdir({})", path); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); _fs->rmdir(path); #ifdef FSPP_LOG LOG(DEBUG, "rmdir({}): success", path); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::rmdir: {}", e.what()); return -EIO; } catch(const fspp::fuse::FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "rmdir({}): failed with errno {}", path, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::symlink(const bf::path &to, const bf::path &from) { ThreadNameForDebugging _threadName("symlink"); #ifdef FSPP_LOG LOG(DEBUG, "symlink({}, {})", to, from); #endif try { ASSERT(is_valid_fspp_path(from), "has to be an absolute path"); auto context = fuse_get_context(); _fs->createSymlink(to, from, context->uid, context->gid); #ifdef FSPP_LOG LOG(DEBUG, "symlink({}, {}): success", to, from); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::symlink: {}", e.what()); return -EIO; } catch(const fspp::fuse::FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "symlink({}, {}): failed with errno {}", to, from, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::rename(const bf::path &from, const bf::path &to) { ThreadNameForDebugging _threadName("rename"); #ifdef FSPP_LOG LOG(DEBUG, "rename({}, {})", from, to); #endif try { ASSERT(is_valid_fspp_path(from), "from has to be an absolute path"); ASSERT(is_valid_fspp_path(to), "rename target has to be an absolute path. If this assert throws, we have to add code here that makes the path absolute."); _fs->rename(from, to); #ifdef FSPP_LOG LOG(DEBUG, "rename({}, {}): success", from, to); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::rename: {}", e.what()); return -EIO; } catch(const fspp::fuse::FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "rename({}, {}): failed with errno {}", from, to, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } //TODO int Fuse::link(const bf::path &from, const bf::path &to) { ThreadNameForDebugging _threadName("link"); LOG(WARN, "NOT IMPLEMENTED: link({}, {})", from, to); //auto real_from = _impl->RootDir() / from; //auto real_to = _impl->RootDir() / to; //int retstat = ::link(real_from.string().c_str(), real_to.string().c_str()); //return errcode_map(retstat); return ENOSYS; } int Fuse::chmod(const bf::path &path, ::mode_t mode) { ThreadNameForDebugging _threadName("chmod"); #ifdef FSPP_LOG LOG(DEBUG, "chmod({}, {})", path, mode); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); _fs->chmod(path, mode); #ifdef FSPP_LOG LOG(DEBUG, "chmod({}, {}): success", path, mode); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::chmod: {}", e.what()); return -EIO; } catch (fspp::fuse::FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "chmod({}, {}): failed with errno {}", path, mode, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::chown(const bf::path &path, ::uid_t uid, ::gid_t gid) { ThreadNameForDebugging _threadName("chown"); #ifdef FSPP_LOG LOG(DEBUG, "chown({}, {}, {})", path, uid, gid); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); _fs->chown(path, uid, gid); #ifdef FSPP_LOG LOG(DEBUG, "chown({}, {}, {}): success", path, uid, gid); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::chown: {}", e.what()); return -EIO; } catch (fspp::fuse::FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "chown({}, {}, {}): failed with errno {}", path, uid, gid, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::truncate(const bf::path &path, int64_t size) { ThreadNameForDebugging _threadName("truncate"); #ifdef FSPP_LOG LOG(DEBUG, "truncate({}, {})", path, size); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); _fs->truncate(path, fspp::num_bytes_t(size)); #ifdef FSPP_LOG LOG(DEBUG, "truncate({}, {}): success", path, size); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::truncate: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "truncate({}, {}): failed with errno {}", path, size, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::ftruncate(const bf::path &path, int64_t size, fuse_file_info *fileinfo) { ThreadNameForDebugging _threadName("ftruncate"); #ifdef FSPP_LOG LOG(DEBUG, "ftruncate({}, {})", path, size); #endif UNUSED(path); try { _fs->ftruncate(fileinfo->fh, fspp::num_bytes_t(size)); #ifdef FSPP_LOG LOG(DEBUG, "ftruncate({}, {}): success", path, size); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::ftruncate: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "ftruncate({}, {}): failed with errno {}", path, size, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::utimens(const bf::path &path, const std::array times) { ThreadNameForDebugging _threadName("utimens"); #ifdef FSPP_LOG LOG(DEBUG, "utimens({}, _)", path); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); _fs->utimens(path, times[0], times[1]); #ifdef FSPP_LOG LOG(DEBUG, "utimens({}, _): success", path); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::utimens: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "utimens({}, _): failed with errno {}", path, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::open(const bf::path &path, fuse_file_info *fileinfo) { ThreadNameForDebugging _threadName("open"); #ifdef FSPP_LOG LOG(DEBUG, "open({}, _)", path); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); fileinfo->fh = _fs->openFile(path, fileinfo->flags); #ifdef FSPP_LOG LOG(DEBUG, "open({}, _): success", path); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::open: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "open({}, _): failed with errno {}", path, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::release(const bf::path &path, fuse_file_info *fileinfo) { ThreadNameForDebugging _threadName("release"); #ifdef FSPP_LOG LOG(DEBUG, "release({}, _)", path); #endif UNUSED(path); try { _fs->closeFile(fileinfo->fh); #ifdef FSPP_LOG LOG(DEBUG, "release({}, _): success", path); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::release: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "release({}, _): failed with errno {}", path, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::read(const bf::path &path, char *buf, size_t size, int64_t offset, fuse_file_info *fileinfo) { ThreadNameForDebugging _threadName("read"); #ifdef FSPP_LOG LOG(DEBUG, "read({}, _, {}, {}, _)", path, size, offset); #endif UNUSED(path); try { int result = _fs->read(fileinfo->fh, buf, fspp::num_bytes_t(size), fspp::num_bytes_t(offset)).value(); #ifdef FSPP_LOG LOG(DEBUG, "read({}, _, {}, {}, _): success with {}", path, size, offset, result); #endif return result; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::read: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "read({}, _, {}, {}, _): failed with errno {}", path, size, offset, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::write(const bf::path &path, const char *buf, size_t size, int64_t offset, fuse_file_info *fileinfo) { ThreadNameForDebugging _threadName("write"); #ifdef FSPP_LOG LOG(DEBUG, "write({}, _, {}, {}, _)", path, size, offset); #endif UNUSED(path); try { _fs->write(fileinfo->fh, buf, fspp::num_bytes_t(size), fspp::num_bytes_t(offset)); #ifdef FSPP_LOG LOG(DEBUG, "write({}, _, {}, {}, _): success", path, size, offset); #endif return size; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::write: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "write({}, _, {}, {}, _): failed with errno {}", path, size, offset, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::statfs(const bf::path &path, struct ::statvfs *fsstat) { ThreadNameForDebugging _threadName("statfs"); #ifdef FSPP_LOG LOG(DEBUG, "statfs({}, _)", path); #endif UNUSED(path); try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); _fs->statfs(fsstat); #ifdef FSPP_LOG LOG(DEBUG, "statfs({}, _): success", path); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::statfs: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "statfs({}, _): failed with errno {}", path, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::flush(const bf::path &path, fuse_file_info *fileinfo) { ThreadNameForDebugging _threadName("flush"); #ifdef FSPP_LOG LOG(WARN, "flush({}, _)", path); #endif UNUSED(path); try { _fs->flush(fileinfo->fh); #ifdef FSPP_LOG LOG(WARN, "flush({}, _): success", path); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::flush: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "flush({}, _): failed with errno {}", path, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::fsync(const bf::path &path, int datasync, fuse_file_info *fileinfo) { ThreadNameForDebugging _threadName("fsync"); #ifdef FSPP_LOG LOG(DEBUG, "fsync({}, {}, _)", path, datasync); #endif UNUSED(path); try { if (datasync) { _fs->fdatasync(fileinfo->fh); } else { _fs->fsync(fileinfo->fh); } #ifdef FSPP_LOG LOG(DEBUG, "fsync({}, {}, _): success", path, datasync); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::fsync: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "fsync({}, {}, _): failed with errno {}", path, datasync, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::opendir(const bf::path &path, fuse_file_info *fileinfo) { UNUSED(path); UNUSED(fileinfo); ThreadNameForDebugging _threadName("opendir"); //LOG(DEBUG, "opendir({}, _)", path); //We don't need opendir, because readdir works directly on the path return 0; } int Fuse::readdir(const bf::path &path, void *buf, fuse_fill_dir_t filler, int64_t offset, fuse_file_info *fileinfo) { ThreadNameForDebugging _threadName("readdir"); #ifdef FSPP_LOG LOG(DEBUG, "readdir({}, _, _, {}, _)", path, offset); #endif UNUSED(fileinfo); UNUSED(offset); try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); auto entries = _fs->readDir(path); fspp::fuse::STAT stbuf{}; for (const auto &entry : *entries) { //We could pass more file metadata to filler() in its third parameter, //but it doesn't help performance since fuse ignores everything in stbuf //except for file-type bits in st_mode and (if used) st_ino. //It does getattr() calls on all entries nevertheless. if (entry.type == Dir::EntryType::DIR) { stbuf.st_mode = S_IFDIR; } else if (entry.type == Dir::EntryType::FILE) { stbuf.st_mode = S_IFREG; } else if (entry.type == Dir::EntryType::SYMLINK) { stbuf.st_mode = S_IFLNK; } else { ASSERT(false, "Unknown entry type"); } if (filler(buf, entry.name.c_str(), &stbuf, 0) != 0) { #ifdef FSPP_LOG LOG(DEBUG, "readdir({}, _, _, {}, _): failure with ENOMEM", path, offset); #endif return -ENOMEM; } } #ifdef FSPP_LOG LOG(DEBUG, "readdir({}, _, _, {}, _): success", path, offset); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::readdir: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "readdir({}, _, _, {}, _): failed with errno {}", path, offset, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::releasedir(const bf::path &path, fuse_file_info *fileinfo) { UNUSED(path); UNUSED(fileinfo); ThreadNameForDebugging _threadName("releasedir"); //LOG(DEBUG, "releasedir({}, _)", path); //We don't need releasedir, because readdir works directly on the path return 0; } //TODO int Fuse::fsyncdir(const bf::path &path, int datasync, fuse_file_info *fileinfo) { UNUSED(fileinfo); UNUSED(datasync); UNUSED(path); ThreadNameForDebugging _threadName("fsyncdir"); //LOG(WARN, "Called non-implemented fsyncdir({}, {}, _)", path, datasync); return 0; } void Fuse::init(fuse_conn_info *conn) { UNUSED(conn); ThreadNameForDebugging _threadName("init"); _fs = _init(this); LOG(INFO, "Filesystem started."); _running = true; _onMounted(); #ifdef FSPP_LOG cpputils::logging::setLevel(DEBUG); #endif } void Fuse::destroy() { ThreadNameForDebugging _threadName("destroy"); _fs = make_shared(); LOG(INFO, "Filesystem stopped."); _running = false; cpputils::logging::logger()->flush(); } int Fuse::access(const bf::path &path, int mask) { ThreadNameForDebugging _threadName("access"); #ifdef FSPP_LOG LOG(DEBUG, "access({}, {})", path, mask); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); _fs->access(path, mask); #ifdef FSPP_LOG LOG(DEBUG, "access({}, {}): success", path, mask); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::access: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "access({}, {}): failed with errno {}", path, mask, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } int Fuse::create(const bf::path &path, ::mode_t mode, fuse_file_info *fileinfo) { ThreadNameForDebugging _threadName("create"); #ifdef FSPP_LOG LOG(DEBUG, "create({}, {}, _)", path, mode); #endif try { ASSERT(is_valid_fspp_path(path), "has to be an absolute path"); auto context = fuse_get_context(); fileinfo->fh = _fs->createAndOpenFile(path, mode, context->uid, context->gid); #ifdef FSPP_LOG LOG(DEBUG, "create({}, {}, _): success", path, mode); #endif return 0; } catch(const cpputils::AssertFailed &e) { LOG(ERR, "AssertFailed in Fuse::create: {}", e.what()); return -EIO; } catch (FuseErrnoException &e) { #ifdef FSPP_LOG LOG(WARN, "create({}, {}, _): failed with errno {}", path, mode, e.getErrno()); #endif return -e.getErrno(); } catch(const std::exception &e) { _logException(e); return -EIO; } catch(...) { _logUnknownException(); return -EIO; } } src/fspp/fuse/Fuse.h000066400000000000000000000100131347701267100146350ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FUSE_FUSE_H_ #define MESSMER_FSPP_FUSE_FUSE_H_ #include "params.h" #include #include #include #include #include #include #include #include #include "stat_compatibility.h" namespace fspp { class Device; namespace fuse { class Filesystem; class Fuse final { public: explicit Fuse(std::function (Fuse *fuse)> init, std::function onMounted, std::string fstype, boost::optional fsname); ~Fuse(); void run(const boost::filesystem::path &mountdir, const std::vector &fuseOptions); bool running() const; void stop(); static void unmount(const boost::filesystem::path &mountdir, bool force = false); int getattr(const boost::filesystem::path &path, fspp::fuse::STAT *stbuf); int fgetattr(const boost::filesystem::path &path, fspp::fuse::STAT *stbuf, fuse_file_info *fileinfo); int readlink(const boost::filesystem::path &path, char *buf, size_t size); int mknod(const boost::filesystem::path &path, ::mode_t mode, dev_t rdev); int mkdir(const boost::filesystem::path &path, ::mode_t mode); int unlink(const boost::filesystem::path &path); int rmdir(const boost::filesystem::path &path); int symlink(const boost::filesystem::path &from, const boost::filesystem::path &to); int rename(const boost::filesystem::path &from, const boost::filesystem::path &to); int link(const boost::filesystem::path &from, const boost::filesystem::path &to); int chmod(const boost::filesystem::path &path, ::mode_t mode); int chown(const boost::filesystem::path &path, ::uid_t uid, ::gid_t gid); int truncate(const boost::filesystem::path &path, int64_t size); int ftruncate(const boost::filesystem::path &path, int64_t size, fuse_file_info *fileinfo); int utimens(const boost::filesystem::path &path, const std::array times); int open(const boost::filesystem::path &path, fuse_file_info *fileinfo); int release(const boost::filesystem::path &path, fuse_file_info *fileinfo); int read(const boost::filesystem::path &path, char *buf, size_t size, int64_t offset, fuse_file_info *fileinfo); int write(const boost::filesystem::path &path, const char *buf, size_t size, int64_t offset, fuse_file_info *fileinfo); int statfs(const boost::filesystem::path &path, struct ::statvfs *fsstat); int flush(const boost::filesystem::path &path, fuse_file_info *fileinfo); int fsync(const boost::filesystem::path &path, int flags, fuse_file_info *fileinfo); int opendir(const boost::filesystem::path &path, fuse_file_info *fileinfo); int readdir(const boost::filesystem::path &path, void *buf, fuse_fill_dir_t filler, int64_t offset, fuse_file_info *fileinfo); int releasedir(const boost::filesystem::path &path, fuse_file_info *fileinfo); int fsyncdir(const boost::filesystem::path &path, int datasync, fuse_file_info *fileinfo); void init(fuse_conn_info *conn); void destroy(); int access(const boost::filesystem::path &path, int mask); int create(const boost::filesystem::path &path, ::mode_t mode, fuse_file_info *fileinfo); private: static void _logException(const std::exception &e); static void _logUnknownException(); static char *_create_c_string(const std::string &str); static bool _has_option(const std::vector &vec, const std::string &key); static bool _has_entry_with_prefix(const std::string &prefix, const std::vector &vec); std::vector _build_argv(const boost::filesystem::path &mountdir, const std::vector &fuseOptions); void _add_fuse_option_if_not_exists(std::vector *argv, const std::string &key, const std::string &value); std::function (Fuse *fuse)> _init; std::function _onMounted; std::shared_ptr _fs; boost::filesystem::path _mountdir; std::vector _argv; std::atomic _running; std::string _fstype; boost::optional _fsname; DISALLOW_COPY_AND_ASSIGN(Fuse); }; } } #endif src/fspp/fuse/InvalidFilesystem.h000066400000000000000000000103131347701267100173710ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FUSE_INVALIDFILESYSTEM_H_ #define MESSMER_FSPP_FUSE_INVALIDFILESYSTEM_H_ #include "Filesystem.h" namespace fspp { namespace fuse { class InvalidFilesystem final : public Filesystem { int createAndOpenFile(const boost::filesystem::path &, ::mode_t , ::uid_t , ::gid_t ) override { throw std::logic_error("Filesystem not initialized yet"); } int openFile(const boost::filesystem::path &, int ) override { throw std::logic_error("Filesystem not initialized yet"); } void flush(int ) override { throw std::logic_error("Filesystem not initialized yet"); } void closeFile(int ) override { throw std::logic_error("Filesystem not initialized yet"); } void lstat(const boost::filesystem::path &, fspp::fuse::STAT *) override { throw std::logic_error("Filesystem not initialized yet"); } void fstat(int , fspp::fuse::STAT *) override { throw std::logic_error("Filesystem not initialized yet"); } void chmod(const boost::filesystem::path &, ::mode_t ) override { throw std::logic_error("Filesystem not initialized yet"); } void chown(const boost::filesystem::path &, ::uid_t , ::gid_t ) override { throw std::logic_error("Filesystem not initialized yet"); } void truncate(const boost::filesystem::path &, fspp::num_bytes_t ) override { throw std::logic_error("Filesystem not initialized yet"); } void ftruncate(int , fspp::num_bytes_t ) override { throw std::logic_error("Filesystem not initialized yet"); } fspp::num_bytes_t read(int , void *, fspp::num_bytes_t , fspp::num_bytes_t ) override { throw std::logic_error("Filesystem not initialized yet"); } void write(int , const void *, fspp::num_bytes_t , fspp::num_bytes_t ) override { throw std::logic_error("Filesystem not initialized yet"); } void fsync(int ) override { throw std::logic_error("Filesystem not initialized yet"); } void fdatasync(int ) override { throw std::logic_error("Filesystem not initialized yet"); } void access(const boost::filesystem::path &, int ) override { throw std::logic_error("Filesystem not initialized yet"); } void mkdir(const boost::filesystem::path &, ::mode_t , ::uid_t , ::gid_t ) override { throw std::logic_error("Filesystem not initialized yet"); } void rmdir(const boost::filesystem::path &) override { throw std::logic_error("Filesystem not initialized yet"); } void unlink(const boost::filesystem::path &) override { throw std::logic_error("Filesystem not initialized yet"); } void rename(const boost::filesystem::path &, const boost::filesystem::path &) override { throw std::logic_error("Filesystem not initialized yet"); } void utimens(const boost::filesystem::path &, timespec , timespec ) override { throw std::logic_error("Filesystem not initialized yet"); } void statfs(struct ::statvfs *) override { throw std::logic_error("Filesystem not initialized yet"); } cpputils::unique_ref> readDir(const boost::filesystem::path &) override { throw std::logic_error("Filesystem not initialized yet"); } void createSymlink(const boost::filesystem::path &, const boost::filesystem::path &, ::uid_t , ::gid_t ) override { throw std::logic_error("Filesystem not initialized yet"); } void readSymlink(const boost::filesystem::path &, char *, fspp::num_bytes_t ) override { throw std::logic_error("Filesystem not initialized yet"); } }; } } #endif src/fspp/fuse/params.h000066400000000000000000000005071347701267100152250ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FUSE_PARAMS_H_ #define MESSMER_FSPP_FUSE_PARAMS_H_ #define FUSE_USE_VERSION 26 #if defined(__linux__) || defined(__FreeBSD__) #include #elif __APPLE__ #include #elif defined(_MSC_VER) #include // Dokany fuse #else #error System not supported #endif #endif src/fspp/fuse/stat_compatibility.h000066400000000000000000000007751347701267100176550ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FUSE_STATCOMPATIBILITY_H #define MESSMER_FSPP_FUSE_STATCOMPATIBILITY_H namespace fspp { namespace fuse { // Dokan has a different "struct stat" called "fspp::fuse::STAT", but it's compatible. // To make our code work with both, we use "STAT" everywhere instead of "stat" // and define it here to the correct type #if defined(_MSC_VER) #include typedef struct FUSE_STAT STAT; #else #include typedef struct ::stat STAT; #endif } } #endif src/fspp/impl/000077500000000000000000000000001347701267100135665ustar00rootroot00000000000000src/fspp/impl/FilesystemImpl.cpp000066400000000000000000000317731347701267100172530ustar00rootroot00000000000000#include "FilesystemImpl.h" #include #include "../fs_interface/Device.h" #include "../fs_interface/Dir.h" #include "../fs_interface/Symlink.h" #include "../fs_interface/FuseErrnoException.h" #include "../fs_interface/File.h" #include "../fs_interface/Node.h" #include #include #include #include using namespace fspp; using cpputils::unique_ref; using std::vector; using std::string; using boost::none; namespace bf = boost::filesystem; using namespace cpputils::logging; #ifdef FSPP_PROFILE #include "Profiler.h" #include #include #define PROFILE(name) Profiler profiler_##name(&name); #else #define PROFILE(name) #endif FilesystemImpl::FilesystemImpl(cpputils::unique_ref device) : #ifdef FSPP_PROFILE _loadFileNanosec(0), _loadDirNanosec(0), _loadSymlinkNanosec(0), _openFileNanosec(0), _flushNanosec(0), _closeFileNanosec(0), _lstatNanosec(0), _fstatNanosec(0), _chmodNanosec(0), _chownNanosec(0), _truncateNanosec(0), _ftruncateNanosec(0), _readNanosec(0), _writeNanosec(0), _fsyncNanosec(0), _fdatasyncNanosec(0), _accessNanosec(0), _createAndOpenFileNanosec(0), _createAndOpenFileNanosec_withoutLoading(0), _mkdirNanosec(0), _mkdirNanosec_withoutLoading(0), _rmdirNanosec(0), _rmdirNanosec_withoutLoading(0), _unlinkNanosec(0), _unlinkNanosec_withoutLoading(0), _renameNanosec(0), _readDirNanosec(0), _readDirNanosec_withoutLoading(0), _utimensNanosec(0), _statfsNanosec(0), _createSymlinkNanosec(0), _createSymlinkNanosec_withoutLoading(0), _readSymlinkNanosec(0), _readSymlinkNanosec_withoutLoading(0), #endif _device(std::move(device)), _open_files() { } FilesystemImpl::~FilesystemImpl() { #ifdef FSPP_PROFILE std::ostringstream profilerInformation; profilerInformation << "Profiler Information\n" << std::fixed << std::setprecision(6) << std::setw(40) << "LoadFile: " << static_cast(_loadFileNanosec)/1000000000 << "\n" << std::setw(40) << "LoadDir: " << static_cast(_loadDirNanosec)/1000000000 << "\n" << std::setw(40) << "LoadSymlink: " << static_cast(_loadSymlinkNanosec)/1000000000 << "\n" << std::setw(40) << "OpenFile: " << static_cast(_openFileNanosec)/1000000000 << "\n" << std::setw(40) << "Flush: " << static_cast(_flushNanosec)/1000000000 << "\n" << std::setw(40) << "CloseFile: " << static_cast(_closeFileNanosec)/1000000000 << "\n" << std::setw(40) << "Lstat: " << static_cast(_lstatNanosec)/1000000000 << "\n" << std::setw(40) << "Fstat: " << static_cast(_fstatNanosec)/1000000000 << "\n" << std::setw(40) << "Chmod: " << static_cast(_chmodNanosec)/1000000000 << "\n" << std::setw(40) << "Chown: " << static_cast(_chownNanosec)/1000000000 << "\n" << std::setw(40) << "Truncate: " << static_cast(_truncateNanosec)/1000000000 << "\n" << std::setw(40) << "Ftruncate: " << static_cast(_ftruncateNanosec)/1000000000 << "\n" << std::setw(40) << "Read: " << static_cast(_readNanosec)/1000000000 << "\n" << std::setw(40) << "Write: " << static_cast(_writeNanosec)/1000000000 << "\n" << std::setw(40) << "Fsync: " << static_cast(_fsyncNanosec)/1000000000 << "\n" << std::setw(40) << "Fdatasync: " << static_cast(_fdatasyncNanosec)/1000000000 << "\n" << std::setw(40) << "Access: " << static_cast(_accessNanosec)/1000000000 << "\n" << std::setw(40) << "CreateAndOpenFile: " << static_cast(_createAndOpenFileNanosec)/1000000000 << "\n" << std::setw(40) << "CreateAndOpenFile (without loading): " << static_cast(_createAndOpenFileNanosec_withoutLoading)/1000000000 << "\n" << std::setw(40) << "Mkdir: " << static_cast(_mkdirNanosec)/1000000000 << "\n" << std::setw(40) << "Mkdir (without loading): " << static_cast(_mkdirNanosec_withoutLoading)/1000000000 << "\n" << std::setw(40) << "Rmdir: " << static_cast(_rmdirNanosec)/1000000000 << "\n" << std::setw(40) << "Rmdir (without loading): " << static_cast(_rmdirNanosec_withoutLoading)/1000000000 << "\n" << std::setw(40) << "Unlink: " << static_cast(_unlinkNanosec)/1000000000 << "\n" << std::setw(40) << "Unlink (without loading): " << static_cast(_unlinkNanosec_withoutLoading)/1000000000 << "\n" << std::setw(40) << "Rename: " << static_cast(_renameNanosec)/1000000000 << "\n" << std::setw(40) << "ReadDir: " << static_cast(_readDirNanosec)/1000000000 << "\n" << std::setw(40) << "ReadDir (without loading): " << static_cast(_readDirNanosec_withoutLoading)/1000000000 << "\n" << std::setw(40) << "Utimens: " << static_cast(_utimensNanosec)/1000000000 << "\n" << std::setw(40) << "Statfs: " << static_cast(_statfsNanosec)/1000000000 << "\n" << std::setw(40) << "CreateSymlink: " << static_cast(_createSymlinkNanosec)/1000000000 << "\n" << std::setw(40) << "CreateSymlink (without loading): " << static_cast(_createSymlinkNanosec_withoutLoading)/1000000000 << "\n" << std::setw(40) << "ReadSymlink: " << static_cast(_readSymlinkNanosec)/1000000000 << "\n" << std::setw(40) << "ReadSymlink (without loading): " << static_cast(_readSymlinkNanosec_withoutLoading)/1000000000 << "\n"; LOG(INFO, profilerInformation.str()); #endif } unique_ref FilesystemImpl::LoadFile(const bf::path &path) { PROFILE(_loadFileNanosec); auto file = _device->LoadFile(path); if (file == none) { throw fuse::FuseErrnoException(EIO); } return std::move(*file); } unique_ref FilesystemImpl::LoadDir(const bf::path &path) { PROFILE(_loadDirNanosec); auto dir = _device->LoadDir(path); if (dir == none) { throw fuse::FuseErrnoException(EIO); } return std::move(*dir); } unique_ref FilesystemImpl::LoadSymlink(const bf::path &path) { PROFILE(_loadSymlinkNanosec); auto lnk = _device->LoadSymlink(path); if (lnk == none) { throw fuse::FuseErrnoException(EIO); } return std::move(*lnk); } int FilesystemImpl::openFile(const bf::path &path, int flags) { auto file = LoadFile(path); return openFile(file.get(), flags); } int FilesystemImpl::openFile(File *file, int flags) { PROFILE(_openFileNanosec); return _open_files.open(file->open(fspp::openflags_t(flags))); } void FilesystemImpl::flush(int descriptor) { PROFILE(_flushNanosec); _open_files.load(descriptor, [](OpenFile* openFile) { openFile->flush(); }); } void FilesystemImpl::closeFile(int descriptor) { PROFILE(_closeFileNanosec); _open_files.close(descriptor); } namespace { void convert_stat_info_(const fspp::Node::stat_info& input, fspp::fuse::STAT *output) { output->st_nlink = input.nlink; output->st_mode = input.mode.value(); output->st_uid = input.uid.value(); output->st_gid = input.gid.value(); output->st_size = input.size.value(); output->st_blocks = input.blocks; output->st_atim = input.atime; output->st_mtim = input.mtime; output->st_ctim = input.ctime; } } void FilesystemImpl::lstat(const bf::path &path, fspp::fuse::STAT *stbuf) { PROFILE(_lstatNanosec); auto node = _device->Load(path); if(node == none) { throw fuse::FuseErrnoException(ENOENT); } else { auto stat_info = (*node)->stat(); convert_stat_info_(stat_info, stbuf); } } void FilesystemImpl::fstat(int descriptor, fspp::fuse::STAT *stbuf) { PROFILE(_fstatNanosec); auto stat_info = _open_files.load(descriptor, [] (OpenFile* openFile) { return openFile->stat(); }); convert_stat_info_(stat_info, stbuf); } void FilesystemImpl::chmod(const boost::filesystem::path &path, ::mode_t mode) { PROFILE(_chmodNanosec); auto node = _device->Load(path); if(node == none) { throw fuse::FuseErrnoException(ENOENT); } else { (*node)->chmod(fspp::mode_t(mode)); } } void FilesystemImpl::chown(const boost::filesystem::path &path, ::uid_t uid, ::gid_t gid) { PROFILE(_chownNanosec); auto node = _device->Load(path); if(node == none) { throw fuse::FuseErrnoException(ENOENT); } else { (*node)->chown(fspp::uid_t(uid), fspp::gid_t(gid)); } } void FilesystemImpl::truncate(const bf::path &path, fspp::num_bytes_t size) { PROFILE(_truncateNanosec); LoadFile(path)->truncate(size); } void FilesystemImpl::ftruncate(int descriptor, fspp::num_bytes_t size) { PROFILE(_ftruncateNanosec); _open_files.load(descriptor, [size] (OpenFile* openFile) { openFile->truncate(size); }); } fspp::num_bytes_t FilesystemImpl::read(int descriptor, void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) { PROFILE(_readNanosec); return _open_files.load(descriptor, [buf, count, offset] (OpenFile* openFile) { return openFile->read(buf, count, offset); }); } void FilesystemImpl::write(int descriptor, const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) { PROFILE(_writeNanosec); return _open_files.load(descriptor, [buf, count, offset] (OpenFile* openFile) { return openFile->write(buf, count, offset); }); } void FilesystemImpl::fsync(int descriptor) { PROFILE(_fsyncNanosec); _open_files.load(descriptor, [] (OpenFile* openFile) { openFile->fsync(); }); } void FilesystemImpl::fdatasync(int descriptor) { PROFILE(_fdatasyncNanosec); _open_files.load(descriptor, [] (OpenFile* openFile) { openFile->fdatasync(); }); } void FilesystemImpl::access(const bf::path &path, int mask) { PROFILE(_accessNanosec); auto node = _device->Load(path); if(node == none) { throw fuse::FuseErrnoException(ENOENT); } else { (*node)->access(mask); } } int FilesystemImpl::createAndOpenFile(const bf::path &path, ::mode_t mode, ::uid_t uid, ::gid_t gid) { PROFILE(_createAndOpenFileNanosec); auto dir = LoadDir(path.parent_path()); PROFILE(_createAndOpenFileNanosec_withoutLoading); auto file = dir->createAndOpenFile(path.filename().string(), fspp::mode_t(mode), fspp::uid_t(uid), fspp::gid_t(gid)); return _open_files.open(std::move(file)); } void FilesystemImpl::mkdir(const bf::path &path, ::mode_t mode, ::uid_t uid, ::gid_t gid) { PROFILE(_mkdirNanosec); auto dir = LoadDir(path.parent_path()); PROFILE(_mkdirNanosec_withoutLoading); dir->createDir(path.filename().string(), fspp::mode_t(mode), fspp::uid_t(uid), fspp::gid_t(gid)); } void FilesystemImpl::rmdir(const bf::path &path) { //TODO Don't allow removing files/symlinks with this PROFILE(_rmdirNanosec); auto node = _device->Load(path); if(node == none) { throw fuse::FuseErrnoException(ENOENT); } PROFILE(_rmdirNanosec_withoutLoading); (*node)->remove(); } void FilesystemImpl::unlink(const bf::path &path) { //TODO Don't allow removing directories with this PROFILE(_unlinkNanosec); auto node = _device->Load(path); if (node == none) { throw fuse::FuseErrnoException(ENOENT); } PROFILE(_unlinkNanosec_withoutLoading); (*node)->remove(); } void FilesystemImpl::rename(const bf::path &from, const bf::path &to) { PROFILE(_renameNanosec); auto node = _device->Load(from); if(node == none) { throw fuse::FuseErrnoException(ENOENT); } else { (*node)->rename(to); } } unique_ref> FilesystemImpl::readDir(const bf::path &path) { PROFILE(_readDirNanosec); auto dir = LoadDir(path); PROFILE(_readDirNanosec_withoutLoading); return dir->children(); } void FilesystemImpl::utimens(const bf::path &path, timespec lastAccessTime, timespec lastModificationTime) { PROFILE(_utimensNanosec); auto node = _device->Load(path); if(node == none) { throw fuse::FuseErrnoException(ENOENT); } else { (*node)->utimens(lastAccessTime, lastModificationTime); } } void FilesystemImpl::statfs(struct ::statvfs *fsstat) { PROFILE(_statfsNanosec); Device::statvfs stat = _device->statfs(); fsstat->f_bsize = stat.blocksize; fsstat->f_blocks = stat.num_total_blocks; fsstat->f_bfree = stat.num_free_blocks; fsstat->f_bavail = stat.num_available_blocks; fsstat->f_files = stat.num_total_inodes; fsstat->f_ffree = stat.num_free_inodes; fsstat->f_favail = stat.num_available_inodes; fsstat->f_namemax = stat.max_filename_length; //f_frsize, f_favail, f_fsid and f_flag are ignored in fuse, see http://fuse.sourcearchive.com/documentation/2.7.0/structfuse__operations_4e765e29122e7b6b533dc99849a52655.html#4e765e29122e7b6b533dc99849a52655 fsstat->f_frsize = fsstat->f_bsize; // even though this is supposed to be ignored, osxfuse needs it. } void FilesystemImpl::createSymlink(const bf::path &to, const bf::path &from, ::uid_t uid, ::gid_t gid) { PROFILE(_createSymlinkNanosec); auto parent = LoadDir(from.parent_path()); PROFILE(_createSymlinkNanosec_withoutLoading); parent->createSymlink(from.filename().string(), to, fspp::uid_t(uid), fspp::gid_t(gid)); } void FilesystemImpl::readSymlink(const bf::path &path, char *buf, fspp::num_bytes_t size) { PROFILE(_readSymlinkNanosec); string target = LoadSymlink(path)->target().string(); PROFILE(_readSymlinkNanosec_withoutLoading); std::memcpy(buf, target.c_str(), std::min(static_cast(target.size()+1), size.value())); buf[size.value()-1] = '\0'; } src/fspp/impl/FilesystemImpl.h000066400000000000000000000106531347701267100167120ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_IMPL_FILESYSTEMIMPL_H_ #define MESSMER_FSPP_IMPL_FILESYSTEMIMPL_H_ #include "FuseOpenFileList.h" #include "../fuse/Filesystem.h" #include #include //Remove this line if you don't want profiling //#define FSPP_PROFILE 1 //TODO Test namespace fspp { class Node; class File; class Symlink; class OpenFile; class FilesystemImpl final: public fuse::Filesystem { public: explicit FilesystemImpl(cpputils::unique_ref device); virtual ~FilesystemImpl(); int openFile(const boost::filesystem::path &path, int flags) override; void flush(int descriptor) override; void closeFile(int descriptor) override; void lstat(const boost::filesystem::path &path, fspp::fuse::STAT *stbuf) override; void fstat(int descriptor, fspp::fuse::STAT *stbuf) override; void chmod(const boost::filesystem::path &path, ::mode_t mode) override; void chown(const boost::filesystem::path &path, ::uid_t uid, ::gid_t gid) override; void truncate(const boost::filesystem::path &path, fspp::num_bytes_t size) override; void ftruncate(int descriptor, fspp::num_bytes_t size) override; fspp::num_bytes_t read(int descriptor, void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) override; void write(int descriptor, const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) override; void fsync(int descriptor) override; void fdatasync(int descriptor) override; void access(const boost::filesystem::path &path, int mask) override; int createAndOpenFile(const boost::filesystem::path &path, ::mode_t mode, ::uid_t uid, ::gid_t gid) override; void mkdir(const boost::filesystem::path &path, ::mode_t mode, ::uid_t uid, ::gid_t gid) override; void rmdir(const boost::filesystem::path &path) override; void unlink(const boost::filesystem::path &path) override; void rename(const boost::filesystem::path &from, const boost::filesystem::path &to) override; cpputils::unique_ref> readDir(const boost::filesystem::path &path) override; void utimens(const boost::filesystem::path &path, timespec lastAccessTime, timespec lastModificationTime) override; void statfs(struct ::statvfs *fsstat) override; void createSymlink(const boost::filesystem::path &to, const boost::filesystem::path &from, ::uid_t uid, ::gid_t gid) override; void readSymlink(const boost::filesystem::path &path, char *buf, fspp::num_bytes_t size) override; private: cpputils::unique_ref LoadFile(const boost::filesystem::path &path); cpputils::unique_ref LoadDir(const boost::filesystem::path &path); cpputils::unique_ref LoadSymlink(const boost::filesystem::path &path); int openFile(File *file, int flags); #ifdef FSPP_PROFILE std::atomic _loadFileNanosec; std::atomic _loadDirNanosec; std::atomic _loadSymlinkNanosec; std::atomic _loadFileOrSymlinkNanosec; std::atomic _openFileNanosec; std::atomic _flushNanosec; std::atomic _closeFileNanosec; std::atomic _lstatNanosec; std::atomic _fstatNanosec; std::atomic _chmodNanosec; std::atomic _chownNanosec; std::atomic _truncateNanosec; std::atomic _ftruncateNanosec; std::atomic _readNanosec; std::atomic _writeNanosec; std::atomic _fsyncNanosec; std::atomic _fdatasyncNanosec; std::atomic _accessNanosec; std::atomic _createAndOpenFileNanosec; std::atomic _createAndOpenFileNanosec_withoutLoading; std::atomic _mkdirNanosec; std::atomic _mkdirNanosec_withoutLoading; std::atomic _rmdirNanosec; std::atomic _rmdirNanosec_withoutLoading; std::atomic _unlinkNanosec; std::atomic _unlinkNanosec_withoutLoading; std::atomic _renameNanosec; std::atomic _readDirNanosec; std::atomic _readDirNanosec_withoutLoading; std::atomic _utimensNanosec; std::atomic _statfsNanosec; std::atomic _createSymlinkNanosec; std::atomic _createSymlinkNanosec_withoutLoading; std::atomic _readSymlinkNanosec; std::atomic _readSymlinkNanosec_withoutLoading; #endif cpputils::unique_ref _device; FuseOpenFileList _open_files; DISALLOW_COPY_AND_ASSIGN(FilesystemImpl); }; } #endif src/fspp/impl/FuseOpenFileList.h000066400000000000000000000056061347701267100171260ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_IMPL_FUSEOPENFILELIST_H_ #define MESSMER_FSPP_IMPL_FUSEOPENFILELIST_H_ #include "../fs_interface/File.h" #include "../fs_interface/OpenFile.h" #include "../fs_interface/FuseErrnoException.h" #include #include #include "IdList.h" #include namespace fspp { namespace detail { class OnScopeExit final { public: explicit OnScopeExit(std::function handler) : _handler(std::move(handler)) {} ~OnScopeExit() { _handler(); } private: std::function _handler; }; } class FuseOpenFileList final { public: FuseOpenFileList(); ~FuseOpenFileList(); int open(cpputils::unique_ref file); template auto load(int descriptor, Func&& callback); void close(int descriptor); private: IdList _open_files; std::unordered_map _refcounts; std::mutex _mutex; std::condition_variable _refcount_zero_cv; DISALLOW_COPY_AND_ASSIGN(FuseOpenFileList); }; inline FuseOpenFileList::FuseOpenFileList() :_open_files(), _refcounts(), _mutex(), _refcount_zero_cv() { } inline FuseOpenFileList::~FuseOpenFileList() { std::unique_lock lock(_mutex); // Wait until all pending requests are done _refcount_zero_cv.wait(lock, [&] { for (const auto& refcount : _refcounts) { if (0 != refcount.second) { return false; } } return true; }); // There might still be open files when the file system is shutdown, so we can't assert it's empty. // But to check that _refcounts has been updated correctly, we can assert the invariant that we have as many // refcounts as open files. ASSERT(_refcounts.size() == _refcounts.size(), "Didn't clean up refcounts properly"); } inline int FuseOpenFileList::open(cpputils::unique_ref file) { std::lock_guard lock(_mutex); int descriptor = _open_files.add(std::move(file)); _refcounts.emplace(descriptor, 0); return descriptor; } template inline auto FuseOpenFileList::load(int descriptor, Func&& callback) { try { std::unique_lock lock(_mutex); _refcounts.at(descriptor) += 1; detail::OnScopeExit _([&] { if (!lock.owns_lock()) { // own_lock can be true when _open_files.get() below fails before the lock is unlocked lock.lock(); } _refcounts.at(descriptor) -= 1; _refcount_zero_cv.notify_all(); }); OpenFile* loaded = _open_files.get(descriptor); lock.unlock(); return std::forward(callback)(loaded); } catch (const std::out_of_range& e) { throw fspp::fuse::FuseErrnoException(EBADF); } } inline void FuseOpenFileList::close(int descriptor) { std::unique_lock lock(_mutex); _refcount_zero_cv.wait(lock, [&] { return 0 == _refcounts.at(descriptor); }); //The destructor of the stored FuseOpenFile closes the file _open_files.remove(descriptor); _refcounts.erase(descriptor); } } #endif src/fspp/impl/IdList.h000066400000000000000000000030251347701267100151270ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_IMPL_IDLIST_H_ #define MESSMER_FSPP_IMPL_IDLIST_H_ #include #include #include #include namespace fspp { template class IdList final { public: IdList(); virtual ~IdList(); int add(cpputils::unique_ref entry); Entry *get(int id); const Entry *get(int id) const; void remove(int id); size_t size() const; private: std::unordered_map> _entries; int _id_counter; DISALLOW_COPY_AND_ASSIGN(IdList); }; template IdList::IdList() : _entries(), _id_counter(0) { } template IdList::~IdList() { } template int IdList::add(cpputils::unique_ref entry) { //TODO Reuse IDs (ids = descriptors) int new_id = ++_id_counter; _entries.emplace(new_id, std::move(entry)); return new_id; } template Entry *IdList::get(int id) { return const_cast(const_cast*>(this)->get(id)); } template const Entry *IdList::get(int id) const { const Entry *result = _entries.at(id).get(); return result; } template void IdList::remove(int id) { auto found_iter = _entries.find(id); if (found_iter == _entries.end()) { throw std::out_of_range("Called IdList::remove() with an invalid ID"); } _entries.erase(found_iter); } template size_t IdList::size() const { return _entries.size(); } } #endif src/fspp/impl/Profiler.cpp000066400000000000000000000000261347701267100160520ustar00rootroot00000000000000#include "Profiler.h" src/fspp/impl/Profiler.h000066400000000000000000000016611347701267100155250ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_IMPL_PROFILER_H #define MESSMER_FSPP_IMPL_PROFILER_H #include #include #include namespace fspp { class Profiler final { public: Profiler(std::atomic_uint_fast64_t *targetForAddingNanosec); ~Profiler(); private: std::atomic_uint_fast64_t *_targetForAddingNanosec; std::chrono::high_resolution_clock::time_point _beginTime; DISALLOW_COPY_AND_ASSIGN(Profiler); }; inline Profiler::Profiler(std::atomic_uint_fast64_t *targetForAddingNanosec) : _targetForAddingNanosec(targetForAddingNanosec), _beginTime(std::chrono::high_resolution_clock::now()) { } inline Profiler::~Profiler() { uint64_t timeDiff = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - _beginTime).count(); *_targetForAddingNanosec += timeDiff; } } #endif src/gitversion/000077500000000000000000000000001347701267100140465ustar00rootroot00000000000000src/gitversion/.gitattributes000066400000000000000000000000711347701267100167370ustar00rootroot00000000000000_version_source.py export-subst _version.py export-subst src/gitversion/CMakeLists.txt000066400000000000000000000007211347701267100166060ustar00rootroot00000000000000project (gitversion) include(gitversion.cmake) get_git_version(GIT_VERSION) set(SOURCES gitversion.cpp versionstring.cpp parser.cpp VersionCompare.cpp ) add_library(${PROJECT_NAME} STATIC ${SOURCES}) target_link_libraries(${PROJECT_NAME}) target_compile_definitions(${PROJECT_NAME} PRIVATE GIT_VERSION_STRING="${GIT_VERSION}") target_add_boost(${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) src/gitversion/MANIFEST.in000066400000000000000000000001051347701267100156000ustar00rootroot00000000000000include versioneer.py include _version_source.py include _version.py src/gitversion/VersionCompare.cpp000066400000000000000000000030201347701267100175010ustar00rootroot00000000000000#include "VersionCompare.h" #include "parser.h" #include #include using std::string; namespace gitversion { bool VersionCompare::isOlderThan(const string &v1Str, const string &v2Str) { VersionInfo v1 = Parser::parse(v1Str); VersionInfo v2 = Parser::parse(v2Str); unsigned long v1_major = std::stoul(v1.majorVersion); unsigned long v2_major = std::stoul(v2.majorVersion); unsigned long v1_minor = std::stoul(v1.minorVersion); unsigned long v2_minor = std::stoul(v2.minorVersion); unsigned long v1_hotfix = std::stoul(v1.hotfixVersion); unsigned long v2_hotfix = std::stoul(v2.hotfixVersion); int versionTagCompare = _versionTagCompare(v1.versionTag, v2.versionTag); return (v1_major < v2_major) || ((v1_major == v2_major) && ( (v1_minor < v2_minor) || ((v1_minor == v2_minor) && ( (v1_hotfix < v2_hotfix) || ((v1_hotfix == v2_hotfix) && ( (0 > versionTagCompare) || ((0 == versionTagCompare) && ( (v1.commitsSinceTag < v2.commitsSinceTag) )))))))); } int VersionCompare::_versionTagCompare(const string &tag1, const string &tag2) { if (tag1 == "") { if (tag2 == "") { return 0; } else { return 1; } } else { if (tag2 == "") { return -1; } else { return strcmp(tag1.c_str(), tag2.c_str()); } } } } src/gitversion/VersionCompare.h000066400000000000000000000005561347701267100171610ustar00rootroot00000000000000#pragma once #ifndef GITVERSION_VERSIONCOMPARE_H #define GITVERSION_VERSIONCOMPARE_H #include namespace gitversion { class VersionCompare { public: static bool isOlderThan(const std::string &v1, const std::string &v2); private: static int _versionTagCompare(const std::string &tag1, const std::string &tag2); }; } #endif src/gitversion/_version.py000066400000000000000000000410621347701267100162470ustar00rootroot00000000000000 # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by # versioneer-0.15+dev (https://github.com/warner/python-versioneer) """Git implementation of _version.py.""" import errno import os import re import subprocess import sys def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = " (HEAD, tag: 0.10.2, origin/release/0.10, release/0.10)" git_full = "cf3023406969b14610df03a043fca8a078c9c195" keywords = {"refnames": git_refnames, "full": git_full} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "pep440" cfg.tag_prefix = "" cfg.parentdir_prefix = "cryfs-" cfg.versionfile_source = "_version.py" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) return None return stdout def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. """ # -- (MESSMER) CHANGED FOLLOWING LINE TO LOOK IN ../.. instead of . -- dirname = os.path.basename(os.path.abspath(os.path.join(os.path.join(root, '..'), '..'))) if not dirname.startswith(parentdir_prefix): if verbose: print("guessing rootdir is '%s', but '%s' doesn't start with " "prefix '%s'" % (root, dirname, parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None} @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs-tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None } # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags"} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ if not os.path.exists(os.path.join(root, ".git")): if verbose: print("no .git in %s" % root) raise NotThisMethod("no .git directory") GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out = run_command(GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%d" % pieces["distance"] else: # exception #1 rendered = "0.post.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"]} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None} def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for i in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree"} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version"} src/gitversion/getversion.py000077500000000000000000000001121347701267100166020ustar00rootroot00000000000000#!/usr/bin/env python import versioneer print(versioneer.get_version()) src/gitversion/gitversion.cmake000066400000000000000000000016531347701267100172460ustar00rootroot00000000000000set(DIR_OF_GITVERSION_TOOL "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "DIR_OF_GITVERSION_TOOL") function (get_git_version OUTPUT_VARIABLE) EXECUTE_PROCESS(COMMAND python ${DIR_OF_GITVERSION_TOOL}/getversion.py WORKING_DIRECTORY ${DIR_OF_GITVERSION_TOOL} OUTPUT_VARIABLE VERSION ERROR_VARIABLE error RESULT_VARIABLE result) STRING(STRIP "${VERSION}" STRIPPED_VERSION) SET(${OUTPUT_VARIABLE} "${STRIPPED_VERSION}" CACHE INTERNAL "${OUTPUT_VARIABLE}") MESSAGE(STATUS "Building version ${${OUTPUT_VARIABLE}}") IF(NOT ${result} EQUAL 0) MESSAGE(FATAL_ERROR "Error running versioneer. Return code is: ${result}, error message is: ${error}") ENDIF() IF("${STRIPPED_VERSION}" STREQUAL "0+unknown") MESSAGE(FATAL_ERROR "Unable to find git version information. Please build directly from a git repository (i.e. after git clone).") ENDIF() endfunction(get_git_version) src/gitversion/gitversion.cpp000066400000000000000000000011171347701267100167430ustar00rootroot00000000000000#include "gitversion.h" #include "parser.h" using std::string; namespace gitversion { const VersionInfo &parse() { static VersionInfo versionInfo = Parser::parse(VersionString()); return versionInfo; } bool IsDevVersion() { return parse().isDevVersion; } bool IsStableVersion() { return parse().isStableVersion; } string GitCommitId() { return parse().gitCommitId; } string MajorVersion() { return parse().majorVersion; } string MinorVersion() { return parse().minorVersion; } } src/gitversion/gitversion.h000066400000000000000000000004371347701267100164140ustar00rootroot00000000000000#pragma once #ifndef GITVERSION_GITVERSION_H #define GITVERSION_GITVERSION_H #include "versionstring.h" namespace gitversion { bool IsDevVersion(); bool IsStableVersion(); std::string MajorVersion(); std::string MinorVersion(); std::string GitCommitId(); } #endif src/gitversion/parser.cpp000066400000000000000000000064511347701267100160540ustar00rootroot00000000000000#include "parser.h" #include using std::string; using std::pair; using std::tuple; using std::tie; using boost::optional; using boost::none; using std::istringstream; using std::getline; namespace gitversion { VersionInfo Parser::parse(const string &versionString) { VersionInfo result; string versionNumber; optional versionInfo; tie(versionNumber, versionInfo) = _splitAt(versionString, '+'); tie(result.majorVersion, result.minorVersion, result.hotfixVersion, result.versionTag) = _extractMajorMinorHotfixTag(versionNumber); result.isDevVersion = (versionInfo != none); result.isStableVersion = !result.isDevVersion && (result.versionTag == "" || result.versionTag == "stable"); if (versionInfo != none && *versionInfo != "unknown") { tie(result.gitCommitId, result.commitsSinceTag) = _extractGitCommitIdAndCommitsSinceTag(*versionInfo); } else { result.gitCommitId = ""; result.commitsSinceTag = 0; } return result; } pair> Parser::_splitAt(const string &versionString, char delimiter) { istringstream stream(versionString); string versionNumber; getline(stream, versionNumber, delimiter); if (!stream.good()) { return std::make_pair(versionNumber, none); } else { string versionInfo; getline(stream, versionInfo); return std::make_pair(versionNumber, versionInfo); } } tuple Parser::_extractMajorMinorHotfixTag(const string &versionNumber) { string majorMinorHotfix; optional versionTag; tie(majorMinorHotfix, versionTag) = _splitAt(versionNumber, '-'); string major, minor, hotfix; tie(major, minor, hotfix) = _extractMajorMinorHotfix(majorMinorHotfix); if (versionTag == none) { versionTag = ""; } return std::make_tuple(major, minor, hotfix, *versionTag); } tuple Parser::_extractMajorMinorHotfix(const string &versionNumber) { istringstream stream(versionNumber); string major, minor, hotfix; getline(stream, major, '.'); if (!stream.good()) { minor = "0"; } else { getline(stream, minor, '.'); } if (!stream.good()) { hotfix = "0"; } else { getline(stream, hotfix); } return std::make_tuple(major, minor, hotfix); }; std::tuple Parser::_extractGitCommitIdAndCommitsSinceTag(const string &versionInfo) { istringstream stream(versionInfo); string commitsSinceTag; getline(stream, commitsSinceTag, '.'); if (!stream.good()) { throw std::logic_error("Invalid version information: Missing delimiter after commitsSinceTag (versionInfo: "+versionInfo+")."); } string gitCommitId; getline(stream, gitCommitId, '.'); if (gitCommitId[0] != 'g') { throw std::logic_error("Invalid version information: Git commit id component doesn't start with 'g' (versionInfo: "+versionInfo+")."); } return std::make_tuple(gitCommitId.substr(1), std::stoul(commitsSinceTag)); } }src/gitversion/parser.h000066400000000000000000000021541347701267100155150ustar00rootroot00000000000000#pragma once #ifndef GITVERSION_PARSER_H #define GITVERSION_PARSER_H #include #include #include namespace gitversion { struct VersionInfo { bool isDevVersion = false; bool isStableVersion = false; std::string versionTag; std::string gitCommitId; std::string majorVersion; std::string minorVersion; std::string hotfixVersion; unsigned int commitsSinceTag = 0; }; class Parser final { public: static VersionInfo parse(const std::string &versionString); private: static std::pair> _splitAt(const std::string &versionString, char delimiter); static std::tuple _extractMajorMinorHotfixTag(const std::string &versionNumber); static std::tuple _extractMajorMinorHotfix(const std::string &versionNumber); static std::tuple _extractGitCommitIdAndCommitsSinceTag(const std::string &versionInfo); }; } #endif src/gitversion/setup.cfg000066400000000000000000000004611347701267100156700ustar00rootroot00000000000000 # See the docstring in versioneer.py for instructions. Note that you must # re-run 'versioneer.py setup' after changing this section, and commit the # resulting files. [versioneer] VCS = git style = pep440 versionfile_source = _version.py versionfile_build = None tag_prefix = parentdir_prefix = cryfs- src/gitversion/setup.py000066400000000000000000000006171347701267100155640ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup import versioneer setup(name='git-version', version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), description='Make git version information (e.g. git tag name, git commit id, ...) available to C++ source files.', author='Sebastian Messmer', author_email='messmer@cryfs.org', license='LGPLv3' ) src/gitversion/versioneer.py000066400000000000000000002012501347701267100166010ustar00rootroot00000000000000 # Version: 0.15+dev """The Versioneer - like a rocketeer, but for versions. The Versioneer ============== * like a rocketeer, but for versions! * https://github.com/warner/python-versioneer * Brian Warner * License: Public Domain * Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, and pypy * [![Latest Version] (https://pypip.in/version/versioneer/badge.svg?style=flat) ](https://pypi.python.org/pypi/versioneer/) * [![Build Status] (https://travis-ci.org/warner/python-versioneer.png?branch=master) ](https://travis-ci.org/warner/python-versioneer) This is a tool for managing a recorded version number in distutils-based python projects. The goal is to remove the tedious and error-prone "update the embedded version string" step from your release process. Making a new release should be as easy as recording a new tag in your version-control system, and maybe making new tarballs. ## Quick Install * `pip install versioneer` to somewhere to your $PATH * add a `[versioneer]` section to your setup.cfg (see below) * run `versioneer install` in your source tree, commit the results ## Version Identifiers Source trees come from a variety of places: * a version-control system checkout (mostly used by developers) * a nightly tarball, produced by build automation * a snapshot tarball, produced by a web-based VCS browser, like github's "tarball from tag" feature * a release tarball, produced by "setup.py sdist", distributed through PyPI Within each source tree, the version identifier (either a string or a number, this tool is format-agnostic) can come from a variety of places: * ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows about recent "tags" and an absolute revision-id * the name of the directory into which the tarball was unpacked * an expanded VCS keyword ($Id$, etc) * a `_version.py` created by some earlier build step For released software, the version identifier is closely related to a VCS tag. Some projects use tag names that include more than just the version string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool needs to strip the tag prefix to extract the version identifier. For unreleased software (between tags), the version identifier should provide enough information to help developers recreate the same tree, while also giving them an idea of roughly how old the tree is (after version 1.2, before version 1.3). Many VCS systems can report a description that captures this, for example `git describe --tags --dirty --always` reports things like "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has uncommitted changes. The version identifier is used for multiple purposes: * to allow the module to self-identify its version: `myproject.__version__` * to choose a name and prefix for a 'setup.py sdist' tarball ## Theory of Operation Versioneer works by adding a special `_version.py` file into your source tree, where your `__init__.py` can import it. This `_version.py` knows how to dynamically ask the VCS tool for version information at import time. `_version.py` also contains `$Revision$` markers, and the installation process marks `_version.py` to have this marker rewritten with a tag name during the `git archive` command. As a result, generated tarballs will contain enough information to get the proper version. To allow `setup.py` to compute a version too, a `versioneer.py` is added to the top level of your source tree, next to `setup.py` and the `setup.cfg` that configures it. This overrides several distutils/setuptools commands to compute the version when invoked, and changes `setup.py build` and `setup.py sdist` to replace `_version.py` with a small static file that contains just the generated version data. ## Installation First, decide on values for the following configuration variables: * `VCS`: the version control system you use. Currently accepts "git". * `style`: the style of version string to be produced. See "Styles" below for details. Defaults to "pep440", which looks like `TAG[+DISTANCE.gSHORTHASH[.dirty]]`. * `versionfile_source`: A project-relative pathname into which the generated version strings should be written. This is usually a `_version.py` next to your project's main `__init__.py` file, so it can be imported at runtime. If your project uses `src/myproject/__init__.py`, this should be `src/myproject/_version.py`. This file should be checked in to your VCS as usual: the copy created below by `setup.py setup_versioneer` will include code that parses expanded VCS keywords in generated tarballs. The 'build' and 'sdist' commands will replace it with a copy that has just the calculated version string. This must be set even if your project does not have any modules (and will therefore never import `_version.py`), since "setup.py sdist" -based trees still need somewhere to record the pre-calculated version strings. Anywhere in the source tree should do. If there is a `__init__.py` next to your `_version.py`, the `setup.py setup_versioneer` command (described below) will append some `__version__`-setting assignments, if they aren't already present. * `versionfile_build`: Like `versionfile_source`, but relative to the build directory instead of the source directory. These will differ when your setup.py uses 'package_dir='. If you have `package_dir={'myproject': 'src/myproject'}`, then you will probably have `versionfile_build='myproject/_version.py'` and `versionfile_source='src/myproject/_version.py'`. If this is set to None, then `setup.py build` will not attempt to rewrite any `_version.py` in the built tree. If your project does not have any libraries (e.g. if it only builds a script), then you should use `versionfile_build = None`. To actually use the computed version string, your `setup.py` will need to override `distutils.command.build_scripts` with a subclass that explicitly inserts a copy of `versioneer.get_version()` into your script file. See `test/demoapp-script-only/setup.py` for an example. * `tag_prefix`: a string, like 'PROJECTNAME-', which appears at the start of all VCS tags. If your tags look like 'myproject-1.2.0', then you should use tag_prefix='myproject-'. If you use unprefixed tags like '1.2.0', this should be an empty string, using either `tag_prefix=` or `tag_prefix=''`. * `parentdir_prefix`: a optional string, frequently the same as tag_prefix, which appears at the start of all unpacked tarball filenames. If your tarball unpacks into 'myproject-1.2.0', this should be 'myproject-'. To disable this feature, just omit the field from your `setup.cfg`. This tool provides one script, named `versioneer`. That script has one mode, "install", which writes a copy of `versioneer.py` into the current directory and runs `versioneer.py setup` to finish the installation. To versioneer-enable your project: * 1: Modify your `setup.cfg`, adding a section named `[versioneer]` and populating it with the configuration values you decided earlier (note that the option names are not case-sensitive): ```` [versioneer] VCS = git style = pep440 versionfile_source = src/myproject/_version.py versionfile_build = myproject/_version.py tag_prefix = parentdir_prefix = myproject- ```` * 2: Run `versioneer install`. This will do the following: * copy `versioneer.py` into the top of your source tree * create `_version.py` in the right place (`versionfile_source`) * modify your `__init__.py` (if one exists next to `_version.py`) to define `__version__` (by calling a function from `_version.py`) * modify your `MANIFEST.in` to include both `versioneer.py` and the generated `_version.py` in sdist tarballs `versioneer install` will complain about any problems it finds with your `setup.py` or `setup.cfg`. Run it multiple times until you have fixed all the problems. * 3: add a `import versioneer` to your setup.py, and add the following arguments to the setup() call: version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), * 4: commit these changes to your VCS. To make sure you won't forget, `versioneer install` will mark everything it touched for addition using `git add`. Don't forget to add `setup.py` and `setup.cfg` too. ## Post-Installation Usage Once established, all uses of your tree from a VCS checkout should get the current version string. All generated tarballs should include an embedded version string (so users who unpack them will not need a VCS tool installed). If you distribute your project through PyPI, then the release process should boil down to two steps: * 1: git tag 1.0 * 2: python setup.py register sdist upload If you distribute it through github (i.e. users use github to generate tarballs with `git archive`), the process is: * 1: git tag 1.0 * 2: git push; git push --tags Versioneer will report "0+untagged.NUMCOMMITS.gHASH" until your tree has at least one tag in its history. ## Version-String Flavors Code which uses Versioneer can learn about its version string at runtime by importing `_version` from your main `__init__.py` file and running the `get_versions()` function. From the "outside" (e.g. in `setup.py`), you can import the top-level `versioneer.py` and run `get_versions()`. Both functions return a dictionary with different flavors of version information: * `['version']`: A condensed version string, rendered using the selected style. This is the most commonly used value for the project's version string. The default "pep440" style yields strings like `0.11`, `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section below for alternative styles. * `['full-revisionid']`: detailed revision identifier. For Git, this is the full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that this is only accurate if run in a VCS checkout, otherwise it is likely to be False or None * `['error']`: if the version string could not be computed, this will be set to a string describing the problem, otherwise it will be None. It may be useful to throw an exception in setup.py if this is set, to avoid e.g. creating tarballs with a version string of "unknown". Some variants are more useful than others. Including `full-revisionid` in a bug report should allow developers to reconstruct the exact code being tested (or indicate the presence of local changes that should be shared with the developers). `version` is suitable for display in an "about" box or a CLI `--version` output: it can be easily compared against release notes and lists of bugs fixed in various releases. The installer adds the following text to your `__init__.py` to place a basic version in `YOURPROJECT.__version__`: from ._version import get_versions __version__ = get_versions()['version'] del get_versions ## Styles The setup.cfg `style=` configuration controls how the VCS information is rendered into a version string. The default style, "pep440", produces a PEP440-compliant string, equal to the un-prefixed tag name for actual releases, and containing an additional "local version" section with more detail for in-between builds. For Git, this is TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags --dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and that this commit is two revisions ("+2") beyond the "0.11" tag. For released software (exactly equal to a known tag), the identifier will only contain the stripped tag, e.g. "0.11". Other styles are available. See details.md in the Versioneer source tree for descriptions. ## Debugging Versioneer tries to avoid fatal errors: if something goes wrong, it will tend to return a version of "0+unknown". To investigate the problem, run `setup.py version`, which will run the version-lookup code in a verbose mode, and will display the full contents of `get_versions()` (including the `error` string, which may help identify what went wrong). ## Updating Versioneer To upgrade your project to a new release of Versioneer, do the following: * install the new Versioneer (`pip install -U versioneer` or equivalent) * edit `setup.cfg`, if necessary, to include any new configuration settings indicated by the release notes * re-run `versioneer install` in your source tree, to replace `SRC/_version.py` * commit any changed files ### Upgrading to 0.15 Starting with this version, Versioneer is configured with a `[versioneer]` section in your `setup.cfg` file. Earlier versions required the `setup.py` to set attributes on the `versioneer` module immediately after import. The new version will refuse to run (raising an exception during import) until you have provided the necessary `setup.cfg` section. In addition, the Versioneer package provides an executable named `versioneer`, and the installation process is driven by running `versioneer install`. In 0.14 and earlier, the executable was named `versioneer-installer` and was run without an argument. ### Upgrading to 0.14 0.14 changes the format of the version string. 0.13 and earlier used hyphen-separated strings like "0.11-2-g1076c97-dirty". 0.14 and beyond use a plus-separated "local version" section strings, with dot-separated components, like "0.11+2.g1076c97". PEP440-strict tools did not like the old format, but should be ok with the new one. ### Upgrading from 0.11 to 0.12 Nothing special. ### Upgrading from 0.10 to 0.11 You must add a `versioneer.VCS = "git"` to your `setup.py` before re-running `setup.py setup_versioneer`. This will enable the use of additional version-control systems (SVN, etc) in the future. ## Future Directions This tool is designed to make it easily extended to other version-control systems: all VCS-specific components are in separate directories like src/git/ . The top-level `versioneer.py` script is assembled from these components by running make-versioneer.py . In the future, make-versioneer.py will take a VCS name as an argument, and will construct a version of `versioneer.py` that is specific to the given VCS. It might also take the configuration arguments that are currently provided manually during installation by editing setup.py . Alternatively, it might go the other direction and include code from all supported VCS systems, reducing the number of intermediate scripts. ## License To make Versioneer easier to embed, all its code is dedicated to the public domain. The `_version.py` that it creates is also in the public domain. Specifically, both are released under the Creative Commons "Public Domain Dedication" license (CC0-1.0), as described in https://creativecommons.org/publicdomain/zero/1.0/ . """ from __future__ import print_function try: import configparser except ImportError: import ConfigParser as configparser import errno import json import os import re import subprocess import sys class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_root(): """Get the project root directory. We require that all commands are run from the project root, i.e. the directory that contains setup.py, setup.cfg, and versioneer.py . """ root = os.path.realpath(os.path.abspath(os.getcwd())) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): # allow 'python path/to/setup.py COMMAND' root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): err = ("Versioneer was unable to run the project root directory. " "Versioneer requires setup.py to be executed from " "its immediate directory (like 'python setup.py COMMAND'), " "or in a way that lets it use sys.argv[0] to find the root " "(like 'python path/to/setup.py COMMAND').") raise VersioneerBadRootError(err) try: # Certain runtime workflows (setup.py install/develop in a setuptools # tree) execute all dependencies in a single python process, so # "versioneer" may be imported multiple times, and python's shared # module-import table will cache the first one. So we can't use # os.path.dirname(__file__), as that will find whichever # versioneer.py was first imported, even in later projects. me = os.path.realpath(os.path.abspath(__file__)) if os.path.splitext(me)[0] != os.path.splitext(versioneer_py)[0]: # -- (MESSMER) changed this to output to stderr instead of stdout, since it caused problems on some systems -- print("Warning: build in %s is using versioneer.py from %s" % (os.path.dirname(me), versioneer_py), file=sys.stderr) except NameError: pass return root def get_config_from_root(root): """Read the project setup.cfg file to determine Versioneer config.""" # This might raise EnvironmentError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . setup_cfg = os.path.join(root, "setup.cfg") parser = configparser.SafeConfigParser() with open(setup_cfg, "r") as f: parser.readfp(f) VCS = parser.get("versioneer", "VCS") # mandatory def get(parser, name): if parser.has_option("versioneer", name): return parser.get("versioneer", name) return None cfg = VersioneerConfig() cfg.VCS = VCS cfg.style = get(parser, "style") or "" cfg.versionfile_source = get(parser, "versionfile_source") cfg.versionfile_build = get(parser, "versionfile_build") cfg.tag_prefix = get(parser, "tag_prefix") if cfg.tag_prefix in ("''", '""'): cfg.tag_prefix = "" cfg.parentdir_prefix = get(parser, "parentdir_prefix") cfg.verbose = get(parser, "verbose") return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" # these dictionaries contain VCS-specific tools LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) return None return stdout LONG_VERSION_PY['git'] = ''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by # versioneer-0.15+dev (https://github.com/warner/python-versioneer) """Git implementation of _version.py.""" import errno import os import re import subprocess import sys def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" keywords = {"refnames": git_refnames, "full": git_full} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "%(STYLE)s" cfg.tag_prefix = "%(TAG_PREFIX)s" cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %%s" %% dispcmd) print(e) return None else: if verbose: print("unable to find command, tried %%s" %% (commands,)) return None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %%s (error)" %% dispcmd) return None return stdout def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. """ # -- (MESSMER) CHANGED FOLLOWING LINE TO LOOK IN ../.. instead of . -- dirname = os.path.basename(os.path.abspath(os.path.join(os.path.join(root, '..'), '..'))) if not dirname.startswith(parentdir_prefix): if verbose: print("guessing rootdir is '%%s', but '%%s' doesn't start with " "prefix '%%s'" %% (root, dirname, parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None} @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %%d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: print("discarding '%%s', no digits" %% ",".join(refs-tags)) if verbose: print("likely tags: %%s" %% ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %%s" %% r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None } # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags"} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ if not os.path.exists(os.path.join(root, ".git")): if verbose: print("no .git in %%s" %% root) raise NotThisMethod("no .git directory") GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out = run_command(GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%%s*" %% tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%%s'" %% describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%%s' doesn't start with prefix '%%s'" print(fmt %% (full_tag, tag_prefix)) pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" %% (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%%d" %% pieces["distance"] else: # exception #1 rendered = "0.post.dev%%d" %% pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%%s" %% pieces["short"] else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%%s" %% pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"]} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%%s'" %% style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None} def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for i in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree"} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version"} ''' @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs-tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None } # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags"} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ # -- (MESSMER) CHANGED FOLLOWING LINE TO LOOK FOR ../../.git instead of .git -- if not os.path.exists(os.path.join(root, "../../.git")): if verbose: print("no .git in %s" % root) raise NotThisMethod("no .git directory") GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out = run_command(GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits return pieces def do_vcs_install(manifest_in, versionfile_source, ipy): """Git-specific installation logic for Versioneer. For Git, this means creating/changing .gitattributes to mark _version.py for export-time keyword substitution. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] files = [manifest_in, versionfile_source] if ipy: files.append(ipy) try: me = __file__ if me.endswith(".pyc") or me.endswith(".pyo"): me = os.path.splitext(me)[0] + ".py" versioneer_file = os.path.relpath(me) except NameError: versioneer_file = "versioneer.py" files.append(versioneer_file) present = False try: f = open(".gitattributes", "r") for line in f.readlines(): if line.strip().startswith(versionfile_source): if "export-subst" in line.strip().split()[1:]: present = True f.close() except EnvironmentError: pass if not present: f = open(".gitattributes", "a+") f.write("%s export-subst\n" % versionfile_source) f.close() files.append(".gitattributes") run_command(GITS, ["add", "--"] + files) def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. """ # -- (MESSMER) CHANGED FOLLOWING LINE TO LOOK IN ../.. instead of . -- dirname = os.path.basename(os.path.abspath(os.path.join(os.path.join(root, '..'), '..'))) if not dirname.startswith(parentdir_prefix): if verbose: print("guessing rootdir is '%s', but '%s' doesn't start with " "prefix '%s'" % (root, dirname, parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None} SHORT_VERSION_PY = """ # This file was generated by 'versioneer.py' (0.15+dev) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. import json import sys version_json = ''' %s ''' # END VERSION_JSON def get_versions(): return json.loads(version_json) """ def versions_from_file(filename): """Try to determine the version from _version.py if present.""" try: with open(filename) as f: contents = f.read() except EnvironmentError: raise NotThisMethod("unable to read _version.py") mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: raise NotThisMethod("no version_json in _version.py") return json.loads(mo.group(1)) def write_to_version_file(filename, versions): """Write the given version number to the given _version.py file.""" os.unlink(filename) contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) with open(filename, "w") as f: f.write(SHORT_VERSION_PY % contents) print("set %s to '%s'" % (filename, versions["version"])) def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%d" % pieces["distance"] else: # exception #1 rendered = "0.post.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"]} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None} class VersioneerBadRootError(Exception): """The project root directory is unknown or missing key files.""" def get_versions(verbose=False): """Get the project version from whatever source is available. Returns dict with two keys: 'version' and 'full'. """ if "versioneer" in sys.modules: # see the discussion in cmdclass.py:get_cmdclass() del sys.modules["versioneer"] root = get_root() cfg = get_config_from_root(root) assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" handlers = HANDLERS.get(cfg.VCS) assert handlers, "unrecognized VCS '%s'" % cfg.VCS verbose = verbose or cfg.verbose assert cfg.versionfile_source is not None, \ "please set versioneer.versionfile_source" assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" versionfile_abs = os.path.join(root, cfg.versionfile_source) # extract version from first of: _version.py, VCS command (e.g. 'git # describe'), parentdir. This is meant to work for developers using a # source checkout, for users of a tarball created by 'setup.py sdist', # and for users of a tarball/zipball created by 'git archive' or github's # download-from-tag feature or the equivalent in other VCSes. get_keywords_f = handlers.get("get_keywords") from_keywords_f = handlers.get("keywords") if get_keywords_f and from_keywords_f: try: keywords = get_keywords_f(versionfile_abs) ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) if verbose: print("got version from expanded keyword %s" % ver) return ver except NotThisMethod: pass try: ver = versions_from_file(versionfile_abs) if verbose: print("got version from file %s %s" % (versionfile_abs, ver)) return ver except NotThisMethod: pass from_vcs_f = handlers.get("pieces_from_vcs") if from_vcs_f: try: pieces = from_vcs_f(cfg.tag_prefix, root, verbose) ver = render(pieces, cfg.style) if verbose: print("got version from VCS %s" % ver) return ver except NotThisMethod: pass try: if cfg.parentdir_prefix: ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) if verbose: print("got version from parentdir %s" % ver) return ver except NotThisMethod: pass if verbose: print("unable to compute version") return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version"} def get_version(): """Get the short version string for this project.""" return get_versions()["version"] def get_cmdclass(): """Get the custom setuptools/distutils subclasses used by Versioneer.""" if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and # 'easy_install .'), in which subdependencies of the main project are # built (using setup.py bdist_egg) in the same python process. Assume # a main project A and a dependency B, which use different versions # of Versioneer. A's setup.py imports A's Versioneer, leaving it in # sys.modules by the time B's setup.py is executed, causing B to run # with the wrong versioneer. Setuptools wraps the sub-dep builds in a # sandbox that restores sys.modules to it's pre-build state, so the # parent is protected against the child's "import versioneer". By # removing ourselves from sys.modules here, before the child build # happens, we protect the child from the parent's versioneer too. # Also see https://github.com/warner/python-versioneer/issues/52 cmds = {} # we add "version" to both distutils and setuptools from distutils.core import Command class cmd_version(Command): description = "report generated version string" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): vers = get_versions(verbose=True) print("Version: %s" % vers["version"]) print(" full-revisionid: %s" % vers.get("full-revisionid")) print(" dirty: %s" % vers.get("dirty")) if vers["error"]: print(" error: %s" % vers["error"]) cmds["version"] = cmd_version # we override "build_py" in both distutils and setuptools # # most invocation pathways end up running build_py: # distutils/build -> build_py # distutils/install -> distutils/build ->.. # setuptools/bdist_wheel -> distutils/install ->.. # setuptools/bdist_egg -> distutils/install_lib -> build_py # setuptools/install -> bdist_egg ->.. # setuptools/develop -> ? # we override different "build_py" commands for both environments if "setuptools" in sys.modules: from setuptools.command.build_py import build_py as _build_py else: from distutils.command.build_py import build_py as _build_py class cmd_build_py(_build_py): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() _build_py.run(self) # now locate _version.py in the new build/ directory and replace # it with an updated value if cfg.versionfile_build: target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) cmds["build_py"] = cmd_build_py if "cx_Freeze" in sys.modules: # cx_freeze enabled? from cx_Freeze.dist import build_exe as _build_exe class cmd_build_exe(_build_exe): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _build_exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["build_exe"] = cmd_build_exe del cmds["build_py"] # we override different "sdist" commands for both environments if "setuptools" in sys.modules: from setuptools.command.sdist import sdist as _sdist else: from distutils.command.sdist import sdist as _sdist class cmd_sdist(_sdist): def run(self): versions = get_versions() self._versioneer_generated_versions = versions # unless we update this, the command will keep using the old # version self.distribution.metadata.version = versions["version"] return _sdist.run(self) def make_release_tree(self, base_dir, files): root = get_root() cfg = get_config_from_root(root) _sdist.make_release_tree(self, base_dir, files) # now locate _version.py in the new base_dir directory # (remembering that it may be a hardlink) and replace it with an # updated value target_versionfile = os.path.join(base_dir, cfg.versionfile_source) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, self._versioneer_generated_versions) cmds["sdist"] = cmd_sdist return cmds CONFIG_ERROR = """ setup.cfg is missing the necessary Versioneer configuration. You need a section like: [versioneer] VCS = git style = pep440 versionfile_source = src/myproject/_version.py versionfile_build = myproject/_version.py tag_prefix = parentdir_prefix = myproject- You will also need to edit your setup.py to use the results: import versioneer setup(version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), ...) Please read the docstring in ./versioneer.py for configuration instructions, edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. """ SAMPLE_CONFIG = """ # See the docstring in versioneer.py for instructions. Note that you must # re-run 'versioneer.py setup' after changing this section, and commit the # resulting files. [versioneer] #VCS = git #style = pep440 #versionfile_source = #versionfile_build = #tag_prefix = #parentdir_prefix = """ INIT_PY_SNIPPET = """ from ._version import get_versions __version__ = get_versions()['version'] del get_versions """ def do_setup(): """Main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) except (EnvironmentError, configparser.NoSectionError, configparser.NoOptionError) as e: if isinstance(e, (EnvironmentError, configparser.NoSectionError)): print("Adding sample versioneer config to setup.cfg", file=sys.stderr) with open(os.path.join(root, "setup.cfg"), "a") as f: f.write(SAMPLE_CONFIG) print(CONFIG_ERROR, file=sys.stderr) return 1 print(" creating %s" % cfg.versionfile_source) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") if os.path.exists(ipy): try: with open(ipy, "r") as f: old = f.read() except EnvironmentError: old = "" if INIT_PY_SNIPPET not in old: print(" appending to %s" % ipy) with open(ipy, "a") as f: f.write(INIT_PY_SNIPPET) else: print(" %s unmodified" % ipy) else: print(" %s doesn't exist, ok" % ipy) ipy = None # Make sure both the top-level "versioneer.py" and versionfile_source # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so # they'll be copied into source distributions. Pip won't be able to # install the package without this. manifest_in = os.path.join(root, "MANIFEST.in") simple_includes = set() try: with open(manifest_in, "r") as f: for line in f: if line.startswith("include "): for include in line.split()[1:]: simple_includes.add(include) except EnvironmentError: pass # That doesn't cover everything MANIFEST.in can do # (http://docs.python.org/2/distutils/sourcedist.html#commands), so # it might give some false negatives. Appending redundant 'include' # lines is safe, though. if "versioneer.py" not in simple_includes: print(" appending 'versioneer.py' to MANIFEST.in") with open(manifest_in, "a") as f: f.write("include versioneer.py\n") else: print(" 'versioneer.py' already in MANIFEST.in") if cfg.versionfile_source not in simple_includes: print(" appending versionfile_source ('%s') to MANIFEST.in" % cfg.versionfile_source) with open(manifest_in, "a") as f: f.write("include %s\n" % cfg.versionfile_source) else: print(" versionfile_source already in MANIFEST.in") # Make VCS-specific changes. For git, this means creating/changing # .gitattributes to mark _version.py for export-time keyword # substitution. do_vcs_install(manifest_in, cfg.versionfile_source, ipy) return 0 def scan_setup_py(): """Validate the contents of setup.py against Versioneer's expectations.""" found = set() setters = False errors = 0 with open("setup.py", "r") as f: for line in f.readlines(): if "import versioneer" in line: found.add("import") if "versioneer.get_cmdclass()" in line: found.add("cmdclass") if "versioneer.get_version()" in line: found.add("get_version") if "versioneer.VCS" in line: setters = True if "versioneer.versionfile_source" in line: setters = True if len(found) != 3: print("") print("Your setup.py appears to be missing some important items") print("(but I might be wrong). Please make sure it has something") print("roughly like the following:") print("") print(" import versioneer") print(" setup( version=versioneer.get_version(),") print(" cmdclass=versioneer.get_cmdclass(), ...)") print("") errors += 1 if setters: print("You should remove lines like 'versioneer.VCS = ' and") print("'versioneer.versionfile_source = ' . This configuration") print("now lives in setup.cfg, and should be removed from setup.py") print("") errors += 1 return errors if __name__ == "__main__": cmd = sys.argv[1] if cmd == "setup": errors = do_setup() errors += scan_setup_py() if errors: sys.exit(1) src/gitversion/versionstring.cpp000066400000000000000000000004161347701267100174670ustar00rootroot00000000000000#include "versionstring.h" using std::string; namespace gitversion { const string &VersionString() { static const string VERSION_STRING = GIT_VERSION_STRING; // GIT_VERSION_STRING is set by our CMake file as a macro return VERSION_STRING; } } src/gitversion/versionstring.h000066400000000000000000000002601347701267100171310ustar00rootroot00000000000000#pragma once #ifndef GITVERSION_VERSIONSTRING_H #define GITVERSION_VERSIONSTRING_H #include namespace gitversion { const std::string &VersionString(); } #endif src/parallelaccessstore/000077500000000000000000000000001347701267100157105ustar00rootroot00000000000000src/parallelaccessstore/CMakeLists.txt000066400000000000000000000005151347701267100204510ustar00rootroot00000000000000project (parallelaccessstore) set(SOURCES ParallelAccessBaseStore.cpp ParallelAccessStore.cpp ) add_library(${PROJECT_NAME} STATIC ${SOURCES}) target_link_libraries(${PROJECT_NAME} PUBLIC cpp-utils) target_add_boost(${PROJECT_NAME} thread) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) src/parallelaccessstore/ParallelAccessBaseStore.cpp000066400000000000000000000000441347701267100231000ustar00rootroot00000000000000#include "ParallelAccessBaseStore.h"src/parallelaccessstore/ParallelAccessBaseStore.h000066400000000000000000000012311347701267100225440ustar00rootroot00000000000000#pragma once #ifndef MESSMER_PARALLELACCESSSTORE_PARALLELACCESSBASESTORE_H_ #define MESSMER_PARALLELACCESSSTORE_PARALLELACCESSBASESTORE_H_ #include #include #include namespace parallelaccessstore { template class ParallelAccessBaseStore { public: virtual ~ParallelAccessBaseStore() {} virtual boost::optional> loadFromBaseStore(const Key &key) = 0; virtual void removeFromBaseStore(cpputils::unique_ref block) = 0; virtual void removeFromBaseStore(const blockstore::BlockId &blockId) = 0; }; } #endif src/parallelaccessstore/ParallelAccessStore.cpp000066400000000000000000000000401347701267100223010ustar00rootroot00000000000000#include "ParallelAccessStore.h"src/parallelaccessstore/ParallelAccessStore.h000066400000000000000000000261021347701267100217550ustar00rootroot00000000000000#pragma once #ifndef MESSMER_PARALLELACCESSSTORE_PARALLELACCESSSTORE_H_ #define MESSMER_PARALLELACCESSSTORE_PARALLELACCESSSTORE_H_ #include #include #include #include #include #include #include #include #include "ParallelAccessBaseStore.h" #include //TODO Refactor //TODO Test cases namespace parallelaccessstore { template class ParallelAccessStore final { public: explicit ParallelAccessStore(cpputils::unique_ref> baseStore); ~ParallelAccessStore() { ASSERT(_openResources.size() == 0, "Still resources open when trying to destruct"); ASSERT(_resourcesToRemove.size() == 0, "Still resources to remove when trying to destruct"); }; class ResourceRefBase { public: //TODO Better way to initialize ResourceRefBase(): _parallelAccessStore(nullptr), _key(Key::Null()) {} void init(ParallelAccessStore *parallelAccessStore, const Key &key) { _parallelAccessStore = parallelAccessStore; _key = key; } virtual ~ResourceRefBase() { _parallelAccessStore->release(_key); } private: ParallelAccessStore *_parallelAccessStore; //TODO We're storing Key twice (here and in the base resource). Rather use getKey() on the base resource if possible somehow. Key _key; DISALLOW_COPY_AND_ASSIGN(ResourceRefBase); }; bool isOpened(const Key &key) const; cpputils::unique_ref add(const Key &key, cpputils::unique_ref resource); template cpputils::unique_ref add(const Key &key, cpputils::unique_ref resource, std::function(Resource*)> createResourceRef); boost::optional> load(const Key &key); boost::optional> load(const Key &key, std::function(Resource*)> createResourceRef); //loadOrAdd: If the resource is open, run onExists() on it. If not, run onAdd() and add the created resource. Then return the resource as if load() was called on it. cpputils::unique_ref loadOrAdd(const Key &key, std::function onExists, std::function ()> onAdd); cpputils::unique_ref loadOrAdd(const Key &key, std::function onExists, std::function ()> onAdd, std::function(Resource*)> createResourceRef); void remove(const Key &key, cpputils::unique_ref block); void remove(const Key &key); private: class OpenResource final { public: OpenResource(cpputils::unique_ref resource): _resource(std::move(resource)), _refCount(0) {} OpenResource(OpenResource &&rhs) noexcept = default; Resource *getReference() { ++_refCount; return _resource.get(); } void releaseReference() { --_refCount; } bool refCountIsZero() const { return 0 == _refCount; } cpputils::unique_ref moveResourceOut() { return std::move(_resource); } private: cpputils::unique_ref _resource; uint32_t _refCount; DISALLOW_COPY_AND_ASSIGN(OpenResource); }; mutable std::mutex _mutex; cpputils::unique_ref> _baseStore; std::unordered_map _openResources; std::map>> _resourcesToRemove; template cpputils::unique_ref _add(const Key &key, cpputils::unique_ref resource, std::function(Resource*)> createResourceRef); boost::future> _resourceToRemoveFuture(const Key &key); cpputils::unique_ref _waitForResourceToRemove(const Key &key, boost::future> resourceToRemoveFuture); void release(const Key &key); friend class CachedResource; DISALLOW_COPY_AND_ASSIGN(ParallelAccessStore); }; template ParallelAccessStore::ParallelAccessStore(cpputils::unique_ref> baseStore) : _mutex(), _baseStore(std::move(baseStore)), _openResources(), _resourcesToRemove() { static_assert(std::is_base_of::value, "ResourceRef must inherit from ResourceRefBase"); } template bool ParallelAccessStore::isOpened(const Key &key) const { std::lock_guard lock(_mutex); return _openResources.find(key) != _openResources.end(); }; template cpputils::unique_ref ParallelAccessStore::add(const Key &key, cpputils::unique_ref resource) { return add(key, std::move(resource), [] (Resource *resource) { return cpputils::make_unique_ref(resource); }); } template template cpputils::unique_ref ParallelAccessStore::add(const Key &key, cpputils::unique_ref resource, std::function(Resource*)> createResourceRef) { static_assert(std::is_base_of::value, "Wrong ResourceRef type"); std::lock_guard lock(_mutex); return _add(key, std::move(resource), createResourceRef); } template template cpputils::unique_ref ParallelAccessStore::_add(const Key &key, cpputils::unique_ref resource, std::function(Resource*)> createResourceRef) { static_assert(std::is_base_of::value, "Wrong ResourceRef type"); auto insertResult = _openResources.emplace(key, std::move(resource)); ASSERT(true == insertResult.second, "Inserting failed. Already exists."); auto resourceRef = createResourceRef(insertResult.first->second.getReference()); resourceRef->init(this, key); return resourceRef; } template cpputils::unique_ref ParallelAccessStore::loadOrAdd(const Key &key, std::function onExists, std::function ()> onAdd) { return loadOrAdd(key, onExists, onAdd, [] (Resource *res) { return cpputils::make_unique_ref(res); }); }; template cpputils::unique_ref ParallelAccessStore::loadOrAdd(const Key &key, std::function onExists, std::function ()> onAdd, std::function(Resource*)> createResourceRef) { std::lock_guard lock(_mutex); auto found = _openResources.find(key); if (found == _openResources.end()) { auto resource = onAdd(); return _add(key, std::move(resource), createResourceRef); } else { auto resourceRef = createResourceRef(found->second.getReference()); resourceRef->init(this, key); onExists(resourceRef.get()); return resourceRef; } }; template boost::optional> ParallelAccessStore::load(const Key &key) { return load(key, [] (Resource *res) { return cpputils::make_unique_ref(res); }); }; template boost::optional> ParallelAccessStore::load(const Key &key, std::function(Resource*)> createResourceRef) { //TODO This lock doesn't allow loading different blocks in parallel. Can we only lock the requested key? std::lock_guard lock(_mutex); auto found = _openResources.find(key); if (found == _openResources.end()) { auto resource = _baseStore->loadFromBaseStore(key); if (resource == boost::none) { return boost::none; } return _add(key, std::move(*resource), createResourceRef); } else { auto resourceRef = createResourceRef(found->second.getReference()); resourceRef->init(this, key); return resourceRef; } } template void ParallelAccessStore::remove(const Key &key, cpputils::unique_ref resource) { auto resourceToRemoveFuture = _resourceToRemoveFuture(key); cpputils::destruct(std::move(resource)); //Wait for last resource user to release it auto resourceToRemove = resourceToRemoveFuture.get(); std::lock_guard lock(_mutex); // TODO Just added this as a precaution on a whim, but I seriously need to rethink locking here. _resourcesToRemove.erase(key); //TODO Is this erase causing a race condition? _baseStore->removeFromBaseStore(std::move(resourceToRemove)); } template boost::future> ParallelAccessStore::_resourceToRemoveFuture(const Key &key) { std::lock_guard lock(_mutex); // TODO Lock needed for _resourcesToRemove? auto insertResult = _resourcesToRemove.emplace(key, boost::promise>()); ASSERT(true == insertResult.second, "Inserting failed"); return insertResult.first->second.get_future(); }; template void ParallelAccessStore::remove(const Key &key) { auto found = _openResources.find(key); if (found != _openResources.end()) { auto resourceToRemoveFuture = _resourceToRemoveFuture(key); //Wait for last resource user to release it auto resourceToRemove = resourceToRemoveFuture.get(); std::lock_guard lock(_mutex); // TODO Just added this as a precaution on a whim, but I seriously need to rethink locking here. _resourcesToRemove.erase(key); //TODO Is this erase causing a race condition? _baseStore->removeFromBaseStore(std::move(resourceToRemove)); } else { _baseStore->removeFromBaseStore(key); } }; template void ParallelAccessStore::release(const Key &key) { std::lock_guard lock(_mutex); auto found = _openResources.find(key); ASSERT(found != _openResources.end(), "Didn't find key"); found->second.releaseReference(); if (found->second.refCountIsZero()) { auto foundToRemove = _resourcesToRemove.find(key); if (foundToRemove != _resourcesToRemove.end()) { foundToRemove->second.set_value(found->second.moveResourceOut()); } _openResources.erase(found); } } } #endif src/stats/000077500000000000000000000000001347701267100130135ustar00rootroot00000000000000src/stats/CMakeLists.txt000066400000000000000000000005041347701267100155520ustar00rootroot00000000000000project (stats) set(SOURCES main.cpp ) add_executable(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} PUBLIC cryfs cpp-utils gitversion) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME cryfs-stats) src/stats/main.cpp000066400000000000000000000231461347701267100144510ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace boost; using namespace boost::filesystem; using namespace std; using namespace cryfs; using namespace cpputils; using namespace blockstore; using namespace blockstore::ondisk; using namespace blockstore::integrity; using namespace blockstore::lowtohighlevel; using namespace blobstore::onblocks; using namespace blobstore::onblocks::datanodestore; using namespace cryfs::fsblobstore; void printNode(unique_ref node) { std::cout << "BlockId: " << node->blockId().ToString() << ", Depth: " << static_cast(node->depth()) << " "; auto innerNode = dynamic_pointer_move(node); if (innerNode != none) { std::cout << "Type: inner\n"; return; } auto leafNode = dynamic_pointer_move(node); if (leafNode != none) { std::cout << "Type: leaf\n"; return; } } void _forEachBlob(FsBlobStore* blobStore, const BlockId& rootId, std::function callback) { callback(rootId); auto rootBlob = blobStore->load(rootId); ASSERT(rootBlob != boost::none, "Blob not found but referenced from directory entry"); auto rootDir = dynamic_pointer_move(*rootBlob); if (rootDir != boost::none) { std::vector children; children.reserve((*rootDir)->NumChildren()); (*rootDir)->AppendChildrenTo(&children); for (const auto& child : children) { auto childEntry = (*rootDir)->GetChild(child.name); ASSERT(childEntry != boost::none, "We just got this from the entry list, it must exist."); auto childId = childEntry->blockId(); _forEachBlob(blobStore, childId, callback); } } } void _forEachBlockInBlob(DataNodeStore* nodeStore, const BlockId& rootId, std::function callback) { callback(rootId); auto node = nodeStore->load(rootId); auto innerNode = dynamic_pointer_move(*node); if (innerNode != boost::none) { for (uint32_t childIndex = 0; childIndex < (*innerNode)->numChildren(); ++childIndex) { auto childId = (*innerNode)->readChild(childIndex).blockId(); _forEachBlockInBlob(nodeStore, childId, callback); } } } unique_ref makeBlockStore(const path& basedir, const CryConfigLoader::ConfigLoadResult& config, LocalStateDir& localStateDir) { auto onDiskBlockStore = make_unique_ref(basedir); auto encryptedBlockStore = CryCiphers::find(config.configFile.config()->Cipher()).createEncryptedBlockstore(std::move(onDiskBlockStore), config.configFile.config()->EncryptionKey()); auto statePath = localStateDir.forFilesystemId(config.configFile.config()->FilesystemId()); auto integrityFilePath = statePath / "integritydata"; auto onIntegrityViolation = [] () { std::cerr << "Warning: Integrity violation encountered" << std::endl; }; auto integrityBlockStore = make_unique_ref(std::move(encryptedBlockStore), integrityFilePath, config.myClientId, false, true, onIntegrityViolation); return make_unique_ref(std::move(integrityBlockStore)); } std::vector _getKnownBlobIds(const path& basedir, const CryConfigLoader::ConfigLoadResult& config, LocalStateDir& localStateDir) { auto blockStore = makeBlockStore(basedir, config, localStateDir); auto fsBlobStore = make_unique_ref(make_unique_ref(std::move(blockStore), config.configFile.config()->BlocksizeBytes())); std::vector result; cout << "Listing all file system entities (i.e. blobs)..." << flush; auto rootId = BlockId::FromString(config.configFile.config()->RootBlob()); _forEachBlob(fsBlobStore.get(), rootId, [&result] (const BlockId& blockId) { result.push_back(blockId); }); cout << "done" << endl; return result; } std::vector _getKnownBlockIds(const path& basedir, const CryConfigLoader::ConfigLoadResult& config, LocalStateDir& localStateDir) { auto knownBlobIds = _getKnownBlobIds(basedir, config, localStateDir); auto blockStore = makeBlockStore(basedir, config, localStateDir); auto nodeStore = make_unique_ref(std::move(blockStore), config.configFile.config()->BlocksizeBytes()); std::vector result; const uint32_t numNodes = nodeStore->numNodes(); result.reserve(numNodes); uint32_t i = 0; cout << "Listing all blocks used by these file system entities..." << endl; for (const auto& blobId : knownBlobIds) { _forEachBlockInBlob(nodeStore.get(), blobId, [&result, &i, numNodes] (const BlockId& blockId) { cout << "\r" << (++i) << "/" << numNodes << flush; result.push_back(blockId); }); } std::cout << "...done" << endl; return result; } set _getAllBlockIds(const path& basedir, const CryConfigLoader::ConfigLoadResult& config, LocalStateDir& localStateDir) { auto blockStore= makeBlockStore(basedir, config, localStateDir); set result; blockStore->forEachBlock([&result] (const BlockId& blockId) { result.insert(blockId); }); return result; } int main(int argc, char* argv[]) { if (argc != 2) { std::cerr << "Usage: cryfs-stats [basedir]" << std::endl; exit(1); } path basedir = argv[1]; std::cout << "Calculating stats for filesystem at " << basedir << std::endl; auto console = std::make_shared(); console->print("Loading config\n"); auto askPassword = [console] () { return console->askPassword("Password: "); }; unique_ref keyProvider = make_unique_ref( console, askPassword, askPassword, make_unique_ref(SCrypt::DefaultSettings) ); auto config_path = basedir / "cryfs.config"; LocalStateDir localStateDir(cpputils::system::HomeDirectory::getXDGDataDir() / "cryfs"); CryConfigLoader config_loader(console, Random::OSRandom(), std::move(keyProvider), localStateDir, boost::none, boost::none, boost::none); auto config = config_loader.load(config_path, false, true); if (config == boost::none) { std::cerr << "Error loading config file" << std::endl; exit(1); } 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; set unaccountedBlocks = _getAllBlockIds(basedir, *config, localStateDir); cout << "done" << endl; vector accountedBlocks = _getKnownBlockIds(basedir, *config, localStateDir); for (const BlockId& blockId : accountedBlocks) { auto num_erased = unaccountedBlocks.erase(blockId); ASSERT(1 == num_erased, "Blob id referenced by directory entry but didn't found it on disk? This can't happen."); } console->print("Calculate statistics\n"); auto blockStore = makeBlockStore(basedir, *config, localStateDir); auto nodeStore = make_unique_ref(std::move(blockStore), config->configFile.config()->BlocksizeBytes()); uint32_t numUnaccountedBlocks = unaccountedBlocks.size(); uint32_t numLeaves = 0; uint32_t numInner = 0; console->print("Unaccounted blocks: " + std::to_string(unaccountedBlocks.size()) + "\n"); for (const auto &blockId : unaccountedBlocks) { console->print("\r" + std::to_string(numLeaves+numInner) + "/" + std::to_string(numUnaccountedBlocks) + ": "); auto node = nodeStore->load(blockId); auto innerNode = dynamic_pointer_move(*node); if (innerNode != none) { ++numInner; printNode(std::move(*innerNode)); } auto leafNode = dynamic_pointer_move(*node); if (leafNode != none) { ++numLeaves; printNode(std::move(*leafNode)); } } console->print("\n" + std::to_string(numLeaves) + " leaves and " + std::to_string(numInner) + " inner nodes\n"); } test/000077500000000000000000000000001347701267100120455ustar00rootroot00000000000000test/CMakeLists.txt000066400000000000000000000006301347701267100146040ustar00rootroot00000000000000if (BUILD_TESTING) include_directories(../src) add_subdirectory(my-gtest-main) add_subdirectory(gitversion) add_subdirectory(cpp-utils) if (NOT MSVC) # TODO Make this build on Windows add_subdirectory(fspp) endif() add_subdirectory(parallelaccessstore) add_subdirectory(blockstore) add_subdirectory(blobstore) add_subdirectory(cryfs) add_subdirectory(cryfs-cli) endif(BUILD_TESTING) test/blobstore/000077500000000000000000000000001347701267100140405ustar00rootroot00000000000000test/blobstore/CMakeLists.txt000066400000000000000000000033041347701267100166000ustar00rootroot00000000000000project (blobstore-test) set(SOURCES implementations/onblocks/utils/MaxZeroSubtractionTest.cpp implementations/onblocks/utils/CeilDivisionTest.cpp implementations/onblocks/utils/IntPowTest.cpp implementations/onblocks/utils/CeilLogTest.cpp implementations/onblocks/testutils/BlobStoreTest.cpp implementations/onblocks/BlobStoreTest.cpp implementations/onblocks/datanodestore/DataLeafNodeTest.cpp implementations/onblocks/datanodestore/DataInnerNodeTest.cpp implementations/onblocks/datanodestore/DataNodeViewTest.cpp implementations/onblocks/datanodestore/DataNodeStoreTest.cpp implementations/onblocks/datatreestore/testutils/DataTreeTest.cpp implementations/onblocks/datatreestore/impl/GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest.cpp implementations/onblocks/datatreestore/impl/GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest.cpp implementations/onblocks/datatreestore/DataTreeTest_Performance.cpp implementations/onblocks/datatreestore/DataTreeTest_ResizeByTraversing.cpp implementations/onblocks/datatreestore/DataTreeTest_NumStoredBytes.cpp implementations/onblocks/datatreestore/DataTreeTest_ResizeNumBytes.cpp implementations/onblocks/datatreestore/DataTreeStoreTest.cpp implementations/onblocks/datatreestore/LeafTraverserTest.cpp implementations/onblocks/BlobSizeTest.cpp implementations/onblocks/BlobReadWriteTest.cpp implementations/onblocks/BigBlobsTest.cpp ) add_executable(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} my-gtest-main googletest blobstore) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) test/blobstore/implementations/000077500000000000000000000000001347701267100172505ustar00rootroot00000000000000test/blobstore/implementations/onblocks/000077500000000000000000000000001347701267100210625ustar00rootroot00000000000000test/blobstore/implementations/onblocks/BigBlobsTest.cpp000066400000000000000000000124551347701267100241200ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "blobstore/implementations/onblocks/BlobStoreOnBlocks.h" #include "blobstore/implementations/onblocks/BlobOnBlocks.h" using namespace blobstore; using namespace blobstore::onblocks; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::DataFixture; using cpputils::Data; using blockstore::inmemory::InMemoryBlockStore2; using blockstore::lowtohighlevel::LowToHighLevelBlockStore; using blockstore::compressing::CompressingBlockStore; using blockstore::compressing::RunLengthEncoding; // Test cases, ensuring that big blobs (>4G) work (i.e. testing that we don't use any 32bit variables for blob size, etc.) class BigBlobsTest : public ::testing::Test { public: static constexpr size_t BLOCKSIZE = 32 * 1024; static constexpr uint64_t SMALL_BLOB_SIZE = UINT64_C(1024)*1024*1024*3.95; // 3.95 GB (<4GB) static constexpr uint64_t LARGE_BLOB_SIZE = UINT64_C(1024)*1024*1024*4.05; // 4.05 GB (>4GB) static constexpr uint64_t max_uint_32 = std::numeric_limits::max(); static_assert(SMALL_BLOB_SIZE < max_uint_32, "LARGE_BLOB_SIZE should need 64bit or the test case is mute"); static_assert(LARGE_BLOB_SIZE > max_uint_32, "LARGE_BLOB_SIZE should need 64bit or the test case is mute"); unique_ref blobStore = make_unique_ref(make_unique_ref>(make_unique_ref(make_unique_ref())), BLOCKSIZE); unique_ref blob = blobStore->create(); }; constexpr size_t BigBlobsTest::BLOCKSIZE; constexpr uint64_t BigBlobsTest::SMALL_BLOB_SIZE; constexpr uint64_t BigBlobsTest::LARGE_BLOB_SIZE; TEST_F(BigBlobsTest, Resize) { //These operations are in one test case and not in many small ones, because it takes quite long to create a >4GB blob. //Resize to >4GB blob->resize(LARGE_BLOB_SIZE); EXPECT_EQ(LARGE_BLOB_SIZE, blob->size()); //Grow while >4GB blob->resize(LARGE_BLOB_SIZE + 1024); EXPECT_EQ(LARGE_BLOB_SIZE + 1024, blob->size()); //Shrink while >4GB blob->resize(LARGE_BLOB_SIZE); EXPECT_EQ(LARGE_BLOB_SIZE, blob->size()); //Shrink to <4GB blob->resize(SMALL_BLOB_SIZE); EXPECT_EQ(SMALL_BLOB_SIZE, blob->size()); //Grow to >4GB blob->resize(LARGE_BLOB_SIZE); EXPECT_EQ(LARGE_BLOB_SIZE, blob->size()); //Flush >4GB blob blob->flush(); //Destruct >4GB blob auto blockId = blob->blockId(); cpputils::destruct(std::move(blob)); //Load >4GB blob blob = blobStore->load(blockId).value(); //Remove >4GB blob blobStore->remove(std::move(blob)); } TEST_F(BigBlobsTest, GrowByWriting_Crossing4GBBorder) { Data fixture = DataFixture::generate(2*(LARGE_BLOB_SIZE-SMALL_BLOB_SIZE)); blob->write(fixture.data(), SMALL_BLOB_SIZE, fixture.size()); EXPECT_EQ(LARGE_BLOB_SIZE+(LARGE_BLOB_SIZE-SMALL_BLOB_SIZE), blob->size()); Data loaded(fixture.size()); blob->read(loaded.data(), SMALL_BLOB_SIZE, loaded.size()); EXPECT_EQ(0, std::memcmp(loaded.data(), fixture.data(), loaded.size())); } TEST_F(BigBlobsTest, GrowByWriting_Outside4GBBorder_StartingSizeZero) { Data fixture = DataFixture::generate(1024); blob->write(fixture.data(), LARGE_BLOB_SIZE, fixture.size()); EXPECT_EQ(LARGE_BLOB_SIZE+1024, blob->size()); Data loaded(fixture.size()); blob->read(loaded.data(), LARGE_BLOB_SIZE, loaded.size()); EXPECT_EQ(0, std::memcmp(loaded.data(), fixture.data(), loaded.size())); } TEST_F(BigBlobsTest, GrowByWriting_Outside4GBBorder_StartingSizeOutside4GBBorder) { blob->resize(LARGE_BLOB_SIZE); Data fixture = DataFixture::generate(1024); blob->write(fixture.data(), LARGE_BLOB_SIZE+1024, fixture.size()); EXPECT_EQ(LARGE_BLOB_SIZE+2048, blob->size()); Data loaded(fixture.size()); blob->read(loaded.data(), LARGE_BLOB_SIZE+1024, loaded.size()); EXPECT_EQ(0, std::memcmp(loaded.data(), fixture.data(), loaded.size())); } TEST_F(BigBlobsTest, ReadWriteAfterGrown_Crossing4GBBorder) { blob->resize(LARGE_BLOB_SIZE+(LARGE_BLOB_SIZE-SMALL_BLOB_SIZE)+1024); Data fixture = DataFixture::generate(2*(LARGE_BLOB_SIZE-SMALL_BLOB_SIZE)); blob->write(fixture.data(), SMALL_BLOB_SIZE, fixture.size()); EXPECT_EQ(LARGE_BLOB_SIZE+(LARGE_BLOB_SIZE-SMALL_BLOB_SIZE)+1024, blob->size()); Data loaded(fixture.size()); blob->read(loaded.data(), SMALL_BLOB_SIZE, loaded.size()); EXPECT_EQ(0, std::memcmp(loaded.data(), fixture.data(), loaded.size())); } TEST_F(BigBlobsTest, ReadWriteAfterGrown_Outside4GBBorder) { blob->resize(LARGE_BLOB_SIZE+2048); Data fixture = DataFixture::generate(1024); blob->write(fixture.data(), LARGE_BLOB_SIZE, fixture.size()); EXPECT_EQ(LARGE_BLOB_SIZE+2048, blob->size()); Data loaded(fixture.size()); blob->read(loaded.data(), LARGE_BLOB_SIZE, loaded.size()); EXPECT_EQ(0, std::memcmp(loaded.data(), fixture.data(), loaded.size())); } //TODO Test Blob::readAll (only on 64bit systems) test/blobstore/implementations/onblocks/BlobReadWriteTest.cpp000066400000000000000000000241461347701267100251220ustar00rootroot00000000000000#include "testutils/BlobStoreTest.h" #include #include #include "blobstore/implementations/onblocks/datanodestore/DataNodeView.h" using cpputils::unique_ref; using ::testing::WithParamInterface; using ::testing::Values; using namespace blobstore; using blobstore::onblocks::datanodestore::DataNodeLayout; using blockstore::BlockId; using cpputils::Data; using cpputils::DataFixture; class BlobReadWriteTest: public BlobStoreTest { public: static constexpr uint32_t LARGE_SIZE = 10 * 1024 * 1024; static constexpr DataNodeLayout LAYOUT = DataNodeLayout(BLOCKSIZE_BYTES); BlobReadWriteTest() :randomData(DataFixture::generate(LARGE_SIZE)), blob(blobStore->create()) { } Data readBlob(const Blob &blob) { Data data(blob.size()); blob.read(data.data(), 0, data.size()); return data; } template void EXPECT_DATA_READS_AS(const DataClass &expected, const Blob &actual, uint64_t offset, uint64_t size) { Data read(size); actual.read(read.data(), offset, size); EXPECT_EQ(0, std::memcmp(expected.data(), read.data(), size)); } Data randomData; unique_ref blob; }; constexpr uint32_t BlobReadWriteTest::LARGE_SIZE; constexpr DataNodeLayout BlobReadWriteTest::LAYOUT; TEST_F(BlobReadWriteTest, WritingImmediatelyFlushes_SmallSize) { blob->resize(5); blob->write(randomData.data(), 0, 5); auto loaded = loadBlob(blob->blockId()); EXPECT_DATA_READS_AS(randomData, *loaded, 0, 5); } TEST_F(BlobReadWriteTest, WritingImmediatelyFlushes_LargeSize) { blob->resize(LARGE_SIZE); blob->write(randomData.data(), 0, LARGE_SIZE); auto loaded = loadBlob(blob->blockId()); EXPECT_DATA_READS_AS(randomData, *loaded, 0, LARGE_SIZE); } // Regression test for a strange bug we had TEST_F(BlobReadWriteTest, WritingCloseTo16ByteLimitDoesntDestroySize) { blob->resize(1); blob->write(randomData.data(), 32776, 4); EXPECT_EQ(32780u, blob->size()); } TEST_F(BlobReadWriteTest, givenEmptyBlob_whenTryReadInFirstLeaf_thenFails) { Data data(5); size_t read = blob->tryRead(data.data(), 3, 5); EXPECT_EQ(0, read); } TEST_F(BlobReadWriteTest, givenEmptyBlob_whenTryReadInLaterLeaf_thenFails) { Data data(5); size_t read = blob->tryRead(data.data(), 2*LAYOUT.maxBytesPerLeaf(), 5); EXPECT_EQ(0, read); } TEST_F(BlobReadWriteTest, givenEmptyBlob_whenReadInFirstLeaf_thenFails) { Data data(5); EXPECT_ANY_THROW( blob->read(data.data(), 3, 5) ); } TEST_F(BlobReadWriteTest, givenEmptyBlob_whenReadInLaterLeaf_thenFails) { Data data(5); EXPECT_ANY_THROW( blob->read(data.data(), 2*LAYOUT.maxBytesPerLeaf(), 5) ); } TEST_F(BlobReadWriteTest, givenEmptyBlob_whenReadAll_thenReturnsZeroSizedData) { Data data = blob->readAll(); EXPECT_EQ(0, data.size()); } TEST_F(BlobReadWriteTest, givenEmptyBlob_whenWrite_thenGrows) { Data data(5); blob->write(data.data(), 4, 5); EXPECT_EQ(9, blob->size()); } TEST_F(BlobReadWriteTest, givenEmptyBlob_whenWriteZeroBytes_thenDoesntGrow) { Data data(5); blob->write(data.data(), 4, 0); EXPECT_EQ(0, blob->size());; } TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenTryReadInFirstLeaf_thenFails) { Data data(5); size_t read = blob->tryRead(data.data(), 3, 5); EXPECT_EQ(0, read); } TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenTryReadInLaterLeaf_thenFails) { Data data(5); size_t read = blob->tryRead(data.data(), 2*LAYOUT.maxBytesPerLeaf(), 5); EXPECT_EQ(0, read); } TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenReadInFirstLeaf_thenFails) { Data data(5); EXPECT_ANY_THROW( blob->read(data.data(), 3, 5) ); } TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenReadInLaterLeaf_thenFails) { Data data(5); EXPECT_ANY_THROW( blob->read(data.data(), 2*LAYOUT.maxBytesPerLeaf(), 5) ); } TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenReadAll_thenReturnsZeroSizedData) { Data data = blob->readAll(); EXPECT_EQ(0, data.size()); } TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenWrite_thenGrows) { Data data(5); blob->write(data.data(), 4, 5); EXPECT_EQ(9, blob->size()); } TEST_F(BlobReadWriteTest, givenBlobResizedToZero_whenWriteZeroBytes_thenDoesntGrow) { Data data(5); blob->write(data.data(), 4, 0); EXPECT_EQ(0, blob->size()); } struct DataRange { uint64_t blobsize; uint64_t offset; uint64_t count; }; class BlobReadWriteDataTest: public BlobReadWriteTest, public WithParamInterface { public: Data foregroundData; Data backgroundData; BlobReadWriteDataTest() : foregroundData(DataFixture::generate(GetParam().count, 0)), backgroundData(DataFixture::generate(GetParam().blobsize, 1)) { } template void EXPECT_DATA_READS_AS_OUTSIDE_OF(const DataClass &expected, const Blob &blob, uint64_t start, uint64_t count) { Data begin(start); Data end(GetParam().blobsize - count - start); std::memcpy(begin.data(), expected.data(), start); std::memcpy(end.data(), expected.dataOffset(start+count), end.size()); EXPECT_DATA_READS_AS(begin, blob, 0, start); EXPECT_DATA_READS_AS(end, blob, start + count, end.size()); } void EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(const Blob &blob, uint64_t start, uint64_t count) { Data ZEROES(GetParam().blobsize); ZEROES.FillWithZeroes(); EXPECT_DATA_READS_AS_OUTSIDE_OF(ZEROES, blob, start, count); } }; INSTANTIATE_TEST_CASE_P(BlobReadWriteDataTest, BlobReadWriteDataTest, Values( //Blob with only one leaf DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf(), 0, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()}, // full size leaf, access beginning to end DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf(), 100, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-200}, // full size leaf, access middle to middle DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf(), 0, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100}, // full size leaf, access beginning to middle DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf(), 100, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100}, // full size leaf, access middle to end DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100, 0, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100}, // non-full size leaf, access beginning to end DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100, 100, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-300}, // non-full size leaf, access middle to middle DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100, 0, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-200}, // non-full size leaf, access beginning to middle DataRange{BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-100, 100, BlobReadWriteDataTest::LAYOUT.maxBytesPerLeaf()-200}, // non-full size leaf, access middle to end //Larger blob DataRange{BlobReadWriteDataTest::LARGE_SIZE, 0, BlobReadWriteDataTest::LARGE_SIZE}, // access beginning to end DataRange{BlobReadWriteDataTest::LARGE_SIZE, 100, BlobReadWriteDataTest::LARGE_SIZE-200}, // access middle first leaf to middle last leaf DataRange{BlobReadWriteDataTest::LARGE_SIZE, 0, BlobReadWriteDataTest::LARGE_SIZE-100}, // access beginning to middle last leaf DataRange{BlobReadWriteDataTest::LARGE_SIZE, 100, BlobReadWriteDataTest::LARGE_SIZE-100}, // access middle first leaf to end DataRange{BlobReadWriteDataTest::LARGE_SIZE, BlobReadWriteDataTest::LARGE_SIZE*1/3, BlobReadWriteDataTest::LARGE_SIZE*1/3}, // access middle to middle DataRange{BlobReadWriteDataTest::LARGE_SIZE, 0, BlobReadWriteDataTest::LARGE_SIZE*2/3}, // access beginning to middle DataRange{BlobReadWriteDataTest::LARGE_SIZE, BlobReadWriteDataTest::LARGE_SIZE*1/3, BlobReadWriteDataTest::LARGE_SIZE*2/3} // access middle to end )); TEST_P(BlobReadWriteDataTest, WritingDoesntChangeSize) { blob->resize(GetParam().blobsize); blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count); EXPECT_EQ(GetParam().blobsize, blob->size()); } TEST_P(BlobReadWriteDataTest, WriteAndReadImmediately) { blob->resize(GetParam().blobsize); blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count); EXPECT_DATA_READS_AS(this->foregroundData, *blob, GetParam().offset, GetParam().count); EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*blob, GetParam().offset, GetParam().count); } TEST_P(BlobReadWriteDataTest, WriteAndReadAfterLoading) { blob->resize(GetParam().blobsize); blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count); auto loaded = loadBlob(blob->blockId()); EXPECT_DATA_READS_AS(this->foregroundData, *loaded, GetParam().offset, GetParam().count); EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*loaded, GetParam().offset, GetParam().count); } TEST_P(BlobReadWriteDataTest, OverwriteAndRead) { blob->resize(GetParam().blobsize); blob->write(this->backgroundData.data(), 0, GetParam().blobsize); blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count); EXPECT_DATA_READS_AS(this->foregroundData, *blob, GetParam().offset, GetParam().count); EXPECT_DATA_READS_AS_OUTSIDE_OF(this->backgroundData, *blob, GetParam().offset, GetParam().count); } TEST_P(BlobReadWriteDataTest, WriteWholeAndReadPart) { blob->resize(GetParam().blobsize); blob->write(this->backgroundData.data(), 0, GetParam().blobsize); Data read(GetParam().count); blob->read(read.data(), GetParam().offset, GetParam().count); EXPECT_EQ(0, std::memcmp(read.data(), this->backgroundData.dataOffset(GetParam().offset), GetParam().count)); } TEST_P(BlobReadWriteDataTest, WritePartAndReadWhole) { blob->resize(GetParam().blobsize); blob->write(this->backgroundData.data(), 0, GetParam().blobsize); blob->write(this->foregroundData.data(), GetParam().offset, GetParam().count); Data read = readBlob(*blob); EXPECT_EQ(0, std::memcmp(read.data(), this->backgroundData.data(), GetParam().offset)); EXPECT_EQ(0, std::memcmp(read.dataOffset(GetParam().offset), this->foregroundData.data(), GetParam().count)); EXPECT_EQ(0, std::memcmp(read.dataOffset(GetParam().offset+GetParam().count), this->backgroundData.dataOffset(GetParam().offset+GetParam().count), GetParam().blobsize-GetParam().count-GetParam().offset)); } test/blobstore/implementations/onblocks/BlobSizeTest.cpp000066400000000000000000000107611347701267100241440ustar00rootroot00000000000000#include "testutils/BlobStoreTest.h" #include #include using namespace blobstore; using blockstore::BlockId; using cpputils::Data; using cpputils::DataFixture; using cpputils::unique_ref; class BlobSizeTest: public BlobStoreTest { public: BlobSizeTest(): blob(blobStore->create()) {} static constexpr uint32_t MEDIUM_SIZE = 5 * 1024 * 1024; static constexpr uint32_t LARGE_SIZE = 10 * 1024 * 1024; unique_ref blob; }; constexpr uint32_t BlobSizeTest::MEDIUM_SIZE; constexpr uint32_t BlobSizeTest::LARGE_SIZE; TEST_F(BlobSizeTest, CreatedBlobIsEmpty) { EXPECT_EQ(0u, blob->size()); } TEST_F(BlobSizeTest, Growing_1Byte) { blob->resize(1); EXPECT_EQ(1u, blob->size()); } TEST_F(BlobSizeTest, Growing_Large) { blob->resize(LARGE_SIZE); EXPECT_EQ(LARGE_SIZE, blob->size()); } TEST_F(BlobSizeTest, Shrinking_Empty) { blob->resize(LARGE_SIZE); blob->resize(0); EXPECT_EQ(0u, blob->size()); } TEST_F(BlobSizeTest, Shrinking_1Byte) { blob->resize(LARGE_SIZE); blob->resize(1); EXPECT_EQ(1u, blob->size()); } TEST_F(BlobSizeTest, ResizingToItself_Empty) { blob->resize(0); EXPECT_EQ(0u, blob->size()); } TEST_F(BlobSizeTest, ResizingToItself_1Byte) { blob->resize(1); blob->resize(1); EXPECT_EQ(1u, blob->size()); } TEST_F(BlobSizeTest, ResizingToItself_Large) { blob->resize(LARGE_SIZE); blob->resize(LARGE_SIZE); EXPECT_EQ(LARGE_SIZE, blob->size()); } TEST_F(BlobSizeTest, EmptyBlobStaysEmptyWhenLoading) { BlockId blockId = blob->blockId(); reset(std::move(blob)); auto loaded = loadBlob(blockId); EXPECT_EQ(0u, loaded->size()); } TEST_F(BlobSizeTest, BlobSizeStaysIntactWhenLoading) { blob->resize(LARGE_SIZE); BlockId blockId = blob->blockId(); reset(std::move(blob)); auto loaded = loadBlob(blockId); EXPECT_EQ(LARGE_SIZE, loaded->size()); } TEST_F(BlobSizeTest, WritingAtEndOfBlobGrowsBlob_Empty) { int value; blob->write(&value, 0, 4); EXPECT_EQ(4u, blob->size()); } TEST_F(BlobSizeTest, WritingAfterEndOfBlobGrowsBlob_Empty) { int value; blob->write(&value, 2, 4); EXPECT_EQ(6u, blob->size()); } TEST_F(BlobSizeTest, WritingOverEndOfBlobGrowsBlob_NonEmpty) { blob->resize(1); int value; blob->write(&value, 0, 4); EXPECT_EQ(4u, blob->size()); } TEST_F(BlobSizeTest, WritingAtEndOfBlobGrowsBlob_NonEmpty) { blob->resize(1); int value; blob->write(&value, 1, 4); EXPECT_EQ(5u, blob->size()); } TEST_F(BlobSizeTest, WritingAfterEndOfBlobGrowsBlob_NonEmpty) { blob->resize(1); int value; blob->write(&value, 2, 4); EXPECT_EQ(6u, blob->size()); } TEST_F(BlobSizeTest, ChangingSizeImmediatelyFlushes) { blob->resize(LARGE_SIZE); auto loaded = loadBlob(blob->blockId()); EXPECT_EQ(LARGE_SIZE, loaded->size()); } class BlobSizeDataTest: public BlobSizeTest { public: BlobSizeDataTest() :ZEROES(LARGE_SIZE), randomData(DataFixture::generate(LARGE_SIZE)) { ZEROES.FillWithZeroes(); } Data readBlob(const Blob &blob) { Data data(blob.size()); blob.read(data.data(), 0, data.size()); return data; } Data ZEROES; Data randomData; }; TEST_F(BlobSizeDataTest, BlobIsZeroedOutAfterGrowing) { //uint32_t LARGE_SIZE = 2*1024*1024; blob->resize(LARGE_SIZE); EXPECT_EQ(0, std::memcmp(readBlob(*blob).data(), ZEROES.data(), LARGE_SIZE)); } TEST_F(BlobSizeDataTest, BlobIsZeroedOutAfterGrowingAndLoading) { blob->resize(LARGE_SIZE); auto loaded = loadBlob(blob->blockId()); EXPECT_EQ(0, std::memcmp(readBlob(*loaded).data(), ZEROES.data(), LARGE_SIZE)); } TEST_F(BlobSizeDataTest, DataStaysIntactWhenGrowing) { blob->resize(MEDIUM_SIZE); blob->write(randomData.data(), 0, MEDIUM_SIZE); blob->resize(LARGE_SIZE); EXPECT_EQ(0, std::memcmp(readBlob(*blob).data(), randomData.data(), MEDIUM_SIZE)); EXPECT_EQ(0, std::memcmp(readBlob(*blob).dataOffset(MEDIUM_SIZE), ZEROES.data(), LARGE_SIZE-MEDIUM_SIZE)); } TEST_F(BlobSizeDataTest, DataStaysIntactWhenShrinking) { blob->resize(LARGE_SIZE); blob->write(randomData.data(), 0, LARGE_SIZE); blob->resize(MEDIUM_SIZE); EXPECT_EQ(0, std::memcmp(readBlob(*blob).data(), randomData.data(), MEDIUM_SIZE)); } TEST_F(BlobSizeDataTest, ChangedAreaIsZeroedOutWhenShrinkingAndRegrowing) { blob->resize(LARGE_SIZE); blob->write(randomData.data(), 0, LARGE_SIZE); blob->resize(MEDIUM_SIZE); blob->resize(LARGE_SIZE); EXPECT_EQ(0, std::memcmp(readBlob(*blob).data(), randomData.data(), MEDIUM_SIZE)); EXPECT_EQ(0, std::memcmp(readBlob(*blob).dataOffset(MEDIUM_SIZE), ZEROES.data(), LARGE_SIZE-MEDIUM_SIZE)); } test/blobstore/implementations/onblocks/BlobStoreTest.cpp000066400000000000000000000027711347701267100243300ustar00rootroot00000000000000#include "testutils/BlobStoreTest.h" #include using blockstore::BlockId; using boost::none; TEST_F(BlobStoreTest, LoadNonexistingKeyOnEmptyBlobstore) { const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_EQ(none, blobStore->load(blockId)); } TEST_F(BlobStoreTest, LoadNonexistingKeyOnNonEmptyBlobstore) { blobStore->create(); const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_EQ(none, blobStore->load(blockId)); } TEST_F(BlobStoreTest, TwoCreatedBlobsHaveDifferentKeys) { auto blob1 = blobStore->create(); auto blob2 = blobStore->create(); EXPECT_NE(blob1->blockId(), blob2->blockId()); } TEST_F(BlobStoreTest, BlobIsNotLoadableAfterDeletion_DeleteDirectly) { auto blob = blobStore->create(); BlockId blockId = blob->blockId(); blobStore->remove(std::move(blob)); EXPECT_FALSE(static_cast(blobStore->load(blockId))); } TEST_F(BlobStoreTest, BlobIsNotLoadableAfterDeletion_DeleteByKey) { auto blockId = blobStore->create()->blockId(); blobStore->remove(blockId); EXPECT_FALSE(static_cast(blobStore->load(blockId))); } TEST_F(BlobStoreTest, BlobIsNotLoadableAfterDeletion_DeleteAfterLoading) { auto blob = blobStore->create(); BlockId blockId = blob->blockId(); reset(std::move(blob)); blobStore->remove(loadBlob(blockId)); EXPECT_FALSE(static_cast(blobStore->load(blockId))); } test/blobstore/implementations/onblocks/datanodestore/000077500000000000000000000000001347701267100237165ustar00rootroot00000000000000test/blobstore/implementations/onblocks/datanodestore/DataInnerNodeTest.cpp000066400000000000000000000201771347701267100277440ustar00rootroot00000000000000#include #include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h" #include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h" #include "blobstore/implementations/onblocks/datanodestore/DataNodeStore.h" #include #include #include #include using ::testing::Test; using cpputils::dynamic_pointer_move; using blockstore::BlockId; using blockstore::testfake::FakeBlockStore; using blockstore::BlockStore; using cpputils::Data; using namespace blobstore; using namespace blobstore::onblocks; using namespace blobstore::onblocks::datanodestore; using cpputils::unique_ref; using cpputils::make_unique_ref; using std::vector; class DataInnerNodeTest: public Test { public: static constexpr uint32_t BLOCKSIZE_BYTES = 1024; DataInnerNodeTest() : _blockStore(make_unique_ref()), blockStore(_blockStore.get()), nodeStore(make_unique_ref(std::move(_blockStore), BLOCKSIZE_BYTES)), ZEROES(nodeStore->layout().maxBytesPerLeaf()), leaf(nodeStore->createNewLeafNode(Data(0))), node(nodeStore->createNewInnerNode(1, {leaf->blockId()})) { ZEROES.FillWithZeroes(); } unique_ref LoadInnerNode(const BlockId &blockId) { auto node = nodeStore->load(blockId).value(); return dynamic_pointer_move(node).value(); } BlockId CreateNewInnerNodeReturnKey(const DataNode &firstChild) { return nodeStore->createNewInnerNode(firstChild.depth()+1, {firstChild.blockId()})->blockId(); } unique_ref CreateNewInnerNode() { auto new_leaf = nodeStore->createNewLeafNode(Data(0)); return nodeStore->createNewInnerNode(1, {new_leaf->blockId()}); } unique_ref CreateAndLoadNewInnerNode(const DataNode &firstChild) { auto blockId = CreateNewInnerNodeReturnKey(firstChild); return LoadInnerNode(blockId); } unique_ref CreateNewInnerNode(uint8_t depth, const vector &children) { return nodeStore->createNewInnerNode(depth, children); } BlockId CreateNewInnerNodeReturnKey(uint8_t depth, const vector &children) { return CreateNewInnerNode(depth, children)->blockId(); } unique_ref CreateAndLoadNewInnerNode(uint8_t depth, const vector &children) { auto blockId = CreateNewInnerNodeReturnKey(depth, children); return LoadInnerNode(blockId); } BlockId AddALeafTo(DataInnerNode *node) { auto leaf2 = nodeStore->createNewLeafNode(Data(0)); node->addChild(*leaf2); return leaf2->blockId(); } BlockId CreateNodeWithDataConvertItToInnerNodeAndReturnKey() { auto node = CreateNewInnerNode(); AddALeafTo(node.get()); AddALeafTo(node.get()); auto child = nodeStore->createNewLeafNode(Data(0)); unique_ref converted = DataNode::convertToNewInnerNode(std::move(node), nodeStore->layout(), *child); return converted->blockId(); } unique_ref CopyInnerNode(const DataInnerNode &node) { auto copied = nodeStore->createNewNodeAsCopyFrom(node); return dynamic_pointer_move(copied).value(); } BlockId InitializeInnerNodeAddLeafReturnKey() { auto node = DataInnerNode::CreateNewNode(blockStore, nodeStore->layout(), 1, {leaf->blockId()}); AddALeafTo(node.get()); return node->blockId(); } unique_ref _blockStore; BlockStore *blockStore; unique_ref nodeStore; Data ZEROES; unique_ref leaf; unique_ref node; private: DISALLOW_COPY_AND_ASSIGN(DataInnerNodeTest); }; constexpr uint32_t DataInnerNodeTest::BLOCKSIZE_BYTES; TEST_F(DataInnerNodeTest, CorrectKeyReturnedAfterLoading) { BlockId blockId = DataInnerNode::CreateNewNode(blockStore, nodeStore->layout(), 1, {leaf->blockId()})->blockId(); auto loaded = nodeStore->load(blockId).value(); EXPECT_EQ(blockId, loaded->blockId()); } TEST_F(DataInnerNodeTest, InitializesCorrectly) { auto node = DataInnerNode::CreateNewNode(blockStore, nodeStore->layout(), 1, {leaf->blockId()}); EXPECT_EQ(1u, node->numChildren()); EXPECT_EQ(leaf->blockId(), node->readChild(0).blockId()); } TEST_F(DataInnerNodeTest, ReinitializesCorrectly) { auto blockId = DataLeafNode::CreateNewNode(blockStore, nodeStore->layout(), Data(0))->blockId(); auto node = DataInnerNode::InitializeNewNode(blockStore->load(blockId).value(), nodeStore->layout(), 1, {leaf->blockId()}); EXPECT_EQ(1u, node->numChildren()); EXPECT_EQ(leaf->blockId(), node->readChild(0).blockId()); } TEST_F(DataInnerNodeTest, IsCorrectlyInitializedAfterLoading) { auto loaded = CreateAndLoadNewInnerNode(*leaf); EXPECT_EQ(1u, loaded->numChildren()); EXPECT_EQ(leaf->blockId(), loaded->readChild(0).blockId()); } TEST_F(DataInnerNodeTest, AddingASecondLeaf) { BlockId leaf2_blockId = AddALeafTo(node.get()); EXPECT_EQ(2u, node->numChildren()); EXPECT_EQ(leaf->blockId(), node->readChild(0).blockId()); EXPECT_EQ(leaf2_blockId, node->readChild(1).blockId()); } TEST_F(DataInnerNodeTest, AddingASecondLeafAndReload) { auto leaf2 = nodeStore->createNewLeafNode(Data(0)); auto loaded = CreateAndLoadNewInnerNode(1, {leaf->blockId(), leaf2->blockId()}); EXPECT_EQ(2u, loaded->numChildren()); EXPECT_EQ(leaf->blockId(), loaded->readChild(0).blockId()); EXPECT_EQ(leaf2->blockId(), loaded->readChild(1).blockId()); } TEST_F(DataInnerNodeTest, BuildingAThreeLevelTree) { auto node2 = CreateNewInnerNode(); auto parent = CreateNewInnerNode(node->depth()+1, {node->blockId(), node2->blockId()}); EXPECT_EQ(2u, parent->numChildren()); EXPECT_EQ(node->blockId(), parent->readChild(0).blockId()); EXPECT_EQ(node2->blockId(), parent->readChild(1).blockId()); } TEST_F(DataInnerNodeTest, BuildingAThreeLevelTreeAndReload) { auto node2 = CreateNewInnerNode(); auto parent = CreateAndLoadNewInnerNode(node->depth()+1, {node->blockId(), node2->blockId()}); EXPECT_EQ(2u, parent->numChildren()); EXPECT_EQ(node->blockId(), parent->readChild(0).blockId()); EXPECT_EQ(node2->blockId(), parent->readChild(1).blockId()); } TEST_F(DataInnerNodeTest, ConvertToInternalNode) { auto child = nodeStore->createNewLeafNode(Data(0)); BlockId node_blockId = node->blockId(); unique_ref converted = DataNode::convertToNewInnerNode(std::move(node), nodeStore->layout(), *child); EXPECT_EQ(1u, converted->numChildren()); EXPECT_EQ(child->blockId(), converted->readChild(0).blockId()); EXPECT_EQ(node_blockId, converted->blockId()); } TEST_F(DataInnerNodeTest, ConvertToInternalNodeZeroesOutChildrenRegion) { BlockId blockId = CreateNodeWithDataConvertItToInnerNodeAndReturnKey(); auto block = blockStore->load(blockId).value(); EXPECT_EQ(0, std::memcmp(ZEROES.data(), static_cast(block->data())+DataNodeLayout::HEADERSIZE_BYTES+sizeof(DataInnerNode::ChildEntry), nodeStore->layout().maxBytesPerLeaf()-sizeof(DataInnerNode::ChildEntry))); } TEST_F(DataInnerNodeTest, CopyingCreatesNewNode) { auto copied = CopyInnerNode(*node); EXPECT_NE(node->blockId(), copied->blockId()); } TEST_F(DataInnerNodeTest, CopyInnerNodeWithOneChild) { auto copied = CopyInnerNode(*node); EXPECT_EQ(node->numChildren(), copied->numChildren()); EXPECT_EQ(node->readChild(0).blockId(), copied->readChild(0).blockId()); } TEST_F(DataInnerNodeTest, CopyInnerNodeWithTwoChildren) { AddALeafTo(node.get()); auto copied = CopyInnerNode(*node); EXPECT_EQ(node->numChildren(), copied->numChildren()); EXPECT_EQ(node->readChild(0).blockId(), copied->readChild(0).blockId()); EXPECT_EQ(node->readChild(1).blockId(), copied->readChild(1).blockId()); } TEST_F(DataInnerNodeTest, LastChildWhenOneChild) { EXPECT_EQ(leaf->blockId(), node->readLastChild().blockId()); } TEST_F(DataInnerNodeTest, LastChildWhenTwoChildren) { BlockId blockId = AddALeafTo(node.get()); EXPECT_EQ(blockId, node->readLastChild().blockId()); } TEST_F(DataInnerNodeTest, LastChildWhenThreeChildren) { AddALeafTo(node.get()); BlockId blockId = AddALeafTo(node.get()); EXPECT_EQ(blockId, node->readLastChild().blockId()); } test/blobstore/implementations/onblocks/datanodestore/DataLeafNodeTest.cpp000066400000000000000000000316331347701267100275370ustar00rootroot00000000000000#include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h" #include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h" #include "blobstore/implementations/onblocks/datanodestore/DataNodeStore.h" #include "blobstore/implementations/onblocks/BlobStoreOnBlocks.h" #include #include #include #include #include using ::testing::Test; using ::testing::WithParamInterface; using ::testing::Values; using cpputils::unique_ref; using cpputils::make_unique_ref; using std::string; using cpputils::DataFixture; using cpputils::deserialize; //TODO Split into multiple files using cpputils::dynamic_pointer_move; using blockstore::BlockStore; using cpputils::Data; using blockstore::BlockId; using blockstore::testfake::FakeBlockStore; using namespace blobstore; using namespace blobstore::onblocks; using namespace blobstore::onblocks::datanodestore; #define EXPECT_IS_PTR_TYPE(Type, ptr) EXPECT_NE(nullptr, dynamic_cast(ptr)) << "Given pointer cannot be cast to the given type" class DataLeafNodeTest: public Test { public: static constexpr uint32_t BLOCKSIZE_BYTES = 1024; static constexpr DataNodeLayout LAYOUT = DataNodeLayout(BLOCKSIZE_BYTES); DataLeafNodeTest(): _blockStore(make_unique_ref()), blockStore(_blockStore.get()), nodeStore(make_unique_ref(std::move(_blockStore), BLOCKSIZE_BYTES)), ZEROES(nodeStore->layout().maxBytesPerLeaf()), randomData(nodeStore->layout().maxBytesPerLeaf()), leaf(nodeStore->createNewLeafNode(Data(0))) { ZEROES.FillWithZeroes(); Data dataFixture(DataFixture::generate(nodeStore->layout().maxBytesPerLeaf())); std::memcpy(randomData.data(), dataFixture.data(), randomData.size()); } Data loadData(const DataLeafNode &leaf) { Data data(leaf.numBytes()); leaf.read(data.data(), 0, leaf.numBytes()); return data; } BlockId WriteDataToNewLeafBlockAndReturnKey() { auto newleaf = nodeStore->createNewLeafNode(Data(0)); newleaf->resize(randomData.size()); newleaf->write(randomData.data(), 0, randomData.size()); return newleaf->blockId(); } void FillLeafBlockWithData() { FillLeafBlockWithData(leaf.get()); } void FillLeafBlockWithData(DataLeafNode *leaf_to_fill) { leaf_to_fill->resize(randomData.size()); leaf_to_fill->write(randomData.data(), 0, randomData.size()); } unique_ref LoadLeafNode(const BlockId &blockId) { auto leaf = nodeStore->load(blockId).value(); return dynamic_pointer_move(leaf).value(); } void ResizeLeaf(const BlockId &blockId, size_t size) { auto leaf = LoadLeafNode(blockId); EXPECT_IS_PTR_TYPE(DataLeafNode, leaf.get()); leaf->resize(size); } BlockId CreateLeafWithDataConvertItToInnerNodeAndReturnKey() { auto leaf = nodeStore->createNewLeafNode(Data(0)); FillLeafBlockWithData(leaf.get()); auto child = nodeStore->createNewLeafNode(Data(0)); unique_ref converted = DataNode::convertToNewInnerNode(std::move(leaf), LAYOUT, *child); return converted->blockId(); } unique_ref CopyLeafNode(const DataLeafNode &node) { auto copied = nodeStore->createNewNodeAsCopyFrom(node); return dynamic_pointer_move(copied).value(); } BlockId InitializeLeafGrowAndReturnKey() { auto leaf = DataLeafNode::CreateNewNode(blockStore, LAYOUT, Data(LAYOUT.maxBytesPerLeaf())); leaf->resize(5); return leaf->blockId(); } unique_ref _blockStore; BlockStore *blockStore; unique_ref nodeStore; Data ZEROES; Data randomData; unique_ref leaf; private: DISALLOW_COPY_AND_ASSIGN(DataLeafNodeTest); }; constexpr uint32_t DataLeafNodeTest::BLOCKSIZE_BYTES; constexpr DataNodeLayout DataLeafNodeTest::LAYOUT; TEST_F(DataLeafNodeTest, CorrectKeyReturnedAfterLoading) { BlockId blockId = DataLeafNode::CreateNewNode(blockStore, LAYOUT, Data(LAYOUT.maxBytesPerLeaf()))->blockId(); auto loaded = nodeStore->load(blockId).value(); EXPECT_EQ(blockId, loaded->blockId()); } TEST_F(DataLeafNodeTest, InitializesCorrectly) { auto leaf = DataLeafNode::CreateNewNode(blockStore, LAYOUT, Data(5)); EXPECT_EQ(5u, leaf->numBytes()); } TEST_F(DataLeafNodeTest, ReadWrittenDataAfterReloadingBlock) { BlockId blockId = WriteDataToNewLeafBlockAndReturnKey(); auto loaded = LoadLeafNode(blockId); EXPECT_EQ(randomData.size(), loaded->numBytes()); EXPECT_EQ(randomData, loadData(*loaded)); } TEST_F(DataLeafNodeTest, NewLeafNodeHasSizeZero) { EXPECT_EQ(0u, leaf->numBytes()); } TEST_F(DataLeafNodeTest, NewLeafNodeHasSizeZero_AfterLoading) { BlockId blockId = nodeStore->createNewLeafNode(Data(0))->blockId(); auto leaf = LoadLeafNode(blockId); EXPECT_EQ(0u, leaf->numBytes()); } class DataLeafNodeSizeTest: public DataLeafNodeTest, public WithParamInterface { public: BlockId CreateLeafResizeItAndReturnKey() { auto leaf = nodeStore->createNewLeafNode(Data(0)); leaf->resize(GetParam()); return leaf->blockId(); } }; INSTANTIATE_TEST_CASE_P(DataLeafNodeSizeTest, DataLeafNodeSizeTest, Values(0, 1, 5, 16, 32, 512, DataNodeLayout(DataLeafNodeTest::BLOCKSIZE_BYTES).maxBytesPerLeaf())); TEST_P(DataLeafNodeSizeTest, ResizeNode_ReadSizeImmediately) { leaf->resize(GetParam()); EXPECT_EQ(GetParam(), leaf->numBytes()); } TEST_P(DataLeafNodeSizeTest, ResizeNode_ReadSizeAfterLoading) { BlockId blockId = CreateLeafResizeItAndReturnKey(); auto leaf = LoadLeafNode(blockId); EXPECT_EQ(GetParam(), leaf->numBytes()); } TEST_F(DataLeafNodeTest, SpaceIsZeroFilledWhenGrowing) { leaf->resize(randomData.size()); EXPECT_EQ(0, std::memcmp(ZEROES.data(), loadData(*leaf).data(), randomData.size())); } TEST_F(DataLeafNodeTest, SpaceGetsZeroFilledWhenShrinkingAndRegrowing) { FillLeafBlockWithData(); // resize it smaller and then back to original size uint32_t smaller_size = randomData.size() - 100; leaf->resize(smaller_size); leaf->resize(randomData.size()); //Check that the space was filled with zeroes EXPECT_EQ(0, std::memcmp(ZEROES.data(), static_cast(loadData(*leaf).data())+smaller_size, 100)); } TEST_F(DataLeafNodeTest, DataGetsZeroFilledWhenShrinking) { BlockId blockId = WriteDataToNewLeafBlockAndReturnKey(); uint32_t smaller_size = randomData.size() - 100; { //At first, we expect there to be random data in the underlying data block auto block = blockStore->load(blockId).value(); EXPECT_EQ(0, std::memcmp(randomData.dataOffset(smaller_size), static_cast(block->data())+DataNodeLayout::HEADERSIZE_BYTES+smaller_size, 100)); } //After shrinking, we expect there to be zeroes in the underlying data block ResizeLeaf(blockId, smaller_size); { auto block = blockStore->load(blockId).value(); EXPECT_EQ(0, std::memcmp(ZEROES.data(), static_cast(block->data())+DataNodeLayout::HEADERSIZE_BYTES+smaller_size, 100)); } } TEST_F(DataLeafNodeTest, ShrinkingDoesntDestroyValidDataRegion) { FillLeafBlockWithData(); uint32_t smaller_size = randomData.size() - 100; leaf->resize(smaller_size); //Check that the remaining data region is unchanged EXPECT_EQ(0, std::memcmp(randomData.data(), loadData(*leaf).data(), smaller_size)); } TEST_F(DataLeafNodeTest, ConvertToInternalNode) { auto child = nodeStore->createNewLeafNode(Data(0)); BlockId leaf_blockId = leaf->blockId(); unique_ref converted = DataNode::convertToNewInnerNode(std::move(leaf), LAYOUT, *child); EXPECT_EQ(1u, converted->numChildren()); EXPECT_EQ(child->blockId(), converted->readChild(0).blockId()); EXPECT_EQ(leaf_blockId, converted->blockId()); } TEST_F(DataLeafNodeTest, ConvertToInternalNodeZeroesOutChildrenRegion) { BlockId blockId = CreateLeafWithDataConvertItToInnerNodeAndReturnKey(); auto block = blockStore->load(blockId).value(); EXPECT_EQ(0, std::memcmp(ZEROES.data(), static_cast(block->data())+DataNodeLayout::HEADERSIZE_BYTES+sizeof(DataInnerNode::ChildEntry), nodeStore->layout().maxBytesPerLeaf()-sizeof(DataInnerNode::ChildEntry))); } TEST_F(DataLeafNodeTest, CopyingCreatesANewLeaf) { auto copied = CopyLeafNode(*leaf); EXPECT_NE(leaf->blockId(), copied->blockId()); } TEST_F(DataLeafNodeTest, CopyEmptyLeaf) { auto copied = CopyLeafNode(*leaf); EXPECT_EQ(leaf->numBytes(), copied->numBytes()); } TEST_F(DataLeafNodeTest, CopyDataLeaf) { FillLeafBlockWithData(); auto copied = CopyLeafNode(*leaf); EXPECT_EQ(leaf->numBytes(), copied->numBytes()); EXPECT_EQ(0, std::memcmp(loadData(*leaf).data(), loadData(*copied).data(), leaf->numBytes())); //Test that they have different data regions (changing the original one doesn't change the copy) uint8_t data = 0; leaf->write(&data, 0, 1); EXPECT_EQ(data, deserialize(loadData(*leaf).data())); EXPECT_NE(data, deserialize(loadData(*copied).data())); } struct DataRange { uint64_t leafsize; uint64_t offset; uint64_t count; }; class DataLeafNodeDataTest: public DataLeafNodeTest, public WithParamInterface { public: Data foregroundData; Data backgroundData; DataLeafNodeDataTest(): foregroundData(DataFixture::generate(GetParam().count, 0)), backgroundData(DataFixture::generate(GetParam().leafsize, 1)) { } BlockId CreateLeafWriteToItAndReturnKey(const Data &to_write) { auto newleaf = nodeStore->createNewLeafNode(Data(0)); newleaf->resize(GetParam().leafsize); newleaf->write(to_write.data(), GetParam().offset, GetParam().count); return newleaf->blockId(); } void EXPECT_DATA_READS_AS(const Data &expected, const DataLeafNode &leaf, uint64_t offset, uint64_t count) { Data read(count); leaf.read(read.data(), offset, count); EXPECT_EQ(expected, read); } void EXPECT_DATA_READS_AS_OUTSIDE_OF(const Data &expected, const DataLeafNode &leaf, uint64_t start, uint64_t count) { Data begin(start); Data end(GetParam().leafsize - count - start); std::memcpy(begin.data(), expected.data(), start); std::memcpy(end.data(), expected.dataOffset(start+count), end.size()); EXPECT_DATA_READS_AS(begin, leaf, 0, start); EXPECT_DATA_READS_AS(end, leaf, start + count, end.size()); } void EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(const DataLeafNode &leaf, uint64_t start, uint64_t count) { Data ZEROES(GetParam().leafsize); ZEROES.FillWithZeroes(); EXPECT_DATA_READS_AS_OUTSIDE_OF(ZEROES, leaf, start, count); } }; INSTANTIATE_TEST_CASE_P(DataLeafNodeDataTest, DataLeafNodeDataTest, Values( DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf(), 0, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()}, // full size leaf, access beginning to end DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf(), 100, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-200}, // full size leaf, access middle to middle DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf(), 0, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100}, // full size leaf, access beginning to middle DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf(), 100, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100}, // full size leaf, access middle to end DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100, 0, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100}, // non-full size leaf, access beginning to end DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100, 100, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-300}, // non-full size leaf, access middle to middle DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100, 0, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-200}, // non-full size leaf, access beginning to middle DataRange{DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-100, 100, DataLeafNodeTest::LAYOUT.maxBytesPerLeaf()-200} // non-full size leaf, access middle to end )); TEST_P(DataLeafNodeDataTest, WriteAndReadImmediately) { leaf->resize(GetParam().leafsize); leaf->write(this->foregroundData.data(), GetParam().offset, GetParam().count); EXPECT_DATA_READS_AS(this->foregroundData, *leaf, GetParam().offset, GetParam().count); EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*leaf, GetParam().offset, GetParam().count); } TEST_P(DataLeafNodeDataTest, WriteAndReadAfterLoading) { BlockId blockId = CreateLeafWriteToItAndReturnKey(this->foregroundData); auto loaded_leaf = LoadLeafNode(blockId); EXPECT_DATA_READS_AS(this->foregroundData, *loaded_leaf, GetParam().offset, GetParam().count); EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*loaded_leaf, GetParam().offset, GetParam().count); } TEST_P(DataLeafNodeDataTest, OverwriteAndRead) { leaf->resize(GetParam().leafsize); leaf->write(this->backgroundData.data(), 0, GetParam().leafsize); leaf->write(this->foregroundData.data(), GetParam().offset, GetParam().count); EXPECT_DATA_READS_AS(this->foregroundData, *leaf, GetParam().offset, GetParam().count); EXPECT_DATA_READS_AS_OUTSIDE_OF(this->backgroundData, *leaf, GetParam().offset, GetParam().count); } test/blobstore/implementations/onblocks/datanodestore/DataNodeStoreTest.cpp000066400000000000000000000123271347701267100277630ustar00rootroot00000000000000#include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h" #include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h" #include "blobstore/implementations/onblocks/datanodestore/DataNode.h" #include "blobstore/implementations/onblocks/datanodestore/DataNodeStore.h" #include "blobstore/implementations/onblocks/BlobStoreOnBlocks.h" #include #include #include #include using ::testing::Test; using cpputils::unique_ref; using cpputils::make_unique_ref; using std::string; using boost::none; using blockstore::BlockStore; using blockstore::testfake::FakeBlockStore; using blockstore::BlockId; using cpputils::Data; using namespace blobstore; using namespace blobstore::onblocks; using namespace blobstore::onblocks::datanodestore; class DataNodeStoreTest: public Test { public: static constexpr uint32_t BLOCKSIZE_BYTES = 1024; unique_ref _blockStore = make_unique_ref(); BlockStore *blockStore = _blockStore.get(); unique_ref nodeStore = make_unique_ref(std::move(_blockStore), BLOCKSIZE_BYTES); }; constexpr uint32_t DataNodeStoreTest::BLOCKSIZE_BYTES; #define EXPECT_IS_PTR_TYPE(Type, ptr) EXPECT_NE(nullptr, dynamic_cast(ptr)) << "Given pointer cannot be cast to the given type" TEST_F(DataNodeStoreTest, CreateLeafNodeCreatesLeafNode) { auto node = nodeStore->createNewLeafNode(Data(0)); EXPECT_IS_PTR_TYPE(DataLeafNode, node.get()); } TEST_F(DataNodeStoreTest, CreateInnerNodeCreatesInnerNode) { auto leaf = nodeStore->createNewLeafNode(Data(0)); auto node = nodeStore->createNewInnerNode(1, {leaf->blockId()}); EXPECT_IS_PTR_TYPE(DataInnerNode, node.get()); } TEST_F(DataNodeStoreTest, LeafNodeIsRecognizedAfterStoreAndLoad) { BlockId blockId = nodeStore->createNewLeafNode(Data(0))->blockId(); auto loaded_node = nodeStore->load(blockId).value(); EXPECT_IS_PTR_TYPE(DataLeafNode, loaded_node.get()); } TEST_F(DataNodeStoreTest, InnerNodeWithDepth1IsRecognizedAfterStoreAndLoad) { auto leaf = nodeStore->createNewLeafNode(Data(0)); BlockId blockId = nodeStore->createNewInnerNode(1, {leaf->blockId()})->blockId(); auto loaded_node = nodeStore->load(blockId).value(); EXPECT_IS_PTR_TYPE(DataInnerNode, loaded_node.get()); } TEST_F(DataNodeStoreTest, InnerNodeWithDepth2IsRecognizedAfterStoreAndLoad) { auto leaf = nodeStore->createNewLeafNode(Data(0)); auto inner = nodeStore->createNewInnerNode(1, {leaf->blockId()}); BlockId blockId = nodeStore->createNewInnerNode(2, {inner->blockId()})->blockId(); auto loaded_node = nodeStore->load(blockId).value(); EXPECT_IS_PTR_TYPE(DataInnerNode, loaded_node.get()); } TEST_F(DataNodeStoreTest, DataNodeCrashesOnLoadIfDepthIsTooHigh) { auto block = blockStore->create(Data(BLOCKSIZE_BYTES)); BlockId blockId = block->blockId(); { DataNodeView view(std::move(block)); view.setDepth(DataNodeStore::MAX_DEPTH + 1); } EXPECT_ANY_THROW( nodeStore->load(blockId) ); } TEST_F(DataNodeStoreTest, CreatedInnerNodeIsInitialized) { auto leaf = nodeStore->createNewLeafNode(Data(0)); auto node = nodeStore->createNewInnerNode(1, {leaf->blockId()}); EXPECT_EQ(1u, node->numChildren()); EXPECT_EQ(leaf->blockId(), node->readChild(0).blockId()); } TEST_F(DataNodeStoreTest, CreatedLeafNodeIsInitialized) { auto leaf = nodeStore->createNewLeafNode(Data(0)); EXPECT_EQ(0u, leaf->numBytes()); } TEST_F(DataNodeStoreTest, NodeIsNotLoadableAfterDeleting) { auto nodekey = nodeStore->createNewLeafNode(Data(0))->blockId(); auto node = nodeStore->load(nodekey); EXPECT_NE(none, node); nodeStore->remove(std::move(*node)); EXPECT_EQ(none, nodeStore->load(nodekey)); } TEST_F(DataNodeStoreTest, NumNodesIsCorrectOnEmptyNodestore) { EXPECT_EQ(0u, nodeStore->numNodes()); } TEST_F(DataNodeStoreTest, NumNodesIsCorrectAfterAddingOneLeafNode) { nodeStore->createNewLeafNode(Data(0)); EXPECT_EQ(1u, nodeStore->numNodes()); } TEST_F(DataNodeStoreTest, NumNodesIsCorrectAfterRemovingTheLastNode) { auto leaf = nodeStore->createNewLeafNode(Data(0)); nodeStore->remove(std::move(leaf)); EXPECT_EQ(0u, nodeStore->numNodes()); } TEST_F(DataNodeStoreTest, NumNodesIsCorrectAfterAddingTwoNodes) { auto leaf = nodeStore->createNewLeafNode(Data(0)); auto node = nodeStore->createNewInnerNode(1, {leaf->blockId()}); EXPECT_EQ(2u, nodeStore->numNodes()); } TEST_F(DataNodeStoreTest, NumNodesIsCorrectAfterRemovingANode) { auto leaf = nodeStore->createNewLeafNode(Data(0)); auto node = nodeStore->createNewInnerNode(1, {leaf->blockId()}); nodeStore->remove(std::move(node)); EXPECT_EQ(1u, nodeStore->numNodes()); } TEST_F(DataNodeStoreTest, PhysicalBlockSize_Leaf) { auto leaf = nodeStore->createNewLeafNode(Data(0)); auto block = blockStore->load(leaf->blockId()).value(); EXPECT_EQ(BLOCKSIZE_BYTES, block->size()); } TEST_F(DataNodeStoreTest, PhysicalBlockSize_Inner) { auto leaf = nodeStore->createNewLeafNode(Data(0)); auto node = nodeStore->createNewInnerNode(1, {leaf->blockId()}); auto block = blockStore->load(node->blockId()).value(); EXPECT_EQ(BLOCKSIZE_BYTES, block->size()); } test/blobstore/implementations/onblocks/datanodestore/DataNodeViewTest.cpp000066400000000000000000000065571347701267100276110ustar00rootroot00000000000000#include "blobstore/implementations/onblocks/datanodestore/DataNodeView.h" #include #include #include #include "blobstore/implementations/onblocks/BlobStoreOnBlocks.h" #include using ::testing::Test; using ::testing::WithParamInterface; using ::testing::Values; using std::string; using blockstore::BlockStore; using blockstore::testfake::FakeBlockStore; using cpputils::Data; using cpputils::DataFixture; using cpputils::unique_ref; using cpputils::make_unique_ref; using namespace blobstore; using namespace blobstore::onblocks; using namespace blobstore::onblocks::datanodestore; class DataNodeViewTest: public Test { public: static constexpr uint32_t BLOCKSIZE_BYTES = 1024; static constexpr uint32_t DATASIZE_BYTES = DataNodeLayout(DataNodeViewTest::BLOCKSIZE_BYTES).datasizeBytes(); unique_ref blockStore = make_unique_ref(); }; class DataNodeViewDepthTest: public DataNodeViewTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(DataNodeViewDepthTest, DataNodeViewDepthTest, Values(0, 1, 3, 10, 100)); TEST_P(DataNodeViewDepthTest, DepthIsStored) { auto block = blockStore->create(Data(BLOCKSIZE_BYTES)); auto blockId = block->blockId(); { DataNodeView view(std::move(block)); view.setDepth(GetParam()); } DataNodeView view(blockStore->load(blockId).value()); EXPECT_EQ(GetParam(), view.Depth()); } class DataNodeViewSizeTest: public DataNodeViewTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(DataNodeViewSizeTest, DataNodeViewSizeTest, Values(0, 50, 64, 1024, 1024*1024*1024)); TEST_P(DataNodeViewSizeTest, SizeIsStored) { auto block = blockStore->create(Data(BLOCKSIZE_BYTES)); auto blockId = block->blockId(); { DataNodeView view(std::move(block)); view.setSize(GetParam()); } DataNodeView view(blockStore->load(blockId).value()); EXPECT_EQ(GetParam(), view.Size()); } TEST_F(DataNodeViewTest, DataIsStored) { Data randomData = DataFixture::generate(DATASIZE_BYTES); auto block = blockStore->create(Data(BLOCKSIZE_BYTES)); auto blockId = block->blockId(); { DataNodeView view(std::move(block)); view.write(randomData.data(), 0, randomData.size()); } DataNodeView view(blockStore->load(blockId).value()); EXPECT_EQ(0, std::memcmp(view.data(), randomData.data(), randomData.size())); } TEST_F(DataNodeViewTest, HeaderAndBodyDontOverlap) { Data randomData = DataFixture::generate(DATASIZE_BYTES); auto block = blockStore->create(Data(BLOCKSIZE_BYTES)); auto blockId = block->blockId(); { DataNodeView view(std::move(block)); view.setDepth(3); view.setSize(1000000000u); view.write(randomData.data(), 0, DATASIZE_BYTES); } DataNodeView view(blockStore->load(blockId).value()); EXPECT_EQ(3, view.Depth()); EXPECT_EQ(1000000000u, view.Size()); EXPECT_EQ(0, std::memcmp(view.data(), randomData.data(), DATASIZE_BYTES)); } TEST_F(DataNodeViewTest, Data) { auto block = blockStore->create(Data(BLOCKSIZE_BYTES)); const uint8_t *blockBegin = static_cast(block->data()); DataNodeView view(std::move(block)); EXPECT_EQ(blockBegin+DataNodeLayout::HEADERSIZE_BYTES, static_cast(view.data())); } //TODO Test that header fields (and data) are also stored over reloads test/blobstore/implementations/onblocks/datatreestore/000077500000000000000000000000001347701267100237305ustar00rootroot00000000000000test/blobstore/implementations/onblocks/datatreestore/DataTreeStoreTest.cpp000066400000000000000000000043461347701267100300110ustar00rootroot00000000000000#include "testutils/DataTreeTest.h" #include "blobstore/implementations/onblocks/datanodestore/DataNodeStore.h" #include "blobstore/implementations/onblocks/datatreestore/DataTreeStore.h" #include #include using blockstore::BlockId; using boost::none; using namespace blobstore::onblocks::datatreestore; class DataTreeStoreTest: public DataTreeTest { }; TEST_F(DataTreeStoreTest, CorrectKeyReturned) { BlockId blockId = treeStore.createNewTree()->blockId(); auto tree = treeStore.load(blockId).value(); EXPECT_EQ(blockId, tree->blockId()); } TEST_F(DataTreeStoreTest, CreatedTreeIsLoadable) { auto blockId = treeStore.createNewTree()->blockId(); auto loaded = treeStore.load(blockId); EXPECT_NE(none, loaded); } TEST_F(DataTreeStoreTest, NewTreeIsLeafOnly) { auto tree = treeStore.createNewTree(); EXPECT_IS_LEAF_NODE(tree->blockId()); } TEST_F(DataTreeStoreTest, TreeIsNotLoadableAfterRemove_DeleteByTree) { BlockId blockId = treeStore.createNewTree()->blockId(); auto tree = treeStore.load(blockId); EXPECT_NE(none, tree); treeStore.remove(std::move(*tree)); EXPECT_EQ(none, treeStore.load(blockId)); } TEST_F(DataTreeStoreTest, TreeIsNotLoadableAfterRemove_DeleteByKey) { BlockId blockId = treeStore.createNewTree()->blockId(); treeStore.remove(blockId); EXPECT_EQ(none, treeStore.load(blockId)); } TEST_F(DataTreeStoreTest, RemovingTreeRemovesAllNodesOfTheTree_DeleteByTree) { auto tree1_blockId = CreateThreeLevelMinData()->blockId(); auto tree2_blockId = treeStore.createNewTree()->blockId(); auto tree1 = treeStore.load(tree1_blockId).value(); treeStore.remove(std::move(tree1)); //Check that the only remaining node is tree2 EXPECT_EQ(1u, nodeStore->numNodes()); EXPECT_NE(none, treeStore.load(tree2_blockId)); } TEST_F(DataTreeStoreTest, RemovingTreeRemovesAllNodesOfTheTree_DeleteByKey) { auto tree1_blockId = CreateThreeLevelMinData()->blockId(); auto tree2_blockId = treeStore.createNewTree()->blockId(); treeStore.remove(tree1_blockId); //Check that the only remaining node is tree2 EXPECT_EQ(1u, nodeStore->numNodes()); EXPECT_NE(none, treeStore.load(tree2_blockId)); } test/blobstore/implementations/onblocks/datatreestore/DataTreeTest_NumStoredBytes.cpp000066400000000000000000000116271347701267100320030ustar00rootroot00000000000000#include "testutils/DataTreeTest.h" #include using ::testing::WithParamInterface; using ::testing::Values; using blobstore::onblocks::datanodestore::DataNodeLayout; using blockstore::BlockId; class DataTreeTest_NumStoredBytes: public DataTreeTest { public: }; TEST_F(DataTreeTest_NumStoredBytes, CreatedTreeIsEmpty) { auto tree = treeStore.createNewTree(); EXPECT_EQ(0u, tree->numBytes()); } class DataTreeTest_NumStoredBytes_P: public DataTreeTest_NumStoredBytes, public WithParamInterface {}; 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(FullLastLeaf, DataTreeTest_NumStoredBytes_P, Values(static_cast(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) { BlockId blockId = CreateLeafWithSize(GetParam())->blockId(); auto tree = treeStore.load(blockId).value(); EXPECT_EQ(GetParam(), tree->numBytes()); EXPECT_EQ(1, tree->numLeaves()); EXPECT_EQ(1, tree->numNodes()); } TEST_P(DataTreeTest_NumStoredBytes_P, TwoLeafTree) { BlockId blockId = CreateTwoLeafWithSecondLeafSize(GetParam())->blockId(); auto tree = treeStore.load(blockId).value(); EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes()); EXPECT_EQ(2, tree->numLeaves()); EXPECT_EQ(3, tree->numNodes()); } TEST_P(DataTreeTest_NumStoredBytes_P, FullTwolevelTree) { BlockId blockId = CreateFullTwoLevelWithLastLeafSize(GetParam())->blockId(); auto tree = treeStore.load(blockId).value(); 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) { BlockId blockId = CreateThreeLevelWithOneChildAndLastLeafSize(GetParam())->blockId(); auto tree = treeStore.load(blockId).value(); EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf() + GetParam(), tree->numBytes()); EXPECT_EQ(2, tree->numLeaves()); EXPECT_EQ(4, tree->numNodes()); } TEST_P(DataTreeTest_NumStoredBytes_P, ThreeLevelTreeWithTwoChildren) { BlockId blockId = CreateThreeLevelWithTwoChildrenAndLastLeafSize(GetParam())->blockId(); auto tree = treeStore.load(blockId).value(); 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) { BlockId blockId = CreateThreeLevelWithThreeChildrenAndLastLeafSize(GetParam())->blockId(); auto tree = treeStore.load(blockId).value(); 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) { BlockId blockId = CreateFullThreeLevelWithLastLeafSize(GetParam())->blockId(); 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().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) { BlockId blockId = CreateFourLevelMinDataWithLastLeafSize(GetParam())->blockId(); auto tree = treeStore.load(blockId).value(); 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()); } test/blobstore/implementations/onblocks/datatreestore/DataTreeTest_Performance.cpp000066400000000000000000000626371347701267100313240ustar00rootroot00000000000000#include "testutils/DataTreeTest.h" #include using blobstore::onblocks::datatreestore::DataTree; using blockstore::BlockId; using cpputils::Data; class DataTreeTest_Performance: public DataTreeTest { public: void TraverseByWriting(DataTree *tree, uint64_t beginIndex, uint64_t endIndex) { uint64_t offset = beginIndex * maxBytesPerLeaf; uint64_t count = endIndex * maxBytesPerLeaf - offset; Data data(count); data.FillWithZeroes(); tree->writeBytes(data.data(), offset, count); } void TraverseByReading(DataTree *tree, uint64_t beginIndex, uint64_t endIndex) { uint64_t offset = beginIndex * maxBytesPerLeaf; uint64_t count = endIndex * maxBytesPerLeaf - offset; Data data(count); tree->readBytes(data.data(), offset, count); } uint64_t maxChildrenPerInnerNode = nodeStore->layout().maxChildrenPerInnerNode(); uint64_t maxBytesPerLeaf = nodeStore->layout().maxBytesPerLeaf(); }; TEST_F(DataTreeTest_Performance, DeletingDoesntLoadLeaves_Twolevel_DeleteByTree) { auto blockId = CreateFullTwoLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); treeStore.remove(std::move(tree)); EXPECT_EQ(0u, blockStore->loadedBlocks().size()); EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(1u + maxChildrenPerInnerNode, blockStore->removedBlocks().size()); EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, DeletingDoesntLoadLeaves_Twolevel_DeleteByKey) { auto blockId = CreateFullTwoLevel()->blockId(); blockStore->resetCounters(); treeStore.remove(blockId); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(1u + maxChildrenPerInnerNode, blockStore->removedBlocks().size()); EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, DeletingDoesntLoadLeaves_Threelevel_DeleteByTree) { auto blockId = CreateFullThreeLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); treeStore.remove(std::move(tree)); EXPECT_EQ(maxChildrenPerInnerNode, blockStore->loadedBlocks().size()); EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(1u + maxChildrenPerInnerNode + maxChildrenPerInnerNode*maxChildrenPerInnerNode, blockStore->removedBlocks().size()); EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, DeletingDoesntLoadLeaves_Threelevel_DeleteByKey) { auto blockId = CreateFullThreeLevel()->blockId(); blockStore->resetCounters(); treeStore.remove(blockId); EXPECT_EQ(1u + maxChildrenPerInnerNode, blockStore->loadedBlocks().size()); EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(1u + maxChildrenPerInnerNode + maxChildrenPerInnerNode*maxChildrenPerInnerNode, blockStore->removedBlocks().size()); EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Twolevel_All_ByWriting) { auto blockId = CreateFullTwoLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), 0, maxChildrenPerInnerNode); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Has to load the rightmost leaf once to adapt its size, rest of the leaves aren't loaded but just overwritten EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(maxChildrenPerInnerNode, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Twolevel_All_ByReading) { auto blockId = CreateFullTwoLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByReading(tree.get(), 0, maxChildrenPerInnerNode); EXPECT_EQ(1u + maxChildrenPerInnerNode, blockStore->loadedBlocks().size()); // Has to read the rightmost leaf an additional time in the beginning to determine size. EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Twolevel_Some_ByWriting) { auto blockId = CreateFullTwoLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), 3, 5); EXPECT_EQ(0u, blockStore->loadedBlocks().size()); EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Twolevel_Some_ByReading) { auto blockId = CreateFullTwoLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByReading(tree.get(), 3, 5); EXPECT_EQ(3u, blockStore->loadedBlocks().size()); // reads 2 leaves and the rightmost leaf to determine size EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_All_ByWriting) { auto blockId = CreateFullThreeLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), 0, maxChildrenPerInnerNode * maxChildrenPerInnerNode); EXPECT_EQ(maxChildrenPerInnerNode + 1, blockStore->loadedBlocks().size()); // Loads inner nodes and has to load the rightmost leaf once to adapt its size, rest of the leaves aren't loaded but just overwritten. EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(maxChildrenPerInnerNode*maxChildrenPerInnerNode, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_All_ByReading) { auto blockId = CreateFullThreeLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByReading(tree.get(), 0, maxChildrenPerInnerNode * maxChildrenPerInnerNode); EXPECT_EQ(maxChildrenPerInnerNode*maxChildrenPerInnerNode + maxChildrenPerInnerNode + 2, blockStore->loadedBlocks().size()); // Loads inner nodes and leaves. Has to load the rightmost inner node and leaf an additional time at the beginning to compute size EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_InOneInner_ByWriting) { auto blockId = CreateFullThreeLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), 3, 5); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads inner node. Doesn't load the leaves, they're just overwritten. EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_InOneInner_ByReading) { auto blockId = CreateFullThreeLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByReading(tree.get(), 3, 5); EXPECT_EQ(5u, blockStore->loadedBlocks().size()); // reads 2 leaves and the inner node, also has to read the rightmost inner node and leaf additionally at the beginning to determine size EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_InTwoInner_ByWriting) { auto blockId = CreateFullThreeLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), 3, 3 + maxChildrenPerInnerNode); EXPECT_EQ(2u, blockStore->loadedBlocks().size()); // Loads both inner node EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(maxChildrenPerInnerNode, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_InTwoInner_ByReading) { auto blockId = CreateFullThreeLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByReading(tree.get(), 3, 3 + maxChildrenPerInnerNode); EXPECT_EQ(4u + maxChildrenPerInnerNode, blockStore->loadedBlocks().size()); // Loads both inner nodes and the requested leaves. Also has to load rightmost inner node and leaf additionally in the beginning to determine size. EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_WholeInner_ByWriting) { auto blockId = CreateFullThreeLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), maxChildrenPerInnerNode, 2*maxChildrenPerInnerNode); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads inner node. Doesn't load the leaves, they're just overwritten. EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(maxChildrenPerInnerNode, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_Threelevel_WholeInner_ByReading) { auto blockId = CreateFullThreeLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByReading(tree.get(), maxChildrenPerInnerNode, 2*maxChildrenPerInnerNode); EXPECT_EQ(3u + maxChildrenPerInnerNode, blockStore->loadedBlocks().size()); // Loads inner node and all requested leaves. Also has to load rightmost inner node and leaf additionally in the beginning to determine size. EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTree_StartingInside) { auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), 1, 4); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old child (for growing it) EXPECT_EQ(2u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // write the data and add children to inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTree_StartingOutside_TwoLevel) { auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), 4, 5); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old leaf for growing it EXPECT_EQ(3u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // add child to inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTree_StartingOutside_ThreeLevel) { auto blockId = CreateInner({CreateFullTwoLevel(), CreateFullTwoLevel()})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), 2*maxChildrenPerInnerNode+1, 2*maxChildrenPerInnerNode+2); EXPECT_EQ(2u, blockStore->loadedBlocks().size()); // Loads last old leaf (and its inner node) for growing it EXPECT_EQ(3u, blockStore->createdBlocks()); // inner node and two leaves EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // add children to existing inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTree_StartingAtBeginOfChild) { auto blockId = CreateInner({CreateFullTwoLevel(), CreateFullTwoLevel()})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), maxChildrenPerInnerNode, 3*maxChildrenPerInnerNode); EXPECT_EQ(2u, blockStore->loadedBlocks().size()); // Loads inner node and one leaf to check whether we have to grow it. Doesn't load the leaves, but returns the keys of the leaves to the callback. EXPECT_EQ(1u + maxChildrenPerInnerNode, blockStore->createdBlocks()); // Creates an inner node and its leaves EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(maxChildrenPerInnerNode + 1u, blockStore->distinctWrittenBlocks().size()); // write data and add children to existing inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTreeDepth_StartingInOldDepth) { auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), 4, maxChildrenPerInnerNode+2); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old leaf for growing it EXPECT_EQ(2u + maxChildrenPerInnerNode, blockStore->createdBlocks()); // 2x new inner node + leaves EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // Add children to existing inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTreeDepth_StartingInOldDepth_ResizeLastLeaf) { auto blockId = CreateInner({CreateLeaf(), CreateLeafWithSize(5)})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), 4, maxChildrenPerInnerNode+2); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old leaf for growing it EXPECT_EQ(2u + maxChildrenPerInnerNode, blockStore->createdBlocks()); // 2x new inner node + leaves EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // Resize last leaf and add children to existing inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTreeDepth_StartingInNewDepth) { auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), maxChildrenPerInnerNode, maxChildrenPerInnerNode+2); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old leaf for growing it EXPECT_EQ(2u + maxChildrenPerInnerNode, blockStore->createdBlocks()); // 2x new inner node + leaves EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // Add children to existing inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, TraverseLeaves_GrowingTreeDepth_StartingInNewDepth_ResizeLastLeaf) { auto blockId = CreateInner({CreateLeaf(), CreateLeafWithSize(5)})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); TraverseByWriting(tree.get(), maxChildrenPerInnerNode, maxChildrenPerInnerNode+2); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // Loads last old leaf for growing it EXPECT_EQ(2u + maxChildrenPerInnerNode, blockStore->createdBlocks()); // 2x new inner node + leaves EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // Resize last leaf and add children to existing inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_ZeroToZero) { auto blockId = CreateLeafWithSize(0)->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(0); EXPECT_EQ(0u, blockStore->loadedBlocks().size()); EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(0u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_GrowOneLeaf) { auto blockId = CreateLeafWithSize(0)->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(5); EXPECT_EQ(0u, blockStore->loadedBlocks().size()); EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_ShrinkOneLeaf) { auto blockId = CreateLeafWithSize(5)->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(2); EXPECT_EQ(0u, blockStore->loadedBlocks().size()); EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_ShrinkOneLeafToZero) { auto blockId = CreateLeafWithSize(5)->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(0); EXPECT_EQ(0u, blockStore->loadedBlocks().size()); EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_GrowOneLeafInLargerTree) { auto blockId = CreateInner({CreateFullTwoLevel(), CreateInner({CreateLeaf(), CreateLeafWithSize(5)})})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(maxBytesPerLeaf*(maxChildrenPerInnerNode+1)+6); // Grow by one byte EXPECT_EQ(2u, blockStore->loadedBlocks().size()); // Load inner node and leaf EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_GrowByOneLeaf) { auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(maxBytesPerLeaf*2+1); // Grow by one byte EXPECT_EQ(1u, blockStore->loadedBlocks().size()); EXPECT_EQ(1u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // add child to inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_GrowByOneLeaf_GrowLastLeaf) { auto blockId = CreateInner({CreateLeaf(), CreateLeafWithSize(5)})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(maxBytesPerLeaf*2+1); // Grow by one byte EXPECT_EQ(1u, blockStore->loadedBlocks().size()); EXPECT_EQ(1u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // add child to inner node and resize old last leaf EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_ShrinkByOneLeaf) { auto blockId = CreateInner({CreateLeaf(), CreateLeaf(), CreateLeaf()})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(2*maxBytesPerLeaf-1); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(1u, blockStore->removedBlocks().size()); EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // resize new last leaf and remove leaf from inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_IncreaseTreeDepth_0to1) { auto blockId = CreateLeaf()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(maxBytesPerLeaf+1); EXPECT_EQ(0u, blockStore->loadedBlocks().size()); EXPECT_EQ(2u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // rewrite root node to be an inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_IncreaseTreeDepth_1to2) { auto blockId = CreateFullTwoLevel()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(maxBytesPerLeaf*maxChildrenPerInnerNode+1); EXPECT_EQ(1u, blockStore->loadedBlocks().size()); // check whether we have to grow last leaf EXPECT_EQ(3u, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // rewrite root node to be an inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_IncreaseTreeDepth_0to2) { auto blockId = CreateLeaf()->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(maxBytesPerLeaf*maxChildrenPerInnerNode+1); EXPECT_EQ(0u, blockStore->loadedBlocks().size()); EXPECT_EQ(3u + maxChildrenPerInnerNode, blockStore->createdBlocks()); EXPECT_EQ(0u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // rewrite root node to be an inner node EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_DecreaseTreeDepth_1to0) { auto blockId = CreateInner({CreateLeaf(), CreateLeaf()})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(maxBytesPerLeaf); EXPECT_EQ(2u, blockStore->loadedBlocks().size()); // read content of first leaf and load first leaf to replace root with it EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(2u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // rewrite root node to be a leaf EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_DecreaseTreeDepth_2to1) { auto blockId = CreateInner({CreateFullTwoLevel(), CreateInner({CreateLeaf()})})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(maxBytesPerLeaf*maxChildrenPerInnerNode); EXPECT_EQ(4u, blockStore->loadedBlocks().size()); // load new last leaf (+inner node), load second inner node to remove its subtree, then load first child of root to replace root with its child. EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(3u, blockStore->removedBlocks().size()); EXPECT_EQ(1u, blockStore->distinctWrittenBlocks().size()); // rewrite root node to be a leaf EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } TEST_F(DataTreeTest_Performance, ResizeNumBytes_DecreaseTreeDepth_2to0) { auto blockId = CreateInner({CreateFullTwoLevel(), CreateInner({CreateLeaf()})})->blockId(); auto tree = treeStore.load(blockId).value(); blockStore->resetCounters(); tree->resizeNumBytes(maxBytesPerLeaf); EXPECT_EQ(5u, blockStore->loadedBlocks().size()); // load new last leaf (+inner node), load second inner node to remove its subtree, then 2x load first child of root to replace root with its child. EXPECT_EQ(0u, blockStore->createdBlocks()); EXPECT_EQ(3u + maxChildrenPerInnerNode, blockStore->removedBlocks().size()); EXPECT_EQ(2u, blockStore->distinctWrittenBlocks().size()); // remove children from inner node and rewrite root node to be a leaf EXPECT_EQ(0u, blockStore->resizedBlocks().size()); } test/blobstore/implementations/onblocks/datatreestore/DataTreeTest_ResizeByTraversing.cpp000066400000000000000000000231131347701267100326460ustar00rootroot00000000000000#include "testutils/DataTreeTest.h" #include "testutils/TwoLevelDataFixture.h" #include "blobstore/implementations/onblocks/utils/Math.h" #include #include using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Combine; using std::tuple; using std::get; using std::function; using std::mem_fn; using cpputils::dynamic_pointer_move; using blobstore::onblocks::datanodestore::DataLeafNode; using blobstore::onblocks::datanodestore::DataInnerNode; using blobstore::onblocks::datanodestore::DataNode; using blobstore::onblocks::datanodestore::DataNodeLayout; using blobstore::onblocks::datatreestore::DataTree; using blobstore::onblocks::utils::ceilDivision; using blockstore::BlockId; using cpputils::Data; using boost::none; using cpputils::unique_ref; class DataTreeTest_ResizeByTraversing: public DataTreeTest { public: static constexpr DataNodeLayout LAYOUT = DataNodeLayout(BLOCKSIZE_BYTES); unique_ref CreateTree(unique_ref root) { BlockId blockId = root->blockId(); cpputils::destruct(std::move(root)); return treeStore.load(blockId).value(); } unique_ref CreateLeafTreeWithSize(uint32_t size) { return CreateTree(CreateLeafWithSize(size)); } unique_ref CreateTwoLeafTreeWithSecondLeafSize(uint32_t size) { return CreateTree(CreateTwoLeafWithSecondLeafSize(size)); } unique_ref CreateFullTwoLevelTreeWithLastLeafSize(uint32_t size) { return CreateTree(CreateFullTwoLevelWithLastLeafSize(size)); } unique_ref CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize(uint32_t size) { return CreateTree(CreateThreeLevelWithTwoChildrenAndLastLeafSize(size)); } unique_ref CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(uint32_t size) { return CreateTree(CreateThreeLevelWithThreeChildrenAndLastLeafSize(size)); } unique_ref CreateFullThreeLevelTreeWithLastLeafSize(uint32_t size) { return CreateTree(CreateFullThreeLevelWithLastLeafSize(size)); } unique_ref CreateFourLevelMinDataTreeWithLastLeafSize(uint32_t size) { return CreateTree(CreateFourLevelMinDataWithLastLeafSize(size)); } void EXPECT_IS_LEFTMAXDATA_TREE(const BlockId &blockId) { auto root = nodeStore->load(blockId).value(); DataInnerNode *inner = dynamic_cast(root.get()); if (inner != nullptr) { for (uint32_t i = 0; i < inner->numChildren()-1; ++i) { EXPECT_IS_MAXDATA_TREE(inner->readChild(i).blockId()); } EXPECT_IS_LEFTMAXDATA_TREE(inner->readLastChild().blockId()); } } void EXPECT_IS_MAXDATA_TREE(const BlockId &blockId) { auto root = nodeStore->load(blockId).value(); DataInnerNode *inner = dynamic_cast(root.get()); if (inner != nullptr) { for (uint32_t i = 0; i < inner->numChildren(); ++i) { EXPECT_IS_MAXDATA_TREE(inner->readChild(i).blockId()); } } else { DataLeafNode *leaf = dynamic_cast(root.get()); EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf(), leaf->numBytes()); } } }; constexpr DataNodeLayout DataTreeTest_ResizeByTraversing::LAYOUT; class DataTreeTest_ResizeByTraversing_P: public DataTreeTest_ResizeByTraversing, public WithParamInterface(DataTreeTest_ResizeByTraversing*, uint32_t)>, uint32_t, uint32_t, std::function>> { public: DataTreeTest_ResizeByTraversing_P() : oldLastLeafSize(get<1>(GetParam())), tree(get<0>(GetParam())(this, oldLastLeafSize)), numberOfLeavesToAdd(get<2>(GetParam())), newNumberOfLeaves(tree->numLeaves()+numberOfLeavesToAdd), traversalBeginIndex(get<3>(GetParam())(tree->numLeaves(), newNumberOfLeaves)), ZEROES(LAYOUT.maxBytesPerLeaf()) { ZEROES.FillWithZeroes(); } void GrowTree(const BlockId &blockId) { auto tree = treeStore.load(blockId); GrowTree(tree.get().get()); } void GrowTree(DataTree *tree) { uint64_t maxBytesPerLeaf = tree->maxBytesPerLeaf(); uint64_t offset = traversalBeginIndex * maxBytesPerLeaf; uint64_t count = newNumberOfLeaves * maxBytesPerLeaf - offset; Data data(count); data.FillWithZeroes(); tree->writeBytes(data.data(), offset, count); tree->flush(); } unique_ref LastLeaf(const BlockId &blockId) { auto root = nodeStore->load(blockId).value(); auto leaf = dynamic_pointer_move(root); if (leaf != none) { return std::move(*leaf); } auto inner = dynamic_pointer_move(root).value(); return LastLeaf(inner->readLastChild().blockId()); } uint32_t oldLastLeafSize; unique_ref tree; uint32_t numberOfLeavesToAdd; uint32_t newNumberOfLeaves; uint32_t traversalBeginIndex; Data ZEROES; }; INSTANTIATE_TEST_CASE_P(DataTreeTest_ResizeByTraversing_P, DataTreeTest_ResizeByTraversing_P, Combine( //Tree we're starting with Values(DataTreeTest_ResizeByTraversing*, uint32_t)>>( mem_fn(&DataTreeTest_ResizeByTraversing::CreateLeafTreeWithSize), mem_fn(&DataTreeTest_ResizeByTraversing::CreateTwoLeafTreeWithSecondLeafSize), mem_fn(&DataTreeTest_ResizeByTraversing::CreateFullTwoLevelTreeWithLastLeafSize), mem_fn(&DataTreeTest_ResizeByTraversing::CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize), mem_fn(&DataTreeTest_ResizeByTraversing::CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize), mem_fn(&DataTreeTest_ResizeByTraversing::CreateFullThreeLevelTreeWithLastLeafSize), mem_fn(&DataTreeTest_ResizeByTraversing::CreateFourLevelMinDataTreeWithLastLeafSize) ), //Last leaf size of the start tree Values( 0u, 1u, 10u, DataTreeTest_ResizeByTraversing::LAYOUT.maxBytesPerLeaf() ), //Number of leaves we're adding Values( 1u, 2u, DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Full two level tree 2* DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Three level tree with two children 3* DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Three level tree with three children DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode() * DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode(), //Full three level tree DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode() * DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode() + 1 //Four level mindata tree ), //Decide the traversal begin index Values( [] (uint32_t /*oldNumberOfLeaves*/, uint32_t newNumberOfLeaves) {return newNumberOfLeaves-1;}, // Traverse last leaf (begin==end-1) [] (uint32_t oldNumberOfLeaves, uint32_t newNumberOfLeaves) {return (oldNumberOfLeaves+newNumberOfLeaves)/2;}, // Start traversal in middle of new leaves [] (uint32_t oldNumberOfLeaves, uint32_t /*newNumberOfLeaves*/) {return oldNumberOfLeaves-1;}, // Start traversal with last old leaf [] (uint32_t oldNumberOfLeaves, uint32_t /*newNumberOfLeaves*/) {return oldNumberOfLeaves;}, // Start traversal with first new leaf [] (uint32_t /*oldNumberOfLeaves*/, uint32_t /*newNumberOfLeaves*/) {return 0;}, // Traverse full tree [] (uint32_t /*oldNumberOfLeaves*/, uint32_t /*newNumberOfLeaves*/) {return 1;} // Traverse full tree except first leaf ) ) ); TEST_P(DataTreeTest_ResizeByTraversing_P, StructureIsValid) { GrowTree(tree.get()); EXPECT_IS_LEFTMAXDATA_TREE(tree->blockId()); } TEST_P(DataTreeTest_ResizeByTraversing_P, NumLeavesIsCorrect_FromCache) { tree->numLeaves(); // fill cache with old value GrowTree(tree.get()); // tree->numLeaves() only goes down the right border nodes and expects the tree to be a left max data tree. // This is what the StructureIsValid test case is for. EXPECT_EQ(newNumberOfLeaves, tree->numLeaves()); } TEST_P(DataTreeTest_ResizeByTraversing_P, NumLeavesIsCorrect) { GrowTree(tree.get()); // tree->forceComputeNumLeaves() only goes down the right border nodes and expects the tree to be a left max data tree. // This is what the StructureIsValid test case is for. EXPECT_EQ(newNumberOfLeaves, tree->forceComputeNumLeaves()); } TEST_P(DataTreeTest_ResizeByTraversing_P, DepthFlagsAreCorrect) { GrowTree(tree.get()); uint32_t depth = ceil(log(newNumberOfLeaves)/log(DataTreeTest_ResizeByTraversing::LAYOUT.maxChildrenPerInnerNode())); CHECK_DEPTH(depth, tree->blockId()); } TEST_P(DataTreeTest_ResizeByTraversing_P, KeyDoesntChange) { BlockId blockId = tree->blockId(); tree->flush(); GrowTree(tree.get()); EXPECT_EQ(blockId, tree->blockId()); } TEST_P(DataTreeTest_ResizeByTraversing_P, DataStaysIntact) { uint32_t oldNumberOfLeaves = std::max(UINT64_C(1), ceilDivision(tree->numBytes(), static_cast(nodeStore->layout().maxBytesPerLeaf()))); TwoLevelDataFixture data(nodeStore, TwoLevelDataFixture::SizePolicy::Unchanged); BlockId blockId = tree->blockId(); cpputils::destruct(std::move(tree)); data.FillInto(nodeStore->load(blockId).get().get()); GrowTree(blockId); if (traversalBeginIndex < oldNumberOfLeaves) { // Traversal wrote over part of the pre-existing data, we can only check the data before it. if (traversalBeginIndex != 0) { data.EXPECT_DATA_CORRECT(nodeStore->load(blockId).get().get(), static_cast(traversalBeginIndex - 1)); } } else { // Here, traversal was entirely outside the preexisting data, we can check all preexisting data. data.EXPECT_DATA_CORRECT(nodeStore->load(blockId).get().get(), oldNumberOfLeaves, oldLastLeafSize); } } test/blobstore/implementations/onblocks/datatreestore/DataTreeTest_ResizeNumBytes.cpp000066400000000000000000000240701347701267100320000ustar00rootroot00000000000000#include "testutils/DataTreeTest.h" #include "testutils/TwoLevelDataFixture.h" #include "blobstore/implementations/onblocks/utils/Math.h" #include #include using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Combine; using std::tuple; using std::get; using std::function; using std::mem_fn; using cpputils::dynamic_pointer_move; using blobstore::onblocks::datanodestore::DataLeafNode; using blobstore::onblocks::datanodestore::DataInnerNode; using blobstore::onblocks::datanodestore::DataNode; using blobstore::onblocks::datanodestore::DataNodeLayout; using blobstore::onblocks::datatreestore::DataTree; using blobstore::onblocks::utils::ceilDivision; using blockstore::BlockId; using cpputils::Data; using boost::none; using cpputils::unique_ref; class DataTreeTest_ResizeNumBytes: public DataTreeTest { public: static constexpr DataNodeLayout LAYOUT = DataNodeLayout(BLOCKSIZE_BYTES); unique_ref CreateTree(unique_ref root) { BlockId blockId = root->blockId(); cpputils::destruct(std::move(root)); return treeStore.load(blockId).value(); } unique_ref CreateLeafTreeWithSize(uint32_t size) { return CreateTree(CreateLeafWithSize(size)); } unique_ref CreateTwoLeafTreeWithSecondLeafSize(uint32_t size) { return CreateTree(CreateTwoLeafWithSecondLeafSize(size)); } unique_ref CreateFullTwoLevelTreeWithLastLeafSize(uint32_t size) { return CreateTree(CreateFullTwoLevelWithLastLeafSize(size)); } unique_ref CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize(uint32_t size) { return CreateTree(CreateThreeLevelWithTwoChildrenAndLastLeafSize(size)); } unique_ref CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(uint32_t size) { return CreateTree(CreateThreeLevelWithThreeChildrenAndLastLeafSize(size)); } unique_ref CreateFullThreeLevelTreeWithLastLeafSize(uint32_t size) { return CreateTree(CreateFullThreeLevelWithLastLeafSize(size)); } unique_ref CreateFourLevelMinDataTreeWithLastLeafSize(uint32_t size) { return CreateTree(CreateFourLevelMinDataWithLastLeafSize(size)); } void EXPECT_IS_LEFTMAXDATA_TREE(const BlockId &blockId) { auto root = nodeStore->load(blockId).value(); DataInnerNode *inner = dynamic_cast(root.get()); if (inner != nullptr) { for (uint32_t i = 0; i < inner->numChildren()-1; ++i) { EXPECT_IS_MAXDATA_TREE(inner->readChild(i).blockId()); } EXPECT_IS_LEFTMAXDATA_TREE(inner->readLastChild().blockId()); } } void EXPECT_IS_MAXDATA_TREE(const BlockId &blockId) { auto root = nodeStore->load(blockId).value(); DataInnerNode *inner = dynamic_cast(root.get()); if (inner != nullptr) { for (uint32_t i = 0; i < inner->numChildren(); ++i) { EXPECT_IS_MAXDATA_TREE(inner->readChild(i).blockId()); } } else { DataLeafNode *leaf = dynamic_cast(root.get()); EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf(), leaf->numBytes()); } } }; constexpr DataNodeLayout DataTreeTest_ResizeNumBytes::LAYOUT; class DataTreeTest_ResizeNumBytes_P: public DataTreeTest_ResizeNumBytes, public WithParamInterface(DataTreeTest_ResizeNumBytes*, uint32_t)>, uint32_t, uint32_t, uint32_t>> { public: DataTreeTest_ResizeNumBytes_P() : oldLastLeafSize(get<1>(GetParam())), tree(get<0>(GetParam())(this, oldLastLeafSize)), newNumberOfLeaves(get<2>(GetParam())), newLastLeafSize(get<3>(GetParam())), newSize((newNumberOfLeaves-1) * LAYOUT.maxBytesPerLeaf() + newLastLeafSize), ZEROES(LAYOUT.maxBytesPerLeaf()) { ZEROES.FillWithZeroes(); } void ResizeTree(const BlockId &blockId, uint64_t size) { treeStore.load(blockId).get()->resizeNumBytes(size); } unique_ref LastLeaf(const BlockId &blockId) { auto root = nodeStore->load(blockId).value(); auto leaf = dynamic_pointer_move(root); if (leaf != none) { return std::move(*leaf); } auto inner = dynamic_pointer_move(root).value(); return LastLeaf(inner->readLastChild().blockId()); } uint32_t oldLastLeafSize; unique_ref tree; uint32_t newNumberOfLeaves; uint32_t newLastLeafSize; uint64_t newSize; Data ZEROES; }; INSTANTIATE_TEST_CASE_P(DataTreeTest_ResizeNumBytes_P, DataTreeTest_ResizeNumBytes_P, Combine( //Tree we're starting with Values(DataTreeTest_ResizeNumBytes*, uint32_t)>>( mem_fn(&DataTreeTest_ResizeNumBytes::CreateLeafTreeWithSize), mem_fn(&DataTreeTest_ResizeNumBytes::CreateTwoLeafTreeWithSecondLeafSize), mem_fn(&DataTreeTest_ResizeNumBytes::CreateFullTwoLevelTreeWithLastLeafSize), mem_fn(&DataTreeTest_ResizeNumBytes::CreateThreeLevelTreeWithTwoChildrenAndLastLeafSize), mem_fn(&DataTreeTest_ResizeNumBytes::CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize), mem_fn(&DataTreeTest_ResizeNumBytes::CreateFullThreeLevelTreeWithLastLeafSize), mem_fn(&DataTreeTest_ResizeNumBytes::CreateFourLevelMinDataTreeWithLastLeafSize) ), //Last leaf size of the start tree Values( 0u, 1u, 10u, DataTreeTest_ResizeNumBytes::LAYOUT.maxBytesPerLeaf() ), //Number of leaves we're resizing to Values( 1u, 2u, DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode(), //Full two level tree 2* DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode(), //Three level tree with two children 3* DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode(), //Three level tree with three children DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode() * DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode(), //Full three level tree DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode() * DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode() + 1 //Four level mindata tree ), //Last leaf size of the resized tree Values( 1u, 10u, DataTreeTest_ResizeNumBytes::LAYOUT.maxBytesPerLeaf() ) ) ); TEST_P(DataTreeTest_ResizeNumBytes_P, StructureIsValid) { tree->resizeNumBytes(newSize); tree->flush(); EXPECT_IS_LEFTMAXDATA_TREE(tree->blockId()); } TEST_P(DataTreeTest_ResizeNumBytes_P, NumBytesIsCorrect) { tree->resizeNumBytes(newSize); tree->flush(); // tree->numBytes() only goes down the right border nodes and expects the tree to be a left max data tree. // This is what the StructureIsValid test case is for. EXPECT_EQ(newSize, tree->numBytes()); } TEST_P(DataTreeTest_ResizeNumBytes_P, NumLeavesIsCorrect) { tree->resizeNumBytes(newSize); tree->flush(); // tree->numLeaves() only goes down the right border nodes and expects the tree to be a left max data tree. // This is what the StructureIsValid test case is for. EXPECT_EQ(newNumberOfLeaves, tree->forceComputeNumLeaves()); } TEST_P(DataTreeTest_ResizeNumBytes_P, NumLeavesIsCorrect_FromCache) { tree->numLeaves(); // fill cache with old value tree->resizeNumBytes(newSize); tree->flush(); // tree->numLeaves() only goes down the right border nodes and expects the tree to be a left max data tree. // This is what the StructureIsValid test case is for. EXPECT_EQ(newNumberOfLeaves, tree->numLeaves()); } TEST_P(DataTreeTest_ResizeNumBytes_P, DepthFlagsAreCorrect) { tree->resizeNumBytes(newSize); tree->flush(); uint32_t depth = ceil(log(newNumberOfLeaves)/log(DataTreeTest_ResizeNumBytes::LAYOUT.maxChildrenPerInnerNode()) - 0.00000000001); // The subtraction takes care of double inaccuracies if newNumberOfLeaves == maxChildrenPerInnerNode CHECK_DEPTH(depth, tree->blockId()); } TEST_P(DataTreeTest_ResizeNumBytes_P, KeyDoesntChange) { BlockId blockId = tree->blockId(); tree->flush(); tree->resizeNumBytes(newSize); EXPECT_EQ(blockId, tree->blockId()); } TEST_P(DataTreeTest_ResizeNumBytes_P, DataStaysIntact) { uint32_t oldNumberOfLeaves = std::max(UINT64_C(1), ceilDivision(tree->numBytes(), static_cast(nodeStore->layout().maxBytesPerLeaf()))); TwoLevelDataFixture data(nodeStore, TwoLevelDataFixture::SizePolicy::Unchanged); BlockId blockId = tree->blockId(); cpputils::destruct(std::move(tree)); data.FillInto(nodeStore->load(blockId).get().get()); ResizeTree(blockId, newSize); if (oldNumberOfLeaves < newNumberOfLeaves || (oldNumberOfLeaves == newNumberOfLeaves && oldLastLeafSize < newLastLeafSize)) { data.EXPECT_DATA_CORRECT(nodeStore->load(blockId).get().get(), oldNumberOfLeaves, oldLastLeafSize); } else { data.EXPECT_DATA_CORRECT(nodeStore->load(blockId).get().get(), newNumberOfLeaves, newLastLeafSize); } } TEST_P(DataTreeTest_ResizeNumBytes_P, UnneededBlocksGetDeletedWhenShrinking) { tree->resizeNumBytes(newSize); tree->flush(); uint64_t expectedNumNodes = 1; // 1 for the root node uint64_t nodesOnCurrentLevel = newNumberOfLeaves; while (nodesOnCurrentLevel > 1) { expectedNumNodes += nodesOnCurrentLevel; nodesOnCurrentLevel = ceilDivision(nodesOnCurrentLevel, nodeStore->layout().maxChildrenPerInnerNode()); } EXPECT_EQ(expectedNumNodes, nodeStore->numNodes()); } //Resize to zero is not caught in the parametrized test above, in the following, we test it separately. TEST_F(DataTreeTest_ResizeNumBytes, ResizeToZero_NumBytesIsCorrect) { auto tree = CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(10u); tree->resizeNumBytes(0); BlockId blockId = tree->blockId(); cpputils::destruct(std::move(tree)); auto leaf = LoadLeafNode(blockId); EXPECT_EQ(0u, leaf->numBytes()); } TEST_F(DataTreeTest_ResizeNumBytes, ResizeToZero_blockIdDoesntChange) { auto tree = CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(10u); BlockId blockId = tree->blockId(); tree->resizeNumBytes(0); tree->flush(); EXPECT_EQ(blockId, tree->blockId()); } TEST_F(DataTreeTest_ResizeNumBytes, ResizeToZero_UnneededBlocksGetDeletedWhenShrinking) { auto tree = CreateThreeLevelTreeWithThreeChildrenAndLastLeafSize(10u); tree->resizeNumBytes(0); tree->flush(); EXPECT_EQ(1u, nodeStore->numNodes()); } test/blobstore/implementations/onblocks/datatreestore/LeafTraverserTest.cpp000066400000000000000000000476321347701267100300550ustar00rootroot00000000000000#include "testutils/DataTreeTest.h" #include #include using ::testing::_; using ::testing::Invoke; using ::testing::Eq; using blobstore::onblocks::datanodestore::DataLeafNode; using blobstore::onblocks::datanodestore::DataInnerNode; using blobstore::onblocks::datanodestore::DataNode; using blobstore::onblocks::datatreestore::LeafHandle; using blobstore::onblocks::datatreestore::LeafTraverser; using blockstore::BlockId; using cpputils::unique_ref; using cpputils::Data; using std::shared_ptr; using std::make_shared; class TraversorMock { public: MOCK_METHOD3(calledExistingLeaf, void(DataLeafNode*, bool, uint32_t)); MOCK_METHOD1(calledCreateLeaf, shared_ptr(uint32_t)); }; MATCHER_P(KeyEq, expected, "node blockId equals") { return arg->blockId() == expected; } class LeafTraverserTest: public DataTreeTest { public: LeafTraverserTest() :traversor() {} unique_ref CreateThreeLevel() { return CreateInner({ CreateFullTwoLevel(), CreateFullTwoLevel(), CreateFullTwoLevel(), CreateFullTwoLevel(), CreateFullTwoLevel(), CreateInner({CreateLeaf(), CreateLeaf(), CreateLeaf()})}); } unique_ref CreateFourLevel() { return CreateInner({ CreateFullThreeLevel(), CreateFullThreeLevel(), CreateInner({CreateFullTwoLevel(), CreateInner({CreateLeaf()})}) }); } void EXPECT_CREATE_LEAF(uint32_t leafIndex) { uint64_t maxBytesPerLeaf = nodeStore->layout().maxBytesPerLeaf(); EXPECT_CALL(traversor, calledCreateLeaf(Eq(leafIndex))).Times(1).WillOnce(Invoke([maxBytesPerLeaf] (uint32_t) { return make_shared(maxBytesPerLeaf); })); } void EXPECT_TRAVERSE_LEAF(const BlockId &blockId, bool isRightBorderLeaf, uint32_t leafIndex) { EXPECT_CALL(traversor, calledExistingLeaf(KeyEq(blockId), isRightBorderLeaf, leafIndex)).Times(1); } void EXPECT_TRAVERSE_ALL_CHILDREN_OF(const DataInnerNode &node, bool isRightBorderNode, uint32_t firstLeafIndex) { for (unsigned int i = 0; i < node.numChildren(); ++i) { EXPECT_TRAVERSE_LEAF(node.readChild(i).blockId(), isRightBorderNode && i == node.numChildren()-1, firstLeafIndex+i); } } void EXPECT_DONT_TRAVERSE_ANY_LEAVES() { EXPECT_CALL(traversor, calledExistingLeaf(_, _, _)).Times(0); EXPECT_CALL(traversor, calledCreateLeaf(_)).Times(0); } void TraverseLeaves(unique_ref root, uint32_t beginIndex, uint32_t endIndex, bool expectReadOnly) { root->flush(); auto tree = treeStore.load(root->blockId()).value(); auto* old_root = root.get(); LeafTraverser(nodeStore, expectReadOnly).traverseAndUpdateRoot(&root, beginIndex, endIndex, [this] (uint32_t nodeIndex, bool isRightBorderNode,LeafHandle leaf) { traversor.calledExistingLeaf(leaf.node(), isRightBorderNode, nodeIndex); }, [this] (uint32_t nodeIndex) -> Data { return traversor.calledCreateLeaf(nodeIndex)->copy(); }, [] (auto) {}); if (expectReadOnly) { EXPECT_EQ(old_root, root.get()); } else { EXPECT_NE(old_root, root.get()); } } TraversorMock traversor; }; TEST_F(LeafTraverserTest, TraverseSingleLeafTree) { unique_ref root = CreateLeaf(); EXPECT_TRAVERSE_LEAF(root->blockId(), true, 0); TraverseLeaves(std::move(root), 0, 1, true); } TEST_F(LeafTraverserTest, TraverseNothingInSingleLeafTree1) { unique_ref root = CreateLeaf(); EXPECT_DONT_TRAVERSE_ANY_LEAVES(); TraverseLeaves(std::move(root), 0, 0, true); } TEST_F(LeafTraverserTest, TraverseNothingInSingleLeafTree2) { unique_ref root = CreateLeaf(); EXPECT_DONT_TRAVERSE_ANY_LEAVES(); TraverseLeaves(std::move(root), 1, 1, true); } TEST_F(LeafTraverserTest, TraverseFirstLeafOfFullTwolevelTree) { auto root = CreateFullTwoLevel(); EXPECT_TRAVERSE_LEAF(root->readChild(0).blockId(), false, 0); TraverseLeaves(std::move(root), 0, 1, true); } TEST_F(LeafTraverserTest, TraverseMiddleLeafOfFullTwolevelTree) { auto root = CreateFullTwoLevel(); EXPECT_TRAVERSE_LEAF(root->readChild(5).blockId(), false, 5); TraverseLeaves(std::move(root), 5, 6, true); } TEST_F(LeafTraverserTest, TraverseLastLeafOfFullTwolevelTree) { auto root = CreateFullTwoLevel(); EXPECT_TRAVERSE_LEAF(root->readChild(nodeStore->layout().maxChildrenPerInnerNode()-1).blockId(), true, nodeStore->layout().maxChildrenPerInnerNode()-1); TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode()-1, nodeStore->layout().maxChildrenPerInnerNode(), true); } TEST_F(LeafTraverserTest, TraverseNothingInFullTwolevelTree1) { auto root = CreateFullTwoLevel(); EXPECT_DONT_TRAVERSE_ANY_LEAVES(); TraverseLeaves(std::move(root), 0, 0, true); } TEST_F(LeafTraverserTest, TraverseNothingInFullTwolevelTree2) { auto root = CreateFullTwoLevel(); EXPECT_DONT_TRAVERSE_ANY_LEAVES(); TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode(), nodeStore->layout().maxChildrenPerInnerNode(), true); } TEST_F(LeafTraverserTest, TraverseFirstLeafOfThreeLevelMinDataTree) { auto root = CreateThreeLevelMinData(); EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(0).blockId())->readChild(0).blockId(), false, 0); TraverseLeaves(std::move(root), 0, 1, true); } TEST_F(LeafTraverserTest, TraverseMiddleLeafOfThreeLevelMinDataTree) { auto root = CreateThreeLevelMinData(); EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(0).blockId())->readChild(5).blockId(), false, 5); TraverseLeaves(std::move(root), 5, 6, true); } TEST_F(LeafTraverserTest, TraverseLastLeafOfThreeLevelMinDataTree) { auto root = CreateThreeLevelMinData(); EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(1).blockId())->readChild(0).blockId(), true, nodeStore->layout().maxChildrenPerInnerNode()); TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode(), nodeStore->layout().maxChildrenPerInnerNode()+1, true); } TEST_F(LeafTraverserTest, TraverseAllLeavesOfFullTwolevelTree) { auto root = CreateFullTwoLevel(); EXPECT_TRAVERSE_ALL_CHILDREN_OF(*root, true, 0); TraverseLeaves(std::move(root), 0, nodeStore->layout().maxChildrenPerInnerNode(), true); } TEST_F(LeafTraverserTest, TraverseAllLeavesOfThreelevelMinDataTree) { auto root = CreateThreeLevelMinData(); EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(0).blockId()), false, 0); EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(1).blockId())->readChild(0).blockId(), true, nodeStore->layout().maxChildrenPerInnerNode()); TraverseLeaves(std::move(root), 0, nodeStore->layout().maxChildrenPerInnerNode()+1, true); } TEST_F(LeafTraverserTest, TraverseFirstChildOfThreelevelMinDataTree) { auto root = CreateThreeLevelMinData(); EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(0).blockId()), false, 0); TraverseLeaves(std::move(root), 0, nodeStore->layout().maxChildrenPerInnerNode(), true); } TEST_F(LeafTraverserTest, TraverseFirstPartOfFullTwolevelTree) { auto root = CreateFullTwoLevel(); for (unsigned int i = 0; i < 5; ++i) { EXPECT_TRAVERSE_LEAF(root->readChild(i).blockId(), false, i); } TraverseLeaves(std::move(root), 0, 5, true); } TEST_F(LeafTraverserTest, TraverseInnerPartOfFullTwolevelTree) { auto root = CreateFullTwoLevel(); for (unsigned int i = 5; i < 10; ++i) { EXPECT_TRAVERSE_LEAF(root->readChild(i).blockId(), false, i); } TraverseLeaves(std::move(root), 5, 10, true); } TEST_F(LeafTraverserTest, TraverseLastPartOfFullTwolevelTree) { auto root = CreateFullTwoLevel(); for (unsigned int i = 5; i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) { EXPECT_TRAVERSE_LEAF(root->readChild(i).blockId(), i==nodeStore->layout().maxChildrenPerInnerNode()-1, i); } TraverseLeaves(std::move(root), 5, nodeStore->layout().maxChildrenPerInnerNode(), true); } TEST_F(LeafTraverserTest, TraverseFirstPartOfThreelevelMinDataTree) { auto root = CreateThreeLevelMinData(); auto node = LoadInnerNode(root->readChild(0).blockId()); for (unsigned int i = 0; i < 5; ++i) { EXPECT_TRAVERSE_LEAF(node->readChild(i).blockId(), false, i); } TraverseLeaves(std::move(root), 0, 5, true); } TEST_F(LeafTraverserTest, TraverseInnerPartOfThreelevelMinDataTree) { auto root = CreateThreeLevelMinData(); auto node = LoadInnerNode(root->readChild(0).blockId()); for (unsigned int i = 5; i < 10; ++i) { EXPECT_TRAVERSE_LEAF(node->readChild(i).blockId(), false, i); } TraverseLeaves(std::move(root), 5, 10, true); } TEST_F(LeafTraverserTest, TraverseLastPartOfThreelevelMinDataTree) { auto root = CreateThreeLevelMinData(); auto node = LoadInnerNode(root->readChild(0).blockId()); for (unsigned int i = 5; i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) { EXPECT_TRAVERSE_LEAF(node->readChild(i).blockId(), false, i); } EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(1).blockId())->readChild(0).blockId(), true, nodeStore->layout().maxChildrenPerInnerNode()); TraverseLeaves(std::move(root), 5, nodeStore->layout().maxChildrenPerInnerNode()+1, true); } TEST_F(LeafTraverserTest, TraverseFirstLeafOfThreelevelTree) { auto root = CreateThreeLevel(); EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(0).blockId())->readChild(0).blockId(), false, 0); TraverseLeaves(std::move(root), 0, 1, true); } TEST_F(LeafTraverserTest, TraverseLastLeafOfThreelevelTree) { auto root = CreateThreeLevel(); uint32_t numLeaves = nodeStore->layout().maxChildrenPerInnerNode() * 5 + 3; EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readLastChild().blockId())->readLastChild().blockId(), true, numLeaves-1); TraverseLeaves(std::move(root), numLeaves-1, numLeaves, true); } TEST_F(LeafTraverserTest, TraverseMiddleLeafOfThreelevelTree) { auto root = CreateThreeLevel(); uint32_t wantedLeafIndex = nodeStore->layout().maxChildrenPerInnerNode() * 2 + 5; EXPECT_TRAVERSE_LEAF(LoadInnerNode(root->readChild(2).blockId())->readChild(5).blockId(), false, wantedLeafIndex); TraverseLeaves(std::move(root), wantedLeafIndex, wantedLeafIndex+1, true); } TEST_F(LeafTraverserTest, TraverseFirstPartOfThreelevelTree) { auto root = CreateThreeLevel(); //Traverse all leaves in the first two children of the root for(unsigned int i = 0; i < 2; ++i) { EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode()); } //Traverse some of the leaves in the third child of the root auto child = LoadInnerNode(root->readChild(2).blockId()); for(unsigned int i = 0; i < 5; ++i) { EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, 2 * nodeStore->layout().maxChildrenPerInnerNode() + i); } TraverseLeaves(std::move(root), 0, 2 * nodeStore->layout().maxChildrenPerInnerNode() + 5, true); } TEST_F(LeafTraverserTest, TraverseMiddlePartOfThreelevelTree_OnlyFullChildren) { auto root = CreateThreeLevel(); //Traverse some of the leaves in the second child of the root auto child = LoadInnerNode(root->readChild(1).blockId()); for(unsigned int i = 5; i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) { EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, nodeStore->layout().maxChildrenPerInnerNode() + i); } //Traverse all leaves in the third and fourth child of the root for(unsigned int i = 2; i < 4; ++i) { EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(i).blockId()),false, i * nodeStore->layout().maxChildrenPerInnerNode()); } //Traverse some of the leaves in the fifth child of the root child = LoadInnerNode(root->readChild(4).blockId()); for(unsigned int i = 0; i < 5; ++i) { EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, 4 * nodeStore->layout().maxChildrenPerInnerNode() + i); } TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode() + 5, 4 * nodeStore->layout().maxChildrenPerInnerNode() + 5, true); } TEST_F(LeafTraverserTest, TraverseMiddlePartOfThreelevelTree_AlsoLastNonfullChild) { auto root = CreateThreeLevel(); //Traverse some of the leaves in the second child of the root auto child = LoadInnerNode(root->readChild(1).blockId()); for(unsigned int i = 5; i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) { EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, nodeStore->layout().maxChildrenPerInnerNode() + i); } //Traverse all leaves in the third, fourth and fifth child of the root for(unsigned int i = 2; i < 5; ++i) { EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode()); } //Traverse some of the leaves in the sixth child of the root child = LoadInnerNode(root->readChild(5).blockId()); for(unsigned int i = 0; i < 2; ++i) { EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, 5 * nodeStore->layout().maxChildrenPerInnerNode() + i); } TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode() + 5, 5 * nodeStore->layout().maxChildrenPerInnerNode() + 2, true); } TEST_F(LeafTraverserTest, TraverseLastPartOfThreelevelTree) { auto root = CreateThreeLevel(); //Traverse some of the leaves in the second child of the root auto child = LoadInnerNode(root->readChild(1).blockId()); for(unsigned int i = 5; i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) { EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), false, nodeStore->layout().maxChildrenPerInnerNode() + i); } //Traverse all leaves in the third, fourth and fifth child of the root for(unsigned int i = 2; i < 5; ++i) { EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode()); } //Traverse all of the leaves in the sixth child of the root child = LoadInnerNode(root->readChild(5).blockId()); for(unsigned int i = 0; i < child->numChildren(); ++i) { EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), i == child->numChildren()-1, 5 * nodeStore->layout().maxChildrenPerInnerNode() + i); } TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode() + 5, 5 * nodeStore->layout().maxChildrenPerInnerNode() + child->numChildren(), true); } TEST_F(LeafTraverserTest, TraverseAllLeavesOfThreelevelTree) { auto root = CreateThreeLevel(); //Traverse all leaves in the third, fourth and fifth child of the root for(unsigned int i = 0; i < 5; ++i) { EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(root->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode()); } //Traverse all of the leaves in the sixth child of the root auto child = LoadInnerNode(root->readChild(5).blockId()); for(unsigned int i = 0; i < child->numChildren(); ++i) { EXPECT_TRAVERSE_LEAF(child->readChild(i).blockId(), i==child->numChildren()-1, 5 * nodeStore->layout().maxChildrenPerInnerNode() + i); } TraverseLeaves(std::move(root), 0, 5 * nodeStore->layout().maxChildrenPerInnerNode() + child->numChildren(), true); } TEST_F(LeafTraverserTest, TraverseAllLeavesOfFourLevelTree) { auto root = CreateFourLevel(); //Traverse all leaves of the full threelevel tree in the first child auto firstChild = LoadInnerNode(root->readChild(0).blockId()); for(unsigned int i = 0; i < firstChild->numChildren(); ++i) { EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(firstChild->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode()); } //Traverse all leaves of the full threelevel tree in the second child auto secondChild = LoadInnerNode(root->readChild(1).blockId()); for(unsigned int i = 0; i < secondChild->numChildren(); ++i) { EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(secondChild->readChild(i).blockId()), false, (nodeStore->layout().maxChildrenPerInnerNode() + i) * nodeStore->layout().maxChildrenPerInnerNode()); } //Traverse all leaves of the non-full threelevel tree in the third child auto thirdChild = LoadInnerNode(root->readChild(2).blockId()); EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(thirdChild->readChild(0).blockId()), false, 2 * nodeStore->layout().maxChildrenPerInnerNode() * nodeStore->layout().maxChildrenPerInnerNode()); EXPECT_TRAVERSE_LEAF(LoadInnerNode(thirdChild->readChild(1).blockId())->readChild(0).blockId(), true, 2 * nodeStore->layout().maxChildrenPerInnerNode() * nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxChildrenPerInnerNode()); TraverseLeaves(std::move(root), 0, 2*nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxChildrenPerInnerNode() + 1, true); } TEST_F(LeafTraverserTest, TraverseMiddlePartOfFourLevelTree) { auto root = CreateFourLevel(); //Traverse some leaves of the full threelevel tree in the first child auto firstChild = LoadInnerNode(root->readChild(0).blockId()); auto secondChildOfFirstChild = LoadInnerNode(firstChild->readChild(1).blockId()); for(unsigned int i = 5; i < secondChildOfFirstChild->numChildren(); ++i) { EXPECT_TRAVERSE_LEAF(secondChildOfFirstChild->readChild(i).blockId(), false, nodeStore->layout().maxChildrenPerInnerNode()+i); } for(unsigned int i = 2; i < firstChild->numChildren(); ++i) { EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(firstChild->readChild(i).blockId()), false, i * nodeStore->layout().maxChildrenPerInnerNode()); } //Traverse all leaves of the full threelevel tree in the second child auto secondChild = LoadInnerNode(root->readChild(1).blockId()); for(unsigned int i = 0; i < secondChild->numChildren(); ++i) { EXPECT_TRAVERSE_ALL_CHILDREN_OF(*LoadInnerNode(secondChild->readChild(i).blockId()), false, (nodeStore->layout().maxChildrenPerInnerNode() + i) * nodeStore->layout().maxChildrenPerInnerNode()); } //Traverse some leaves of the non-full threelevel tree in the third child auto thirdChild = LoadInnerNode(root->readChild(2).blockId()); auto firstChildOfThirdChild = LoadInnerNode(thirdChild->readChild(0).blockId()); for(unsigned int i = 0; i < firstChildOfThirdChild->numChildren()-1; ++i) { EXPECT_TRAVERSE_LEAF(firstChildOfThirdChild->readChild(i).blockId(), false, 2 * nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode()+i); } TraverseLeaves(std::move(root), nodeStore->layout().maxChildrenPerInnerNode()+5, 2*nodeStore->layout().maxChildrenPerInnerNode()*nodeStore->layout().maxChildrenPerInnerNode() + nodeStore->layout().maxChildrenPerInnerNode() -1, true); } TEST_F(LeafTraverserTest, LastLeafIsAlreadyResizedInCallback) { unique_ref root = CreateLeaf(); root->flush(); auto* old_root = root.get(); auto tree = treeStore.load(root->blockId()).value(); LeafTraverser(nodeStore, false).traverseAndUpdateRoot(&root, 0, 2, [this] (uint32_t leafIndex, bool /*isRightBorderNode*/, LeafHandle leaf) { if (leafIndex == 0) { EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf(), leaf.node()->numBytes()); } else { EXPECT_TRUE(false) << "only two nodes"; } }, [] (uint32_t /*nodeIndex*/) -> Data { return Data(1); }, [] (auto) {}); EXPECT_NE(old_root, root.get()); // expect that we grew the tree } TEST_F(LeafTraverserTest, LastLeafIsAlreadyResizedInCallback_TwoLevel) { unique_ref root = CreateFullTwoLevelWithLastLeafSize(5); root->flush(); auto* old_root = root.get(); auto tree = treeStore.load(root->blockId()).value(); LeafTraverser(nodeStore, false).traverseAndUpdateRoot(&root, 0, nodeStore->layout().maxChildrenPerInnerNode()+1, [this] (uint32_t /*leafIndex*/, bool /*isRightBorderNode*/, LeafHandle leaf) { EXPECT_EQ(nodeStore->layout().maxBytesPerLeaf(), leaf.node()->numBytes()); }, [] (uint32_t /*nodeIndex*/) -> Data { return Data(1); }, [] (auto) {}); EXPECT_NE(old_root, root.get()); // expect that we grew the tree } TEST_F(LeafTraverserTest, ResizeFromOneLeafToMultipleLeaves) { auto root = CreateLeaf(); EXPECT_TRAVERSE_LEAF(root->blockId(), false, 0); //EXPECT_CALL(traversor, calledExistingLeaf(_, false, 0)).Times(1); for (uint32_t i = 1; i < 10; ++i) { EXPECT_CREATE_LEAF(i); } TraverseLeaves(std::move(root), 0, 10, false); } ////TODO Refactor the test cases that are too long test/blobstore/implementations/onblocks/datatreestore/impl/000077500000000000000000000000001347701267100246715ustar00rootroot00000000000000GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest.cpp000066400000000000000000000061211347701267100412470ustar00rootroot00000000000000test/blobstore/implementations/onblocks/datatreestore/impl#include #include "../testutils/DataTreeTest.h" #include "blobstore/implementations/onblocks/datatreestore/DataTree.h" #include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h" #include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h" #include #include "blobstore/implementations/onblocks/datatreestore/impl/algorithms.h" using blockstore::BlockId; using cpputils::Data; using namespace blobstore::onblocks::datatreestore::algorithms; class GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest: public DataTreeTest { public: struct TestData { BlockId rootNode; BlockId expectedResult; }; void check(const TestData &testData) { auto root = nodeStore->load(testData.rootNode).value(); auto result = GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNull(nodeStore, root.get()); EXPECT_EQ(testData.expectedResult, result->blockId()); } TestData CreateTwoRightBorderNodes() { auto node = CreateInner({CreateLeaf()}); return TestData{node->blockId(), node->blockId()}; } TestData CreateThreeRightBorderNodes() { auto node = CreateInner({CreateLeaf()}); auto root = CreateInner({node.get()}); return TestData{root->blockId(), node->blockId()}; } TestData CreateThreeRightBorderNodes_LastFull() { auto root = CreateInner({CreateFullTwoLevel()}); return TestData{root->blockId(), root->blockId()}; } TestData CreateLargerTree() { auto node = CreateInner({CreateLeaf(), CreateLeaf()}); auto root = CreateInner({CreateFullTwoLevel().get(), node.get()}); return TestData{root->blockId(), node->blockId()}; } }; TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, Leaf) { auto leaf = nodeStore->createNewLeafNode(Data(0)); auto result = GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNull(nodeStore, leaf.get()); EXPECT_EQ(nullptr, result.get()); } TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, TwoRightBorderNodes) { auto testData = CreateTwoRightBorderNodes(); check(testData); } TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, ThreeRightBorderNodes) { auto testData = CreateThreeRightBorderNodes(); check(testData); } TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, ThreeRightBorderNodes_LastFull) { auto testData = CreateThreeRightBorderNodes_LastFull(); check(testData); } TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, LargerTree) { auto testData = CreateLargerTree(); check(testData); } TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, FullTwoLevelTree) { auto root = CreateFullTwoLevel(); auto result = GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNull(nodeStore, root.get()); EXPECT_EQ(nullptr, result.get()); } TEST_F(GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest, FullThreeLevelTree) { auto root = CreateFullThreeLevel(); auto result = GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNull(nodeStore, root.get()); EXPECT_EQ(nullptr, result.get()); } GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest.cpp000066400000000000000000000101171347701267100400710ustar00rootroot00000000000000test/blobstore/implementations/onblocks/datatreestore/impl#include #include "../testutils/DataTreeTest.h" #include "blobstore/implementations/onblocks/datatreestore/DataTree.h" #include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h" #include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h" #include #include "blobstore/implementations/onblocks/datatreestore/impl/algorithms.h" using blockstore::BlockId; using namespace blobstore::onblocks::datatreestore::algorithms; class GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest: public DataTreeTest { public: struct TestData { BlockId rootNode; BlockId expectedResult; }; void check(const TestData &testData) { auto root = nodeStore->load(testData.rootNode).value(); auto result = GetLowestRightBorderNodeWithMoreThanOneChildOrNull(nodeStore, root.get()); EXPECT_EQ(testData.expectedResult, result->blockId()); } BlockId CreateLeafOnlyTree() { return CreateLeaf()->blockId(); } BlockId CreateTwoRightBorderNodes() { return CreateInner({CreateLeaf()})->blockId(); } BlockId CreateThreeRightBorderNodes() { return CreateInner({CreateInner({CreateLeaf()})})->blockId(); } TestData CreateThreeRightBorderNodes_LastFull() { auto node = CreateFullTwoLevel(); auto root = CreateInner({node.get()}); return TestData{root->blockId(), node->blockId()}; } TestData CreateLargerTree() { auto node = CreateInner({CreateLeaf(), CreateLeaf()}); auto root = CreateInner({CreateFullTwoLevel().get(), node.get()}); return TestData{root->blockId(), node->blockId()}; } TestData CreateThreeLevelTreeWithRightBorderSingleNodeChain() { auto root = CreateInner({CreateFullTwoLevel(), CreateInner({CreateLeaf()})}); return TestData{root->blockId(), root->blockId()}; } TestData CreateThreeLevelTree() { auto node = CreateInner({CreateLeaf(), CreateLeaf()}); auto root = CreateInner({CreateFullTwoLevel().get(), node.get()}); return TestData{root->blockId(), node->blockId()}; } TestData CreateFullTwoLevelTree() { auto node = CreateFullTwoLevel(); return TestData{node->blockId(), node->blockId()}; } TestData CreateFullThreeLevelTree() { auto root = CreateFullThreeLevel(); return TestData{root->blockId(), root->readLastChild().blockId()}; } }; TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, Leaf) { auto leaf = nodeStore->load(CreateLeafOnlyTree()).value(); auto result = GetLowestRightBorderNodeWithMoreThanOneChildOrNull(nodeStore, leaf.get()); EXPECT_EQ(nullptr, result.get()); } TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, TwoRightBorderNodes) { auto node = nodeStore->load(CreateTwoRightBorderNodes()).value(); auto result = GetLowestRightBorderNodeWithMoreThanOneChildOrNull(nodeStore, node.get()); EXPECT_EQ(nullptr, result.get()); } TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, ThreeRightBorderNodes) { auto node = nodeStore->load(CreateThreeRightBorderNodes()).value(); auto result = GetLowestRightBorderNodeWithMoreThanOneChildOrNull(nodeStore, node.get()); EXPECT_EQ(nullptr, result.get()); } TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, ThreeRightBorderNodes_LastFull) { auto testData = CreateThreeRightBorderNodes_LastFull(); check(testData); } TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, LargerTree) { auto testData = CreateLargerTree(); check(testData); } TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, FullTwoLevelTree) { auto testData = CreateFullTwoLevelTree(); check(testData); } TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, FullThreeLevelTree) { auto testData = CreateFullThreeLevelTree(); check(testData); } TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, ThreeLevelTreeWithRightBorderSingleNodeChain) { auto testData = CreateThreeLevelTreeWithRightBorderSingleNodeChain(); check(testData); } TEST_F(GetLowestRightBorderNodeWithMoreThanOneChildOrNullTest, ThreeLevelTree) { auto testData = CreateThreeLevelTree(); check(testData); } test/blobstore/implementations/onblocks/datatreestore/testutils/000077500000000000000000000000001347701267100257705ustar00rootroot00000000000000test/blobstore/implementations/onblocks/datatreestore/testutils/DataTreeTest.cpp000066400000000000000000000203641347701267100310320ustar00rootroot00000000000000#include "DataTreeTest.h" #include #include #include using blobstore::onblocks::datanodestore::DataNodeStore; using blobstore::onblocks::datanodestore::DataNode; using blobstore::onblocks::datanodestore::DataInnerNode; using blobstore::onblocks::datanodestore::DataLeafNode; using blobstore::onblocks::datatreestore::DataTree; using blockstore::mock::MockBlockStore; using blockstore::BlockId; using cpputils::unique_ref; using cpputils::make_unique_ref; using std::initializer_list; using std::vector; using boost::none; using cpputils::dynamic_pointer_move; using cpputils::Data; constexpr uint32_t DataTreeTest::BLOCKSIZE_BYTES; DataTreeTest::DataTreeTest() :_blockStore(make_unique_ref()), blockStore(_blockStore.get()), _nodeStore(make_unique_ref(std::move(_blockStore), BLOCKSIZE_BYTES)), nodeStore(_nodeStore.get()), treeStore(std::move(_nodeStore)) { } unique_ref DataTreeTest::CreateLeaf() { return nodeStore->createNewLeafNode(Data(nodeStore->layout().maxBytesPerLeaf())); } unique_ref DataTreeTest::CreateInner(initializer_list> children) { vector childrenVector(children.size()); std::transform(children.begin(), children.end(), childrenVector.begin(), [](const unique_ref &ptr) {return ptr.get();}); return CreateInner(childrenVector); } unique_ref DataTreeTest::CreateInner(initializer_list children) { return CreateInner(vector(children)); } unique_ref DataTreeTest::CreateInner(vector children) { ASSERT(children.size() >= 1, "An inner node must have at least one child"); vector childrenKeys; childrenKeys.reserve(children.size()); for (const DataNode *child : children) { ASSERT(child->depth() == (*children.begin())->depth(), "Children with different depth"); childrenKeys.push_back(child->blockId()); } auto node = nodeStore->createNewInnerNode((*children.begin())->depth()+1, childrenKeys); return node; } unique_ref DataTreeTest::CreateLeafOnlyTree() { auto blockId = CreateLeaf()->blockId(); return treeStore.load(blockId).value(); } void DataTreeTest::FillNode(DataInnerNode *node) { for(unsigned int i=node->numChildren(); i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) { node->addChild(*CreateLeaf()); } } void DataTreeTest::FillNodeTwoLevel(DataInnerNode *node) { for(unsigned int i=node->numChildren(); i < nodeStore->layout().maxChildrenPerInnerNode(); ++i) { node->addChild(*CreateFullTwoLevel()); } } unique_ref DataTreeTest::CreateFullTwoLevel() { auto root = CreateInner({CreateLeaf().get()}); FillNode(root.get()); return root; } unique_ref DataTreeTest::CreateThreeLevelMinData() { return CreateInner({ CreateFullTwoLevel(), CreateInner({CreateLeaf()}) }); } unique_ref DataTreeTest::CreateFourLevelMinData() { return CreateInner({ CreateFullThreeLevel(), CreateInner({CreateInner({CreateLeaf()})}) }); } unique_ref DataTreeTest::CreateFullThreeLevel() { auto root = CreateInner({CreateFullTwoLevel().get()}); FillNodeTwoLevel(root.get()); return root; } unique_ref DataTreeTest::LoadInnerNode(const BlockId &blockId) { auto node = nodeStore->load(blockId).value(); auto casted = dynamic_pointer_move(node); EXPECT_NE(none, casted) << "Is not an inner node"; return std::move(*casted); } unique_ref DataTreeTest::LoadLeafNode(const BlockId &blockId) { auto node = nodeStore->load(blockId).value(); auto casted = dynamic_pointer_move(node); EXPECT_NE(none, casted) << "Is not a leaf node"; return std::move(*casted); } unique_ref DataTreeTest::CreateTwoLeaf() { return CreateInner({CreateLeaf().get(), CreateLeaf().get()}); } unique_ref DataTreeTest::CreateTwoLeafTree() { auto blockId = CreateTwoLeaf()->blockId(); return treeStore.load(blockId).value(); } unique_ref DataTreeTest::CreateLeafWithSize(uint32_t size) { auto leaf = CreateLeaf(); leaf->resize(size); return leaf; } unique_ref DataTreeTest::CreateTwoLeafWithSecondLeafSize(uint32_t size) { return CreateInner({ CreateLeafWithSize(nodeStore->layout().maxBytesPerLeaf()), CreateLeafWithSize(size) }); } unique_ref DataTreeTest::CreateFullTwoLevelWithLastLeafSize(uint32_t size) { auto root = CreateFullTwoLevel(); for (uint32_t i = 0; i < root->numChildren()-1; ++i) { LoadLeafNode(root->readChild(i).blockId())->resize(nodeStore->layout().maxBytesPerLeaf()); } LoadLeafNode(root->readLastChild().blockId())->resize(size); return root; } unique_ref DataTreeTest::CreateThreeLevelWithOneChildAndLastLeafSize(uint32_t size) { return CreateInner({ CreateInner({ CreateLeafWithSize(nodeStore->layout().maxBytesPerLeaf()), CreateLeafWithSize(size) }) }); } unique_ref DataTreeTest::CreateThreeLevelWithTwoChildrenAndLastLeafSize(uint32_t size) { return CreateInner({ CreateFullTwoLevelWithLastLeafSize(nodeStore->layout().maxBytesPerLeaf()), CreateInner({ CreateLeafWithSize(nodeStore->layout().maxBytesPerLeaf()), CreateLeafWithSize(size) }) }); } unique_ref DataTreeTest::CreateThreeLevelWithThreeChildrenAndLastLeafSize(uint32_t size) { return CreateInner({ CreateFullTwoLevelWithLastLeafSize(nodeStore->layout().maxBytesPerLeaf()), CreateFullTwoLevelWithLastLeafSize(nodeStore->layout().maxBytesPerLeaf()), CreateInner({ CreateLeafWithSize(nodeStore->layout().maxBytesPerLeaf()), CreateLeafWithSize(size) }) }); } unique_ref DataTreeTest::CreateFullThreeLevelWithLastLeafSize(uint32_t size) { auto root = CreateFullThreeLevel(); for (uint32_t i = 0; i < root->numChildren(); ++i) { auto node = LoadInnerNode(root->readChild(i).blockId()); for (uint32_t j = 0; j < node->numChildren(); ++j) { LoadLeafNode(node->readChild(j).blockId())->resize(nodeStore->layout().maxBytesPerLeaf()); } } LoadLeafNode(LoadInnerNode(root->readLastChild().blockId())->readLastChild().blockId())->resize(size); return root; } unique_ref DataTreeTest::CreateFourLevelMinDataWithLastLeafSize(uint32_t size) { return CreateInner({ CreateFullThreeLevelWithLastLeafSize(nodeStore->layout().maxBytesPerLeaf()), CreateInner({CreateInner({CreateLeafWithSize(size)})}) }); } void DataTreeTest::EXPECT_IS_LEAF_NODE(const BlockId &blockId) { auto node = LoadLeafNode(blockId); EXPECT_NE(nullptr, node.get()); } void DataTreeTest::EXPECT_IS_INNER_NODE(const BlockId &blockId) { auto node = LoadInnerNode(blockId); EXPECT_NE(nullptr, node.get()); } void DataTreeTest::EXPECT_IS_TWONODE_CHAIN(const BlockId &blockId) { auto node = LoadInnerNode(blockId); EXPECT_EQ(1u, node->numChildren()); EXPECT_IS_LEAF_NODE(node->readChild(0).blockId()); } void DataTreeTest::EXPECT_IS_FULL_TWOLEVEL_TREE(const BlockId &blockId) { auto node = LoadInnerNode(blockId); EXPECT_EQ(nodeStore->layout().maxChildrenPerInnerNode(), node->numChildren()); for (unsigned int i = 0; i < node->numChildren(); ++i) { EXPECT_IS_LEAF_NODE(node->readChild(i).blockId()); } } void DataTreeTest::EXPECT_IS_FULL_THREELEVEL_TREE(const BlockId &blockId) { auto root = LoadInnerNode(blockId); EXPECT_EQ(nodeStore->layout().maxChildrenPerInnerNode(), root->numChildren()); for (unsigned int i = 0; i < root->numChildren(); ++i) { auto node = LoadInnerNode(root->readChild(i).blockId()); EXPECT_EQ(nodeStore->layout().maxChildrenPerInnerNode(), node->numChildren()); for (unsigned int j = 0; j < node->numChildren(); ++j) { EXPECT_IS_LEAF_NODE(node->readChild(j).blockId()); } } } void DataTreeTest::CHECK_DEPTH(int depth, const BlockId &blockId) { if (depth == 0) { EXPECT_IS_LEAF_NODE(blockId); } else { auto node = LoadInnerNode(blockId); EXPECT_EQ(depth, node->depth()); for (uint32_t i = 0; i < node->numChildren(); ++i) { CHECK_DEPTH(depth-1, node->readChild(i).blockId()); } } } test/blobstore/implementations/onblocks/datatreestore/testutils/DataTreeTest.h000066400000000000000000000103771347701267100305020ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_DATATREETEST_H_ #define MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_DATATREETEST_H_ #include #include #include "blobstore/implementations/onblocks/datanodestore/DataNodeStore.h" #include "blobstore/implementations/onblocks/datanodestore/DataInnerNode.h" #include "blobstore/implementations/onblocks/datanodestore/DataLeafNode.h" #include "blobstore/implementations/onblocks/datatreestore/DataTree.h" #include "blobstore/implementations/onblocks/datatreestore/DataTreeStore.h" #include "blockstore/implementations/mock/MockBlockStore.h" class DataTreeTest: public ::testing::Test { public: DataTreeTest(); static constexpr uint32_t BLOCKSIZE_BYTES = 256; cpputils::unique_ref CreateLeaf(); cpputils::unique_ref CreateInner(std::vector children); cpputils::unique_ref CreateInner(std::initializer_list children); cpputils::unique_ref CreateInner(std::initializer_list> children); cpputils::unique_ref CreateLeafOnlyTree(); cpputils::unique_ref CreateTwoLeaf(); cpputils::unique_ref CreateTwoLeafTree(); void FillNode(blobstore::onblocks::datanodestore::DataInnerNode *node); void FillNodeTwoLevel(blobstore::onblocks::datanodestore::DataInnerNode *node); cpputils::unique_ref CreateFullTwoLevel(); cpputils::unique_ref CreateFullThreeLevel(); cpputils::unique_ref CreateThreeLevelMinData(); cpputils::unique_ref CreateFourLevelMinData(); cpputils::unique_ref LoadInnerNode(const blockstore::BlockId &blockId); cpputils::unique_ref LoadLeafNode(const blockstore::BlockId &blockId); cpputils::unique_ref CreateLeafWithSize(uint32_t size); cpputils::unique_ref CreateTwoLeafWithSecondLeafSize(uint32_t size); cpputils::unique_ref CreateFullTwoLevelWithLastLeafSize(uint32_t size); cpputils::unique_ref CreateThreeLevelWithOneChildAndLastLeafSize(uint32_t size); cpputils::unique_ref CreateThreeLevelWithTwoChildrenAndLastLeafSize(uint32_t size); cpputils::unique_ref CreateThreeLevelWithThreeChildrenAndLastLeafSize(uint32_t size); cpputils::unique_ref CreateFullThreeLevelWithLastLeafSize(uint32_t size); cpputils::unique_ref CreateFourLevelMinDataWithLastLeafSize(uint32_t size); cpputils::unique_ref _blockStore; blockstore::mock::MockBlockStore *blockStore; cpputils::unique_ref _nodeStore; blobstore::onblocks::datanodestore::DataNodeStore *nodeStore; blobstore::onblocks::datatreestore::DataTreeStore treeStore; void EXPECT_IS_LEAF_NODE(const blockstore::BlockId &blockId); void EXPECT_IS_INNER_NODE(const blockstore::BlockId &blockId); void EXPECT_IS_TWONODE_CHAIN(const blockstore::BlockId &blockId); void EXPECT_IS_FULL_TWOLEVEL_TREE(const blockstore::BlockId &blockId); void EXPECT_IS_FULL_THREELEVEL_TREE(const blockstore::BlockId &blockId); void CHECK_DEPTH(int depth, const blockstore::BlockId &blockId); private: DISALLOW_COPY_AND_ASSIGN(DataTreeTest); }; #endif test/blobstore/implementations/onblocks/datatreestore/testutils/LeafDataFixture.h000066400000000000000000000027041347701267100311540ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_LEAFDATAFIXTURE_H_ #define MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_LEAFDATAFIXTURE_H_ #include #include // A data fixture containing data for a leaf. // The class can fill this data into a given leaf // and check, whether the data stored in a given leaf is correct. class LeafDataFixture { public: LeafDataFixture(int size, int iv = 0): _data(cpputils::DataFixture::generate(size, iv)) {} void FillInto(blobstore::onblocks::datanodestore::DataLeafNode *leaf) const { leaf->resize(_data.size()); leaf->write(_data.data(), 0, _data.size()); } void EXPECT_DATA_CORRECT(const blobstore::onblocks::datanodestore::DataLeafNode &leaf, int onlyCheckNumBytes = -1) const { if (onlyCheckNumBytes == -1) { EXPECT_EQ(_data.size(), leaf.numBytes()); EXPECT_EQ(0, std::memcmp(_data.data(), loadData(leaf).data(), _data.size())); } else { EXPECT_LE(onlyCheckNumBytes, static_cast(leaf.numBytes())); EXPECT_EQ(0, std::memcmp(_data.data(), loadData(leaf).data(), onlyCheckNumBytes)); } } private: static cpputils::Data loadData(const blobstore::onblocks::datanodestore::DataLeafNode &leaf) { cpputils::Data data(leaf.numBytes()); leaf.read(data.data(), 0, leaf.numBytes()); return data; } cpputils::Data _data; }; #endif test/blobstore/implementations/onblocks/datatreestore/testutils/TwoLevelDataFixture.h000066400000000000000000000067051347701267100320530ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_TWOLEVELDATAFIXTURE_H_ #define MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_DATATREESTORE_GROWING_TESTUTILS_TWOLEVELDATAFIXTURE_H_ #include #include #include "LeafDataFixture.h" #include //TODO Rename, since we now allow any number of levels // A data fixture containing data for a two-level tree (one inner node with leaf children). // The class can fill this data into the leaf children of a given inner node // and given an inner node can check, whether the data stored is correct. class TwoLevelDataFixture { public: enum class SizePolicy { Random, Full, Unchanged }; TwoLevelDataFixture(blobstore::onblocks::datanodestore::DataNodeStore *dataNodeStore, SizePolicy sizePolicy, int iv=0): _dataNodeStore(dataNodeStore), _iv(iv), _sizePolicy(sizePolicy) {} void FillInto(blobstore::onblocks::datanodestore::DataNode *node) { // _iv-1 means there is no endLeafIndex - we fill all leaves. ForEachLeaf(node, _iv, _iv-1, [this] (blobstore::onblocks::datanodestore::DataLeafNode *leaf, int leafIndex) { LeafDataFixture(size(leafIndex, leaf), leafIndex).FillInto(leaf); }); } void EXPECT_DATA_CORRECT(blobstore::onblocks::datanodestore::DataNode *node, int maxCheckedLeaves = 0, int lastLeafMaxCheckedBytes = -1) { ForEachLeaf(node, _iv, _iv+maxCheckedLeaves, [this, maxCheckedLeaves, lastLeafMaxCheckedBytes] (blobstore::onblocks::datanodestore::DataLeafNode *leaf, int leafIndex) { if (leafIndex == _iv+maxCheckedLeaves-1) { // It is the last leaf LeafDataFixture(size(leafIndex, leaf), leafIndex).EXPECT_DATA_CORRECT(*leaf, lastLeafMaxCheckedBytes); } else { LeafDataFixture(size(leafIndex, leaf), leafIndex).EXPECT_DATA_CORRECT(*leaf); } }); } private: int ForEachLeaf(blobstore::onblocks::datanodestore::DataNode *node, int firstLeafIndex, int endLeafIndex, std::function action) { if (firstLeafIndex == endLeafIndex) { return firstLeafIndex; } auto leaf = dynamic_cast(node); if (leaf != nullptr) { action(leaf, firstLeafIndex); return firstLeafIndex + 1; } else { auto inner = dynamic_cast(node); int leafIndex = firstLeafIndex; for (uint32_t i = 0; i < inner->numChildren(); ++i) { auto child = _dataNodeStore->load(inner->readChild(i).blockId()).value(); leafIndex = ForEachLeaf(child.get(), leafIndex, endLeafIndex, action); } return leafIndex; } } blobstore::onblocks::datanodestore::DataNodeStore *_dataNodeStore; int _iv; SizePolicy _sizePolicy; int size(int childIndex, blobstore::onblocks::datanodestore::DataLeafNode *leaf) { switch (_sizePolicy) { case SizePolicy::Full: return _dataNodeStore->layout().maxBytesPerLeaf(); case SizePolicy::Random: return mod(static_cast(_dataNodeStore->layout().maxBytesPerLeaf() - childIndex), static_cast(_dataNodeStore->layout().maxBytesPerLeaf())); case SizePolicy::Unchanged: return leaf->numBytes(); default: ASSERT(false, "Unknown size policy"); } } int mod(int value, int mod) { while(value < 0) { value += mod; } return value; } }; #endif test/blobstore/implementations/onblocks/testutils/000077500000000000000000000000001347701267100231225ustar00rootroot00000000000000test/blobstore/implementations/onblocks/testutils/BlobStoreTest.cpp000066400000000000000000000010151347701267100263560ustar00rootroot00000000000000#include "BlobStoreTest.h" #include #include "blobstore/implementations/onblocks/BlobStoreOnBlocks.h" #include using blobstore::onblocks::BlobStoreOnBlocks; using blockstore::testfake::FakeBlockStore; using cpputils::make_unique_ref; constexpr uint32_t BlobStoreTest::BLOCKSIZE_BYTES; BlobStoreTest::BlobStoreTest() : blobStore(make_unique_ref(make_unique_ref(), BLOCKSIZE_BYTES)) { } test/blobstore/implementations/onblocks/testutils/BlobStoreTest.h000066400000000000000000000014201347701267100260230ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_TESTUTILS_BLOBSTORETEST_H_ #define MESSMER_BLOBSTORE_TEST_IMPLEMENTATIONS_ONBLOCKS_TESTUTILS_BLOBSTORETEST_H_ #include #include "blobstore/interface/BlobStore.h" class BlobStoreTest: public ::testing::Test { public: BlobStoreTest(); static constexpr uint32_t BLOCKSIZE_BYTES = 4096; cpputils::unique_ref blobStore; cpputils::unique_ref loadBlob(const blockstore::BlockId &blockId) { auto loaded = blobStore->load(blockId); EXPECT_TRUE(static_cast(loaded)); return std::move(*loaded); } void reset(cpputils::unique_ref ref) { UNUSED(ref); //ref is moved into here and then destructed } }; #endif test/blobstore/implementations/onblocks/utils/000077500000000000000000000000001347701267100222225ustar00rootroot00000000000000test/blobstore/implementations/onblocks/utils/CeilDivisionTest.cpp000066400000000000000000000034641347701267100261560ustar00rootroot00000000000000#include #include "blobstore/implementations/onblocks/utils/Math.h" #include using namespace blobstore::onblocks::utils; using ::testing::Test; using std::numeric_limits; class CeilDivisionTest: public Test {}; TEST_F(CeilDivisionTest, Divide0_4) { EXPECT_EQ(0, ceilDivision(0, 4)); } TEST_F(CeilDivisionTest, Divide1_4) { EXPECT_EQ(1, ceilDivision(1, 4)); } TEST_F(CeilDivisionTest, Divide2_4) { EXPECT_EQ(1, ceilDivision(2, 4)); } TEST_F(CeilDivisionTest, Divide3_4) { EXPECT_EQ(1, ceilDivision(3, 4)); } TEST_F(CeilDivisionTest, Divide4_4) { EXPECT_EQ(1, ceilDivision(4, 4)); } TEST_F(CeilDivisionTest, Divide5_4) { EXPECT_EQ(2, ceilDivision(5, 4)); } TEST_F(CeilDivisionTest, Divide6_4) { EXPECT_EQ(2, ceilDivision(6, 4)); } TEST_F(CeilDivisionTest, Divide7_4) { EXPECT_EQ(2, ceilDivision(7, 4)); } TEST_F(CeilDivisionTest, Divide8_4) { EXPECT_EQ(2, ceilDivision(8, 4)); } TEST_F(CeilDivisionTest, Divide9_4) { EXPECT_EQ(3, ceilDivision(9, 4)); } TEST_F(CeilDivisionTest, Divide0_1) { EXPECT_EQ(0, ceilDivision(0, 1)); } TEST_F(CeilDivisionTest, Divide5_1) { EXPECT_EQ(5, ceilDivision(5, 1)); } TEST_F(CeilDivisionTest, Divide7_2) { EXPECT_EQ(4, ceilDivision(7, 2)); } TEST_F(CeilDivisionTest, Divide8_2) { EXPECT_EQ(4, ceilDivision(8, 2)); } TEST_F(CeilDivisionTest, Divide9_2) { EXPECT_EQ(5, ceilDivision(9, 2)); } TEST_F(CeilDivisionTest, Divide1_1) { EXPECT_EQ(1, ceilDivision(1, 1)); } TEST_F(CeilDivisionTest, Divide5_5) { EXPECT_EQ(1, ceilDivision(5, 5)); } TEST_F(CeilDivisionTest, DivideLargeByItself) { EXPECT_EQ(1, ceilDivision(183495303, 183495303)); } TEST_F(CeilDivisionTest, 64bit) { uint64_t base = UINT64_C(1024)*1024*1024*1024; EXPECT_GT(base, std::numeric_limits::max()); EXPECT_EQ(base/1024, ceilDivision(base, UINT64_C(1024))); } test/blobstore/implementations/onblocks/utils/CeilLogTest.cpp000066400000000000000000000012551347701267100251070ustar00rootroot00000000000000#include #include "blobstore/implementations/onblocks/utils/Math.h" #include using namespace blobstore::onblocks::utils; using ::testing::Test; using std::numeric_limits; class CeilLogTest: public Test {}; TEST_F(CeilLogTest, Log3_1) { EXPECT_EQ(0, ceilLog(3, 1)); } TEST_F(CeilLogTest, Log3_2) { EXPECT_EQ(1, ceilLog(3, 2)); } TEST_F(CeilLogTest, Log3_3) { EXPECT_EQ(1, ceilLog(3, 3)); } TEST_F(CeilLogTest, Log3_4) { EXPECT_EQ(2, ceilLog(3, 4)); } TEST_F(CeilLogTest, 64bit) { uint64_t value = UINT64_C(1024)*1024*1024*1024; EXPECT_GT(value, std::numeric_limits::max()); EXPECT_EQ(4u, ceilLog(UINT64_C(1024), value)); } //TODO ... test/blobstore/implementations/onblocks/utils/IntPowTest.cpp000066400000000000000000000030321347701267100250040ustar00rootroot00000000000000#include #include "blobstore/implementations/onblocks/utils/Math.h" using namespace blobstore::onblocks::utils; using ::testing::Test; class IntPowTest: public Test {}; TEST_F(IntPowTest, ExponentAndBaseAreZero) { EXPECT_EQ(1, intPow(0, 0)); } TEST_F(IntPowTest, ExponentIsZero1) { EXPECT_EQ(1, intPow(1, 0)); } TEST_F(IntPowTest, ExponentIsZero2) { EXPECT_EQ(1, intPow(1000, 0)); } TEST_F(IntPowTest, BaseIsZero1) { EXPECT_EQ(0, intPow(0, 1)); } TEST_F(IntPowTest, BaseIsZero2) { EXPECT_EQ(0, intPow(0, 1000)); } TEST_F(IntPowTest, ExponentIsOne1) { EXPECT_EQ(0, intPow(0, 1)); } TEST_F(IntPowTest, ExponentIsOne2) { EXPECT_EQ(2, intPow(2, 1)); } TEST_F(IntPowTest, ExponentIsOne3) { EXPECT_EQ(1000, intPow(1000, 1)); } TEST_F(IntPowTest, BaseIsTwo1) { EXPECT_EQ(1024, intPow(2, 10)); } TEST_F(IntPowTest, BaseIsTwo2) { EXPECT_EQ(1024*1024, intPow(2, 20)); } TEST_F(IntPowTest, BaseIsTwo3) { EXPECT_EQ(1024*1024*1024, intPow(2, 30)); } TEST_F(IntPowTest, BaseIsTen1) { EXPECT_EQ(100, intPow(10, 2)); } TEST_F(IntPowTest, BaseIsTen2) { EXPECT_EQ(1000000, intPow(10, 6)); } TEST_F(IntPowTest, ArbitraryNumbers1) { EXPECT_EQ(4096, intPow(4, 6)); } TEST_F(IntPowTest, ArbitraryNumbers2) { EXPECT_EQ(1296, intPow(6, 4)); } TEST_F(IntPowTest, ArbitraryNumbers3) { EXPECT_EQ(282475249, intPow(7, 10)); } TEST_F(IntPowTest, 64bit) { uint64_t value = UINT64_C(1024)*1024*1024*1024; EXPECT_GT(value, std::numeric_limits::max()); EXPECT_EQ(value*value*value, intPow(value, UINT64_C(3))); }test/blobstore/implementations/onblocks/utils/MaxZeroSubtractionTest.cpp000066400000000000000000000054141347701267100273750ustar00rootroot00000000000000#include #include "blobstore/implementations/onblocks/utils/Math.h" #include using namespace blobstore::onblocks::utils; using ::testing::Test; using std::numeric_limits; class MaxZeroSubtractionTest: public Test {}; TEST_F(MaxZeroSubtractionTest, SubtractToZero1) { EXPECT_EQ(0, maxZeroSubtraction(0, 0)); } TEST_F(MaxZeroSubtractionTest, SubtractToZero2) { EXPECT_EQ(0, maxZeroSubtraction(5, 5)); } TEST_F(MaxZeroSubtractionTest, SubtractToZero3) { EXPECT_EQ(0, maxZeroSubtraction(184930, 184930)); } TEST_F(MaxZeroSubtractionTest, SubtractToZero4) { EXPECT_EQ(0u, maxZeroSubtraction(numeric_limits::max()-1, numeric_limits::max()-1)); } TEST_F(MaxZeroSubtractionTest, SubtractToZero5) { EXPECT_EQ(0u, maxZeroSubtraction(numeric_limits::max(), numeric_limits::max())); } TEST_F(MaxZeroSubtractionTest, SubtractPositive1) { EXPECT_EQ(1, maxZeroSubtraction(5, 4)); } TEST_F(MaxZeroSubtractionTest, SubtractPositive2) { EXPECT_EQ(181081, maxZeroSubtraction(184930, 3849)); } TEST_F(MaxZeroSubtractionTest, SubtractPositive3) { EXPECT_EQ(numeric_limits::max()-1, maxZeroSubtraction(numeric_limits::max(), UINT32_C(1))); } TEST_F(MaxZeroSubtractionTest, SubtractPositive4) { EXPECT_EQ(5u, maxZeroSubtraction(numeric_limits::max(), numeric_limits::max()-5)); } TEST_F(MaxZeroSubtractionTest, SubtractNegative1) { EXPECT_EQ(0, maxZeroSubtraction(4, 5)); } TEST_F(MaxZeroSubtractionTest, SubtractNegative2) { EXPECT_EQ(0, maxZeroSubtraction(3849, 184930)); } TEST_F(MaxZeroSubtractionTest, SubtractNegative3) { EXPECT_EQ(0u, maxZeroSubtraction(numeric_limits::max()-1, numeric_limits::max())); } TEST_F(MaxZeroSubtractionTest, SubtractNegative4) { EXPECT_EQ(0u, maxZeroSubtraction(numeric_limits::max()-5, numeric_limits::max())); } TEST_F(MaxZeroSubtractionTest, SubtractNegative5) { EXPECT_EQ(0u, maxZeroSubtraction(UINT32_C(5), numeric_limits::max())); } TEST_F(MaxZeroSubtractionTest, SubtractFromZero1) { EXPECT_EQ(0, maxZeroSubtraction(0, 1)); } TEST_F(MaxZeroSubtractionTest, SubtractFromZero2) { EXPECT_EQ(0, maxZeroSubtraction(0, 184930)); } TEST_F(MaxZeroSubtractionTest, SubtractFromZero3) { EXPECT_EQ(0u, maxZeroSubtraction(UINT32_C(0), numeric_limits::max())); } TEST_F(MaxZeroSubtractionTest, 64bit_valid) { uint64_t value = UINT64_C(1024)*1024*1024*1024; EXPECT_GT(value, std::numeric_limits::max()); EXPECT_EQ(value*1024-value, maxZeroSubtraction(value*1024, value)); } TEST_F(MaxZeroSubtractionTest, 64bit_zero) { uint64_t value = UINT64_C(1024)*1024*1024*1024; EXPECT_GT(value, std::numeric_limits::max()); EXPECT_EQ(0u, maxZeroSubtraction(value, value*1024)); } test/blockstore/000077500000000000000000000000001347701267100142145ustar00rootroot00000000000000test/blockstore/CMakeLists.txt000066400000000000000000000050061347701267100167550ustar00rootroot00000000000000project (blockstore-test) set(SOURCES utils/BlockStoreUtilsTest.cpp interface/BlockStoreTest.cpp interface/BlockStore2Test.cpp interface/BlockTest.cpp implementations/testfake/TestFakeBlockStoreTest.cpp implementations/mock/MockBlockStoreTest.cpp implementations/inmemory/InMemoryBlockStoreTest.cpp implementations/parallelaccess/ParallelAccessBlockStoreTest_Generic.cpp implementations/parallelaccess/ParallelAccessBlockStoreTest_Specific.cpp implementations/compressing/CompressingBlockStoreTest.cpp implementations/compressing/compressors/testutils/CompressorTest.cpp implementations/encrypted/EncryptedBlockStoreTest_Generic.cpp implementations/encrypted/EncryptedBlockStoreTest_Specific.cpp implementations/ondisk/OnDiskBlockStoreTest_Generic.cpp implementations/ondisk/OnDiskBlockStoreTest_Specific.cpp implementations/ondisk/OnDiskBlockTest/OnDiskBlockCreateTest.cpp implementations/ondisk/OnDiskBlockTest/OnDiskBlockFlushTest.cpp implementations/ondisk/OnDiskBlockTest/OnDiskBlockLoadTest.cpp implementations/caching/CachingBlockStore2Test_Generic.cpp implementations/caching/CachingBlockStore2Test_Specific.cpp implementations/caching/cache/QueueMapTest_Values.cpp implementations/caching/cache/testutils/MinimalKeyType.cpp implementations/caching/cache/testutils/CopyableMovableValueType.cpp implementations/caching/cache/testutils/MinimalValueType.cpp implementations/caching/cache/testutils/QueueMapTest.cpp implementations/caching/cache/testutils/CacheTest.cpp implementations/caching/cache/QueueMapTest_Size.cpp implementations/caching/cache/CacheTest_MoveConstructor.cpp implementations/caching/cache/CacheTest_PushAndPop.cpp implementations/caching/cache/QueueMapTest_MoveConstructor.cpp implementations/caching/cache/QueueMapTest_MemoryLeak.cpp implementations/caching/cache/CacheTest_RaceCondition.cpp implementations/caching/cache/PeriodicTaskTest.cpp implementations/caching/cache/QueueMapTest_Peek.cpp implementations/integrity/KnownBlockVersionsTest.cpp implementations/integrity/IntegrityBlockStoreTest_Generic.cpp implementations/integrity/IntegrityBlockStoreTest_Specific.cpp implementations/low2highlevel/LowToHighLevelBlockStoreTest.cpp ) add_executable(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} my-gtest-main googletest blockstore) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) test/blockstore/implementations/000077500000000000000000000000001347701267100174245ustar00rootroot00000000000000test/blockstore/implementations/caching/000077500000000000000000000000001347701267100210205ustar00rootroot00000000000000test/blockstore/implementations/caching/CachingBlockStore2Test_Generic.cpp000066400000000000000000000024241347701267100274300ustar00rootroot00000000000000#include #include "blockstore/implementations/caching/CachingBlockStore2.h" #include "blockstore/implementations/inmemory/InMemoryBlockStore2.h" #include "../../testutils/BlockStoreTest.h" #include "../../testutils/BlockStore2Test.h" #include using blockstore::BlockStore; using blockstore::BlockStore2; using blockstore::caching::CachingBlockStore2; using blockstore::lowtohighlevel::LowToHighLevelBlockStore; using blockstore::inmemory::InMemoryBlockStore2; using cpputils::make_unique_ref; using cpputils::unique_ref; class CachingBlockStoreTestFixture: public BlockStoreTestFixture { public: unique_ref createBlockStore() override { return make_unique_ref( make_unique_ref(make_unique_ref()) ); } }; INSTANTIATE_TYPED_TEST_CASE_P(Caching2, BlockStoreTest, CachingBlockStoreTestFixture); class CachingBlockStore2TestFixture: public BlockStore2TestFixture { public: unique_ref createBlockStore() override { return make_unique_ref(make_unique_ref()); } }; INSTANTIATE_TYPED_TEST_CASE_P(Caching, BlockStore2Test, CachingBlockStore2TestFixture); test/blockstore/implementations/caching/CachingBlockStore2Test_Specific.cpp000066400000000000000000000042431347701267100276020ustar00rootroot00000000000000#include #include "blockstore/implementations/caching/CachingBlockStore2.h" #include "blockstore/implementations/inmemory/InMemoryBlockStore2.h" using ::testing::Test; using cpputils::Data; using blockstore::inmemory::InMemoryBlockStore2; using namespace blockstore::caching; class CachingBlockStore2Test: public Test { public: CachingBlockStore2Test(): baseBlockStore(new InMemoryBlockStore2), blockStore(std::move(cpputils::nullcheck(std::unique_ptr(baseBlockStore)).value())) { } InMemoryBlockStore2 *baseBlockStore; CachingBlockStore2 blockStore; }; TEST_F(CachingBlockStore2Test, PhysicalBlockSize_zerophysical) { EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(0)); } TEST_F(CachingBlockStore2Test, PhysicalBlockSize_zerovirtual) { auto blockId = blockStore.create(Data(0)); blockStore.flush(); auto base = baseBlockStore->load(blockId).value(); EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(base.size())); } TEST_F(CachingBlockStore2Test, PhysicalBlockSize_negativeboundaries) { // This tests that a potential if/else in blockSizeFromPhysicalBlockSize that catches negative values has the // correct boundary set. We test the highest value that is negative and the smallest value that is positive. auto blockId = blockStore.create(Data(0)); blockStore.flush(); auto physicalSizeForVirtualSizeZero = baseBlockStore->load(blockId).value().size(); if (physicalSizeForVirtualSizeZero > 0) { EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero - 1)); } EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero)); EXPECT_EQ(1u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero + 1)); } TEST_F(CachingBlockStore2Test, PhysicalBlockSize_positive) { auto blockId = blockStore.create(Data(10*1024u)); blockStore.flush(); auto base = baseBlockStore->load(blockId).value(); EXPECT_EQ(10*1024u, blockStore.blockSizeFromPhysicalBlockSize(base.size())); } // TODO Add test cases that flushing the block store doesn't destroy things (i.e. all test cases from BlockStoreTest, but with flushes inbetween) test/blockstore/implementations/caching/cache/000077500000000000000000000000001347701267100220635ustar00rootroot00000000000000test/blockstore/implementations/caching/cache/CacheTest_MoveConstructor.cpp000066400000000000000000000026341347701267100276730ustar00rootroot00000000000000#include #include #include "blockstore/implementations/caching/cache/Cache.h" #include "testutils/MinimalKeyType.h" #include "testutils/CopyableMovableValueType.h" using namespace blockstore::caching; using cpputils::unique_ref; using cpputils::make_unique_ref; using ::testing::Test; //Test that Cache uses a move constructor for Value if possible class CacheTest_MoveConstructor: public Test { public: CacheTest_MoveConstructor(): cache(make_unique_ref>("test")) { CopyableMovableValueType::numCopyConstructorCalled = 0; } unique_ref> cache; }; TEST_F(CacheTest_MoveConstructor, MoveIntoCache) { cache->push(MinimalKeyType::create(0), CopyableMovableValueType(2)); CopyableMovableValueType val = cache->pop(MinimalKeyType::create(0)).value(); val.value(); //Access it to avoid the compiler optimizing the assignment away EXPECT_EQ(0, CopyableMovableValueType::numCopyConstructorCalled); } TEST_F(CacheTest_MoveConstructor, CopyIntoCache) { CopyableMovableValueType value(2); cache->push(MinimalKeyType::create(0), value); CopyableMovableValueType val = cache->pop(MinimalKeyType::create(0)).value(); val.value(); //Access it to avoid the compiler optimizing the assignment away EXPECT_EQ(1, CopyableMovableValueType::numCopyConstructorCalled); } test/blockstore/implementations/caching/cache/CacheTest_PushAndPop.cpp000066400000000000000000000072541347701267100265430ustar00rootroot00000000000000#include "testutils/CacheTest.h" #include "blockstore/implementations/caching/cache/Cache.h" #include "testutils/MinimalKeyType.h" #include "testutils/MinimalValueType.h" #include using namespace blockstore::caching; class CacheTest_PushAndPop: public CacheTest {}; TEST_F(CacheTest_PushAndPop, PopNonExistingEntry_EmptyCache) { EXPECT_EQ(boost::none, pop(10)); } TEST_F(CacheTest_PushAndPop, PopNonExistingEntry_NonEmptyCache) { push(9, 10); EXPECT_EQ(boost::none, pop(10)); } TEST_F(CacheTest_PushAndPop, PopNonExistingEntry_FullCache) { //Add a lot of even numbered keys for (int i = 0; i < static_cast(MAX_ENTRIES); ++i) { push(2*i, 2*i); } //Request an odd numbered key EXPECT_EQ(boost::none, pop(9)); } TEST_F(CacheTest_PushAndPop, OneEntry) { push(10, 20); EXPECT_EQ(20, pop(10).value()); } TEST_F(CacheTest_PushAndPop, MultipleEntries) { push(10, 20); push(20, 30); push(30, 40); EXPECT_EQ(30, pop(20).value()); EXPECT_EQ(20, pop(10).value()); EXPECT_EQ(40, pop(30).value()); } TEST_F(CacheTest_PushAndPop, FullCache) { for(int i = 0; i < static_cast(MAX_ENTRIES); ++i) { push(i, 2*i); } for(int i = 0; i < static_cast(MAX_ENTRIES); ++i) { EXPECT_EQ(2*i, pop(i).value()); } } TEST_F(CacheTest_PushAndPop, FullCache_PushNonOrdered_PopOrdered) { for(int i = 1; i < static_cast(MAX_ENTRIES); i += 2) { push(i, 2*i); } for(int i = 0; i < static_cast(MAX_ENTRIES); i += 2) { push(i, 2*i); } for(int i = 0; i < static_cast(MAX_ENTRIES); ++i) { EXPECT_EQ(2*i, pop(i).value()); } } TEST_F(CacheTest_PushAndPop, FullCache_PushOrdered_PopNonOrdered) { for(int i = 0; i < static_cast(MAX_ENTRIES); ++i) { push(i, 2*i); } for(int i = 1; i < static_cast(MAX_ENTRIES); i += 2) { EXPECT_EQ(2*i, pop(i).value()); } for(int i = 0; i < static_cast(MAX_ENTRIES); i += 2) { EXPECT_EQ(2*i, pop(i).value()); } } int roundDownToEven(int number) { if (number % 2 == 0) { return number; } else { return number - 1; } } int roundDownToOdd(int number) { if (number % 2 != 0) { return number; } else { return number - 1; } } TEST_F(CacheTest_PushAndPop, FullCache_PushNonOrdered_PopNonOrdered) { for(int i = roundDownToEven(MAX_ENTRIES - 1); i >= 0; i -= 2) { push(i, 2*i); } for(int i = 1; i < static_cast(MAX_ENTRIES); i += 2) { push(i, 2*i); } for(int i = roundDownToOdd(MAX_ENTRIES-1); i >= 0; i -= 2) { EXPECT_EQ(2*i, pop(i).value()); } for(int i = 0; i < static_cast(MAX_ENTRIES); i += 2) { EXPECT_EQ(2*i, pop(i).value()); } } TEST_F(CacheTest_PushAndPop, MoreThanFullCache) { for(int i = 0; i < static_cast(MAX_ENTRIES + 2); ++i) { push(i, 2*i); } //Check that the oldest two elements got deleted automatically EXPECT_EQ(boost::none, pop(0)); EXPECT_EQ(boost::none, pop(1)); //Check the other elements are still there for(int i = 2; i < static_cast(MAX_ENTRIES + 2); ++i) { EXPECT_EQ(2*i, pop(i).value()); } } TEST_F(CacheTest_PushAndPop, AfterTimeout) { constexpr double TIMEOUT1_SEC = Cache::MAX_LIFETIME_SEC * 3/4; constexpr double TIMEOUT2_SEC = Cache::PURGE_LIFETIME_SEC * 3/4; static_assert(TIMEOUT1_SEC + TIMEOUT2_SEC > Cache::MAX_LIFETIME_SEC, "Ensure that our chosen timeouts push the first entry out of the cache"); push(10, 20); boost::this_thread::sleep_for(boost::chrono::milliseconds(static_cast(1000 * TIMEOUT1_SEC))); push(20, 30); boost::this_thread::sleep_for(boost::chrono::milliseconds(static_cast(1000 * TIMEOUT2_SEC))); EXPECT_EQ(boost::none, pop(10)); EXPECT_EQ(30, pop(20).value()); } test/blockstore/implementations/caching/cache/CacheTest_RaceCondition.cpp000066400000000000000000000071671347701267100272460ustar00rootroot00000000000000#include "testutils/CacheTest.h" #include #include #include #include #include using namespace blockstore::caching; using std::chrono::seconds; using std::string; using cpputils::ConditionBarrier; using std::unique_ptr; using std::make_unique; using std::future; // Regression tests for a race condition. // An element could be in the process of being thrown out of the cache and while the destructor is running, another // thread calls pop() for the element and gets none returned. But since the destructor isn't finished yet, the data from // the cache element also isn't completely written back yet and an application loading it runs into a race condition. class ObjectWithLongDestructor { public: ObjectWithLongDestructor(ConditionBarrier *onDestructorStarted, std::atomic *destructorFinished) : _onDestructorStarted(onDestructorStarted), _destructorFinished(destructorFinished) {} ~ObjectWithLongDestructor() { _onDestructorStarted->release(); std::this_thread::sleep_for(seconds(1)); *_destructorFinished = true; } private: ConditionBarrier *_onDestructorStarted; std::atomic *_destructorFinished; DISALLOW_COPY_AND_ASSIGN(ObjectWithLongDestructor); }; class CacheTest_RaceCondition: public ::testing::Test { public: CacheTest_RaceCondition(): cache("test"), destructorStarted(), destructorFinished(false) {} static constexpr unsigned int MAX_ENTRIES = 100; Cache, MAX_ENTRIES> cache; ConditionBarrier destructorStarted; std::atomic destructorFinished; int pushObjectWithLongDestructor() { cache.push(2, make_unique(&destructorStarted, &destructorFinished)); return 2; } int pushDummyObject() { cache.push(3, nullptr); return 3; } future causeCacheOverflowInOtherThread() { //Add maximum+1 element in another thread (this causes the cache to flush the first element in another thread) return std::async(std::launch::async, [this] { for(unsigned int i = 0; i < MAX_ENTRIES+1; ++i) { cache.push(MAX_ENTRIES+i, nullptr); } }); } void EXPECT_POP_BLOCKS_UNTIL_DESTRUCTOR_FINISHED(int key) { EXPECT_FALSE(destructorFinished); cache.pop(key); EXPECT_TRUE(destructorFinished); } void EXPECT_POP_DOESNT_BLOCK_UNTIL_DESTRUCTOR_FINISHED(int key) { EXPECT_FALSE(destructorFinished); cache.pop(key); EXPECT_FALSE(destructorFinished); } }; TEST_F(CacheTest_RaceCondition, PopBlocksWhileRequestedElementIsThrownOut_ByAge) { auto id = pushObjectWithLongDestructor(); destructorStarted.wait(); EXPECT_POP_BLOCKS_UNTIL_DESTRUCTOR_FINISHED(id); } TEST_F(CacheTest_RaceCondition, PopDoesntBlockWhileOtherElementIsThrownOut_ByAge) { pushObjectWithLongDestructor(); auto id = pushDummyObject(); destructorStarted.wait(); EXPECT_POP_DOESNT_BLOCK_UNTIL_DESTRUCTOR_FINISHED(id); } TEST_F(CacheTest_RaceCondition, PopBlocksWhileRequestedElementIsThrownOut_ByPush) { auto id = pushObjectWithLongDestructor(); auto future = causeCacheOverflowInOtherThread(); destructorStarted.wait(); EXPECT_POP_BLOCKS_UNTIL_DESTRUCTOR_FINISHED(id); } TEST_F(CacheTest_RaceCondition, PopDoesntBlockWhileOtherElementIsThrownOut_ByPush) { pushObjectWithLongDestructor(); auto id = pushDummyObject(); auto future = causeCacheOverflowInOtherThread(); destructorStarted.wait(); EXPECT_POP_DOESNT_BLOCK_UNTIL_DESTRUCTOR_FINISHED(id); } test/blockstore/implementations/caching/cache/PeriodicTaskTest.cpp000066400000000000000000000025701347701267100260140ustar00rootroot00000000000000#include #include "blockstore/implementations/caching/cache/PeriodicTask.h" #include #include #include using ::testing::Test; using std::mutex; using std::unique_lock; using std::condition_variable; using namespace blockstore::caching; class AtomicCounter { public: AtomicCounter(int count): _mutex(), _cv(), _counter(count) {} void decrease() { unique_lock lock(_mutex); --_counter; _cv.notify_all(); } void waitForZero() { unique_lock lock(_mutex); _cv.wait(lock, [this] () {return _counter <= 0;}); } private: mutex _mutex; condition_variable _cv; int _counter; }; class PeriodicTaskTest: public Test { }; TEST_F(PeriodicTaskTest, DoesntDeadlockInDestructorWhenDestructedImmediately) { PeriodicTask task([](){}, 1, "test"); } TEST_F(PeriodicTaskTest, CallsCallbackAtLeast10Times) { AtomicCounter counter(10); PeriodicTask task([&counter](){ counter.decrease(); }, 0.001, "test"); counter.waitForZero(); } TEST_F(PeriodicTaskTest, DoesntCallCallbackAfterDestruction) { std::atomic callCount(0); { PeriodicTask task([&callCount](){ callCount += 1; }, 0.001, "test"); } int callCountDirectlyAfterDestruction = callCount; boost::this_thread::sleep_for(boost::chrono::seconds(1)); EXPECT_EQ(callCountDirectlyAfterDestruction, callCount); } test/blockstore/implementations/caching/cache/QueueMapTest_MemoryLeak.cpp000066400000000000000000000034571347701267100273070ustar00rootroot00000000000000#include "testutils/QueueMapTest.h" // Tests that QueueMap calls destructors correctly. // This is needed, because QueueMap does its own memory management. class QueueMapTest_MemoryLeak: public QueueMapTest { public: void EXPECT_NUM_INSTANCES(int num) { EXPECT_EQ(num, MinimalKeyType::instances); EXPECT_EQ(num, MinimalValueType::instances); } }; TEST_F(QueueMapTest_MemoryLeak, Empty) { EXPECT_NUM_INSTANCES(0); } TEST_F(QueueMapTest_MemoryLeak, AfterPushingOne) { push(2, 3); EXPECT_NUM_INSTANCES(1); } TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwo) { push(2, 3); push(3, 4); EXPECT_NUM_INSTANCES(2); } TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwoAndPoppingOldest) { push(2, 3); push(3, 4); pop(); EXPECT_NUM_INSTANCES(1); } TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwoAndPoppingFirst) { push(2, 3); push(3, 4); pop(2); EXPECT_NUM_INSTANCES(1); } TEST_F(QueueMapTest_MemoryLeak, AfterPushingTwoAndPoppingLast) { push(2, 3); push(3, 4); pop(3); EXPECT_NUM_INSTANCES(1); } TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOne) { push(2, 3); pop(); EXPECT_NUM_INSTANCES(0); } TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePerKey) { push(2, 3); pop(2); EXPECT_NUM_INSTANCES(0); } TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePushingOne) { push(2, 3); pop(); push(3, 4); EXPECT_NUM_INSTANCES(1); } TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePerKeyPushingOne) { push(2, 3); pop(2); push(3, 4); EXPECT_NUM_INSTANCES(1); } TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePushingSame) { push(2, 3); pop(); push(2, 3); EXPECT_NUM_INSTANCES(1); } TEST_F(QueueMapTest_MemoryLeak, AfterPushingOnePoppingOnePerKeyPushingSame) { push(2, 3); pop(2); push(2, 3); EXPECT_NUM_INSTANCES(1); } test/blockstore/implementations/caching/cache/QueueMapTest_MoveConstructor.cpp000066400000000000000000000042011347701267100304020ustar00rootroot00000000000000#include #include #include "blockstore/implementations/caching/cache/QueueMap.h" #include "testutils/MinimalKeyType.h" #include "testutils/CopyableMovableValueType.h" using namespace blockstore::caching; using ::testing::Test; using cpputils::unique_ref; using cpputils::make_unique_ref; //Test that QueueMap uses a move constructor for Value if possible class QueueMapTest_MoveConstructor: public Test { public: QueueMapTest_MoveConstructor(): map(make_unique_ref>()) { CopyableMovableValueType::numCopyConstructorCalled = 0; } unique_ref> map; }; TEST_F(QueueMapTest_MoveConstructor, PushingAndPopping_MoveIntoMap) { map->push(MinimalKeyType::create(0), CopyableMovableValueType(2)); CopyableMovableValueType val = map->pop().value(); val.value(); //Access it to avoid the compiler optimizing the assignment away EXPECT_EQ(0, CopyableMovableValueType::numCopyConstructorCalled); } TEST_F(QueueMapTest_MoveConstructor, PushingAndPoppingPerKey_MoveIntoMap) { map->push(MinimalKeyType::create(0), CopyableMovableValueType(2)); CopyableMovableValueType val = map->pop(MinimalKeyType::create(0)).value(); val.value(); //Access it to avoid the compiler optimizing the assignment away EXPECT_EQ(0, CopyableMovableValueType::numCopyConstructorCalled); } TEST_F(QueueMapTest_MoveConstructor, PushingAndPopping_CopyIntoMap) { CopyableMovableValueType value(2); map->push(MinimalKeyType::create(0), value); CopyableMovableValueType val = map->pop().value(); val.value(); //Access it to avoid the compiler optimizing the assignment away EXPECT_EQ(1, CopyableMovableValueType::numCopyConstructorCalled); } TEST_F(QueueMapTest_MoveConstructor, PushingAndPoppingPerKey_CopyIntoMap) { CopyableMovableValueType value(2); map->push(MinimalKeyType::create(0), value); CopyableMovableValueType val = map->pop(MinimalKeyType::create(0)).value(); val.value(); //Access it to avoid the compiler optimizing the assignment away EXPECT_EQ(1, CopyableMovableValueType::numCopyConstructorCalled); } test/blockstore/implementations/caching/cache/QueueMapTest_Peek.cpp000066400000000000000000000014261347701267100261200ustar00rootroot00000000000000#include "testutils/QueueMapTest.h" #include class QueueMapPeekTest: public QueueMapTest {}; TEST_F(QueueMapPeekTest, PoppingFromEmpty) { EXPECT_EQ(boost::none, peek()); } TEST_F(QueueMapPeekTest, PushingOne) { push(3, 2); EXPECT_EQ(2, peek().value()); } TEST_F(QueueMapPeekTest, PushingTwo) { push(2, 3); push(3, 4); EXPECT_EQ(3, peek().value()); EXPECT_EQ(3, peek().value()); EXPECT_EQ(3, pop().value()); EXPECT_EQ(4, peek().value()); EXPECT_EQ(4, peek().value()); EXPECT_EQ(4, pop().value()); EXPECT_EQ(boost::none, peek()); EXPECT_EQ(boost::none, pop()); } TEST_F(QueueMapPeekTest, AfterPushingTwoAndPoppingFirst) { push(2, 3); push(3, 4); pop(2); EXPECT_EQ(boost::none, pop(2)); EXPECT_EQ(4, peek().value()); } test/blockstore/implementations/caching/cache/QueueMapTest_Size.cpp000066400000000000000000000026611347701267100261500ustar00rootroot00000000000000#include "testutils/QueueMapTest.h" class QueueMapTest_Size: public QueueMapTest {}; TEST_F(QueueMapTest_Size, Empty) { EXPECT_EQ(0, size()); } TEST_F(QueueMapTest_Size, AfterPushingOne) { push(2, 3); EXPECT_EQ(1, size()); } TEST_F(QueueMapTest_Size, AfterPushingTwo) { push(2, 3); push(3, 4); EXPECT_EQ(2, size()); } TEST_F(QueueMapTest_Size, AfterPushingTwoAndPoppingOldest) { push(2, 3); push(3, 4); pop(); EXPECT_EQ(1, size()); } TEST_F(QueueMapTest_Size, AfterPushingTwoAndPoppingFirst) { push(2, 3); push(3, 4); pop(2); EXPECT_EQ(1, size()); } TEST_F(QueueMapTest_Size, AfterPushingTwoAndPoppingLast) { push(2, 3); push(3, 4); pop(3); EXPECT_EQ(1, size()); } TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOne) { push(2, 3); pop(); EXPECT_EQ(0, size()); } TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePerKey) { push(2, 3); pop(2); EXPECT_EQ(0, size()); } TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePushingOne) { push(2, 3); pop(); push(3, 4); EXPECT_EQ(1, size()); } TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePerKeyPushingOne) { push(2, 3); pop(2); push(3, 4); EXPECT_EQ(1, size()); } TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePushingSame) { push(2, 3); pop(); push(2, 3); EXPECT_EQ(1, size()); } TEST_F(QueueMapTest_Size, AfterPushingOnePoppingOnePerKeyPushingSame) { push(2, 3); pop(2); push(2, 3); EXPECT_EQ(1, size()); } test/blockstore/implementations/caching/cache/QueueMapTest_Values.cpp000066400000000000000000000070361347701267100264760ustar00rootroot00000000000000#include "testutils/QueueMapTest.h" #include class QueueMapTest_Values: public QueueMapTest {}; TEST_F(QueueMapTest_Values, PoppingFromEmpty) { EXPECT_EQ(boost::none, pop()); } TEST_F(QueueMapTest_Values, PoppingFromEmptyPerKey) { EXPECT_EQ(boost::none, pop(2)); } TEST_F(QueueMapTest_Values, PoppingNonexistingPerKey) { push(3, 2); EXPECT_EQ(boost::none, pop(2)); } TEST_F(QueueMapTest_Values, PushingOne) { push(3, 2); EXPECT_EQ(2, pop(3).value()); EXPECT_EQ(boost::none, pop()); } TEST_F(QueueMapTest_Values, PushingTwo) { push(2, 3); push(3, 4); EXPECT_EQ(3, pop().value()); EXPECT_EQ(4, pop().value()); EXPECT_EQ(boost::none, pop()); } TEST_F(QueueMapTest_Values, AfterPushingTwoAndPoppingFirst) { push(2, 3); push(3, 4); pop(2); EXPECT_EQ(boost::none, pop(2)); EXPECT_EQ(4, pop(3).value()); EXPECT_EQ(boost::none, pop()); } TEST_F(QueueMapTest_Values, AfterPushingTwoAndPoppingLast) { push(2, 3); push(3, 4); pop(3); EXPECT_EQ(boost::none, pop(3)); EXPECT_EQ(3, pop(2).value()); EXPECT_EQ(boost::none, pop()); } TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOne) { push(2, 3); pop(); EXPECT_EQ(boost::none, pop()); EXPECT_EQ(boost::none, pop(2)); } TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOnePerKey) { push(2, 3); pop(2); EXPECT_EQ(boost::none, pop()); EXPECT_EQ(boost::none, pop(2)); } TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOnePushingOne) { push(2, 3); pop(); push(3, 4); EXPECT_EQ(boost::none, pop(2)); EXPECT_EQ(4, pop(3).value()); EXPECT_EQ(boost::none, pop()); } TEST_F(QueueMapTest_Values, AfterPushingOnePoppingOnePerKeyPushingOne) { push(2, 3); pop(2); push(3, 4); EXPECT_EQ(boost::none, pop(2)); EXPECT_EQ(4, pop(3).value()); EXPECT_EQ(boost::none, pop()); } TEST_F(QueueMapTest_Values, PushingSomePoppingMiddlePerKey) { push(1, 2); push(2, 3); push(3, 4); push(4, 5); push(5, 6); EXPECT_EQ(3, pop(2).value()); EXPECT_EQ(5, pop(4).value()); EXPECT_EQ(2, pop().value()); EXPECT_EQ(4, pop().value()); EXPECT_EQ(6, pop().value()); EXPECT_EQ(boost::none, pop()); } TEST_F(QueueMapTest_Values, PushingSomePoppingFirstPerKey) { push(1, 2); push(2, 3); push(3, 4); push(4, 5); push(5, 6); EXPECT_EQ(2, pop(1).value()); EXPECT_EQ(3, pop(2).value()); EXPECT_EQ(4, pop().value()); EXPECT_EQ(5, pop().value()); EXPECT_EQ(6, pop().value()); EXPECT_EQ(boost::none, pop()); } TEST_F(QueueMapTest_Values, PushingSomePoppingLastPerKey) { push(1, 2); push(2, 3); push(3, 4); push(4, 5); push(5, 6); EXPECT_EQ(6, pop(5).value()); EXPECT_EQ(5, pop(4).value()); EXPECT_EQ(2, pop().value()); EXPECT_EQ(3, pop().value()); EXPECT_EQ(4, pop().value()); EXPECT_EQ(boost::none, pop()); } //This test forces the underlying datastructure (std::map or std::unordered_map) to grow and reallocate memory. //So it tests, that QueueMap still works after reallocating memory. TEST_F(QueueMapTest_Values, ManyValues) { //Push 1 million entries for (int i = 0; i < 1000000; ++i) { push(i, 2*i); } //pop every other one by key for (int i = 0; i < 1000000; i += 2) { EXPECT_EQ(2*i, pop(i).value()); } //pop the rest in queue order for (int i = 1; i < 1000000; i += 2) { EXPECT_EQ(2*i, peek().value()); EXPECT_EQ(2*i, pop().value()); } EXPECT_EQ(0, size()); EXPECT_EQ(boost::none, pop()); EXPECT_EQ(boost::none, peek()); } TEST_F(QueueMapTest_Values, PushAlreadyExistingValue) { push(2, 3); EXPECT_ANY_THROW( push(2, 4); ); } test/blockstore/implementations/caching/cache/testutils/000077500000000000000000000000001347701267100241235ustar00rootroot00000000000000test/blockstore/implementations/caching/cache/testutils/CacheTest.cpp000066400000000000000000000005451347701267100264760ustar00rootroot00000000000000#include "CacheTest.h" void CacheTest::push(int key, int value) { return _cache.push(MinimalKeyType::create(key), MinimalValueType::create(value)); } boost::optional CacheTest::pop(int key) { boost::optional entry = _cache.pop(MinimalKeyType::create(key)); if (!entry) { return boost::none; } return entry->value(); } test/blockstore/implementations/caching/cache/testutils/CacheTest.h000066400000000000000000000020101347701267100261300ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_ #define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_ #include #include "blockstore/implementations/caching/cache/Cache.h" #include "MinimalKeyType.h" #include "MinimalValueType.h" #include // This class is a parent class for tests on QueueMap. // It offers functions to work with a QueueMap test object which is built using types having only the minimal type requirements. // Furthermore, the class checks that there are no memory leaks left after destructing the QueueMap (by counting leftover instances of Keys/Values). class CacheTest: public ::testing::Test { public: CacheTest(): _cache("test") {} void push(int key, int value); boost::optional pop(int key); static constexpr unsigned int MAX_ENTRIES = 100; using Cache = blockstore::caching::Cache; private: Cache _cache; }; #endif test/blockstore/implementations/caching/cache/testutils/CopyableMovableValueType.cpp000066400000000000000000000001431347701267100315300ustar00rootroot00000000000000#include "CopyableMovableValueType.h" int CopyableMovableValueType::numCopyConstructorCalled = 0; test/blockstore/implementations/caching/cache/testutils/CopyableMovableValueType.h000066400000000000000000000020421347701267100311750ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_COPYABLEMOVABLEVALUETYPE_H_ #define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_COPYABLEMOVABLEVALUETYPE_H_ class CopyableMovableValueType { public: static int numCopyConstructorCalled; CopyableMovableValueType(int value): _value(value) {} CopyableMovableValueType(const CopyableMovableValueType &rhs): CopyableMovableValueType(rhs._value) { ++numCopyConstructorCalled; } CopyableMovableValueType &operator=(const CopyableMovableValueType &rhs) { _value = rhs._value; ++numCopyConstructorCalled; return *this; } CopyableMovableValueType(CopyableMovableValueType &&rhs) noexcept: CopyableMovableValueType(rhs._value) { //Don't increase numCopyConstructorCalled } CopyableMovableValueType &operator=(CopyableMovableValueType &&rhs) noexcept { //Don't increase numCopyConstructorCalled _value = rhs._value; return *this; } int value() const { return _value; } private: int _value; }; #endif test/blockstore/implementations/caching/cache/testutils/MinimalKeyType.cpp000066400000000000000000000001141347701267100275240ustar00rootroot00000000000000#include "MinimalKeyType.h" std::atomic MinimalKeyType::instances(0); test/blockstore/implementations/caching/cache/testutils/MinimalKeyType.h000066400000000000000000000017171347701267100272030ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALKEYTYPE_H_ #define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALKEYTYPE_H_ #include #include // This is a not-default-constructible Key type class MinimalKeyType { public: static std::atomic instances; static MinimalKeyType create(int value) { return MinimalKeyType(value); } MinimalKeyType(const MinimalKeyType &rhs): MinimalKeyType(rhs.value()) { } ~MinimalKeyType() { --instances; } int value() const { return _value; } private: MinimalKeyType(int value): _value(value) { ++instances; } int _value; }; namespace std { template <> struct hash { size_t operator()(const MinimalKeyType &obj) const { return obj.value(); } }; } inline bool operator==(const MinimalKeyType &lhs, const MinimalKeyType &rhs) { return lhs.value() == rhs.value(); } #endif test/blockstore/implementations/caching/cache/testutils/MinimalValueType.cpp000066400000000000000000000001201347701267100300450ustar00rootroot00000000000000#include "MinimalValueType.h" std::atomic MinimalValueType::instances(0); test/blockstore/implementations/caching/cache/testutils/MinimalValueType.h000066400000000000000000000024111347701267100275170ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALVALUETYPE_H_ #define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_MINIMALVALUETYPE_H_ #include #include #include #include // This is a not-default-constructible non-copyable but moveable Value type class MinimalValueType { public: static std::atomic instances; static MinimalValueType create(int value) { return MinimalValueType(value); } MinimalValueType(MinimalValueType &&rhs) noexcept: MinimalValueType(rhs.value()) { rhs._isMoved = true; } MinimalValueType &operator=(MinimalValueType &&rhs) noexcept { _value = rhs.value(); _isMoved = false; rhs._isMoved = true; return *this; } ~MinimalValueType() { ASSERT(!_isDestructed, "Object was already destructed before"); --instances; _isDestructed = true; } int value() const { ASSERT(!_isMoved && !_isDestructed, "Object is invalid"); return _value; } private: MinimalValueType(int value): _value(value), _isMoved(false), _isDestructed(false) { ++instances; } int _value; bool _isMoved; bool _isDestructed; DISALLOW_COPY_AND_ASSIGN(MinimalValueType); }; #endif test/blockstore/implementations/caching/cache/testutils/QueueMapTest.cpp000066400000000000000000000020251347701267100272100ustar00rootroot00000000000000#include "QueueMapTest.h" QueueMapTest::QueueMapTest(): _map(cpputils::make_unique_ref>()) { MinimalKeyType::instances = 0; MinimalValueType::instances = 0; } QueueMapTest::~QueueMapTest() { cpputils::destruct(std::move(_map)); EXPECT_EQ(0, MinimalKeyType::instances); EXPECT_EQ(0, MinimalValueType::instances); } void QueueMapTest::push(int key, int value) { _map->push(MinimalKeyType::create(key), MinimalValueType::create(value)); } boost::optional QueueMapTest::pop() { auto elem = _map->pop(); if (!elem) { return boost::none; } return elem.value().value(); } boost::optional QueueMapTest::pop(int key) { auto elem = _map->pop(MinimalKeyType::create(key)); if (!elem) { return boost::none; } return elem.value().value(); } boost::optional QueueMapTest::peek() { auto elem = _map->peek(); if (!elem) { return boost::none; } return elem.value().value(); } int QueueMapTest::size() { return _map->size(); } test/blockstore/implementations/caching/cache/testutils/QueueMapTest.h000066400000000000000000000021061347701267100266550ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_ #define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_CACHING_CACHE_TESTUTILS_QUEUEMAPTEST_H_ #include #include #include "blockstore/implementations/caching/cache/QueueMap.h" #include "MinimalKeyType.h" #include "MinimalValueType.h" #include // This class is a parent class for tests on QueueMap. // It offers functions to work with a QueueMap test object which is built using types having only the minimal type requirements. // Furthermore, the class checks that there are no memory leaks left after destructing the QueueMap (by counting leftover instances of Keys/Values). class QueueMapTest: public ::testing::Test { public: QueueMapTest(); ~QueueMapTest(); void push(int key, int value); boost::optional pop(); boost::optional pop(int key); boost::optional peek(); int size(); private: cpputils::unique_ref> _map; }; #endif test/blockstore/implementations/compressing/000077500000000000000000000000001347701267100217555ustar00rootroot00000000000000test/blockstore/implementations/compressing/CompressingBlockStoreTest.cpp000066400000000000000000000021651347701267100276060ustar00rootroot00000000000000#include "blockstore/implementations/compressing/CompressingBlockStore.h" #include "blockstore/implementations/compressing/compressors/Gzip.h" #include "blockstore/implementations/compressing/compressors/RunLengthEncoding.h" #include "blockstore/implementations/testfake/FakeBlockStore.h" #include "../../testutils/BlockStoreTest.h" #include using blockstore::BlockStore; using blockstore::compressing::CompressingBlockStore; using blockstore::compressing::Gzip; using blockstore::compressing::RunLengthEncoding; using blockstore::testfake::FakeBlockStore; using cpputils::make_unique_ref; using cpputils::unique_ref; template class CompressingBlockStoreTestFixture: public BlockStoreTestFixture { public: unique_ref createBlockStore() override { return make_unique_ref>(make_unique_ref()); } }; INSTANTIATE_TYPED_TEST_CASE_P(Compressing_Gzip, BlockStoreTest, CompressingBlockStoreTestFixture); INSTANTIATE_TYPED_TEST_CASE_P(Compressing_RunLengthEncoding, BlockStoreTest, CompressingBlockStoreTestFixture); test/blockstore/implementations/compressing/compressors/000077500000000000000000000000001347701267100243345ustar00rootroot00000000000000test/blockstore/implementations/compressing/compressors/testutils/000077500000000000000000000000001347701267100263745ustar00rootroot00000000000000test/blockstore/implementations/compressing/compressors/testutils/CompressorTest.cpp000066400000000000000000000064261347701267100321040ustar00rootroot00000000000000#include #include "blockstore/implementations/compressing/compressors/Gzip.h" #include "blockstore/implementations/compressing/compressors/RunLengthEncoding.h" #include using namespace blockstore::compressing; using cpputils::Data; using cpputils::DataFixture; template class CompressorTest: public ::testing::Test { public: void EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(const Data &data) { Data compressed = Compressor::Compress(data); Data decompressed = Compressor::Decompress(compressed.data(), compressed.size()); EXPECT_EQ(data, decompressed); } }; TYPED_TEST_CASE_P(CompressorTest); TYPED_TEST_P(CompressorTest, Empty) { Data empty(0); this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(empty); } TYPED_TEST_P(CompressorTest, ArbitraryData) { Data fixture = DataFixture::generate(10240); this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(fixture); } TYPED_TEST_P(CompressorTest, Zeroes) { Data zeroes(10240); zeroes.FillWithZeroes(); this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(zeroes); } TYPED_TEST_P(CompressorTest, Runs) { Data data(4096); std::memset(data.dataOffset(0), 0xF2, 1024); std::memset(data.dataOffset(1024), 0x00, 1024); std::memset(data.dataOffset(2048), 0x01, 2048); this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(data); } TYPED_TEST_P(CompressorTest, RunsAndArbitrary) { Data data(4096); std::memset(data.dataOffset(0), 0xF2, 1024); std::memcpy(data.dataOffset(1024), DataFixture::generate(1024).data(), 1024); std::memset(data.dataOffset(2048), 0x01, 2048); this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(data); } TYPED_TEST_P(CompressorTest, LargeData) { // this is larger than what fits into 16bit (16bit are for example used as run length indicator in RunLengthEncoding) Data fixture = DataFixture::generate(200000); this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(fixture); } TYPED_TEST_P(CompressorTest, LargeRuns) { // each run is larger than what fits into 16bit (16bit are for example used as run length indicator in RunLengthEncoding) constexpr size_t RUN_SIZE = 200000; Data data(3*RUN_SIZE); std::memset(data.dataOffset(0), 0xF2, RUN_SIZE); std::memset(data.dataOffset(RUN_SIZE), 0x00, RUN_SIZE); std::memset(data.dataOffset(2*RUN_SIZE), 0x01, RUN_SIZE); this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(data); } TYPED_TEST_P(CompressorTest, LargeRunsAndArbitrary) { // each run is larger than what fits into 16bit (16bit are for example used as run length indicator in RunLengthEncoding) constexpr size_t RUN_SIZE = 200000; Data data(3*RUN_SIZE); std::memset(data.dataOffset(0), 0xF2, RUN_SIZE); std::memcpy(data.dataOffset(RUN_SIZE), DataFixture::generate(RUN_SIZE).data(), RUN_SIZE); std::memset(data.dataOffset(2*RUN_SIZE), 0x01, RUN_SIZE); this->EXPECT_COMPRESS_AND_DECOMPRESS_IS_IDENTITY(data); } REGISTER_TYPED_TEST_CASE_P(CompressorTest, Empty, ArbitraryData, Zeroes, Runs, RunsAndArbitrary, LargeData, LargeRuns, LargeRunsAndArbitrary ); INSTANTIATE_TYPED_TEST_CASE_P(Gzip, CompressorTest, Gzip); INSTANTIATE_TYPED_TEST_CASE_P(RunLengthEncoding, CompressorTest, RunLengthEncoding); test/blockstore/implementations/encrypted/000077500000000000000000000000001347701267100214215ustar00rootroot00000000000000test/blockstore/implementations/encrypted/EncryptedBlockStoreTest_Generic.cpp000066400000000000000000000054431347701267100303540ustar00rootroot00000000000000#include #include #include #include "blockstore/implementations/encrypted/EncryptedBlockStore2.h" #include "blockstore/implementations/testfake/FakeBlockStore.h" #include "blockstore/implementations/inmemory/InMemoryBlockStore2.h" #include "../../testutils/BlockStoreTest.h" #include "../../testutils/BlockStore2Test.h" //TODO Move FakeAuthenticatedCipher out of test folder to normal folder. Dependencies should not point into tests of other modules. #include "cpp-utils/crypto/symmetric/testutils/FakeAuthenticatedCipher.h" #include using blockstore::BlockStore; using blockstore::BlockStore2; using blockstore::encrypted::EncryptedBlockStore2; using blockstore::lowtohighlevel::LowToHighLevelBlockStore; using blockstore::inmemory::InMemoryBlockStore2; using cpputils::AES256_GCM; using cpputils::AES256_CFB; using cpputils::FakeAuthenticatedCipher; using cpputils::DataFixture; using cpputils::make_unique_ref; using cpputils::unique_ref; template class EncryptedBlockStoreTestFixture: public BlockStoreTestFixture { public: unique_ref createBlockStore() override { return make_unique_ref( make_unique_ref>(make_unique_ref(), createKeyFixture()) ); } private: static typename Cipher::EncryptionKey createKeyFixture(int seed = 0) { return Cipher::EncryptionKey::FromString( DataFixture::generate(Cipher::KEYSIZE, seed).ToString() ); } }; INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_FakeCipher, BlockStoreTest, EncryptedBlockStoreTestFixture); INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_AES256_GCM, BlockStoreTest, EncryptedBlockStoreTestFixture); INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_AES256_CFB, BlockStoreTest, EncryptedBlockStoreTestFixture); template class EncryptedBlockStore2TestFixture: public BlockStore2TestFixture { public: unique_ref createBlockStore() override { return make_unique_ref>(make_unique_ref(), createKeyFixture()); } private: static typename Cipher::EncryptionKey createKeyFixture(int seed = 0) { return Cipher::EncryptionKey::FromString( DataFixture::generate(Cipher::KEYSIZE, seed).ToString() ); } }; INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_FakeCipher, BlockStore2Test, EncryptedBlockStore2TestFixture); INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_AES256_GCM, BlockStore2Test, EncryptedBlockStore2TestFixture); INSTANTIATE_TYPED_TEST_CASE_P(Encrypted_AES256_CFB, BlockStore2Test, EncryptedBlockStore2TestFixture); test/blockstore/implementations/encrypted/EncryptedBlockStoreTest_Specific.cpp000066400000000000000000000122311347701267100305160ustar00rootroot00000000000000#include "cpp-utils/crypto/cryptopp_byte.h" #include #include "cpp-utils/crypto/symmetric/testutils/FakeAuthenticatedCipher.h" #include "blockstore/implementations/encrypted/EncryptedBlockStore2.h" #include "blockstore/implementations/inmemory/InMemoryBlockStore2.h" #include "blockstore/utils/BlockStoreUtils.h" #include "../../testutils/gtest_printers.h" #include using ::testing::Test; using cpputils::DataFixture; using cpputils::Data; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::FakeAuthenticatedCipher; using blockstore::inmemory::InMemoryBlockStore2; using namespace blockstore::encrypted; class EncryptedBlockStoreTest: public Test { public: static constexpr unsigned int BLOCKSIZE = 1024; EncryptedBlockStoreTest(): baseBlockStore(new InMemoryBlockStore2), blockStore(make_unique_ref>(std::move(cpputils::nullcheck(std::unique_ptr(baseBlockStore)).value()), FakeAuthenticatedCipher::Key1())), data(DataFixture::generate(BLOCKSIZE)) { } InMemoryBlockStore2 *baseBlockStore; unique_ref> blockStore; Data data; blockstore::BlockId CreateBlockDirectlyWithFixtureAndReturnKey() { return CreateBlockReturnKey(data); } blockstore::BlockId CreateBlockReturnKey(const Data &initData) { return blockStore->create(initData.copy()); } blockstore::BlockId CreateBlockWriteFixtureToItAndReturnKey() { auto blockId = blockStore->create(Data(data.size())); blockStore->store(blockId, data); return blockId; } void ModifyBaseBlock(const blockstore::BlockId &blockId) { auto block = baseBlockStore->load(blockId).value(); CryptoPP::byte* middle_byte = static_cast(block.data()) + 10; *middle_byte = *middle_byte + 1; baseBlockStore->store(blockId, block); } blockstore::BlockId CopyBaseBlock(const blockstore::BlockId &blockId) { auto source = baseBlockStore->load(blockId).value(); return baseBlockStore->create(source); } private: DISALLOW_COPY_AND_ASSIGN(EncryptedBlockStoreTest); }; TEST_F(EncryptedBlockStoreTest, LoadingWithSameKeyWorks_WriteOnCreate) { auto blockId = CreateBlockDirectlyWithFixtureAndReturnKey(); auto loaded = blockStore->load(blockId); EXPECT_NE(boost::none, loaded); EXPECT_EQ(data.size(), loaded->size()); EXPECT_EQ(0, std::memcmp(data.data(), loaded->data(), data.size())); } TEST_F(EncryptedBlockStoreTest, LoadingWithSameKeyWorks_WriteSeparately) { auto blockId = CreateBlockWriteFixtureToItAndReturnKey(); auto loaded = blockStore->load(blockId); EXPECT_NE(boost::none, loaded); EXPECT_EQ(data.size(), loaded->size()); EXPECT_EQ(0, std::memcmp(data.data(), loaded->data(), data.size())); } TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentKeyDoesntWork_WriteOnCreate) { auto blockId = CreateBlockDirectlyWithFixtureAndReturnKey(); blockStore->__setKey(FakeAuthenticatedCipher::Key2()); auto loaded = blockStore->load(blockId); EXPECT_EQ(boost::none, loaded); } TEST_F(EncryptedBlockStoreTest, LoadingWithDifferentKeyDoesntWork_WriteSeparately) { auto blockId = CreateBlockWriteFixtureToItAndReturnKey(); blockStore->__setKey(FakeAuthenticatedCipher::Key2()); auto loaded = blockStore->load(blockId); EXPECT_EQ(boost::none, loaded); } TEST_F(EncryptedBlockStoreTest, LoadingModifiedBlockFails_WriteOnCreate) { auto blockId = CreateBlockDirectlyWithFixtureAndReturnKey(); ModifyBaseBlock(blockId); auto loaded = blockStore->load(blockId); EXPECT_EQ(boost::none, loaded); } TEST_F(EncryptedBlockStoreTest, LoadingModifiedBlockFails_WriteSeparately) { auto blockId = CreateBlockWriteFixtureToItAndReturnKey(); ModifyBaseBlock(blockId); auto loaded = blockStore->load(blockId); EXPECT_EQ(boost::none, loaded); } TEST_F(EncryptedBlockStoreTest, PhysicalBlockSize_zerophysical) { EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(0)); } TEST_F(EncryptedBlockStoreTest, PhysicalBlockSize_zerovirtual) { auto blockId = CreateBlockReturnKey(Data(0)); auto base = baseBlockStore->load(blockId).value(); EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(base.size())); } TEST_F(EncryptedBlockStoreTest, PhysicalBlockSize_negativeboundaries) { // This tests that a potential if/else in blockSizeFromPhysicalBlockSize that catches negative values has the // correct boundary set. We test the highest value that is negative and the smallest value that is positive. auto physicalSizeForVirtualSizeZero = baseBlockStore->load(CreateBlockReturnKey(Data(0))).value().size(); if (physicalSizeForVirtualSizeZero > 0) { EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero - 1)); } EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero)); EXPECT_EQ(1u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero + 1)); } TEST_F(EncryptedBlockStoreTest, PhysicalBlockSize_positive) { auto blockId = CreateBlockReturnKey(Data(10*1024)); auto base = baseBlockStore->load(blockId).value(); EXPECT_EQ(10*1024u, blockStore->blockSizeFromPhysicalBlockSize(base.size())); } test/blockstore/implementations/inmemory/000077500000000000000000000000001347701267100212635ustar00rootroot00000000000000test/blockstore/implementations/inmemory/InMemoryBlockStoreTest.cpp000066400000000000000000000023641347701267100263630ustar00rootroot00000000000000#include "blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h" #include "blockstore/implementations/inmemory/InMemoryBlockStore2.h" #include "../../testutils/BlockStoreTest.h" #include "../../testutils/BlockStore2Test.h" #include #include #include using blockstore::BlockStore; using blockstore::BlockStore2; using blockstore::lowtohighlevel::LowToHighLevelBlockStore; using blockstore::inmemory::InMemoryBlockStore2; using cpputils::unique_ref; using cpputils::make_unique_ref; class InMemoryBlockStoreTestFixture: public BlockStoreTestFixture { public: unique_ref createBlockStore() override { return make_unique_ref( make_unique_ref() ); } }; INSTANTIATE_TYPED_TEST_CASE_P(InMemory, BlockStoreTest, InMemoryBlockStoreTestFixture); class InMemoryBlockStore2TestFixture: public BlockStore2TestFixture { public: unique_ref createBlockStore() override { return make_unique_ref(); } }; INSTANTIATE_TYPED_TEST_CASE_P(InMemory, BlockStore2Test, InMemoryBlockStore2TestFixture); test/blockstore/implementations/integrity/000077500000000000000000000000001347701267100214425ustar00rootroot00000000000000test/blockstore/implementations/integrity/IntegrityBlockStoreTest_Generic.cpp000066400000000000000000000072211347701267100304120ustar00rootroot00000000000000#include "blockstore/implementations/integrity/IntegrityBlockStore2.h" #include "blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h" #include "blockstore/implementations/inmemory/InMemoryBlockStore2.h" #include "../../testutils/BlockStoreTest.h" #include "../../testutils/BlockStore2Test.h" #include #include using blockstore::BlockStore; using blockstore::BlockStore2; using blockstore::integrity::IntegrityBlockStore2; using blockstore::lowtohighlevel::LowToHighLevelBlockStore; using blockstore::inmemory::InMemoryBlockStore2; using cpputils::make_unique_ref; using cpputils::unique_ref; using cpputils::TempFile; template class IntegrityBlockStoreTestFixture: public BlockStoreTestFixture { public: IntegrityBlockStoreTestFixture() :stateFile(false) {} TempFile stateFile; unique_ref createBlockStore() override { return make_unique_ref( make_unique_ref(make_unique_ref(), stateFile.path(), 0x12345678, AllowIntegrityViolations, MissingBlockIsIntegrityViolation, [] {}) ); } }; using IntegrityBlockStoreTestFixture_multiclient = IntegrityBlockStoreTestFixture; using IntegrityBlockStoreTestFixture_singleclient = IntegrityBlockStoreTestFixture; using IntegrityBlockStoreTestFixture_multiclient_allowIntegrityViolations = IntegrityBlockStoreTestFixture; using IntegrityBlockStoreTestFixture_singleclient_allowIntegrityViolations = IntegrityBlockStoreTestFixture; INSTANTIATE_TYPED_TEST_CASE_P(Integrity_multiclient, BlockStoreTest, IntegrityBlockStoreTestFixture_multiclient); INSTANTIATE_TYPED_TEST_CASE_P(Integrity_singleclient, BlockStoreTest, IntegrityBlockStoreTestFixture_singleclient); INSTANTIATE_TYPED_TEST_CASE_P(Integrity_multiclient_allowIntegrityViolations, BlockStoreTest, IntegrityBlockStoreTestFixture_multiclient_allowIntegrityViolations); INSTANTIATE_TYPED_TEST_CASE_P(Integrity_singleclient_allowIntegrityViolations, BlockStoreTest, IntegrityBlockStoreTestFixture_singleclient_allowIntegrityViolations); template class IntegrityBlockStore2TestFixture: public BlockStore2TestFixture { public: IntegrityBlockStore2TestFixture() :stateFile(false) {} TempFile stateFile; unique_ref createBlockStore() override { return make_unique_ref(make_unique_ref(), stateFile.path(), 0x12345678, AllowIntegrityViolations, MissingBlockIsIntegrityViolation, [] {}); } }; using IntegrityBlockStore2TestFixture_multiclient = IntegrityBlockStore2TestFixture; using IntegrityBlockStore2TestFixture_singleclient = IntegrityBlockStore2TestFixture; using IntegrityBlockStore2TestFixture_multiclient_allowIntegrityViolations = IntegrityBlockStore2TestFixture; using IntegrityBlockStore2TestFixture_singleclient_allowIntegrityViolations = IntegrityBlockStore2TestFixture; INSTANTIATE_TYPED_TEST_CASE_P(Integrity_multiclient, BlockStore2Test, IntegrityBlockStore2TestFixture_multiclient); INSTANTIATE_TYPED_TEST_CASE_P(Integrity_singleclient, BlockStore2Test, IntegrityBlockStore2TestFixture_singleclient); INSTANTIATE_TYPED_TEST_CASE_P(Integrity_multiclient_allowIntegrityViolations, BlockStore2Test, IntegrityBlockStore2TestFixture_multiclient_allowIntegrityViolations); INSTANTIATE_TYPED_TEST_CASE_P(Integrity_singleclient_allowIntegrityViolations, BlockStore2Test, IntegrityBlockStore2TestFixture_singleclient_allowIntegrityViolations); test/blockstore/implementations/integrity/IntegrityBlockStoreTest_Specific.cpp000066400000000000000000000407761347701267100305770ustar00rootroot00000000000000#include #include "blockstore/implementations/integrity/IntegrityBlockStore2.h" #include "blockstore/implementations/inmemory/InMemoryBlockStore2.h" #include "blockstore/utils/BlockStoreUtils.h" #include #include #include "../../testutils/gtest_printers.h" using ::testing::Test; using cpputils::DataFixture; using cpputils::Data; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::TempFile; using cpputils::serialize; using cpputils::deserialize; using boost::none; using std::unique_ptr; using blockstore::inmemory::InMemoryBlockStore2; using namespace blockstore::integrity; namespace { class FakeCallback final { public: FakeCallback(): wasCalled_(false) {} bool wasCalled() const { return wasCalled_; } std::function callback() { return [this] () { wasCalled_ = true; }; } private: bool wasCalled_; }; } template class IntegrityBlockStoreTest: public Test { public: static constexpr unsigned int BLOCKSIZE = 1024; IntegrityBlockStoreTest(): stateFile(false), onIntegrityViolation(), baseBlockStore(new InMemoryBlockStore2), blockStore(make_unique_ref(std::move(cpputils::nullcheck(std::unique_ptr(baseBlockStore)).value()), stateFile.path(), myClientId, AllowIntegrityViolations, MissingBlockIsIntegrityViolation, onIntegrityViolation.callback())), data(DataFixture::generate(BLOCKSIZE)) { } static constexpr uint32_t myClientId = 0x12345678; TempFile stateFile; FakeCallback onIntegrityViolation; InMemoryBlockStore2 *baseBlockStore; unique_ref blockStore; Data data; blockstore::BlockId CreateBlockReturnKey() { return CreateBlockReturnKey(data); } blockstore::BlockId CreateBlockReturnKey(const Data &initData) { return blockStore->create(initData.copy()); } Data loadBaseBlock(const blockstore::BlockId &blockId) { return baseBlockStore->load(blockId).value(); } Data loadBlock(const blockstore::BlockId &blockId) { return blockStore->load(blockId).value(); } void modifyBlock(const blockstore::BlockId &blockId) { auto block = blockStore->load(blockId).value(); CryptoPP::byte* first_byte = static_cast(block.data()); *first_byte = *first_byte + 1; blockStore->store(blockId, block); } void rollbackBaseBlock(const blockstore::BlockId &blockId, const Data &data) { baseBlockStore->store(blockId, data); } void decreaseVersionNumber(const blockstore::BlockId &blockId) { auto baseBlock = baseBlockStore->load(blockId).value(); void* versionPtr = static_cast(baseBlock.data()) + IntegrityBlockStore2::VERSION_HEADER_OFFSET; uint64_t version = deserialize(versionPtr); ASSERT(version > 1, "Can't decrease the lowest allowed version number"); serialize(versionPtr, version-1); baseBlockStore->store(blockId, baseBlock); } void increaseVersionNumber(const blockstore::BlockId &blockId) { auto baseBlock = baseBlockStore->load(blockId).value(); void* versionPtr = static_cast(baseBlock.data()) + IntegrityBlockStore2::VERSION_HEADER_OFFSET; uint64_t version = deserialize(versionPtr); serialize(versionPtr, version+1); baseBlockStore->store(blockId, baseBlock); } void changeClientId(const blockstore::BlockId &blockId) { auto baseBlock = baseBlockStore->load(blockId).value(); void* clientIdPtr = static_cast(baseBlock.data()) + IntegrityBlockStore2::CLIENTID_HEADER_OFFSET; uint64_t clientId = deserialize(clientIdPtr); serialize(clientIdPtr, clientId+1); baseBlockStore->store(blockId, baseBlock); } void deleteBlock(const blockstore::BlockId &blockId) { blockStore->remove(blockId); } void insertBaseBlock(const blockstore::BlockId &blockId, Data data) { EXPECT_TRUE(baseBlockStore->tryCreate(blockId, data)); } private: DISALLOW_COPY_AND_ASSIGN(IntegrityBlockStoreTest); }; using IntegrityBlockStoreTest_Default = IntegrityBlockStoreTest; using IntegrityBlockStoreTest_MissingBlockIsIntegrityViolation = IntegrityBlockStoreTest; using IntegrityBlockStoreTest_AllowIntegrityViolations = IntegrityBlockStoreTest; using IntegrityBlockStoreTest_AllowIntegrityViolations_MissingBlockIsIntegrityViolation = IntegrityBlockStoreTest; template constexpr uint32_t IntegrityBlockStoreTest::myClientId; // Test that a decreasing version number is not allowed TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_DoesntAllowDecreasingVersionNumberForSameClient_1) { auto blockId = CreateBlockReturnKey(); Data oldBaseBlock = loadBaseBlock(blockId); modifyBlock(blockId); rollbackBaseBlock(blockId, oldBaseBlock); EXPECT_EQ(boost::none, blockStore->load(blockId)); EXPECT_TRUE(onIntegrityViolation.wasCalled()); } // Test that a decreasing version number is allowed if allowIntegrityViolations is set. TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_AllowsDecreasingVersionNumberForSameClient_1) { auto blockId = CreateBlockReturnKey(); Data oldBaseBlock = loadBaseBlock(blockId); modifyBlock(blockId); rollbackBaseBlock(blockId, oldBaseBlock); EXPECT_NE(boost::none, blockStore->load(blockId)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_DoesntAllowDecreasingVersionNumberForSameClient_2) { auto blockId = CreateBlockReturnKey(); // Increase the version number modifyBlock(blockId); // Decrease the version number again decreaseVersionNumber(blockId); EXPECT_EQ(boost::none, blockStore->load(blockId)); EXPECT_TRUE(onIntegrityViolation.wasCalled()); } TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_AllowsDecreasingVersionNumberForSameClient_2) { auto blockId = CreateBlockReturnKey(); // Increase the version number modifyBlock(blockId); // Decrease the version number again decreaseVersionNumber(blockId); EXPECT_NE(boost::none, blockStore->load(blockId)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } // Test that a different client doesn't need to have a higher version number (i.e. version numbers are per client). TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_DoesAllowDecreasingVersionNumberForDifferentClient) { auto blockId = CreateBlockReturnKey(); // Increase the version number modifyBlock(blockId); // Fake a modification by a different client with lower version numbers changeClientId(blockId); decreaseVersionNumber(blockId); EXPECT_NE(boost::none, blockStore->load(blockId)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_DoesAllowDecreasingVersionNumberForDifferentClient) { auto blockId = CreateBlockReturnKey(); // Increase the version number modifyBlock(blockId); // Fake a modification by a different client with lower version numbers changeClientId(blockId); decreaseVersionNumber(blockId); EXPECT_NE(boost::none, blockStore->load(blockId)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } // Test that it doesn't allow a rollback to the "newest" block of a client, when this block was superseded by a version of a different client TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_DoesntAllowSameVersionNumberForOldClient) { auto blockId = CreateBlockReturnKey(); // Increase the version number modifyBlock(blockId); Data oldBaseBlock = loadBaseBlock(blockId); // Fake a modification by a different client with lower version numbers changeClientId(blockId); loadBlock(blockId); // make the block store know about this other client's modification // Rollback to old client rollbackBaseBlock(blockId, oldBaseBlock); EXPECT_EQ(boost::none, blockStore->load(blockId)); EXPECT_TRUE(onIntegrityViolation.wasCalled()); } // Test that it does allow a rollback to the "newest" block of a client, when this block was superseded by a version of a different client, but integrity violations are allowed TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_AllowsSameVersionNumberForOldClient) { auto blockId = CreateBlockReturnKey(); // Increase the version number modifyBlock(blockId); Data oldBaseBlock = loadBaseBlock(blockId); // Fake a modification by a different client with lower version numbers changeClientId(blockId); loadBlock(blockId); // make the block store know about this other client's modification // Rollback to old client rollbackBaseBlock(blockId, oldBaseBlock); EXPECT_NE(boost::none, blockStore->load(blockId)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } // Test that deleted blocks cannot be re-introduced TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_DoesntAllowReintroducingDeletedBlocks) { auto blockId = CreateBlockReturnKey(); Data oldBaseBlock = loadBaseBlock(blockId); deleteBlock(blockId); insertBaseBlock(blockId, std::move(oldBaseBlock)); EXPECT_EQ(boost::none, blockStore->load(blockId)); EXPECT_TRUE(onIntegrityViolation.wasCalled()); } // Test that deleted blocks can be re-introduced if integrity violations are allowed TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_AllowsReintroducingDeletedBlocks) { auto blockId = CreateBlockReturnKey(); Data oldBaseBlock = loadBaseBlock(blockId); deleteBlock(blockId); insertBaseBlock(blockId, std::move(oldBaseBlock)); EXPECT_NE(boost::none, blockStore->load(blockId)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } // This can happen if a client synchronization is delayed. Another client might have won the conflict and pushed a new version for the deleted block. TEST_F(IntegrityBlockStoreTest_Default, RollbackPrevention_AllowsReintroducingDeletedBlocksWithNewVersionNumber) { auto blockId = CreateBlockReturnKey(); Data oldBaseBlock = loadBaseBlock(blockId); deleteBlock(blockId); insertBaseBlock(blockId, std::move(oldBaseBlock)); increaseVersionNumber(blockId); EXPECT_NE(boost::none, blockStore->load(blockId)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, RollbackPrevention_AllowsReintroducingDeletedBlocksWithNewVersionNumber) { auto blockId = CreateBlockReturnKey(); Data oldBaseBlock = loadBaseBlock(blockId); deleteBlock(blockId); insertBaseBlock(blockId, std::move(oldBaseBlock)); increaseVersionNumber(blockId); EXPECT_NE(boost::none, blockStore->load(blockId)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } // Check that in a multi-client scenario, missing blocks are not integrity errors, because another client might have deleted them. TEST_F(IntegrityBlockStoreTest_Default, DeletionPrevention_AllowsDeletingBlocksWhenDeactivated) { auto blockId = blockStore->create(Data(0)); baseBlockStore->remove(blockId); EXPECT_EQ(boost::none, blockStore->load(blockId)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, DeletionPrevention_AllowsDeletingBlocksWhenDeactivated) { auto blockId = blockStore->create(Data(0)); baseBlockStore->remove(blockId); EXPECT_EQ(boost::none, blockStore->load(blockId)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } // Check that in a single-client scenario, missing blocks are integrity errors. TEST_F(IntegrityBlockStoreTest_MissingBlockIsIntegrityViolation, DeletionPrevention_DoesntAllowDeletingBlocksWhenActivated) { auto blockId = blockStore->create(Data(0)); baseBlockStore->remove(blockId); EXPECT_EQ(boost::none, blockStore->load(blockId)); EXPECT_TRUE(onIntegrityViolation.wasCalled()); } // Check that in a single-client scenario, missing blocks don't throw if integrity violations are allowed. TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations_MissingBlockIsIntegrityViolation, DeletionPrevention_AllowsDeletingBlocksWhenActivated) { auto blockId = blockStore->create(Data(0)); baseBlockStore->remove(blockId); EXPECT_EQ(boost::none, blockStore->load(blockId)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } // Check that in a multi-client scenario, missing blocks are not integrity errors, because another client might have deleted them. TEST_F(IntegrityBlockStoreTest_Default, DeletionPrevention_InForEachBlock_AllowsDeletingBlocksWhenDeactivated) { auto blockId = blockStore->create(Data(0)); baseBlockStore->remove(blockId); int count = 0; blockStore->forEachBlock([&count] (const blockstore::BlockId &) { ++count; }); EXPECT_EQ(0, count); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, DeletionPrevention_InForEachBlock_AllowsDeletingBlocksWhenDeactivated) { auto blockId = blockStore->create(Data(0)); baseBlockStore->remove(blockId); int count = 0; blockStore->forEachBlock([&count] (const blockstore::BlockId &) { ++count; }); EXPECT_EQ(0, count); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } // Check that in a single-client scenario, missing blocks are integrity errors. TEST_F(IntegrityBlockStoreTest_MissingBlockIsIntegrityViolation, DeletionPrevention_InForEachBlock_DoesntAllowDeletingBlocksWhenActivated) { auto blockId = blockStore->create(Data(0)); baseBlockStore->remove(blockId); blockStore->forEachBlock([] (const blockstore::BlockId &) {}); EXPECT_TRUE(onIntegrityViolation.wasCalled()); } // Check that in a single-client scenario, missing blocks don't throw if integrity violations are allowed. TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations_MissingBlockIsIntegrityViolation, DeletionPrevention_InForEachBlock_AllowsDeletingBlocksWhenActivated) { auto blockId = blockStore->create(Data(0)); baseBlockStore->remove(blockId); blockStore->forEachBlock([] (const blockstore::BlockId &) {}); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } TEST_F(IntegrityBlockStoreTest_Default, LoadingWithDifferentBlockIdFails) { auto blockId = CreateBlockReturnKey(); blockstore::BlockId key2 = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); baseBlockStore->store(key2, baseBlockStore->load(blockId).value()); EXPECT_EQ(boost::none, blockStore->load(key2)); EXPECT_TRUE(onIntegrityViolation.wasCalled()); } TEST_F(IntegrityBlockStoreTest_AllowIntegrityViolations, LoadingWithDifferentBlockIdDoesntFail) { auto blockId = CreateBlockReturnKey(); blockstore::BlockId key2 = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); baseBlockStore->store(key2, baseBlockStore->load(blockId).value()); EXPECT_NE(boost::none, blockStore->load(key2)); EXPECT_FALSE(onIntegrityViolation.wasCalled()); } // TODO Test more integrity cases: // - RollbackPrevention_DoesntAllowReintroducingDeletedBlocks with different client id (i.e. trying to re-introduce the newest block of a different client) // - RollbackPrevention_AllowsReintroducingDeletedBlocksWithNewVersionNumber with different client id // - Think about more... // TODO Test that disabling integrity checks allows all these cases TEST_F(IntegrityBlockStoreTest_Default, PhysicalBlockSize_zerophysical) { EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(0)); } TEST_F(IntegrityBlockStoreTest_Default, PhysicalBlockSize_zerovirtual) { auto blockId = CreateBlockReturnKey(Data(0)); auto base = baseBlockStore->load(blockId).value(); EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(base.size())); } TEST_F(IntegrityBlockStoreTest_Default, PhysicalBlockSize_negativeboundaries) { // This tests that a potential if/else in blockSizeFromPhysicalBlockSize that catches negative values has the // correct boundary set. We test the highest value that is negative and the smallest value that is positive. auto physicalSizeForVirtualSizeZero = baseBlockStore->load(CreateBlockReturnKey(Data(0))).value().size(); if (physicalSizeForVirtualSizeZero > 0) { EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero - 1)); } EXPECT_EQ(0u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero)); EXPECT_EQ(1u, blockStore->blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero + 1)); } TEST_F(IntegrityBlockStoreTest_Default, PhysicalBlockSize_positive) { auto blockId = CreateBlockReturnKey(Data(10*1024)); auto base = baseBlockStore->load(blockId).value(); EXPECT_EQ(10*1024u, blockStore->blockSizeFromPhysicalBlockSize(base.size())); } test/blockstore/implementations/integrity/KnownBlockVersionsTest.cpp000066400000000000000000000364151347701267100266170ustar00rootroot00000000000000#include #include #include using blockstore::integrity::KnownBlockVersions; using blockstore::BlockId; using cpputils::TempFile; using std::unordered_set; class KnownBlockVersionsTest : public ::testing::Test { public: KnownBlockVersionsTest() :stateFile(false), testobj(stateFile.path(), myClientId) {} blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); blockstore::BlockId blockId2 = blockstore::BlockId::FromString("C772972491BB4932A1389EE14BC7090A"); static constexpr uint32_t myClientId = 0x12345678; static constexpr uint32_t clientId = 0x23456789; static constexpr uint32_t clientId2 = 0x34567890; TempFile stateFile; KnownBlockVersions testobj; void setVersion(KnownBlockVersions *testobj, uint32_t clientId, const blockstore::BlockId &blockId, uint64_t version) { if (!testobj->checkAndUpdateVersion(clientId, blockId, version)) { throw std::runtime_error("Couldn't increase version"); } } void EXPECT_VERSION_IS(uint64_t version, KnownBlockVersions *testobj, blockstore::BlockId &blockId, uint32_t clientId) { EXPECT_FALSE(testobj->checkAndUpdateVersion(clientId, blockId, version-1)); EXPECT_TRUE(testobj->checkAndUpdateVersion(clientId, blockId, version+1)); } }; TEST_F(KnownBlockVersionsTest, setandget) { setVersion(&testobj, clientId, blockId, 5); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); } TEST_F(KnownBlockVersionsTest, setandget_isPerClientId) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, clientId2, blockId, 3); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); EXPECT_EQ(3u, testobj.getBlockVersion(clientId2, blockId)); } TEST_F(KnownBlockVersionsTest, setandget_isPerBlock) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, clientId, blockId2, 3); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); EXPECT_EQ(3u, testobj.getBlockVersion(clientId, blockId2)); } TEST_F(KnownBlockVersionsTest, setandget_allowsIncreasing) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, clientId, blockId, 6); EXPECT_EQ(6u, testobj.getBlockVersion(clientId, blockId)); } TEST_F(KnownBlockVersionsTest, setandget_doesntAllowDecreasing) { setVersion(&testobj, clientId, blockId, 5); EXPECT_ANY_THROW( setVersion(&testobj, clientId, blockId, 4); ); } TEST_F(KnownBlockVersionsTest, myClientId_isConsistent) { EXPECT_EQ(testobj.myClientId(), testobj.myClientId()); } TEST_F(KnownBlockVersionsTest, incrementVersion_newentry) { auto version = testobj.incrementVersion(blockId); EXPECT_EQ(1u, version); EXPECT_EQ(1u, testobj.getBlockVersion(testobj.myClientId(), blockId)); } TEST_F(KnownBlockVersionsTest, incrementVersion_oldentry) { setVersion(&testobj, testobj.myClientId(), blockId, 5); auto version = testobj.incrementVersion(blockId); EXPECT_EQ(6u, version); EXPECT_EQ(6u, testobj.getBlockVersion(testobj.myClientId(), blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_newentry) { EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 5)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientSameVersion) { setVersion(&testobj, clientId, blockId, 5); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 5)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientLowerVersion) { setVersion(&testobj, clientId, blockId, 5); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 4)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_sameClientNewerVersion) { setVersion(&testobj, clientId, blockId, 5); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 6)); EXPECT_EQ(6u, testobj.getBlockVersion(clientId, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientSameVersion) { setVersion(&testobj, clientId, blockId, 5); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 5)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId2, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientLowerVersion) { setVersion(&testobj, clientId, blockId, 5); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 3)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); EXPECT_EQ(3u, testobj.getBlockVersion(clientId2, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_differentClientHigherVersion) { setVersion(&testobj, clientId, blockId, 5); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVersion) { setVersion(&testobj, clientId, blockId, 5); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7)); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 3)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersion) { setVersion(&testobj, clientId, blockId, 5); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7)); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 5)); // Don't allow rollback to old client's newest block, if it was superseded by another client EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientHigherVersion) { setVersion(&testobj, clientId, blockId, 5); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 6)); EXPECT_EQ(6u, testobj.getBlockVersion(clientId, blockId)); EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVersion_oldClientIsSelf) { setVersion(&testobj, testobj.myClientId(), blockId, 5); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7)); EXPECT_FALSE(testobj.checkAndUpdateVersion(testobj.myClientId(), blockId, 3)); EXPECT_EQ(5u, testobj.getBlockVersion(testobj.myClientId(), blockId)); EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersion_oldClientIsSelf) { setVersion(&testobj, testobj.myClientId(), blockId, 5); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7)); EXPECT_FALSE(testobj.checkAndUpdateVersion(testobj.myClientId(), blockId, 5)); // Don't allow rollback to old client's newest block, if it was superseded by another client EXPECT_EQ(5u, testobj.getBlockVersion(testobj.myClientId(), blockId)); EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientHigherVersion_oldClientIsSelf) { setVersion(&testobj, testobj.myClientId(), blockId, 4); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 7)); EXPECT_TRUE(testobj.checkAndUpdateVersion(testobj.myClientId(), blockId, 6)); EXPECT_EQ(6u, testobj.getBlockVersion(testobj.myClientId(), blockId)); EXPECT_EQ(7u, testobj.getBlockVersion(clientId2, blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientLowerVersion_newClientIsSelf) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, testobj.myClientId(), blockId, 7); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 3)); EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); EXPECT_EQ(7u, testobj.getBlockVersion(testobj.myClientId(), blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientSameVersion_newClientIsSelf) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, testobj.myClientId(), blockId, 7); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 5)); // Don't allow rollback to old client's newest block, if it was superseded by another client EXPECT_EQ(5u, testobj.getBlockVersion(clientId, blockId)); EXPECT_EQ(7u, testobj.getBlockVersion(testobj.myClientId(), blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdateVersion_oldentry_oldClientHigherVersion_newClientIsSelf) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, testobj.myClientId(), blockId, 7); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 6)); EXPECT_EQ(6u, testobj.getBlockVersion(clientId, blockId)); EXPECT_EQ(7u, testobj.getBlockVersion(testobj.myClientId(), blockId)); } TEST_F(KnownBlockVersionsTest, checkAndUpdate_twoEntriesDontInfluenceEachOther_differentKeys) { // Setup EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId2, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 150)); // Checks EXPECT_VERSION_IS(150, &testobj, blockId, clientId); EXPECT_VERSION_IS(100, &testobj, blockId2, clientId); } TEST_F(KnownBlockVersionsTest, checkAndUpdate_twoEntriesDontInfluenceEachOther_differentClientIds) { // Setup EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 150)); EXPECT_VERSION_IS(150, &testobj, blockId, clientId); EXPECT_VERSION_IS(100, &testobj, blockId, clientId2); } TEST_F(KnownBlockVersionsTest, checkAndUpdate_allowsRollbackToSameClientWithSameVersionNumber) { EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 100)); } TEST_F(KnownBlockVersionsTest, checkAndUpdate_doesntAllowRollbackToOldClientWithSameVersionNumber) { EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId, blockId, 100)); EXPECT_TRUE(testobj.checkAndUpdateVersion(clientId2, blockId, 10)); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 100)); } TEST_F(KnownBlockVersionsTest, saveAndLoad_empty) { TempFile stateFile(false); { KnownBlockVersions _1(stateFile.path(), myClientId); } EXPECT_TRUE(KnownBlockVersions(stateFile.path(), myClientId).checkAndUpdateVersion(clientId, blockId, 1)); } TEST_F(KnownBlockVersionsTest, saveAndLoad_oneentry) { TempFile stateFile(false); EXPECT_TRUE(KnownBlockVersions(stateFile.path(), myClientId).checkAndUpdateVersion(clientId, blockId, 100)); KnownBlockVersions obj(stateFile.path(), myClientId); EXPECT_EQ(100u, obj.getBlockVersion(clientId, blockId)); } TEST_F(KnownBlockVersionsTest, saveAndLoad_threeentries) { TempFile stateFile(false); { KnownBlockVersions obj(stateFile.path(), myClientId); EXPECT_TRUE(obj.checkAndUpdateVersion(obj.myClientId(), blockId, 100)); EXPECT_TRUE(obj.checkAndUpdateVersion(obj.myClientId(), blockId2, 50)); EXPECT_TRUE(obj.checkAndUpdateVersion(clientId, blockId, 150)); } KnownBlockVersions obj(stateFile.path(), myClientId); EXPECT_EQ(100u, obj.getBlockVersion(obj.myClientId(), blockId)); EXPECT_EQ(50u, obj.getBlockVersion(obj.myClientId(), blockId2)); EXPECT_EQ(150u, obj.getBlockVersion(clientId, blockId)); } TEST_F(KnownBlockVersionsTest, saveAndLoad_lastUpdateClientIdIsStored) { { KnownBlockVersions obj(stateFile.path(), myClientId); EXPECT_TRUE(obj.checkAndUpdateVersion(clientId, blockId, 100)); EXPECT_TRUE(obj.checkAndUpdateVersion(clientId2, blockId, 10)); } KnownBlockVersions obj(stateFile.path(), myClientId); EXPECT_FALSE(obj.checkAndUpdateVersion(clientId, blockId, 100)); EXPECT_TRUE(obj.checkAndUpdateVersion(clientId2, blockId, 10)); EXPECT_TRUE(obj.checkAndUpdateVersion(clientId, blockId, 101)); } TEST_F(KnownBlockVersionsTest, markAsDeleted_doesntAllowReIntroducing_sameClientId) { setVersion(&testobj, clientId, blockId, 5); testobj.markBlockAsDeleted(blockId); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 5)); } TEST_F(KnownBlockVersionsTest, markAsDeleted_doesntAllowReIntroducing_oldClientId) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, clientId2, blockId, 5); testobj.markBlockAsDeleted(blockId); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 5)); } TEST_F(KnownBlockVersionsTest, markAsDeleted_checkAndUpdateDoesntDestroyState) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, clientId2, blockId, 5); testobj.markBlockAsDeleted(blockId); EXPECT_FALSE(testobj.checkAndUpdateVersion(clientId, blockId, 5)); // Check block is still deleted EXPECT_FALSE(testobj.blockShouldExist(blockId)); } TEST_F(KnownBlockVersionsTest, blockShouldExist_unknownBlock) { EXPECT_FALSE(testobj.blockShouldExist(blockId)); } TEST_F(KnownBlockVersionsTest, blockShouldExist_knownBlock) { setVersion(&testobj, clientId, blockId, 5); EXPECT_TRUE(testobj.blockShouldExist(blockId)); } TEST_F(KnownBlockVersionsTest, blockShouldExist_deletedBlock) { setVersion(&testobj, clientId, blockId, 5); testobj.markBlockAsDeleted(blockId); EXPECT_FALSE(testobj.blockShouldExist(blockId)); } TEST_F(KnownBlockVersionsTest, path) { KnownBlockVersions obj(stateFile.path(), myClientId); EXPECT_EQ(stateFile.path(), obj.path()); } TEST_F(KnownBlockVersionsTest, existingBlocks_empty) { EXPECT_EQ(unordered_set({}), testobj.existingBlocks()); } TEST_F(KnownBlockVersionsTest, existingBlocks_oneentry) { setVersion(&testobj, clientId, blockId, 5); EXPECT_EQ(unordered_set({blockId}), testobj.existingBlocks()); } TEST_F(KnownBlockVersionsTest, existingBlocks_twoentries) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, clientId2, blockId2, 5); EXPECT_EQ(unordered_set({blockId, blockId2}), testobj.existingBlocks()); } TEST_F(KnownBlockVersionsTest, existingBlocks_twoentries_sameKey) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, clientId2, blockId, 5); EXPECT_EQ(unordered_set({blockId}), testobj.existingBlocks()); } TEST_F(KnownBlockVersionsTest, existingBlocks_deletedEntry) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, clientId2, blockId2, 5); testobj.markBlockAsDeleted(blockId2); EXPECT_EQ(unordered_set({blockId}), testobj.existingBlocks()); } TEST_F(KnownBlockVersionsTest, existingBlocks_deletedEntries) { setVersion(&testobj, clientId, blockId, 5); setVersion(&testobj, clientId2, blockId2, 5); testobj.markBlockAsDeleted(blockId); testobj.markBlockAsDeleted(blockId2); EXPECT_EQ(unordered_set({}), testobj.existingBlocks()); } test/blockstore/implementations/low2highlevel/000077500000000000000000000000001347701267100221775ustar00rootroot00000000000000test/blockstore/implementations/low2highlevel/LowToHighLevelBlockStoreTest.cpp000066400000000000000000000016151347701267100303720ustar00rootroot00000000000000#include "blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h" #include "blockstore/implementations/testfake/FakeBlockStore.h" #include "blockstore/implementations/inmemory/InMemoryBlockStore2.h" #include "../../testutils/BlockStoreTest.h" #include #include using blockstore::BlockStore; using blockstore::lowtohighlevel::LowToHighLevelBlockStore; using blockstore::inmemory::InMemoryBlockStore2; using cpputils::make_unique_ref; using cpputils::unique_ref; class LowToHighLevelBlockStoreTestFixture: public BlockStoreTestFixture { public: LowToHighLevelBlockStoreTestFixture() {} unique_ref createBlockStore() override { return make_unique_ref(make_unique_ref()); } }; INSTANTIATE_TYPED_TEST_CASE_P(LowToHighLevel, BlockStoreTest, LowToHighLevelBlockStoreTestFixture); test/blockstore/implementations/mock/000077500000000000000000000000001347701267100203555ustar00rootroot00000000000000test/blockstore/implementations/mock/MockBlockStoreTest.cpp000066400000000000000000000013311347701267100246000ustar00rootroot00000000000000#include "blockstore/implementations/mock/MockBlock.h" #include "blockstore/implementations/mock/MockBlockStore.h" #include "../../testutils/BlockStoreTest.h" #include #include using blockstore::BlockStore; using blockstore::mock::MockBlockStore; using blockstore::testfake::FakeBlockStore; using cpputils::unique_ref; using cpputils::make_unique_ref; class MockBlockStoreTestFixture: public BlockStoreTestFixture { public: unique_ref createBlockStore() override { return make_unique_ref(make_unique_ref()); } }; INSTANTIATE_TYPED_TEST_CASE_P(Mock, BlockStoreTest, MockBlockStoreTestFixture); test/blockstore/implementations/ondisk/000077500000000000000000000000001347701267100207135ustar00rootroot00000000000000test/blockstore/implementations/ondisk/OnDiskBlockStoreTest_Generic.cpp000066400000000000000000000024671347701267100271030ustar00rootroot00000000000000#include "blockstore/implementations/low2highlevel/LowToHighLevelBlockStore.h" #include "blockstore/implementations/ondisk/OnDiskBlockStore2.h" #include "../../testutils/BlockStoreTest.h" #include "../../testutils/BlockStore2Test.h" #include #include using blockstore::BlockStore; using blockstore::ondisk::OnDiskBlockStore2; using blockstore::BlockStore2; using blockstore::lowtohighlevel::LowToHighLevelBlockStore; using cpputils::TempDir; using cpputils::unique_ref; using cpputils::make_unique_ref; class OnDiskBlockStoreTestFixture: public BlockStoreTestFixture { public: OnDiskBlockStoreTestFixture(): tempdir() {} unique_ref createBlockStore() override { return make_unique_ref( make_unique_ref(tempdir.path()) ); } private: TempDir tempdir; }; INSTANTIATE_TYPED_TEST_CASE_P(OnDisk, BlockStoreTest, OnDiskBlockStoreTestFixture); class OnDiskBlockStore2TestFixture: public BlockStore2TestFixture { public: OnDiskBlockStore2TestFixture(): tempdir() {} unique_ref createBlockStore() override { return make_unique_ref(tempdir.path()); } private: TempDir tempdir; }; INSTANTIATE_TYPED_TEST_CASE_P(OnDisk, BlockStore2Test, OnDiskBlockStore2TestFixture); test/blockstore/implementations/ondisk/OnDiskBlockStoreTest_Specific.cpp000066400000000000000000000050051347701267100272430ustar00rootroot00000000000000#include #include "blockstore/implementations/ondisk/OnDiskBlockStore2.h" #include using ::testing::Test; using cpputils::TempDir; using cpputils::Data; using std::ifstream; using blockstore::BlockId; using namespace blockstore::ondisk; class OnDiskBlockStoreTest: public Test { public: OnDiskBlockStoreTest(): baseDir(), blockStore(baseDir.path()) { } TempDir baseDir; OnDiskBlockStore2 blockStore; blockstore::BlockId CreateBlockReturnKey(const Data &initData) { return blockStore.create(initData.copy()); } uint64_t getPhysicalBlockSize(const BlockId &blockId) { ifstream stream((baseDir.path() / blockId.ToString().substr(0,3) / blockId.ToString().substr(3)).c_str()); stream.seekg(0, stream.end); return stream.tellg(); } }; TEST_F(OnDiskBlockStoreTest, PhysicalBlockSize_zerophysical) { EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(0)); } TEST_F(OnDiskBlockStoreTest, PhysicalBlockSize_zerovirtual) { auto blockId = CreateBlockReturnKey(Data(0)); auto baseSize = getPhysicalBlockSize(blockId); EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(baseSize)); } TEST_F(OnDiskBlockStoreTest, PhysicalBlockSize_negativeboundaries) { // This tests that a potential if/else in blockSizeFromPhysicalBlockSize that catches negative values has the // correct boundary set. We test the highest value that is negative and the smallest value that is positive. auto physicalSizeForVirtualSizeZero = getPhysicalBlockSize(CreateBlockReturnKey(Data(0))); if (physicalSizeForVirtualSizeZero > 0) { EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero - 1)); } EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero)); EXPECT_EQ(1u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero + 1)); } TEST_F(OnDiskBlockStoreTest, PhysicalBlockSize_positive) { auto blockId = CreateBlockReturnKey(Data(10*1024)); auto baseSize = getPhysicalBlockSize(blockId); EXPECT_EQ(10*1024u, blockStore.blockSizeFromPhysicalBlockSize(baseSize)); } TEST_F(OnDiskBlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocksWithSameKeyPrefix) { const BlockId key1 = BlockId::FromString("4CE72ECDD20877A12ADBF4E3927C0A13"); const BlockId key2 = BlockId::FromString("4CE72ECDD20877A12ADBF4E3927C0A14"); EXPECT_TRUE(blockStore.tryCreate(key1, cpputils::Data(0))); EXPECT_TRUE(blockStore.tryCreate(key2, cpputils::Data(0))); EXPECT_EQ(2u, blockStore.numBlocks()); } test/blockstore/implementations/ondisk/OnDiskBlockTest/000077500000000000000000000000001347701267100237155ustar00rootroot00000000000000test/blockstore/implementations/ondisk/OnDiskBlockTest/OnDiskBlockCreateTest.cpp000066400000000000000000000057521347701267100305600ustar00rootroot00000000000000#include #include #include // TODO This should be ported to BlockStore2 /* using ::testing::Test; using ::testing::WithParamInterface; using ::testing::Values; using cpputils::Data; using cpputils::TempFile; using cpputils::TempDir; using cpputils::unique_ref; using namespace blockstore; using namespace blockstore::ondisk; namespace bf = boost::filesystem; class OnDiskBlockCreateTest: public Test { public: OnDiskBlockCreateTest() // Don't create the temp file yet (therefore pass false to the TempFile constructor) : dir(), key(BlockId::FromString("1491BB4932A389EE14BC7090AC772972")), file(dir.path() / blockId.ToString().substr(0,3) / blockId.ToString().substr(3), false) { } TempDir dir; BlockId key; TempFile file; }; TEST_F(OnDiskBlockCreateTest, CreatingBlockCreatesFile) { EXPECT_FALSE(bf::exists(file.path())); auto block = OnDiskBlock::CreateOnDisk(dir.path(), blockId, Data(0)); EXPECT_TRUE(bf::exists(file.path())); EXPECT_TRUE(bf::is_regular_file(file.path())); } TEST_F(OnDiskBlockCreateTest, CreatingExistingBlockReturnsNull) { auto block1 = OnDiskBlock::CreateOnDisk(dir.path(), blockId, Data(0)); auto block2 = OnDiskBlock::CreateOnDisk(dir.path(), blockId, Data(0)); EXPECT_TRUE((bool)block1); EXPECT_FALSE((bool)block2); } class OnDiskBlockCreateSizeTest: public OnDiskBlockCreateTest, public WithParamInterface { public: unique_ref block; Data ZEROES; OnDiskBlockCreateSizeTest(): block(OnDiskBlock::CreateOnDisk(dir.path(), blockId, Data(GetParam()).FillWithZeroes()).value()), ZEROES(block->size()) { ZEROES.FillWithZeroes(); } }; INSTANTIATE_TEST_CASE_P(OnDiskBlockCreateSizeTest, OnDiskBlockCreateSizeTest, Values(0, 1, 5, 1024, 10*1024*1024)); TEST_P(OnDiskBlockCreateSizeTest, OnDiskSizeIsCorrect) { Data fileContent = Data::LoadFromFile(file.path()).value(); EXPECT_EQ(GetParam() + OnDiskBlock::formatVersionHeaderSize(), fileContent.size()); } TEST_P(OnDiskBlockCreateSizeTest, OnDiskBlockIsZeroedOut) { Data fileContent = Data::LoadFromFile(file.path()).value(); Data fileContentWithoutHeader(fileContent.size() - OnDiskBlock::formatVersionHeaderSize()); std::memcpy(fileContentWithoutHeader.data(), fileContent.dataOffset(OnDiskBlock::formatVersionHeaderSize()), fileContentWithoutHeader.size()); EXPECT_EQ(ZEROES, fileContentWithoutHeader); } // This test is also tested by OnDiskBlockStoreTest, but there the block is created using the BlockStore interface. // Here, we create it using OnDiskBlock::CreateOnDisk() TEST_P(OnDiskBlockCreateSizeTest, InMemorySizeIsCorrect) { EXPECT_EQ(GetParam(), block->size()); } // This test is also tested by OnDiskBlockStoreTest, but there the block is created using the BlockStore interface. // Here, we create it using OnDiskBlock::CreateOnDisk() TEST_P(OnDiskBlockCreateSizeTest, InMemoryBlockIsZeroedOut) { EXPECT_EQ(0, std::memcmp(ZEROES.data(), block->data(), block->size())); } */test/blockstore/implementations/ondisk/OnDiskBlockTest/OnDiskBlockFlushTest.cpp000066400000000000000000000076561347701267100304430ustar00rootroot00000000000000#include #include #include #include // TODO This should be ported to BlockStore2 /* using ::testing::Test; using ::testing::WithParamInterface; using ::testing::Values; using cpputils::Data; using cpputils::DataFixture; using cpputils::TempFile; using cpputils::TempDir; using cpputils::unique_ref; using namespace blockstore; using namespace blockstore::ondisk; namespace bf = boost::filesystem; class OnDiskBlockFlushTest: public Test, public WithParamInterface { public: OnDiskBlockFlushTest() // Don't create the temp file yet (therefore pass false to the TempFile constructor) : dir(), key(BlockId::FromString("1491BB4932A389EE14BC7090AC772972")), file(dir.path() / blockId.ToString().substr(0,3) / blockId.ToString().substr(3), false), randomData(DataFixture::generate(GetParam())) { } TempDir dir; BlockId key; TempFile file; Data randomData; unique_ref CreateBlockAndLoadItFromDisk() { { OnDiskBlock::CreateOnDisk(dir.path(), blockId, randomData.copy()).value(); } return OnDiskBlock::LoadFromDisk(dir.path(), blockId).value(); } unique_ref CreateBlock() { return OnDiskBlock::CreateOnDisk(dir.path(), blockId, randomData.copy()).value(); } void WriteDataToBlock(const unique_ref &block) { block->write(randomData.data(), 0, randomData.size()); } void EXPECT_BLOCK_DATA_CORRECT(const unique_ref &block) { EXPECT_EQ(randomData.size(), block->size()); EXPECT_EQ(0, std::memcmp(randomData.data(), block->data(), randomData.size())); } void EXPECT_STORED_FILE_DATA_CORRECT() { Data fileContent = Data::LoadFromFile(file.path()).value(); Data fileContentWithoutHeader(fileContent.size() - OnDiskBlock::formatVersionHeaderSize()); std::memcpy(fileContentWithoutHeader.data(), fileContent.dataOffset(OnDiskBlock::formatVersionHeaderSize()), fileContentWithoutHeader.size()); EXPECT_EQ(randomData, fileContentWithoutHeader); } }; INSTANTIATE_TEST_CASE_P(OnDiskBlockFlushTest, OnDiskBlockFlushTest, Values((size_t)0, (size_t)1, (size_t)1024, (size_t)4096, (size_t)10*1024*1024)); // This test is also tested by OnDiskBlockStoreTest, but there the block is created using the BlockStore interface. // Here, we create it using OnDiskBlock::CreateOnDisk() TEST_P(OnDiskBlockFlushTest, AfterCreate_FlushingDoesntChangeBlock) { auto block = CreateBlock(); WriteDataToBlock(block); EXPECT_BLOCK_DATA_CORRECT(block); } // This test is also tested by OnDiskBlockStoreTest, but there the block is created using the BlockStore interface. // Here, we create it using OnDiskBlock::CreateOnDisk() / OnDiskBlock::LoadFromDisk() TEST_P(OnDiskBlockFlushTest, AfterLoad_FlushingDoesntChangeBlock) { auto block = CreateBlockAndLoadItFromDisk(); WriteDataToBlock(block); EXPECT_BLOCK_DATA_CORRECT(block); } TEST_P(OnDiskBlockFlushTest, AfterCreate_FlushingWritesCorrectData) { auto block = CreateBlock(); WriteDataToBlock(block); EXPECT_STORED_FILE_DATA_CORRECT(); } TEST_P(OnDiskBlockFlushTest, AfterLoad_FlushingWritesCorrectData) { auto block = CreateBlockAndLoadItFromDisk(); WriteDataToBlock(block); EXPECT_STORED_FILE_DATA_CORRECT(); } // This test is also tested by OnDiskBlockStoreTest, but there it can only checks block content by loading it again. // Here, we check the content on disk. TEST_P(OnDiskBlockFlushTest, AfterCreate_FlushesWhenDestructed) { { auto block = CreateBlock(); WriteDataToBlock(block); } EXPECT_STORED_FILE_DATA_CORRECT(); } // This test is also tested by OnDiskBlockStoreTest, but there it can only checks block content by loading it again. // Here, we check the content on disk. TEST_P(OnDiskBlockFlushTest, AfterLoad_FlushesWhenDestructed) { { auto block = CreateBlockAndLoadItFromDisk(); WriteDataToBlock(block); } EXPECT_STORED_FILE_DATA_CORRECT(); } */test/blockstore/implementations/ondisk/OnDiskBlockTest/OnDiskBlockLoadTest.cpp000066400000000000000000000043271347701267100302310ustar00rootroot00000000000000#include #include "blockstore/utils/FileDoesntExistException.h" #include #include #include #include #include #include // TODO This should be ported to BlockStore2 /* using ::testing::Test; using ::testing::WithParamInterface; using ::testing::Values; using std::ofstream; using std::ios; using cpputils::Data; using cpputils::DataFixture; using cpputils::TempFile; using cpputils::TempDir; using cpputils::unique_ref; using namespace blockstore; using namespace blockstore::ondisk; namespace bf = boost::filesystem; class OnDiskBlockLoadTest: public Test, public WithParamInterface { public: OnDiskBlockLoadTest(): dir(), key(BlockId::FromString("1491BB4932A389EE14BC7090AC772972")), file(dir.path() / blockId.ToString(), false) { } TempDir dir; BlockId key; TempFile file; void CreateBlockWithSize(size_t size) { Data data(size); OnDiskBlock::CreateOnDisk(dir.path(), blockId, std::move(data)); } void StoreData(Data data) { OnDiskBlock::CreateOnDisk(dir.path(), blockId, std::move(data)); } unique_ref LoadBlock() { return OnDiskBlock::LoadFromDisk(dir.path(), blockId).value(); } void EXPECT_BLOCK_DATA_EQ(const Data &expected, const OnDiskBlock &actual) { EXPECT_EQ(expected.size(), actual.size()); EXPECT_EQ(0, std::memcmp(expected.data(), actual.data(), expected.size())); } }; INSTANTIATE_TEST_CASE_P(OnDiskBlockLoadTest, OnDiskBlockLoadTest, Values(0, 1, 5, 1024, 10*1024*1024)); TEST_P(OnDiskBlockLoadTest, LoadsCorrectSize) { CreateBlockWithSize(GetParam()); auto block = LoadBlock(); EXPECT_EQ(GetParam(), block->size()); } TEST_P(OnDiskBlockLoadTest, LoadedDataIsCorrect) { Data randomData = DataFixture::generate(GetParam()); StoreData(randomData.copy()); auto block = LoadBlock(); EXPECT_BLOCK_DATA_EQ(randomData, *block); } TEST_F(OnDiskBlockLoadTest, LoadNotExistingBlock) { BlockId key2 = BlockId::FromString("272EE5517627CFA147A971A8E6E747E0"); EXPECT_EQ(boost::none, OnDiskBlock::LoadFromDisk(dir.path(), key2)); } */test/blockstore/implementations/parallelaccess/000077500000000000000000000000001347701267100224025ustar00rootroot00000000000000test/blockstore/implementations/parallelaccess/ParallelAccessBlockStoreTest_Generic.cpp000066400000000000000000000015451347701267100322550ustar00rootroot00000000000000#include "blockstore/implementations/parallelaccess/ParallelAccessBlockStore.h" #include "blockstore/implementations/testfake/FakeBlockStore.h" #include "../../testutils/BlockStoreTest.h" #include using blockstore::BlockStore; using blockstore::parallelaccess::ParallelAccessBlockStore; using blockstore::testfake::FakeBlockStore; using cpputils::make_unique_ref; using cpputils::unique_ref; class ParallelAccessBlockStoreTestFixture: public BlockStoreTestFixture { public: unique_ref createBlockStore() override { return make_unique_ref(make_unique_ref()); } }; INSTANTIATE_TYPED_TEST_CASE_P(ParallelAccess, BlockStoreTest, ParallelAccessBlockStoreTestFixture); //TODO Add specific tests ensuring that loading the same block twice doesn't load it twice from the underlying blockstore test/blockstore/implementations/parallelaccess/ParallelAccessBlockStoreTest_Specific.cpp000066400000000000000000000042311347701267100324210ustar00rootroot00000000000000#include #include "blockstore/implementations/parallelaccess/ParallelAccessBlockStore.h" #include "blockstore/implementations/testfake/FakeBlockStore.h" using ::testing::Test; using cpputils::Data; using blockstore::testfake::FakeBlockStore; using namespace blockstore::parallelaccess; class ParallelAccessBlockStoreTest: public Test { public: ParallelAccessBlockStoreTest(): baseBlockStore(new FakeBlockStore), blockStore(std::move(cpputils::nullcheck(std::unique_ptr(baseBlockStore)).value())) { } FakeBlockStore *baseBlockStore; ParallelAccessBlockStore blockStore; blockstore::BlockId CreateBlockReturnKey(const Data &initData) { return blockStore.create(initData)->blockId(); } }; TEST_F(ParallelAccessBlockStoreTest, PhysicalBlockSize_zerophysical) { EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(0)); } TEST_F(ParallelAccessBlockStoreTest, PhysicalBlockSize_zerovirtual) { auto blockId = CreateBlockReturnKey(Data(0)); auto base = baseBlockStore->load(blockId).value(); EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(base->size())); } TEST_F(ParallelAccessBlockStoreTest, PhysicalBlockSize_negativeboundaries) { // This tests that a potential if/else in blockSizeFromPhysicalBlockSize that catches negative values has the // correct boundary set. We test the highest value that is negative and the smallest value that is positive. auto physicalSizeForVirtualSizeZero = baseBlockStore->load(CreateBlockReturnKey(Data(0))).value()->size(); if (physicalSizeForVirtualSizeZero > 0) { EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero - 1)); } EXPECT_EQ(0u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero)); EXPECT_EQ(1u, blockStore.blockSizeFromPhysicalBlockSize(physicalSizeForVirtualSizeZero + 1)); } TEST_F(ParallelAccessBlockStoreTest, PhysicalBlockSize_positive) { auto blockId = CreateBlockReturnKey(Data(10*1024)); auto base = baseBlockStore->load(blockId).value(); EXPECT_EQ(10*1024u, blockStore.blockSizeFromPhysicalBlockSize(base->size())); } test/blockstore/implementations/testfake/000077500000000000000000000000001347701267100212325ustar00rootroot00000000000000test/blockstore/implementations/testfake/TestFakeBlockStoreTest.cpp000066400000000000000000000012341347701267100262740ustar00rootroot00000000000000#include "blockstore/implementations/testfake/FakeBlock.h" #include "blockstore/implementations/testfake/FakeBlockStore.h" #include "../../testutils/BlockStoreTest.h" #include #include using blockstore::BlockStore; using blockstore::testfake::FakeBlockStore; using cpputils::unique_ref; using cpputils::make_unique_ref; class FakeBlockStoreTestFixture: public BlockStoreTestFixture { public: unique_ref createBlockStore() override { return make_unique_ref(); } }; INSTANTIATE_TYPED_TEST_CASE_P(TestFake, BlockStoreTest, FakeBlockStoreTestFixture); test/blockstore/interface/000077500000000000000000000000001347701267100161545ustar00rootroot00000000000000test/blockstore/interface/BlockStore2Test.cpp000066400000000000000000000125061347701267100216550ustar00rootroot00000000000000#include "blockstore/interface/BlockStore2.h" #include #include #include using ::testing::Test; using ::testing::_; using ::testing::Return; using ::testing::Invoke; using ::testing::Eq; using ::testing::ByRef; using std::string; using cpputils::Data; using cpputils::DataFixture; using boost::optional; namespace boost { inline void PrintTo(const optional &, ::std::ostream *os) { *os << "optional"; } } using namespace blockstore; class BlockStore2Mock: public BlockStore2 { public: MOCK_CONST_METHOD0(createBlockId, BlockId()); MOCK_METHOD2(tryCreate, bool(const BlockId &blockId, const cpputils::Data &data)); MOCK_METHOD2(store, void(const BlockId &, const Data &data)); MOCK_CONST_METHOD1(load, optional(const BlockId &)); MOCK_METHOD1(remove, bool(const BlockId &)); MOCK_CONST_METHOD0(numBlocks, uint64_t()); MOCK_CONST_METHOD0(estimateNumFreeBytes, uint64_t()); MOCK_CONST_METHOD1(blockSizeFromPhysicalBlockSize, uint64_t(uint64_t)); MOCK_CONST_METHOD1(forEachBlock, void(std::function)); }; class BlockStore2Test: public Test { public: BlockStore2Test() :blockStoreMock(), blockStore(blockStoreMock), blockId1(BlockId::FromString("1491BB4932A389EE14BC7090AC772972")), blockId2(BlockId::FromString("AC772971491BB4932A389EE14BC7090A")), blockId3(BlockId::FromString("1BB4932A38AC77C7090A2971499EE14B")) {} BlockStore2Mock blockStoreMock; BlockStore2 &blockStore; const BlockId blockId1; const BlockId blockId2; const BlockId blockId3; Data createDataWithSize(size_t size) { Data fixture(DataFixture::generate(size)); Data data(size); std::memcpy(data.data(), fixture.data(), size); return data; } }; TEST_F(BlockStore2Test, DataIsPassedThrough0) { Data data = createDataWithSize(0); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, tryCreate(_, Eq(ByRef(data)))).WillOnce(Return(true)); EXPECT_EQ(blockId1, blockStore.create(data)); } TEST_F(BlockStore2Test, DataIsPassedThrough1) { Data data = createDataWithSize(1); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, tryCreate(_, Eq(ByRef(data)))).WillOnce(Return(true)); EXPECT_EQ(blockId1, blockStore.create(data)); } TEST_F(BlockStore2Test, DataIsPassedThrough1024) { Data data = createDataWithSize(1024); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, tryCreate(_, Eq(ByRef(data)))).WillOnce(Return(true)); EXPECT_EQ(blockId1, blockStore.create(data)); } TEST_F(BlockStore2Test, BlockIdIsCorrect) { Data data = createDataWithSize(1024); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, tryCreate(blockId1, _)).WillOnce(Return(true)); EXPECT_EQ(blockId1, blockStore.create(data)); } TEST_F(BlockStore2Test, TwoBlocksGetDifferentIds) { EXPECT_CALL(blockStoreMock, createBlockId()) .WillOnce(Return(blockId1)) .WillOnce(Return(blockId2)); EXPECT_CALL(blockStoreMock, tryCreate(_, _)) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId1, blockId); return true; })) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId2, blockId); return true; })); Data data = createDataWithSize(1024); EXPECT_EQ(blockId1, blockStore.create(data)); EXPECT_EQ(blockId2, blockStore.create(data)); } TEST_F(BlockStore2Test, WillTryADifferentIdIfKeyAlreadyExists) { Data data = createDataWithSize(1024); EXPECT_CALL(blockStoreMock, createBlockId()) .WillOnce(Return(blockId1)) .WillOnce(Return(blockId2)); EXPECT_CALL(blockStoreMock, tryCreate(_, Eq(ByRef(data)))) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId1, blockId); return false; })) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId2, blockId); return true; })); EXPECT_EQ(blockId2, blockStore.create(data)); } TEST_F(BlockStore2Test, WillTryADifferentIdIfIdAlreadyExistsTwoTimes) { Data data = createDataWithSize(1024); EXPECT_CALL(blockStoreMock, createBlockId()) .WillOnce(Return(blockId1)) .WillOnce(Return(blockId2)) .WillOnce(Return(blockId3)); EXPECT_CALL(blockStoreMock, tryCreate(_, Eq(ByRef(data)))) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId1, blockId); return false; })) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId2, blockId); return false; })) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId3, blockId); return true; })); EXPECT_EQ(blockId3, blockStore.create(data)); } test/blockstore/interface/BlockStoreTest.cpp000066400000000000000000000137451347701267100216010ustar00rootroot00000000000000#include "blockstore/interface/BlockStore.h" #include #include #include using ::testing::Test; using ::testing::_; using ::testing::Return; using ::testing::Invoke; using ::testing::Eq; using ::testing::ByRef; using std::string; using cpputils::Data; using cpputils::DataFixture; using cpputils::unique_ref; using boost::optional; using namespace blockstore; class BlockStoreMock: public BlockStore { public: MOCK_METHOD0(createBlockId, BlockId()); optional> tryCreate(const BlockId &blockId, Data data) { return cpputils::nullcheck(std::unique_ptr(do_create(blockId, data))); } MOCK_METHOD2(do_create, Block*(const BlockId &, const Data &data)); unique_ref overwrite(const BlockId &blockId, Data data) { return cpputils::nullcheck(std::unique_ptr(do_overwrite(blockId, data))).value(); } MOCK_METHOD2(do_overwrite, Block*(const BlockId &, const Data &data)); optional> load(const BlockId &blockId) { return cpputils::nullcheck(std::unique_ptr(do_load(blockId))); } MOCK_METHOD1(do_load, Block*(const BlockId &)); void remove(unique_ref block) {UNUSED(block);} MOCK_METHOD1(remove, void(const BlockId &)); MOCK_CONST_METHOD0(numBlocks, uint64_t()); MOCK_CONST_METHOD0(estimateNumFreeBytes, uint64_t()); MOCK_CONST_METHOD1(blockSizeFromPhysicalBlockSize, uint64_t(uint64_t)); MOCK_CONST_METHOD1(forEachBlock, void(std::function)); }; class BlockMock: public Block { public: BlockMock(): Block(BlockId::Random()) {} MOCK_CONST_METHOD0(data, const void*()); MOCK_METHOD3(write, void(const void*, uint64_t, uint64_t)); MOCK_METHOD0(flush, void()); MOCK_CONST_METHOD0(size, size_t()); MOCK_METHOD1(resize, void(size_t)); MOCK_CONST_METHOD0(blockId, const BlockId&()); }; class BlockStoreTest: public Test { public: BlockStoreTest() :blockStoreMock(), blockStore(blockStoreMock), blockId1(BlockId::FromString("1491BB4932A389EE14BC7090AC772972")), blockId2(BlockId::FromString("AC772971491BB4932A389EE14BC7090A")), blockId3(BlockId::FromString("1BB4932A38AC77C7090A2971499EE14B")) {} BlockStoreMock blockStoreMock; BlockStore &blockStore; const BlockId blockId1; const BlockId blockId2; const BlockId blockId3; Data createDataWithSize(size_t size) { Data fixture(DataFixture::generate(size)); Data data(size); std::memcpy(data.data(), fixture.data(), size); return data; } }; TEST_F(BlockStoreTest, DataIsPassedThrough0) { Data data = createDataWithSize(0); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, do_create(_, Eq(ByRef(data)))).WillOnce(Return(new BlockMock)); blockStore.create(data); } TEST_F(BlockStoreTest, DataIsPassedThrough1) { Data data = createDataWithSize(1); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, do_create(_, Eq(ByRef(data)))).WillOnce(Return(new BlockMock)); blockStore.create(data); } TEST_F(BlockStoreTest, DataIsPassedThrough1024) { Data data = createDataWithSize(1024); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, do_create(_, Eq(ByRef(data)))).WillOnce(Return(new BlockMock)); blockStore.create(data); } TEST_F(BlockStoreTest, BlockIdIsCorrect) { Data data = createDataWithSize(1024); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, do_create(blockId1, _)).WillOnce(Return(new BlockMock)); blockStore.create(data); } TEST_F(BlockStoreTest, TwoBlocksGetDifferentIds) { EXPECT_CALL(blockStoreMock, createBlockId()) .WillOnce(Return(blockId1)) .WillOnce(Return(blockId2)); EXPECT_CALL(blockStoreMock, do_create(_, _)) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId1, blockId); return new BlockMock; })) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId2, blockId); return new BlockMock; })); Data data = createDataWithSize(1024); blockStore.create(data); blockStore.create(data); } TEST_F(BlockStoreTest, WillTryADifferentIdIfKeyAlreadyExists) { Data data = createDataWithSize(1024); EXPECT_CALL(blockStoreMock, createBlockId()) .WillOnce(Return(blockId1)) .WillOnce(Return(blockId2)); EXPECT_CALL(blockStoreMock, do_create(_, Eq(ByRef(data)))) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId1, blockId); return nullptr; })) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId2, blockId); return new BlockMock; })); blockStore.create(data); } TEST_F(BlockStoreTest, WillTryADifferentIdIfIdAlreadyExistsTwoTimes) { Data data = createDataWithSize(1024); EXPECT_CALL(blockStoreMock, createBlockId()) .WillOnce(Return(blockId1)) .WillOnce(Return(blockId2)) .WillOnce(Return(blockId3)); EXPECT_CALL(blockStoreMock, do_create(_, Eq(ByRef(data)))) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId1, blockId); return nullptr; })) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId2, blockId); return nullptr; })) .WillOnce(Invoke([this](const BlockId &blockId, const Data &) { EXPECT_EQ(blockId3, blockId); return new BlockMock; })); blockStore.create(data); } test/blockstore/interface/BlockTest.cpp000066400000000000000000000002241347701267100205500ustar00rootroot00000000000000/* * Tests that the header can be included without needing additional header includes as dependencies. */ #include "blockstore/interface/Block.h" test/blockstore/testutils/000077500000000000000000000000001347701267100162545ustar00rootroot00000000000000test/blockstore/testutils/BlockStore2Test.h000066400000000000000000000553511347701267100214270ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_TESTUTILS_BLOCKSTORE2TEST_H_ #define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_TESTUTILS_BLOCKSTORE2TEST_H_ #include #include #include #include #include "blockstore/interface/BlockStore2.h" namespace boost { inline void PrintTo(const optional &, ::std::ostream *os) { *os << "optional"; } } class BlockStore2TestFixture { public: virtual ~BlockStore2TestFixture() {} virtual cpputils::unique_ref createBlockStore() = 0; }; template class BlockStore2Test: public ::testing::Test { public: BlockStore2Test() :fixture(), blockStore(this->fixture.createBlockStore()) {} BOOST_STATIC_ASSERT_MSG( (std::is_base_of::value), "Given test fixture for instantiating the (type parameterized) BlockStoreTest must inherit from BlockStoreTestFixture" ); ConcreteBlockStoreTestFixture fixture; cpputils::unique_ref blockStore; template void EXPECT_UNORDERED_EQ(const std::vector &expected, std::vector actual) { EXPECT_EQ(expected.size(), actual.size()); for (const Entry &expectedEntry : expected) { removeOne(&actual, expectedEntry); } } template void removeOne(std::vector *entries, const Entry &toRemove) { auto found = std::find(entries->begin(), entries->end(), toRemove); if (found != entries->end()) { entries->erase(found); return; } EXPECT_TRUE(false); } }; TYPED_TEST_CASE_P(BlockStore2Test); TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCallingTryCreateOnExistingBlock_thenFails) { blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024)); EXPECT_FALSE(this->blockStore->tryCreate(blockId, cpputils::Data(1024))); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds) { blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_TRUE(this->blockStore->tryCreate(blockId, cpputils::Data(1024))); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds) { this->blockStore->create(cpputils::Data(512)); blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_TRUE(this->blockStore->tryCreate(blockId, cpputils::Data(1024))); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenLoadExistingBlock_thenSucceeds) { blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024)); EXPECT_NE(boost::none, this->blockStore->load(blockId)); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenLoadNonexistingBlock_thenFails) { const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_EQ(boost::none, this->blockStore->load(blockId)); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenLoadNonexistingBlock_thenFails) { this->blockStore->create(cpputils::Data(512)); const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_EQ(boost::none, this->blockStore->load(blockId)); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringExistingBlock_thenSucceeds) { blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024)); this->blockStore->store(blockId, cpputils::Data(1024)); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds) { const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); this->blockStore->store(blockId, cpputils::Data(1024)); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds) { this->blockStore->create(cpputils::Data(512)); const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); this->blockStore->store(blockId, cpputils::Data(1024)); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCreatingTwoBlocks_thenTheyGetDifferentKeys) { blockstore::BlockId blockId1 = this->blockStore->create(cpputils::Data(1024)); blockstore::BlockId blockId2 = this->blockStore->create(cpputils::Data(1024)); EXPECT_NE(blockId1, blockId2); } TYPED_TEST_P(BlockStore2Test, givenOtherwiseEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore) { blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024)); EXPECT_NE(boost::none, this->blockStore->load(blockId)); EXPECT_TRUE(this->blockStore->remove(blockId)); EXPECT_EQ(boost::none, this->blockStore->load(blockId)); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore) { blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024)); this->blockStore->create(cpputils::Data(512)); EXPECT_NE(boost::none, this->blockStore->load(blockId)); EXPECT_TRUE(this->blockStore->remove(blockId)); EXPECT_EQ(boost::none, this->blockStore->load(blockId)); } TYPED_TEST_P(BlockStore2Test, givenOtherwiseEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds) { blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024)); EXPECT_TRUE(this->blockStore->remove(blockId)); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds) { blockstore::BlockId blockId = this->blockStore->create(cpputils::Data(1024)); this->blockStore->create(cpputils::Data(512)); EXPECT_EQ(true, this->blockStore->remove(blockId)); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenRemovingNonexistingBlock_thenFails) { const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); auto result = this->blockStore->remove(blockId); EXPECT_EQ(false, result); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenRemovingNonexistingBlock_thenFails) { blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772973"); blockstore::BlockId differentKey = blockstore::BlockId::FromString("290AC2C7097274A389EE14B91B72B493"); ASSERT_TRUE(this->blockStore->tryCreate(blockId, cpputils::Data(1024))); EXPECT_EQ(false, this->blockStore->remove(differentKey)); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads) { auto blockId = this->blockStore->create(cpputils::Data(0)); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(0u, loaded.size()); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads) { this->blockStore->create(cpputils::Data(512)); auto blockId = this->blockStore->create(cpputils::Data(0)); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(0u, loaded.size()); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads) { cpputils::Data data = cpputils::DataFixture::generate(1024); auto blockId = this->blockStore->create(data.copy()); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(loaded, data); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads) { this->blockStore->create(cpputils::Data(512)); cpputils::Data data = cpputils::DataFixture::generate(1024); auto blockId = this->blockStore->create(data.copy()); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(loaded, data); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenTryCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads) { blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772973"); ASSERT_TRUE(this->blockStore->tryCreate(blockId, cpputils::Data(0))); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(0u, loaded.size()); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenTryCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads) { blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772973"); this->blockStore->create(cpputils::Data(512)); ASSERT_TRUE(this->blockStore->tryCreate(blockId, cpputils::Data(0))); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(0u, loaded.size()); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenTryCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads) { blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772973"); cpputils::Data data = cpputils::DataFixture::generate(1024); ASSERT_TRUE(this->blockStore->tryCreate(blockId, data.copy())); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(loaded, data); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenTryCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads) { blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772973"); this->blockStore->create(cpputils::Data(512)); cpputils::Data data = cpputils::DataFixture::generate(1024); ASSERT_TRUE(this->blockStore->tryCreate(blockId, data.copy())); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(loaded, data); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads) { const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); this->blockStore->store(blockId, cpputils::Data(0)); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(0u, loaded.size()); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads) { this->blockStore->create(cpputils::Data(512)); const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); this->blockStore->store(blockId, cpputils::Data(0)); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(0u, loaded.size()); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads) { const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); cpputils::Data data = cpputils::DataFixture::generate(1024); this->blockStore->store(blockId, data.copy()); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(data, loaded); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads) { this->blockStore->create(cpputils::Data(512)); const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); cpputils::Data data = cpputils::DataFixture::generate(1024); this->blockStore->store(blockId, data.copy()); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(data, loaded); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads) { auto blockId = this->blockStore->create(cpputils::Data(512)); this->blockStore->store(blockId, cpputils::Data(0)); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(0u, loaded.size()); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads) { this->blockStore->create(cpputils::Data(512)); auto blockId = this->blockStore->create(cpputils::Data(512)); this->blockStore->store(blockId, cpputils::Data(0)); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(0u, loaded.size()); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads) { auto blockId = this->blockStore->create(cpputils::Data(512)); cpputils::Data data = cpputils::DataFixture::generate(1024); this->blockStore->store(blockId, data.copy()); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(data, loaded); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads) { this->blockStore->create(cpputils::Data(512)); auto blockId = this->blockStore->create(cpputils::Data(512)); cpputils::Data data = cpputils::DataFixture::generate(1024); this->blockStore->store(blockId, data.copy()); auto loaded = this->blockStore->load(blockId).value(); EXPECT_EQ(data, loaded); } TYPED_TEST_P(BlockStore2Test, givenEmptyBlockStore_whenLoadingNonExistingBlock_thenFails) { const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_EQ(boost::none, this->blockStore->load(blockId)); } TYPED_TEST_P(BlockStore2Test, givenNonEmptyBlockStore_whenLoadingNonExistingBlock_thenFails) { this->blockStore->create(cpputils::Data(512)); const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_EQ(boost::none, this->blockStore->load(blockId)); } TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectOnEmptyBlockstore) { auto blockStore = this->fixture.createBlockStore(); EXPECT_EQ(0u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterCreatingOneBlock) { auto blockStore = this->fixture.createBlockStore(); blockStore->create(cpputils::Data(1)); EXPECT_EQ(1u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterRemovingTheLastBlock) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockStore->create(cpputils::Data(1)); EXPECT_TRUE(blockStore->remove(blockId)); EXPECT_EQ(0u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterCreatingTwoBlocks) { auto blockStore = this->fixture.createBlockStore(); blockStore->create(cpputils::Data(1)); blockStore->create(cpputils::Data(0)); EXPECT_EQ(2u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterRemovingABlock) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockStore->create(cpputils::Data(1)); blockStore->create(cpputils::Data(1)); EXPECT_TRUE(blockStore->remove(blockId)); EXPECT_EQ(1u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterStoringANewBlock) { const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); auto blockStore = this->fixture.createBlockStore(); blockStore->store(blockId, cpputils::Data(1)); EXPECT_EQ(1u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStore2Test, NumBlocksIsCorrectAfterStoringAnExistingBlock) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockStore->create(cpputils::Data(1)); blockStore->store(blockId, cpputils::Data(1)); EXPECT_EQ(1u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStore2Test, ForEachBlock_zeroblocks) { auto blockStore = this->fixture.createBlockStore(); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStore2Test, ForEachBlock_oneblock) { auto blockStore = this->fixture.createBlockStore(); auto blockId = blockStore->create(cpputils::Data(1)); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({blockId}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStore2Test, ForEachBlock_twoblocks) { auto blockStore = this->fixture.createBlockStore(); auto blockId1 = blockStore->create(cpputils::Data(1)); auto blockId2 = blockStore->create(cpputils::Data(1)); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({blockId1, blockId2}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStore2Test, ForEachBlock_threeblocks) { auto blockStore = this->fixture.createBlockStore(); auto blockId1 = blockStore->create(cpputils::Data(1)); auto blockId2 = blockStore->create(cpputils::Data(1)); auto blockId3 = blockStore->create(cpputils::Data(1)); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({blockId1, blockId2, blockId3}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStore2Test, ForEachBlock_doesntListRemovedBlocks_oneblock) { auto blockStore = this->fixture.createBlockStore(); auto blockId1 = blockStore->create(cpputils::Data(1)); EXPECT_TRUE(blockStore->remove(blockId1)); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStore2Test, ForEachBlock_doesntListRemovedBlocks_twoblocks) { auto blockStore = this->fixture.createBlockStore(); auto blockId1 = blockStore->create(cpputils::Data(1)); auto blockId2 = blockStore->create(cpputils::Data(1)); EXPECT_TRUE(blockStore->remove(blockId1)); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({blockId2}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStore2Test, TryCreateTwoBlocksWithSameBlockIdAndSameSize) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_TRUE(blockStore->tryCreate(blockId, cpputils::Data(1024))); EXPECT_FALSE(blockStore->tryCreate(blockId, cpputils::Data(1024))); } TYPED_TEST_P(BlockStore2Test, TryCreateTwoBlocksWithSameBlockIdAndDifferentSize) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_TRUE(blockStore->tryCreate(blockId, cpputils::Data(1024))); EXPECT_FALSE(blockStore->tryCreate(blockId, cpputils::Data(4096))); } TYPED_TEST_P(BlockStore2Test, TryCreateTwoBlocksWithSameBlockIdAndFirstNullSize) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_TRUE(blockStore->tryCreate(blockId, cpputils::Data(0))); EXPECT_FALSE(blockStore->tryCreate(blockId, cpputils::Data(1024))); } /* TYPED_TEST_P(BlockStore2Test, TryCreateTwoBlocksWithSameBlockIdAndSecondNullSize) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_TRUE(blockStore->tryCreate(blockId, cpputils::Data(1024))); EXPECT_FALSE(blockStore->tryCreate(blockId, cpputils::Data(0))); } TYPED_TEST_P(BlockStore2Test, TryCreateTwoBlocksWithSameBlockIdAndBothNullSize) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); EXPECT_TRUE(blockStore->tryCreate(blockId, cpputils::Data(0))); EXPECT_FALSE(blockStore->tryCreate(blockId, cpputils::Data(0))); }*/ REGISTER_TYPED_TEST_CASE_P(BlockStore2Test, givenNonEmptyBlockStore_whenCallingTryCreateOnExistingBlock_thenFails, givenEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds, givenNonEmptyBlockStore_whenCallingTryCreateOnNonExistingBlock_thenSucceeds, givenNonEmptyBlockStore_whenLoadExistingBlock_thenSucceeds, givenEmptyBlockStore_whenLoadNonexistingBlock_thenFails, givenNonEmptyBlockStore_whenLoadNonexistingBlock_thenFails, givenNonEmptyBlockStore_whenStoringExistingBlock_thenSucceeds, givenEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds, givenNonEmptyBlockStore_whenStoringNonexistingBlock_thenSucceeds, givenEmptyBlockStore_whenCreatingTwoBlocks_thenTheyGetDifferentKeys, givenOtherwiseEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore, givenNonEmptyBlockStore_whenRemovingBlock_thenBlockIsNotLoadableAnymore, givenOtherwiseEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds, givenNonEmptyBlockStore_whenRemovingExistingBlock_thenSucceeds, givenEmptyBlockStore_whenRemovingNonexistingBlock_thenFails, givenNonEmptyBlockStore_whenRemovingNonexistingBlock_thenFails, givenEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads, givenNonEmptyBlockStore_whenCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads, givenEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads, givenNonEmptyBlockStore_whenCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads, givenEmptyBlockStore_whenTryCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads, givenNonEmptyBlockStore_whenTryCreatingAndLoadingEmptyBlock_thenCorrectBlockLoads, givenEmptyBlockStore_whenTryCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads, givenNonEmptyBlockStore_whenTryCreatingAndLoadingNonEmptyBlock_thenCorrectBlockLoads, givenEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads, givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingEmptyBlock_thenCorrectBlockLoads, givenEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads, givenNonEmptyBlockStore_whenStoringAndLoadingNonExistingNonEmptyBlock_thenCorrectBlockLoads, givenEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads, givenNonEmptyBlockStore_whenStoringAndLoadingExistingEmptyBlock_thenCorrectBlockLoads, givenEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads, givenNonEmptyBlockStore_whenStoringAndLoadingExistingNonEmptyBlock_thenCorrectBlockLoads, givenEmptyBlockStore_whenLoadingNonExistingBlock_thenFails, givenNonEmptyBlockStore_whenLoadingNonExistingBlock_thenFails, NumBlocksIsCorrectOnEmptyBlockstore, NumBlocksIsCorrectAfterCreatingOneBlock, NumBlocksIsCorrectAfterRemovingTheLastBlock, NumBlocksIsCorrectAfterCreatingTwoBlocks, NumBlocksIsCorrectAfterRemovingABlock, NumBlocksIsCorrectAfterStoringANewBlock, NumBlocksIsCorrectAfterStoringAnExistingBlock, ForEachBlock_zeroblocks, ForEachBlock_oneblock, ForEachBlock_twoblocks, ForEachBlock_threeblocks, ForEachBlock_doesntListRemovedBlocks_oneblock, ForEachBlock_doesntListRemovedBlocks_twoblocks, TryCreateTwoBlocksWithSameBlockIdAndSameSize, TryCreateTwoBlocksWithSameBlockIdAndDifferentSize, TryCreateTwoBlocksWithSameBlockIdAndFirstNullSize //TODO Just disabled because gtest doesn't allow more template parameters. Fix and reenable! // see https://github.com/google/googletest/issues/1267 //TryCreateTwoBlocksWithSameBlockIdAndSecondNullSize, //TryCreateTwoBlocksWithSameBlockIdAndBothNullSize ); #endif test/blockstore/testutils/BlockStoreTest.h000066400000000000000000000400351347701267100213360ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_TESTUTILS_BLOCKSTORETEST_H_ #define MESSMER_BLOCKSTORE_TEST_IMPLEMENTATIONS_TESTUTILS_BLOCKSTORETEST_H_ #include #include #include #include #include "blockstore/interface/BlockStore.h" class MockForEachBlockCallback final { public: std::function callback() { return [this] (const blockstore::BlockId &blockId) { called_with.push_back(blockId); }; } std::vector called_with; }; class BlockStoreTestFixture { public: virtual ~BlockStoreTestFixture() {} virtual cpputils::unique_ref createBlockStore() = 0; }; template class BlockStoreTest: public ::testing::Test { public: BlockStoreTest() :fixture() {} BOOST_STATIC_ASSERT_MSG( (std::is_base_of::value), "Given test fixture for instantiating the (type parameterized) BlockStoreTest must inherit from BlockStoreTestFixture" ); ConcreteBlockStoreTestFixture fixture; void TestBlockIsUsable(cpputils::unique_ref block, blockstore::BlockStore *blockStore) { // Write full block space and check it was correctly written cpputils::Data fixture = cpputils::DataFixture::generate(block->size()); block->write(fixture.data(), 0, fixture.size()); EXPECT_EQ(0, std::memcmp(fixture.data(), block->data(), fixture.size())); // Store and reload block and check data is still correct auto blockId = block->blockId(); cpputils::destruct(std::move(block)); block = blockStore->load(blockId).value(); EXPECT_EQ(0, std::memcmp(fixture.data(), block->data(), fixture.size())); } template void EXPECT_UNORDERED_EQ(const std::vector &expected, std::vector actual) { EXPECT_EQ(expected.size(), actual.size()); for (const Entry &expectedEntry : expected) { removeOne(&actual, expectedEntry); } } template void removeOne(std::vector *entries, const Entry &toRemove) { auto found = std::find(entries->begin(), entries->end(), toRemove); if (found != entries->end()) { entries->erase(found); return; } EXPECT_TRUE(false); } }; TYPED_TEST_CASE_P(BlockStoreTest); TYPED_TEST_P(BlockStoreTest, TwoCreatedBlocksHaveDifferentBlockIds) { auto blockStore = this->fixture.createBlockStore(); auto block1 = blockStore->create(cpputils::Data(1024)); auto block2 = blockStore->create(cpputils::Data(1024)); EXPECT_NE(block1->blockId(), block2->blockId()); } TYPED_TEST_P(BlockStoreTest, BlockIsNotLoadableAfterDeleting_DeleteByBlock) { auto blockStore = this->fixture.createBlockStore(); auto blockId = blockStore->create(cpputils::Data(1024))->blockId(); auto block = blockStore->load(blockId); EXPECT_NE(boost::none, block); blockStore->remove(std::move(*block)); EXPECT_EQ(boost::none, blockStore->load(blockId)); } TYPED_TEST_P(BlockStoreTest, BlockIsNotLoadableAfterDeleting_DeleteByBlockId) { auto blockStore = this->fixture.createBlockStore(); auto blockId = blockStore->create(cpputils::Data(1024))->blockId(); blockStore->remove(blockId); EXPECT_EQ(boost::none, blockStore->load(blockId)); } TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectOnEmptyBlockstore) { auto blockStore = this->fixture.createBlockStore(); EXPECT_EQ(0u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingOneBlock) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(1)); EXPECT_EQ(1u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingOneBlock_AfterClosingBlock) { auto blockStore = this->fixture.createBlockStore(); blockStore->create(cpputils::Data(1)); EXPECT_EQ(1u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterRemovingTheLastBlock_DeleteByBlock) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(1)); blockStore->remove(std::move(block)); EXPECT_EQ(0u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterRemovingTheLastBlock_DeleteByBlockId) { auto blockStore = this->fixture.createBlockStore(); auto blockId = blockStore->create(cpputils::Data(1))->blockId(); blockStore->remove(blockId); EXPECT_EQ(0u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks) { auto blockStore = this->fixture.createBlockStore(); auto block1 = blockStore->create(cpputils::Data(1)); auto block2 = blockStore->create(cpputils::Data(0)); EXPECT_EQ(2u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingFirstBlock) { auto blockStore = this->fixture.createBlockStore(); blockStore->create(cpputils::Data(1)); auto block2 = blockStore->create(cpputils::Data(0)); EXPECT_EQ(2u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingSecondBlock) { auto blockStore = this->fixture.createBlockStore(); auto block1 = blockStore->create(cpputils::Data(1)); blockStore->create(cpputils::Data(0)); EXPECT_EQ(2u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingBothBlocks) { auto blockStore = this->fixture.createBlockStore(); blockStore->create(cpputils::Data(1)); blockStore->create(cpputils::Data(0)); EXPECT_EQ(2u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterRemovingABlock_DeleteByBlock) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(1)); blockStore->create(cpputils::Data(1)); blockStore->remove(std::move(block)); EXPECT_EQ(1u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, NumBlocksIsCorrectAfterRemovingABlock_DeleteByBlockId) { auto blockStore = this->fixture.createBlockStore(); auto blockId = blockStore->create(cpputils::Data(1))->blockId(); blockStore->create(cpputils::Data(1)); blockStore->remove(blockId); EXPECT_EQ(1u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, CanRemoveModifiedBlock) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(5)); block->write("data", 0, 4); blockStore->remove(std::move(block)); EXPECT_EQ(0u, blockStore->numBlocks()); } TYPED_TEST_P(BlockStoreTest, ForEachBlock_zeroblocks) { auto blockStore = this->fixture.createBlockStore(); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStoreTest, ForEachBlock_oneblock) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(1)); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({block->blockId()}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStoreTest, ForEachBlock_twoblocks) { auto blockStore = this->fixture.createBlockStore(); auto block1 = blockStore->create(cpputils::Data(1)); auto block2 = blockStore->create(cpputils::Data(1)); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({block1->blockId(), block2->blockId()}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStoreTest, ForEachBlock_threeblocks) { auto blockStore = this->fixture.createBlockStore(); auto block1 = blockStore->create(cpputils::Data(1)); auto block2 = blockStore->create(cpputils::Data(1)); auto block3 = blockStore->create(cpputils::Data(1)); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({block1->blockId(), block2->blockId(), block3->blockId()}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStoreTest, ForEachBlock_doesntListRemovedBlocks_oneblock) { auto blockStore = this->fixture.createBlockStore(); auto block1 = blockStore->create(cpputils::Data(1)); blockStore->remove(std::move(block1)); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStoreTest, ForEachBlock_doesntListRemovedBlocks_twoblocks) { auto blockStore = this->fixture.createBlockStore(); auto block1 = blockStore->create(cpputils::Data(1)); auto block2 = blockStore->create(cpputils::Data(1)); blockStore->remove(std::move(block1)); MockForEachBlockCallback mockForEachBlockCallback; blockStore->forEachBlock(mockForEachBlockCallback.callback()); this->EXPECT_UNORDERED_EQ({block2->blockId()}, mockForEachBlockCallback.called_with); } TYPED_TEST_P(BlockStoreTest, Resize_Larger_FromZero) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(0)); block->resize(10); EXPECT_EQ(10u, block->size()); } TYPED_TEST_P(BlockStoreTest, Resize_Larger_FromZero_BlockIsStillUsable) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(0)); block->resize(10); this->TestBlockIsUsable(std::move(block), blockStore.get()); } TYPED_TEST_P(BlockStoreTest, Resize_Larger) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(10)); block->resize(20); EXPECT_EQ(20u, block->size()); } TYPED_TEST_P(BlockStoreTest, Resize_Larger_BlockIsStillUsable) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(10)); block->resize(20); this->TestBlockIsUsable(std::move(block), blockStore.get()); } TYPED_TEST_P(BlockStoreTest, Resize_Smaller) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(10)); block->resize(5); EXPECT_EQ(5u, block->size()); } TYPED_TEST_P(BlockStoreTest, Resize_Smaller_BlockIsStillUsable) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(10)); block->resize(5); this->TestBlockIsUsable(std::move(block), blockStore.get()); } TYPED_TEST_P(BlockStoreTest, Resize_Smaller_ToZero) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(10)); block->resize(0); EXPECT_EQ(0u, block->size()); } TYPED_TEST_P(BlockStoreTest, Resize_Smaller_ToZero_BlockIsStillUsable) { auto blockStore = this->fixture.createBlockStore(); auto block = blockStore->create(cpputils::Data(10)); block->resize(0); this->TestBlockIsUsable(std::move(block), blockStore.get()); } /* TYPED_TEST_P(BlockStoreTest, TryCreateTwoBlocksWithSameBlockIdAndSameSize) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); auto block = blockStore->tryCreate(blockId, cpputils::Data(1024)); (*block)->flush(); //TODO Ideally, flush shouldn't be necessary here. auto block2 = blockStore->tryCreate(blockId, cpputils::Data(1024)); EXPECT_NE(boost::none, block); EXPECT_EQ(boost::none, block2); } TYPED_TEST_P(BlockStoreTest, TryCreateTwoBlocksWithSameBlockIdAndDifferentSize) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); auto block = blockStore->tryCreate(blockId, cpputils::Data(1024)); (*block)->flush(); //TODO Ideally, flush shouldn't be necessary here. auto block2 = blockStore->tryCreate(blockId, cpputils::Data(4096)); EXPECT_NE(boost::none, block); EXPECT_EQ(boost::none, block2); } TYPED_TEST_P(BlockStoreTest, TryCreateTwoBlocksWithSameBlockIdAndFirstNullSize) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); auto block = blockStore->tryCreate(blockId, cpputils::Data(0)); (*block)->flush(); //TODO Ideally, flush shouldn't be necessary here. auto block2 = blockStore->tryCreate(blockId, cpputils::Data(1024)); EXPECT_NE(boost::none, block); EXPECT_EQ(boost::none, block2); } TYPED_TEST_P(BlockStoreTest, TryCreateTwoBlocksWithSameBlockIdAndSecondNullSize) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); auto block = blockStore->tryCreate(blockId, cpputils::Data(1024)); (*block)->flush(); //TODO Ideally, flush shouldn't be necessary here. auto block2 = blockStore->tryCreate(blockId, cpputils::Data(0)); EXPECT_NE(boost::none, block); EXPECT_EQ(boost::none, block2); } TYPED_TEST_P(BlockStoreTest, TryCreateTwoBlocksWithSameBlockIdAndBothNullSize) { auto blockStore = this->fixture.createBlockStore(); blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); auto block = blockStore->tryCreate(blockId, cpputils::Data(0)); (*block)->flush(); //TODO Ideally, flush shouldn't be necessary here. auto block2 = blockStore->tryCreate(blockId, cpputils::Data(0)); EXPECT_NE(boost::none, block); EXPECT_EQ(boost::none, block2); }*/ #include "BlockStoreTest_Size.h" #include "BlockStoreTest_Data.h" REGISTER_TYPED_TEST_CASE_P(BlockStoreTest, CreatedBlockHasCorrectSize, LoadingUnchangedBlockHasCorrectSize, CreatedBlockData, LoadingUnchangedBlockData, LoadedBlockIsCorrect, // LoadedBlockIsCorrectWhenLoadedDirectlyAfterFlushing, AfterCreate_FlushingDoesntChangeBlock, AfterLoad_FlushingDoesntChangeBlock, AfterCreate_FlushesWhenDestructed, AfterLoad_FlushesWhenDestructed, LoadNonExistingBlock, TwoCreatedBlocksHaveDifferentBlockIds, BlockIsNotLoadableAfterDeleting_DeleteByBlock, BlockIsNotLoadableAfterDeleting_DeleteByBlockId, NumBlocksIsCorrectOnEmptyBlockstore, NumBlocksIsCorrectAfterAddingOneBlock, NumBlocksIsCorrectAfterAddingOneBlock_AfterClosingBlock, NumBlocksIsCorrectAfterRemovingTheLastBlock_DeleteByBlock, NumBlocksIsCorrectAfterRemovingTheLastBlock_DeleteByBlockId, NumBlocksIsCorrectAfterAddingTwoBlocks, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingFirstBlock, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingSecondBlock, NumBlocksIsCorrectAfterAddingTwoBlocks_AfterClosingBothBlocks, NumBlocksIsCorrectAfterRemovingABlock_DeleteByBlock, NumBlocksIsCorrectAfterRemovingABlock_DeleteByBlockId, WriteAndReadImmediately, WriteAndReadAfterLoading, WriteTwiceAndRead, OverwriteSameSizeAndReadImmediately, OverwriteSameSizeAndReadAfterLoading, OverwriteSmallerSizeAndReadImmediately, OverwriteSmallerSizeAndReadAfterLoading, OverwriteLargerSizeAndReadAfterLoading, OverwriteLargerSizeAndReadImmediately, OverwriteNonexistingAndReadAfterLoading, OverwriteNonexistingAndReadImmediately, CanRemoveModifiedBlock, ForEachBlock_zeroblocks, ForEachBlock_oneblock, ForEachBlock_twoblocks, ForEachBlock_threeblocks, ForEachBlock_doesntListRemovedBlocks_oneblock, ForEachBlock_doesntListRemovedBlocks_twoblocks, Resize_Larger_FromZero, Resize_Larger_FromZero_BlockIsStillUsable, Resize_Larger, Resize_Larger_BlockIsStillUsable, Resize_Smaller, Resize_Smaller_BlockIsStillUsable, Resize_Smaller_ToZero, Resize_Smaller_ToZero_BlockIsStillUsable //TODO Just disabled because gtest doesn't allow more template parameters. Fix and reenable! // see https://github.com/google/googletest/issues/1267 //TryCreateTwoBlocksWithSameBlockIdAndSameSize, //TryCreateTwoBlocksWithSameBlockIdAndDifferentSize, //TryCreateTwoBlocksWithSameBlockIdAndFirstNullSize, //TryCreateTwoBlocksWithSameBlockIdAndSecondNullSize, //TryCreateTwoBlocksWithSameBlockIdAndBothNullSize, ); #endif test/blockstore/testutils/BlockStoreTest_Data.h000066400000000000000000000177541347701267100223030ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_TEST_TESTUTILS_BLOCKSTORETEST_DATA_H_ #define MESSMER_BLOCKSTORE_TEST_TESTUTILS_BLOCKSTORETEST_DATA_H_ // This file is meant to be included by BlockStoreTest.h only struct DataRange { uint64_t blocksize; uint64_t offset; uint64_t count; }; class BlockStoreDataParametrizedTest { public: BlockStoreDataParametrizedTest(cpputils::unique_ref blockStore_, const DataRange &testData_) : blockStore(std::move(blockStore_)), testData(testData_), foregroundData(cpputils::DataFixture::generate(testData.count, 0)), backgroundData(cpputils::DataFixture::generate(testData.blocksize, 1)) { } void TestWriteAndReadImmediately() { auto block = blockStore->create(cpputils::Data(testData.blocksize).FillWithZeroes()); block->write(foregroundData.data(), testData.offset, testData.count); EXPECT_DATA_READS_AS(foregroundData, *block, testData.offset, testData.count); EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*block, testData.offset, testData.count); } void TestWriteAndReadAfterLoading() { blockstore::BlockId blockId = CreateBlockWriteToItAndReturnKey(foregroundData); auto loaded_block = blockStore->load(blockId).value(); EXPECT_DATA_READS_AS(foregroundData, *loaded_block, testData.offset, testData.count); EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(*loaded_block, testData.offset, testData.count); } void TestWriteTwiceAndRead() { auto block = blockStore->create(cpputils::Data(testData.blocksize)); block->write(backgroundData.data(), 0, testData.blocksize); block->write(foregroundData.data(), testData.offset, testData.count); EXPECT_DATA_READS_AS(foregroundData, *block, testData.offset, testData.count); EXPECT_DATA_READS_AS_OUTSIDE_OF(backgroundData, *block, testData.offset, testData.count); } void TestOverwriteSameSizeAndReadImmediately() { auto blockId = blockStore->create(cpputils::Data(testData.blocksize))->blockId(); auto block = blockStore->overwrite(blockId, backgroundData.copy()); EXPECT_EQ(testData.blocksize, block->size()); EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize); } void TestOverwriteSameSizeAndReadAfterLoading() { auto blockId = blockStore->create(cpputils::Data(testData.blocksize))->blockId(); blockStore->overwrite(blockId, backgroundData.copy()); auto block = blockStore->load(blockId).value(); EXPECT_EQ(testData.blocksize, block->size()); EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize); } void TestOverwriteSmallerSizeAndReadImmediately() { auto blockId = blockStore->create(cpputils::Data(testData.blocksize))->blockId(); auto block = blockStore->overwrite(blockId, foregroundData.copy()); EXPECT_EQ(testData.count, block->size()); EXPECT_DATA_READS_AS(foregroundData, *block, 0, testData.count); } void TestOverwriteSmallerSizeAndReadAfterLoading() { auto blockId = blockStore->create(cpputils::Data(testData.blocksize))->blockId(); blockStore->overwrite(blockId, foregroundData.copy()); auto block = blockStore->load(blockId).value(); EXPECT_EQ(testData.count, block->size()); EXPECT_DATA_READS_AS(foregroundData, *block, 0, testData.count); } void TestOverwriteLargerSizeAndReadImmediately() { auto blockId = blockStore->create(cpputils::Data(testData.count))->blockId(); auto block = blockStore->overwrite(blockId, backgroundData.copy()); EXPECT_EQ(testData.blocksize, block->size()); EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize); } void TestOverwriteLargerSizeAndReadAfterLoading() { auto blockId = blockStore->create(cpputils::Data(testData.count))->blockId(); blockStore->overwrite(blockId, backgroundData.copy()); auto block = blockStore->load(blockId).value(); EXPECT_EQ(testData.blocksize, block->size()); EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize); } void TestOverwriteNonexistingAndReadImmediately() { auto blockId = blockStore->createBlockId(); auto block = blockStore->overwrite(blockId, backgroundData.copy()); EXPECT_EQ(testData.blocksize, block->size()); EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize); } void TestOverwriteNonexistingAndReadAfterLoading() { auto blockId = blockStore->createBlockId(); blockStore->overwrite(blockId, backgroundData.copy()); auto block = blockStore->load(blockId).value(); EXPECT_EQ(testData.blocksize, block->size()); EXPECT_DATA_READS_AS(backgroundData, *block, 0, testData.blocksize); } private: cpputils::unique_ref blockStore; DataRange testData; cpputils::Data foregroundData; cpputils::Data backgroundData; blockstore::BlockId CreateBlockWriteToItAndReturnKey(const cpputils::Data &to_write) { auto newblock = blockStore->create(cpputils::Data(testData.blocksize).FillWithZeroes()); newblock->write(to_write.data(), testData.offset, testData.count); return newblock->blockId(); } void EXPECT_DATA_READS_AS(const cpputils::Data &expected, const blockstore::Block &block, uint64_t offset, uint64_t count) { cpputils::Data read(count); std::memcpy(read.data(), static_cast(block.data()) + offset, count); EXPECT_EQ(expected, read); } void EXPECT_DATA_READS_AS_OUTSIDE_OF(const cpputils::Data &expected, const blockstore::Block &block, uint64_t start, uint64_t count) { cpputils::Data begin(start); cpputils::Data end(testData.blocksize - count - start); std::memcpy(begin.data(), expected.data(), start); std::memcpy(end.data(), expected.dataOffset(start+count), end.size()); EXPECT_DATA_READS_AS(begin, block, 0, start); EXPECT_DATA_READS_AS(end, block, start + count, end.size()); } void EXPECT_DATA_IS_ZEROES_OUTSIDE_OF(const blockstore::Block &block, uint64_t start, uint64_t count) { cpputils::Data ZEROES(testData.blocksize); ZEROES.FillWithZeroes(); EXPECT_DATA_READS_AS_OUTSIDE_OF(ZEROES, block, start, count); } }; inline std::vector DATA_RANGES() { return { DataRange{1024, 0, 1024}, // full size leaf, access beginning to end DataRange{1024, 100, 1024 - 200}, // full size leaf, access middle to middle DataRange{1024, 0, 1024 - 100}, // full size leaf, access beginning to middle DataRange{1024, 100, 1024 - 100}, // full size leaf, access middle to end DataRange{1024 - 100, 0, 1024 - 100}, // non-full size leaf, access beginning to end DataRange{1024 - 100, 100, 1024 - 300}, // non-full size leaf, access middle to middle DataRange{1024 - 100, 0, 1024 - 200}, // non-full size leaf, access beginning to middle DataRange{1024 - 100, 100, 1024 - 200} // non-full size leaf, access middle to end }; }; #define TYPED_TEST_P_FOR_ALL_DATA_RANGES(TestName) \ TYPED_TEST_P(BlockStoreTest, TestName) { \ for (auto dataRange: DATA_RANGES()) { \ BlockStoreDataParametrizedTest(this->fixture.createBlockStore(), dataRange) \ .Test##TestName(); \ } \ } TYPED_TEST_P_FOR_ALL_DATA_RANGES(WriteAndReadImmediately); TYPED_TEST_P_FOR_ALL_DATA_RANGES(WriteAndReadAfterLoading); TYPED_TEST_P_FOR_ALL_DATA_RANGES(WriteTwiceAndRead); TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteSameSizeAndReadImmediately); TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteSameSizeAndReadAfterLoading); TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteSmallerSizeAndReadImmediately); TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteSmallerSizeAndReadAfterLoading); TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteLargerSizeAndReadImmediately); TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteLargerSizeAndReadAfterLoading); TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteNonexistingAndReadImmediately); TYPED_TEST_P_FOR_ALL_DATA_RANGES(OverwriteNonexistingAndReadAfterLoading); #endif test/blockstore/testutils/BlockStoreTest_Size.h000066400000000000000000000145231347701267100223330ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_TEST_TESTUTILS_BLOCKSTORETEST_SIZE_H_ #define MESSMER_BLOCKSTORE_TEST_TESTUTILS_BLOCKSTORETEST_SIZE_H_ // This file is meant to be included by BlockStoreTest.h only #include #include class BlockStoreSizeParameterizedTest { public: BlockStoreSizeParameterizedTest(cpputils::unique_ref blockStore_, size_t size_): blockStore(std::move(blockStore_)), size(size_) {} void TestCreatedBlockHasCorrectSize() { auto block = CreateBlock(); EXPECT_EQ(size, block->size()); } void TestLoadingUnchangedBlockHasCorrectSize() { blockstore::BlockId blockId = CreateBlock()->blockId(); auto loaded_block = blockStore->load(blockId).value(); EXPECT_EQ(size, loaded_block->size()); } void TestCreatedBlockData() { cpputils::Data data = cpputils::DataFixture::generate(size); auto block = blockStore->create(data); EXPECT_EQ(0, std::memcmp(data.data(), block->data(), size)); } void TestLoadingUnchangedBlockData() { cpputils::Data data = cpputils::DataFixture::generate(size); blockstore::BlockId blockId = blockStore->create(data)->blockId(); auto loaded_block = blockStore->load(blockId).value(); EXPECT_EQ(0, std::memcmp(data.data(), loaded_block->data(), size)); } void TestLoadedBlockIsCorrect() { cpputils::Data randomData = cpputils::DataFixture::generate(size); auto loaded_block = StoreDataToBlockAndLoadIt(randomData); EXPECT_EQ(size, loaded_block->size()); EXPECT_EQ(0, std::memcmp(randomData.data(), loaded_block->data(), size)); } void TestLoadedBlockIsCorrectWhenLoadedDirectlyAfterFlushing() { cpputils::Data randomData = cpputils::DataFixture::generate(size); auto loaded_block = StoreDataToBlockAndLoadItDirectlyAfterFlushing(randomData); EXPECT_EQ(size, loaded_block->size()); EXPECT_EQ(0, std::memcmp(randomData.data(), loaded_block->data(), size)); } void TestAfterCreate_FlushingDoesntChangeBlock() { cpputils::Data randomData = cpputils::DataFixture::generate(size); auto block = CreateBlock(); WriteDataToBlock(block.get(), randomData); block->flush(); EXPECT_BLOCK_DATA_CORRECT(*block, randomData); } void TestAfterLoad_FlushingDoesntChangeBlock() { cpputils::Data randomData = cpputils::DataFixture::generate(size); auto block = CreateBlockAndLoadIt(); WriteDataToBlock(block.get(), randomData); block->flush(); EXPECT_BLOCK_DATA_CORRECT(*block, randomData); } void TestAfterCreate_FlushesWhenDestructed() { cpputils::Data randomData = cpputils::DataFixture::generate(size); blockstore::BlockId blockId = blockstore::BlockId::Null(); { auto block = blockStore->create(cpputils::Data(size)); blockId = block->blockId(); WriteDataToBlock(block.get(), randomData); } auto loaded_block = blockStore->load(blockId).value(); EXPECT_BLOCK_DATA_CORRECT(*loaded_block, randomData); } void TestAfterLoad_FlushesWhenDestructed() { cpputils::Data randomData = cpputils::DataFixture::generate(size); blockstore::BlockId blockId = blockstore::BlockId::Null(); { blockId = CreateBlock()->blockId(); auto block = blockStore->load(blockId).value(); WriteDataToBlock(block.get(), randomData); } auto loaded_block = blockStore->load(blockId).value(); EXPECT_BLOCK_DATA_CORRECT(*loaded_block, randomData); } void TestLoadNonExistingBlock() { EXPECT_EQ(boost::none, blockStore->load(blockId)); } private: const blockstore::BlockId blockId = blockstore::BlockId::FromString("1491BB4932A389EE14BC7090AC772972"); cpputils::unique_ref blockStore; size_t size; cpputils::Data ZEROES(size_t size) { cpputils::Data ZEROES(size); ZEROES.FillWithZeroes(); return ZEROES; } cpputils::unique_ref StoreDataToBlockAndLoadIt(const cpputils::Data &data) { blockstore::BlockId blockId = StoreDataToBlockAndGetKey(data); return blockStore->load(blockId).value(); } blockstore::BlockId StoreDataToBlockAndGetKey(const cpputils::Data &data) { return blockStore->create(data)->blockId(); } cpputils::unique_ref StoreDataToBlockAndLoadItDirectlyAfterFlushing(const cpputils::Data &data) { auto block = blockStore->create(data); block->flush(); return blockStore->load(block->blockId()).value(); } cpputils::unique_ref CreateBlockAndLoadIt() { blockstore::BlockId blockId = CreateBlock()->blockId(); return blockStore->load(blockId).value(); } cpputils::unique_ref CreateBlock() { return blockStore->create(cpputils::Data(size)); } void WriteDataToBlock(blockstore::Block *block, const cpputils::Data &randomData) { block->write(randomData.data(), 0, randomData.size()); } void EXPECT_BLOCK_DATA_CORRECT(const blockstore::Block &block, const cpputils::Data &randomData) { EXPECT_EQ(randomData.size(), block.size()); EXPECT_EQ(0, std::memcmp(randomData.data(), block.data(), randomData.size())); } }; constexpr std::array SIZES = {{0, 1, 1024, 4096, 10*1024*1024}}; #define TYPED_TEST_P_FOR_ALL_SIZES(TestName) \ TYPED_TEST_P(BlockStoreTest, TestName) { \ for (auto size: SIZES) { \ BlockStoreSizeParameterizedTest(this->fixture.createBlockStore(), size) \ .Test##TestName(); \ } \ } \ TYPED_TEST_P_FOR_ALL_SIZES(CreatedBlockHasCorrectSize); TYPED_TEST_P_FOR_ALL_SIZES(LoadingUnchangedBlockHasCorrectSize); TYPED_TEST_P_FOR_ALL_SIZES(CreatedBlockData); TYPED_TEST_P_FOR_ALL_SIZES(LoadingUnchangedBlockData); TYPED_TEST_P_FOR_ALL_SIZES(LoadedBlockIsCorrect); //TYPED_TEST_P_FOR_ALL_SIZES(LoadedBlockIsCorrectWhenLoadedDirectlyAfterFlushing); TYPED_TEST_P_FOR_ALL_SIZES(AfterCreate_FlushingDoesntChangeBlock); TYPED_TEST_P_FOR_ALL_SIZES(AfterLoad_FlushingDoesntChangeBlock); TYPED_TEST_P_FOR_ALL_SIZES(AfterCreate_FlushesWhenDestructed); TYPED_TEST_P_FOR_ALL_SIZES(AfterLoad_FlushesWhenDestructed); TYPED_TEST_P_FOR_ALL_SIZES(LoadNonExistingBlock); #endif test/blockstore/testutils/gtest_printers.h000066400000000000000000000006711347701267100215050ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_TEST_TESTUTILS_GTESTPRINTERS_H_ #define MESSMER_BLOCKSTORE_TEST_TESTUTILS_GTESTPRINTERS_H_ namespace cpputils { inline void PrintTo(const Data& /*data*/, ::std::ostream* os) { *os << "cpputils::Data"; } inline void PrintTo(const boost::optional& data, ::std::ostream* os) { if (data == boost::none) { *os << "none"; } else { PrintTo(*data, os); } } } #endif test/blockstore/utils/000077500000000000000000000000001347701267100153545ustar00rootroot00000000000000test/blockstore/utils/BlockStoreUtilsTest.cpp000066400000000000000000000066151347701267100220200ustar00rootroot00000000000000#include "blockstore/implementations/testfake/FakeBlockStore.h" #include #include "blockstore/utils/BlockStoreUtils.h" #include #include using ::testing::Test; using cpputils::Data; using cpputils::DataFixture; using cpputils::unique_ref; using cpputils::make_unique_ref; using namespace blockstore; using namespace blockstore::utils; using blockstore::testfake::FakeBlockStore; class BlockStoreUtilsTest: public Test { public: unsigned int SIZE = 1024 * 1024; BlockStoreUtilsTest(): ZEROES(SIZE), dataFixture(DataFixture::generate(SIZE)), blockStore(make_unique_ref()) { ZEROES.FillWithZeroes(); } Data ZEROES; Data dataFixture; unique_ref blockStore; }; TEST_F(BlockStoreUtilsTest, FillWithZeroes) { auto block = blockStore->create(Data(SIZE)); block->write(dataFixture.data(), 0, SIZE); EXPECT_NE(0, std::memcmp(ZEROES.data(), block->data(), SIZE)); fillWithZeroes(block.get()); EXPECT_EQ(0, std::memcmp(ZEROES.data(), block->data(), SIZE)); } class BlockStoreUtilsTest_CopyToNewBlock: public BlockStoreUtilsTest {}; TEST_F(BlockStoreUtilsTest_CopyToNewBlock, CopyEmptyBlock) { auto block = blockStore->create(Data(0)); auto block2 = copyToNewBlock(blockStore.get(), *block); EXPECT_EQ(0u, block2->size()); } TEST_F(BlockStoreUtilsTest_CopyToNewBlock, CopyZeroBlock) { auto block = blockStore->create(ZEROES); auto block2 = copyToNewBlock(blockStore.get(), *block); EXPECT_EQ(SIZE, block2->size()); EXPECT_EQ(0, std::memcmp(ZEROES.data(), block2->data(), SIZE)); } TEST_F(BlockStoreUtilsTest_CopyToNewBlock, CopyDataBlock) { auto block = blockStore->create(Data(SIZE)); block->write(dataFixture.data(), 0, SIZE); auto block2 = copyToNewBlock(blockStore.get(), *block); EXPECT_EQ(SIZE, block2->size()); EXPECT_EQ(0, std::memcmp(dataFixture.data(), block2->data(), SIZE)); } TEST_F(BlockStoreUtilsTest_CopyToNewBlock, OriginalBlockUnchanged) { auto block = blockStore->create(Data(SIZE)); block->write(dataFixture.data(), 0, SIZE); auto block2 = copyToNewBlock(blockStore.get(), *block); EXPECT_EQ(SIZE, block->size()); EXPECT_EQ(0, std::memcmp(dataFixture.data(), block->data(), SIZE)); } class BlockStoreUtilsTest_CopyToExistingBlock: public BlockStoreUtilsTest {}; TEST_F(BlockStoreUtilsTest_CopyToExistingBlock, CopyEmptyBlock) { auto block = blockStore->create(Data(0)); auto block2 = blockStore->create(Data(0)); copyTo(block2.get(), *block); } TEST_F(BlockStoreUtilsTest_CopyToExistingBlock, CopyZeroBlock) { auto block = blockStore->create(ZEROES); auto block2 = blockStore->create(Data(SIZE)); block2->write(dataFixture.data(), 0, SIZE); copyTo(block2.get(), *block); EXPECT_EQ(0, std::memcmp(ZEROES.data(), block2->data(), SIZE)); } TEST_F(BlockStoreUtilsTest_CopyToExistingBlock, CopyDataBlock) { auto block = blockStore->create(Data(SIZE)); block->write(dataFixture.data(), 0, SIZE); auto block2 = blockStore->create(Data(SIZE)); copyTo(block2.get(), *block); EXPECT_EQ(0, std::memcmp(dataFixture.data(), block2->data(), SIZE)); } TEST_F(BlockStoreUtilsTest_CopyToExistingBlock, OriginalBlockUnchanged) { auto block = blockStore->create(Data(SIZE)); block->write(dataFixture.data(), 0, SIZE); auto block2 = blockStore->create(Data(SIZE)); copyTo(block2.get(), *block); EXPECT_EQ(0, std::memcmp(dataFixture.data(), block->data(), SIZE)); } test/cpp-utils/000077500000000000000000000000001347701267100137655ustar00rootroot00000000000000test/cpp-utils/CMakeLists.txt000066400000000000000000000050631347701267100165310ustar00rootroot00000000000000project (cpp-utils-test) set(SOURCES crypto/symmetric/CipherTest.cpp crypto/kdf/SCryptTest.cpp crypto/kdf/SCryptParametersTest.cpp crypto/hash/HashTest.cpp MacrosIncludeTest.cpp pointer/unique_ref_test.cpp pointer/cast_include_test.cpp pointer/cast_test.cpp pointer/unique_ref_boost_optional_gtest_workaround_include_test.cpp pointer/optional_ownership_ptr_include_test.cpp pointer/optional_ownership_ptr_test.cpp pointer/unique_ref_include_test.cpp process/daemonize_include_test.cpp process/subprocess_include_test.cpp process/SubprocessTest.cpp process/SignalCatcherTest.cpp process/SignalHandlerTest.cpp tempfile/TempFileTest.cpp tempfile/TempFileIncludeTest.cpp tempfile/TempDirIncludeTest.cpp tempfile/TempDirTest.cpp network/CurlHttpClientTest.cpp network/FakeHttpClientTest.cpp io/DontEchoStdinToStdoutRAIITest.cpp io/ConsoleIncludeTest.cpp io/ConsoleTest_AskYesNo.cpp io/ConsoleTest_Print.cpp io/ConsoleTest_Ask.cpp io/ConsoleTest_AskPassword.cpp io/ProgressBarTest.cpp random/RandomIncludeTest.cpp lock/LockPoolIncludeTest.cpp lock/ConditionBarrierIncludeTest.cpp lock/MutexPoolLockIncludeTest.cpp data/FixedSizeDataTest.cpp data/DataFixtureIncludeTest.cpp data/DataFixtureTest.cpp data/DataTest.cpp data/FixedSizeDataIncludeTest.cpp data/SerializationHelperTest.cpp data/DataIncludeTest.cpp logging/LoggingLevelTest.cpp logging/LoggerTest.cpp logging/LoggingTest.cpp logging/LoggerIncludeTest.cpp logging/LoggingIncludeTest.cpp assert/assert_release_test.cpp assert/backtrace_test.cpp assert/assert_debug_test.cpp system/GetTotalMemoryTest.cpp system/TimeTest.cpp system/PathTest.cpp system/FiletimeTest.cpp system/MemoryTest.cpp system/HomedirTest.cpp system/EnvTest.cpp thread/debugging_test.cpp thread/LeftRightTest.cpp value_type/ValueTypeTest.cpp ) add_executable(${PROJECT_NAME}_exit_status process/exit_status.cpp) target_activate_cpp14(${PROJECT_NAME}_exit_status) add_executable(${PROJECT_NAME}_exit_signal assert/exit_signal.cpp) 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} my-gtest-main googletest cpp-utils) add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}_exit_status ${PROJECT_NAME}_exit_signal) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) test/cpp-utils/MacrosIncludeTest.cpp000066400000000000000000000001551347701267100200620ustar00rootroot00000000000000#include "cpp-utils/macros.h" // Test that macros.h can be included without needing additional dependencies test/cpp-utils/assert/000077500000000000000000000000001347701267100152665ustar00rootroot00000000000000test/cpp-utils/assert/assert_debug_test.cpp000066400000000000000000000021061347701267100214770ustar00rootroot00000000000000#include #include #ifdef NDEBUG #define _REAL_NDEBUG #endif //Include the ASSERT macro for a debug build #undef NDEBUG #include "cpp-utils/assert/assert.h" TEST(AssertTest_DebugBuild, DoesntDieIfTrue) { ASSERT(true, "bla"); } TEST(AssertTest_DebugBuild, DiesIfFalse) { EXPECT_DEATH( ASSERT(false, "bla"), "" ); } TEST(AssertTest_DebugBuild, AssertMessage) { #if defined(_MSC_VER) constexpr const char* EXPECTED = R"(Assertion \[2==5\] failed in .*assert_debug_test.cpp:\d+: my message)"; #else constexpr const char* EXPECTED = R"(Assertion \[2==5\] failed in .*assert_debug_test.cpp:[0-9]+: my message)"; #endif EXPECT_DEATH( ASSERT(2==5, "my message"), EXPECTED ); } #if !(defined(_MSC_VER) && defined(_REAL_NDEBUG)) TEST(AssertTest_DebugBuild, AssertMessageContainsBacktrace) { EXPECT_DEATH( ASSERT(2==5, "my message"), "cpputils::" ); } #else TEST(AssertTest_DebugBuild, AssertMessageContainsBacktrace) { EXPECT_DEATH( ASSERT(2==5, "my message"), "#1" ); } #endif test/cpp-utils/assert/assert_release_test.cpp000066400000000000000000000030041347701267100220270ustar00rootroot00000000000000#include #include #include #ifdef NDEBUG #define _REAL_NDEBUG #endif //Include the ASSERT macro for a release build #ifndef NDEBUG #define NDEBUG #endif #include "cpp-utils/assert/assert.h" using testing::HasSubstr; TEST(AssertTest_ReleaseBuild, DoesntThrowIfTrue) { ASSERT(true, "bla"); } TEST(AssertTest_ReleaseBuild, ThrowsIfFalse) { EXPECT_THROW( ASSERT(false, "bla"), cpputils::AssertFailed ); } TEST(AssertTest_ReleaseBuild, AssertMessage) { try { ASSERT(2==5, "my message"); FAIL(); } catch (const cpputils::AssertFailed &e) { std::string msg = e.what(); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? /*EXPECT_THAT(e.what(), MatchesRegex( R"(Assertion \[2==5\] failed in .*assert_release_test.cpp:27: my message)" ));*/ EXPECT_TRUE(std::regex_search(e.what(), std::regex(R"(Assertion \[2==5\] failed in .*assert_release_test.cpp:30: my message)"))); } } #if !(defined(_MSC_VER) && defined(_REAL_NDEBUG)) TEST(AssertTest_ReleaseBuild, AssertMessageContainsBacktrace) { try { ASSERT(2==5, "my message"); FAIL(); } catch (const cpputils::AssertFailed &e) { EXPECT_THAT(e.what(), HasSubstr( "cpputils::" )); } } #else TEST(AssertTest_ReleaseBuild, AssertMessageContainsBacktrace) { try { ASSERT(2==5, "my message"); FAIL(); } catch (const cpputils::AssertFailed &e) { EXPECT_THAT(e.what(), HasSubstr( "#1" )); } } #endif test/cpp-utils/assert/backtrace_test.cpp000066400000000000000000000141051347701267100207510ustar00rootroot00000000000000#include #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) auto executable = get_executable().parent_path() / "cpp-utils-test_exit_signal.exe"; #else auto executable = get_executable().parent_path() / "cpp-utils-test_exit_signal"; #endif 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; } } #if !(defined(_MSC_VER) && defined(NDEBUG)) TEST(BacktraceTest, ContainsTopLevelLine) { string backtrace = cpputils::backtrace(); EXPECT_THAT(backtrace, HasSubstr("BacktraceTest")); EXPECT_THAT(backtrace, HasSubstr("ContainsTopLevelLine")); } #endif namespace { std::string call_process_exiting_with_nullptr_violation() { return call_process_exiting_with("nullptr"); } std::string call_process_exiting_with_exception(const std::string& message) { return call_process_exiting_with("exception", message); } } #if defined(_MSC_VER) #include namespace { std::string call_process_exiting_with_sigsegv() { return call_process_exiting_with("signal", std::to_string(EXCEPTION_ACCESS_VIOLATION)); } std::string call_process_exiting_with_sigill() { return call_process_exiting_with("signal", std::to_string(EXCEPTION_ILLEGAL_INSTRUCTION)); } std::string call_process_exiting_with_code(DWORD code) { return call_process_exiting_with("signal", std::to_string(code)); } } #else namespace { std::string call_process_exiting_with_sigsegv() { return call_process_exiting_with("signal", std::to_string(SIGSEGV)); } std::string call_process_exiting_with_sigabrt() { return call_process_exiting_with("signal", std::to_string(SIGABRT)); } std::string call_process_exiting_with_sigill() { return call_process_exiting_with("signal", std::to_string(SIGILL)); } } #endif TEST(BacktraceTest, DoesntCrashOnCaughtException) { // This is needed to make sure we don't use some kind of vectored exception handler on Windows // that ignores the call stack and always jumps on when an exception happens. cpputils::showBacktraceOnCrash(); try { throw std::logic_error("exception"); } catch (const std::logic_error& e) { // intentionally empty } } #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("handle_exit_signal")); #else EXPECT_THAT(output, HasSubstr("cpputils::backtrace")); #endif } TEST(BacktraceTest, ShowBacktraceOnSigSegv) { auto output = call_process_exiting_with_sigsegv(); #if defined(_MSC_VER) EXPECT_THAT(output, HasSubstr("handle_exit_signal")); #else EXPECT_THAT(output, HasSubstr("cpputils::backtrace")); #endif } TEST(BacktraceTest, ShowBacktraceOnUnhandledException) { auto output = call_process_exiting_with_exception("my_exception_message"); #if defined(_MSC_VER) EXPECT_THAT(output, HasSubstr("handle_exit_signal")); #else 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")); #else 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")); } TEST(BacktraceTest, ShowBacktraceOnSigSegv) { auto output = call_process_exiting_with_sigsegv(); EXPECT_THAT(output, HasSubstr("#1")); } TEST(BacktraceTest, ShowBacktraceOnUnhandledException) { auto output = call_process_exiting_with_exception("my_exception_message"); EXPECT_THAT(output, HasSubstr("#1")); } TEST(BacktraceTest, ShowBacktraceOnSigIll) { auto output = call_process_exiting_with_sigill(); EXPECT_THAT(output, HasSubstr("#1")); } #endif #if !defined(_MSC_VER) TEST(BacktraceTest, ShowBacktraceOnSigAbrt) { auto output = call_process_exiting_with_sigabrt(); EXPECT_THAT(output, HasSubstr("cpputils::backtrace")); } TEST(BacktraceTest, ShowBacktraceOnSigAbrt_ShowsCorrectSignalName) { auto output = call_process_exiting_with_sigabrt(); EXPECT_THAT(output, HasSubstr("SIGABRT")); } #endif #if !defined(_MSC_VER) constexpr const char* sigsegv_message = "SIGSEGV"; constexpr const char* sigill_message = "SIGILL"; #else constexpr const char* sigsegv_message = "EXCEPTION_ACCESS_VIOLATION"; constexpr const char* sigill_message = "EXCEPTION_ILLEGAL_INSTRUCTION"; #endif TEST(BacktraceTest, ShowBacktraceOnSigSegv_ShowsCorrectSignalName) { auto output = call_process_exiting_with_sigsegv(); EXPECT_THAT(output, HasSubstr(sigsegv_message)); } TEST(BacktraceTest, ShowBacktraceOnSigIll_ShowsCorrectSignalName) { auto output = call_process_exiting_with_sigill(); EXPECT_THAT(output, HasSubstr(sigill_message)); } #if !defined(_MSC_VER) TEST(BacktraceTest, ShowBacktraceOnUnhandledException_ShowsCorrectExceptionMessage) { auto output = call_process_exiting_with_exception("my_exception_message"); EXPECT_THAT(output, HasSubstr("my_exception_message")); } #endif #if defined(_MSC_VER) TEST(BacktraceTest, UnknownCode_ShowsCorrectSignalName) { auto output = call_process_exiting_with_code(0x1234567); EXPECT_THAT(output, HasSubstr("UNKNOWN_CODE(0x1234567)")); } #endif test/cpp-utils/assert/exit_signal.cpp000066400000000000000000000014671347701267100203100ustar00rootroot00000000000000#include #include #include #if defined(_MSC_VER) #include #endif void handle_exit_signal(char **argv) { const std::string kind = argv[1]; if (kind == "exception") { throw std::logic_error(argv[2]); } else if (kind == "nullptr") { int* ptr = nullptr; *ptr = 5; // NOLINT } else if (kind == "signal") { #if defined(_MSC_VER) DWORD code = std::atoll(argv[2]); ::RaiseException(code, EXCEPTION_NONCONTINUABLE, 0, NULL); #else int code = static_cast(std::strtol(argv[2], nullptr, 10)); ::raise(code); #endif } } int main(int /*argc*/, char* argv[]) { cpputils::showBacktraceOnCrash(); #if defined(_MSC_VER) // don't show windows error box _set_abort_behavior(0, _WRITE_ABORT_MSG); #endif handle_exit_signal(argv); return 0; } test/cpp-utils/crypto/000077500000000000000000000000001347701267100153055ustar00rootroot00000000000000test/cpp-utils/crypto/hash/000077500000000000000000000000001347701267100162305ustar00rootroot00000000000000test/cpp-utils/crypto/hash/HashTest.cpp000066400000000000000000000026351347701267100204650ustar00rootroot00000000000000#include #include #include using namespace cpputils::hash; using cpputils::DataFixture; using cpputils::Data; TEST(HashTest, generateSalt_isIndeterministic) { EXPECT_NE(generateSalt(), generateSalt()); } TEST(HashTest, hash_setsSaltCorrectly) { Salt salt = generateSalt(); Data data = DataFixture::generate(1024); EXPECT_EQ(salt, hash(data, salt).salt); } TEST(HashTest, hash_isDeterministicWithSameDataSameSalt) { Salt salt = generateSalt(); Data data = DataFixture::generate(1024); EXPECT_EQ(hash(data, salt).digest, hash(data, salt).digest); } TEST(HashTest, hash_isIndeterministicWithSameDataDifferentSalt) { Salt salt1 = generateSalt(); Salt salt2 = generateSalt(); Data data = DataFixture::generate(1024); EXPECT_NE(hash(data, salt1).digest, hash(data, salt2).digest); } TEST(HashTest, hash_isIndeterministicWithDifferentDataSameSalt) { Salt salt = generateSalt(); Data data1 = DataFixture::generate(1024, 1); Data data2 = DataFixture::generate(1024, 2); EXPECT_NE(hash(data1, salt).digest, hash(data2, salt).digest); } TEST(HashTest, hash_isIndeterministicWithDifferentDataDifferentSalt) { Salt salt1 = generateSalt(); Salt salt2 = generateSalt(); Data data1 = DataFixture::generate(1024, 1); Data data2 = DataFixture::generate(1024, 2); EXPECT_NE(hash(data1, salt1).digest, hash(data2, salt2).digest); } test/cpp-utils/crypto/kdf/000077500000000000000000000000001347701267100160515ustar00rootroot00000000000000test/cpp-utils/crypto/kdf/SCryptParametersTest.cpp000066400000000000000000000044421347701267100226710ustar00rootroot00000000000000#include #include #include #include using namespace cpputils; class SCryptParametersTest : public ::testing::Test { public: SCryptParameters SaveAndLoad(const SCryptParameters &source) { Data serialized = source.serialize(); return SCryptParameters::deserialize(serialized); } }; TEST_F(SCryptParametersTest, Salt) { SCryptParameters cfg(DataFixture::generate(32), 0, 0, 0); EXPECT_EQ(DataFixture::generate(32), cfg.salt()); } TEST_F(SCryptParametersTest, Salt_Move) { SCryptParameters cfg(DataFixture::generate(32), 0, 0, 0); SCryptParameters moved = std::move(cfg); EXPECT_EQ(DataFixture::generate(32), moved.salt()); } TEST_F(SCryptParametersTest, Salt_SaveAndLoad) { SCryptParameters cfg(DataFixture::generate(32), 0, 0, 0); SCryptParameters loaded = SaveAndLoad(cfg); EXPECT_EQ(DataFixture::generate(32), loaded.salt()); } TEST_F(SCryptParametersTest, N) { SCryptParameters cfg(Data(0), 1024, 0, 0); EXPECT_EQ(1024u, cfg.N()); } TEST_F(SCryptParametersTest, N_Move) { SCryptParameters cfg(Data(0), 1024, 0, 0); SCryptParameters moved = std::move(cfg); EXPECT_EQ(1024u, moved.N()); } TEST_F(SCryptParametersTest, N_SaveAndLoad) { SCryptParameters cfg(Data(0), 1024, 0, 0); SCryptParameters loaded = SaveAndLoad(cfg); EXPECT_EQ(1024u, loaded.N()); } TEST_F(SCryptParametersTest, r) { SCryptParameters cfg(Data(0), 0, 8, 0); EXPECT_EQ(8u, cfg.r()); } TEST_F(SCryptParametersTest, r_Move) { SCryptParameters cfg(Data(0), 0, 8, 0); SCryptParameters moved = std::move(cfg); EXPECT_EQ(8u, moved.r()); } TEST_F(SCryptParametersTest, r_SaveAndLoad) { SCryptParameters cfg(Data(0), 0, 8, 0); SCryptParameters loaded = SaveAndLoad(cfg); EXPECT_EQ(8u, loaded.r()); } TEST_F(SCryptParametersTest, p) { SCryptParameters cfg(Data(0), 0, 0, 16); EXPECT_EQ(16u, cfg.p()); } TEST_F(SCryptParametersTest, p_Move) { SCryptParameters cfg(Data(0), 0, 0, 16); SCryptParameters moved = std::move(cfg); EXPECT_EQ(16u, moved.p()); } TEST_F(SCryptParametersTest, p_SaveAndLoad) { SCryptParameters cfg(Data(0), 0, 0, 16); SCryptParameters loaded = SaveAndLoad(cfg); EXPECT_EQ(16u, loaded.p()); }test/cpp-utils/crypto/kdf/SCryptTest.cpp000066400000000000000000000056101347701267100206430ustar00rootroot00000000000000#include #include "cpp-utils/crypto/kdf/Scrypt.h" using namespace cpputils; using std::string; class SCryptTest : public ::testing::Test { public: bool keyEquals(const EncryptionKey& lhs, const EncryptionKey& rhs) { ASSERT(lhs.binaryLength() == rhs.binaryLength(), "Keys must have equal size to be comparable"); return 0 == std::memcmp(lhs.data(), rhs.data(), lhs.binaryLength()); } }; TEST_F(SCryptTest, GeneratedKeyIsReproductible_448) { SCrypt scrypt(SCrypt::TestSettings); auto derivedKey = scrypt.deriveNewKey(56, "mypassword"); auto rederivedKey = scrypt.deriveExistingKey(56, "mypassword", derivedKey.kdfParameters); EXPECT_TRUE(keyEquals(derivedKey.key, rederivedKey)); } TEST_F(SCryptTest, GeneratedKeyIsReproductible_256) { SCrypt scrypt(SCrypt::TestSettings); auto derivedKey = scrypt.deriveNewKey(32, "mypassword"); auto rederivedKey = scrypt.deriveExistingKey(32, "mypassword", derivedKey.kdfParameters); EXPECT_TRUE(keyEquals(derivedKey.key, rederivedKey)); } TEST_F(SCryptTest, GeneratedKeyIsReproductible_128) { SCrypt scrypt(SCrypt::TestSettings); auto derivedKey = scrypt.deriveNewKey(16, "mypassword"); auto rederivedKey = scrypt.deriveExistingKey(16, "mypassword", derivedKey.kdfParameters); EXPECT_TRUE(keyEquals(derivedKey.key, rederivedKey)); } TEST_F(SCryptTest, GeneratedKeyIsReproductible_DefaultSettings) { SCrypt scrypt(SCrypt::TestSettings); auto derivedKey = scrypt.deriveNewKey(16, "mypassword"); auto rederivedKey = scrypt.deriveExistingKey(16, "mypassword", derivedKey.kdfParameters); EXPECT_TRUE(keyEquals(derivedKey.key, rederivedKey)); } TEST_F(SCryptTest, DifferentPasswordResultsInDifferentKey) { SCrypt scrypt(SCrypt::TestSettings); auto derivedKey = scrypt.deriveNewKey(16, "mypassword"); auto rederivedKey = scrypt.deriveExistingKey(16, "mypassword2", derivedKey.kdfParameters); EXPECT_FALSE(keyEquals(derivedKey.key, rederivedKey)); } TEST_F(SCryptTest, UsesCorrectSettings) { SCrypt scrypt(SCrypt::TestSettings); auto derivedKey = scrypt.deriveNewKey(16, "mypassword"); auto parameters = SCryptParameters::deserialize(derivedKey.kdfParameters); EXPECT_EQ(SCrypt::TestSettings.SALT_LEN, parameters.salt().size()); EXPECT_EQ(SCrypt::TestSettings.N, parameters.N()); EXPECT_EQ(SCrypt::TestSettings.r, parameters.r()); EXPECT_EQ(SCrypt::TestSettings.p, parameters.p()); } TEST_F(SCryptTest, UsesCorrectDefaultSettings) { SCrypt scrypt(SCrypt::DefaultSettings); auto derivedKey = scrypt.deriveNewKey(16, "mypassword"); auto parameters = SCryptParameters::deserialize(derivedKey.kdfParameters); EXPECT_EQ(SCrypt::DefaultSettings.SALT_LEN, parameters.salt().size()); EXPECT_EQ(SCrypt::DefaultSettings.N, parameters.N()); EXPECT_EQ(SCrypt::DefaultSettings.r, parameters.r()); EXPECT_EQ(SCrypt::DefaultSettings.p, parameters.p()); } test/cpp-utils/crypto/symmetric/000077500000000000000000000000001347701267100173215ustar00rootroot00000000000000test/cpp-utils/crypto/symmetric/CipherTest.cpp000066400000000000000000000266501347701267100221100ustar00rootroot00000000000000#include "cpp-utils/crypto/cryptopp_byte.h" #include #include "cpp-utils/crypto/symmetric/Cipher.h" #include "cpp-utils/crypto/symmetric/ciphers.h" #include "cpp-utils/crypto/symmetric/testutils/FakeAuthenticatedCipher.h" #include "cpp-utils/data/DataFixture.h" #include "cpp-utils/data/Data.h" #include using namespace cpputils; using std::string; template class CipherTest: public ::testing::Test { public: BOOST_CONCEPT_ASSERT((CipherConcept)); typename Cipher::EncryptionKey encKey = createKeyFixture(); static typename Cipher::EncryptionKey createKeyFixture(int seed = 0) { Data data = DataFixture::generate(Cipher::KEYSIZE, seed); return Cipher::EncryptionKey::FromString(data.ToString()); } void CheckEncryptThenDecryptIsIdentity(const Data &plaintext) { Data ciphertext = Encrypt(plaintext); Data decrypted = Decrypt(ciphertext); EXPECT_EQ(plaintext, decrypted); } void CheckEncryptIsIndeterministic(const Data &plaintext) { Data ciphertext = Encrypt(plaintext); Data ciphertext2 = Encrypt(plaintext); EXPECT_NE(ciphertext, ciphertext2); } void CheckEncryptedSize(const Data &plaintext) { Data ciphertext = Encrypt(plaintext); EXPECT_EQ(Cipher::ciphertextSize(plaintext.size()), ciphertext.size()); } void ExpectDoesntDecrypt(const Data &ciphertext) { auto decrypted = Cipher::decrypt(static_cast(ciphertext.data()), ciphertext.size(), this->encKey); EXPECT_FALSE(decrypted); } Data Encrypt(const Data &plaintext) { return Cipher::encrypt(static_cast(plaintext.data()), plaintext.size(), this->encKey); } Data Decrypt(const Data &ciphertext) { return Cipher::decrypt(static_cast(ciphertext.data()), ciphertext.size(), this->encKey).value(); } static Data CreateZeroes(unsigned int size) { return Data(size).FillWithZeroes(); } static Data CreateData(unsigned int size, unsigned int seed = 0) { return DataFixture::generate(size, seed); } }; TYPED_TEST_CASE_P(CipherTest); constexpr std::array SIZES = {{0, 1, 100, 1024, 5000, 1048576, 20971520}}; TYPED_TEST_P(CipherTest, Size) { for (auto size: SIZES) { EXPECT_EQ(size, TypeParam::ciphertextSize(TypeParam::plaintextSize(size))); EXPECT_EQ(size, TypeParam::plaintextSize(TypeParam::ciphertextSize(size))); } } TYPED_TEST_P(CipherTest, EncryptThenDecrypt_Zeroes) { for (auto size: SIZES) { Data plaintext = this->CreateZeroes(size); this->CheckEncryptThenDecryptIsIdentity(plaintext); } } TYPED_TEST_P(CipherTest, EncryptThenDecrypt_Data) { for (auto size: SIZES) { Data plaintext = this->CreateData(size); this->CheckEncryptThenDecryptIsIdentity(plaintext); } } TYPED_TEST_P(CipherTest, EncryptIsIndeterministic_Zeroes) { for (auto size: SIZES) { Data plaintext = this->CreateZeroes(size); this->CheckEncryptIsIndeterministic(plaintext); } } TYPED_TEST_P(CipherTest, EncryptIsIndeterministic_Data) { for (auto size: SIZES) { Data plaintext = this->CreateData(size); this->CheckEncryptIsIndeterministic(plaintext); } } TYPED_TEST_P(CipherTest, EncryptedSize) { for (auto size: SIZES) { Data plaintext = this->CreateData(size); this->CheckEncryptedSize(plaintext); } } TYPED_TEST_P(CipherTest, TryDecryptDataThatIsTooSmall) { Data tooSmallCiphertext(TypeParam::ciphertextSize(0) - 1); this->ExpectDoesntDecrypt(tooSmallCiphertext); } TYPED_TEST_P(CipherTest, TryDecryptDataThatIsMuchTooSmall_0) { static_assert(TypeParam::ciphertextSize(0) > 0, "If this fails, the test case doesn't make sense."); Data tooSmallCiphertext(0); this->ExpectDoesntDecrypt(tooSmallCiphertext); } TYPED_TEST_P(CipherTest, TryDecryptDataThatIsMuchTooSmall_1) { static_assert(TypeParam::ciphertextSize(0) > 1, "If this fails, the test case doesn't make sense."); Data tooSmallCiphertext(1); this->ExpectDoesntDecrypt(tooSmallCiphertext); } REGISTER_TYPED_TEST_CASE_P(CipherTest, Size, EncryptThenDecrypt_Zeroes, EncryptThenDecrypt_Data, EncryptIsIndeterministic_Zeroes, EncryptIsIndeterministic_Data, EncryptedSize, TryDecryptDataThatIsTooSmall, TryDecryptDataThatIsMuchTooSmall_0, TryDecryptDataThatIsMuchTooSmall_1 ); template class AuthenticatedCipherTest: public CipherTest { public: Data zeroes1 = CipherTest::CreateZeroes(1); Data plaintext1 = CipherTest::CreateData(1); Data zeroes2 = CipherTest::CreateZeroes(100 * 1024); Data plaintext2 = CipherTest::CreateData(100 * 1024); }; TYPED_TEST_CASE_P(AuthenticatedCipherTest); TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Zeroes_Size1) { Data ciphertext = this->Encrypt(this->zeroes1); void* firstByte = ciphertext.data(); serialize(firstByte, deserialize(firstByte) + 1); this->ExpectDoesntDecrypt(ciphertext); } TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Data_Size1) { Data ciphertext = this->Encrypt(this->plaintext1); void* firstByte = ciphertext.data(); serialize(firstByte, deserialize(firstByte) + 1); this->ExpectDoesntDecrypt(ciphertext); } TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Zeroes) { Data ciphertext = this->Encrypt(this->zeroes2); void* firstByte = ciphertext.data(); serialize(firstByte, deserialize(firstByte) + 1); this->ExpectDoesntDecrypt(ciphertext); } TYPED_TEST_P(AuthenticatedCipherTest, ModifyFirstByte_Data) { Data ciphertext = this->Encrypt(this->plaintext2); void* firstByte = ciphertext.data(); serialize(firstByte, deserialize(firstByte) + 1); this->ExpectDoesntDecrypt(ciphertext); } TYPED_TEST_P(AuthenticatedCipherTest, ModifyLastByte_Zeroes) { Data ciphertext = this->Encrypt(this->zeroes2); void* lastByte = ciphertext.dataOffset(ciphertext.size() - 1); serialize(lastByte, deserialize(lastByte) + 1); this->ExpectDoesntDecrypt(ciphertext); } TYPED_TEST_P(AuthenticatedCipherTest, ModifyLastByte_Data) { Data ciphertext = this->Encrypt(this->plaintext2); void* lastByte = ciphertext.dataOffset(ciphertext.size() - 1); serialize(lastByte, deserialize(lastByte) + 1); this->ExpectDoesntDecrypt(ciphertext); } TYPED_TEST_P(AuthenticatedCipherTest, ModifyMiddleByte_Zeroes) { Data ciphertext = this->Encrypt(this->zeroes2); void* middleByte = ciphertext.dataOffset(ciphertext.size()/2); serialize(middleByte, deserialize(middleByte) + 1); this->ExpectDoesntDecrypt(ciphertext); } TYPED_TEST_P(AuthenticatedCipherTest, ModifyMiddleByte_Data) { Data ciphertext = this->Encrypt(this->plaintext2); void* middleByte = ciphertext.dataOffset(ciphertext.size()/2); serialize(middleByte, deserialize(middleByte) + 1); this->ExpectDoesntDecrypt(ciphertext); } TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptZeroesData) { this->ExpectDoesntDecrypt(this->zeroes2); } TYPED_TEST_P(AuthenticatedCipherTest, TryDecryptRandomData) { this->ExpectDoesntDecrypt(this->plaintext2); } REGISTER_TYPED_TEST_CASE_P(AuthenticatedCipherTest, ModifyFirstByte_Zeroes_Size1, ModifyFirstByte_Zeroes, ModifyFirstByte_Data_Size1, ModifyFirstByte_Data, ModifyLastByte_Zeroes, ModifyLastByte_Data, ModifyMiddleByte_Zeroes, ModifyMiddleByte_Data, TryDecryptZeroesData, TryDecryptRandomData ); INSTANTIATE_TYPED_TEST_CASE_P(Fake, CipherTest, FakeAuthenticatedCipher); INSTANTIATE_TYPED_TEST_CASE_P(Fake, AuthenticatedCipherTest, FakeAuthenticatedCipher); INSTANTIATE_TYPED_TEST_CASE_P(AES256_CFB, CipherTest, AES256_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_CASE_P(AES256_GCM, CipherTest, AES256_GCM); INSTANTIATE_TYPED_TEST_CASE_P(AES256_GCM, AuthenticatedCipherTest, AES256_GCM); INSTANTIATE_TYPED_TEST_CASE_P(AES128_CFB, CipherTest, AES128_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_CASE_P(AES128_GCM, CipherTest, AES128_GCM); INSTANTIATE_TYPED_TEST_CASE_P(AES128_GCM, AuthenticatedCipherTest, AES128_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Twofish256_CFB, CipherTest, Twofish256_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_CASE_P(Twofish256_GCM, CipherTest, Twofish256_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Twofish256_GCM, AuthenticatedCipherTest, Twofish256_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Twofish128_CFB, CipherTest, Twofish128_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_CASE_P(Twofish128_GCM, CipherTest, Twofish128_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Twofish128_GCM, AuthenticatedCipherTest, Twofish128_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Serpent256_CFB, CipherTest, Serpent256_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_CASE_P(Serpent256_GCM, CipherTest, Serpent256_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Serpent256_GCM, AuthenticatedCipherTest, Serpent256_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Serpent128_CFB, CipherTest, Serpent128_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_CASE_P(Serpent128_GCM, CipherTest, Serpent128_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Serpent128_GCM, AuthenticatedCipherTest, Serpent128_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Cast256_CFB, CipherTest, Cast256_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_CASE_P(Cast256_GCM, CipherTest, Cast256_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Cast256_GCM, AuthenticatedCipherTest, Cast256_GCM); #if CRYPTOPP_VERSION != 564 INSTANTIATE_TYPED_TEST_CASE_P(Mars448_CFB, CipherTest, Mars448_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_CASE_P(Mars448_GCM, CipherTest, Mars448_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Mars448_GCM, AuthenticatedCipherTest, Mars448_GCM); #endif INSTANTIATE_TYPED_TEST_CASE_P(Mars256_CFB, CipherTest, Mars256_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_CASE_P(Mars256_GCM, CipherTest, Mars256_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Mars256_GCM, AuthenticatedCipherTest, Mars256_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Mars128_CFB, CipherTest, Mars128_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_CASE_P(Mars128_GCM, CipherTest, Mars128_GCM); INSTANTIATE_TYPED_TEST_CASE_P(Mars128_GCM, AuthenticatedCipherTest, Mars128_GCM); // Test cipher names TEST(CipherNameTest, TestCipherNames) { EXPECT_EQ("aes-256-gcm", string(AES256_GCM::NAME)); EXPECT_EQ("aes-256-cfb", string(AES256_CFB::NAME)); EXPECT_EQ("aes-128-gcm", string(AES128_GCM::NAME)); EXPECT_EQ("aes-128-cfb", string(AES128_CFB::NAME)); EXPECT_EQ("twofish-256-gcm", string(Twofish256_GCM::NAME)); EXPECT_EQ("twofish-256-cfb", string(Twofish256_CFB::NAME)); EXPECT_EQ("twofish-128-gcm", string(Twofish128_GCM::NAME)); EXPECT_EQ("twofish-128-cfb", string(Twofish128_CFB::NAME)); EXPECT_EQ("serpent-256-gcm", string(Serpent256_GCM::NAME)); EXPECT_EQ("serpent-256-cfb", string(Serpent256_CFB::NAME)); EXPECT_EQ("serpent-128-gcm", string(Serpent128_GCM::NAME)); EXPECT_EQ("serpent-128-cfb", string(Serpent128_CFB::NAME)); EXPECT_EQ("cast-256-gcm", string(Cast256_GCM::NAME)); EXPECT_EQ("cast-256-cfb", string(Cast256_CFB::NAME)); #if CRYPTOPP_VERSION != 564 EXPECT_EQ("mars-448-gcm", string(Mars448_GCM::NAME)); EXPECT_EQ("mars-448-cfb", string(Mars448_CFB::NAME)); #endif EXPECT_EQ("mars-256-gcm", string(Mars256_GCM::NAME)); EXPECT_EQ("mars-256-cfb", string(Mars256_CFB::NAME)); EXPECT_EQ("mars-128-gcm", string(Mars128_GCM::NAME)); EXPECT_EQ("mars-128-cfb", string(Mars128_CFB::NAME)); } test/cpp-utils/data/000077500000000000000000000000001347701267100146765ustar00rootroot00000000000000test/cpp-utils/data/DataFixtureIncludeTest.cpp000066400000000000000000000001631347701267100217660ustar00rootroot00000000000000#include "cpp-utils/data/DataFixture.h" // Test the header can be included without needing additional dependenciestest/cpp-utils/data/DataFixtureTest.cpp000066400000000000000000000044151347701267100204660ustar00rootroot00000000000000#include #include "cpp-utils/data/Data.h" #include "cpp-utils/data/DataFixture.h" using ::testing::Test; using namespace cpputils; class DataFixtureTest: public Test { }; TEST_F(DataFixtureTest, CreateEmptyFixture) { Data data = DataFixture::generate(0); EXPECT_EQ(0u, data.size()); } TEST_F(DataFixtureTest, CreateOneByteFixture) { Data data = DataFixture::generate(1); EXPECT_EQ(1u, data.size()); } TEST_F(DataFixtureTest, CreateLargerFixture) { Data data = DataFixture::generate(20 * 1024 * 1024); EXPECT_EQ(20u * 1024u * 1024u, data.size()); } TEST_F(DataFixtureTest, FixturesAreDeterministic_DefaultSeed) { Data data1 = DataFixture::generate(1024 * 1024); Data data2 = DataFixture::generate(1024 * 1024); EXPECT_EQ(data1, data2); } TEST_F(DataFixtureTest, FixturesAreDeterministic_SeedIs5) { Data data1 = DataFixture::generate(1024 * 1024, 5); Data data2 = DataFixture::generate(1024 * 1024, 5); EXPECT_EQ(data1, data2); } TEST_F(DataFixtureTest, DifferentSeedIsDifferentFixture) { Data data1 = DataFixture::generate(1024 * 1024, 0); Data data2 = DataFixture::generate(1024 * 1024, 1); EXPECT_NE(data1, data2); } TEST_F(DataFixtureTest, FixturesAreDeterministic_DifferentSize_DefaultSeed_1) { Data data1 = DataFixture::generate(1024); Data data2 = DataFixture::generate(1); EXPECT_EQ(0, std::memcmp(data1.data(), data2.data(), 1)); } TEST_F(DataFixtureTest, FixturesAreDeterministic_DifferentSize_DefaultSeed_2) { Data data1 = DataFixture::generate(1024); Data data2 = DataFixture::generate(501); //Intentionally not 64bit-aligned, because the generate() function generates 64bit values for performance EXPECT_EQ(0, std::memcmp(data1.data(), data2.data(), 501)); } TEST_F(DataFixtureTest, FixturesAreDeterministic_DifferentSize_SeedIs5_1) { Data data1 = DataFixture::generate(1024, 5); Data data2 = DataFixture::generate(1, 5); EXPECT_EQ(0, std::memcmp(data1.data(), data2.data(), 1)); } TEST_F(DataFixtureTest, FixturesAreDeterministic_DifferentSize_SeedIs5_2) { Data data1 = DataFixture::generate(1024, 5); Data data2 = DataFixture::generate(501, 5); //Intentionally not 64bit-aligned, because the generate() function generates 64bit values for performance EXPECT_EQ(0, std::memcmp(data1.data(), data2.data(), 501)); } test/cpp-utils/data/DataIncludeTest.cpp000066400000000000000000000001541347701267100204170ustar00rootroot00000000000000#include "cpp-utils/data/Data.h" // Test the header can be included without needing additional dependenciestest/cpp-utils/data/DataTest.cpp000066400000000000000000000216741347701267100171250ustar00rootroot00000000000000#include "cpp-utils/data/DataFixture.h" #include "cpp-utils/data/Data.h" #include "cpp-utils/data/SerializationHelper.h" #include #include "cpp-utils/tempfile/TempFile.h" #include using ::testing::Test; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Return; using ::testing::_; using cpputils::TempFile; using std::ifstream; using std::ofstream; using std::string; namespace bf = boost::filesystem; using namespace cpputils; class DataTest: public Test { public: bool DataIsZeroes(const Data &data) { for (size_t i = 0; i != data.size(); ++ i) { if (deserialize(data.dataOffset(i)) != 0) { return false; } } return true; } }; class DataTestWithSizeParam: public DataTest, public WithParamInterface { public: Data randomData; DataTestWithSizeParam(): randomData(DataFixture::generate(GetParam())) {} static void StoreData(const Data &data, const bf::path &filepath) { ofstream file(filepath.string().c_str(), std::ios::binary | std::ios::trunc); file.write(static_cast(data.data()), data.size()); } static void EXPECT_STORED_FILE_DATA_CORRECT(const Data &data, const bf::path &filepath) { EXPECT_EQ(data.size(), bf::file_size(filepath)); ifstream file(filepath.string().c_str(), std::ios::binary); char *read_data = new char[data.size()]; file.read(read_data, data.size()); EXPECT_EQ(0, std::memcmp(data.data(), read_data, data.size())); delete[] read_data; } }; INSTANTIATE_TEST_CASE_P(DataTestWithSizeParam, DataTestWithSizeParam, Values(0, 1, 2, 1024, 4096, 10*1024*1024)); TEST_P(DataTestWithSizeParam, ZeroInitializedDataIsDifferentToRandomData) { if (GetParam() != 0) { Data data(GetParam()); data.FillWithZeroes(); EXPECT_NE(randomData, data); } } // Working on a large data area without a crash is a good indicator that we // are actually working on memory that was validly allocated for us. TEST_P(DataTestWithSizeParam, WriteAndCheck) { Data data = randomData.copy(); EXPECT_EQ(randomData, data); } TEST_P(DataTestWithSizeParam, Size) { Data data(GetParam()); EXPECT_EQ(GetParam(), data.size()); } TEST_P(DataTestWithSizeParam, CheckStoredFile) { TempFile file; randomData.StoreToFile(file.path()); EXPECT_STORED_FILE_DATA_CORRECT(randomData, file.path()); } TEST_P(DataTestWithSizeParam, CheckLoadedData) { TempFile file; StoreData(randomData, file.path()); Data data = Data::LoadFromFile(file.path()).value(); EXPECT_EQ(randomData, data); } TEST_P(DataTestWithSizeParam, StoreDoesntChangeData) { Data data = randomData.copy(); TempFile file; data.StoreToFile(file.path()); EXPECT_EQ(randomData, data); } TEST_P(DataTestWithSizeParam, StoreAndLoad) { TempFile file; randomData.StoreToFile(file.path()); Data loaded_data = Data::LoadFromFile(file.path()).value(); EXPECT_EQ(randomData, loaded_data); } TEST_P(DataTestWithSizeParam, Copy) { Data copy = randomData.copy(); EXPECT_EQ(randomData, copy); } TEST_F(DataTest, ChangingCopyDoesntChangeOriginal) { Data original = DataFixture::generate(1024); Data copy = original.copy(); serialize(copy.data(), deserialize(copy.data()) + 1); EXPECT_EQ(DataFixture::generate(1024), original); EXPECT_NE(copy, original); } TEST_F(DataTest, InitializeWithZeroes) { Data data(10*1024); data.FillWithZeroes(); EXPECT_TRUE(DataIsZeroes(data)); } TEST_F(DataTest, FillModifiedDataWithZeroes) { Data data = DataFixture::generate(10*1024); EXPECT_FALSE(DataIsZeroes(data)); data.FillWithZeroes(); EXPECT_TRUE(DataIsZeroes(data)); } TEST_F(DataTest, MoveConstructor) { Data original = DataFixture::generate(1024); Data copy(std::move(original)); EXPECT_EQ(DataFixture::generate(1024), copy); EXPECT_EQ(nullptr, original.data()); // NOLINT (intentional use-after-move) EXPECT_EQ(0u, original.size()); // NOLINT (intentional use-after-move) } TEST_F(DataTest, MoveAssignment) { Data original = DataFixture::generate(1024); Data copy(0); copy = std::move(original); EXPECT_EQ(DataFixture::generate(1024), copy); EXPECT_EQ(nullptr, original.data()); // NOLINT (intentional use-after-move) EXPECT_EQ(0u, original.size()); // NOLINT (intentional use-after-move) } TEST_F(DataTest, Equality) { Data data1 = DataFixture::generate(1024); Data data2 = DataFixture::generate(1024); EXPECT_TRUE(data1 == data2); EXPECT_FALSE(data1 != data2); } TEST_F(DataTest, Inequality_DifferentSize) { Data data1 = DataFixture::generate(1024); Data data2 = DataFixture::generate(1023); EXPECT_FALSE(data1 == data2); EXPECT_TRUE(data1 != data2); } TEST_F(DataTest, Inequality_DifferentFirstByte) { Data data1 = DataFixture::generate(1024); Data data2 = DataFixture::generate(1024); serialize(data2.data(), deserialize(data2.data()) + 1); EXPECT_FALSE(data1 == data2); EXPECT_TRUE(data1 != data2); } TEST_F(DataTest, Inequality_DifferentMiddleByte) { Data data1 = DataFixture::generate(1024); Data data2 = DataFixture::generate(1024); serialize(data2.dataOffset(500), deserialize(data2.dataOffset(500)) + 1); EXPECT_FALSE(data1 == data2); EXPECT_TRUE(data1 != data2); } TEST_F(DataTest, Inequality_DifferentLastByte) { Data data1 = DataFixture::generate(1024); Data data2 = DataFixture::generate(1024); serialize(data2.dataOffset(1023), deserialize(data2.dataOffset(1023)) + 1); EXPECT_FALSE(data1 == data2); EXPECT_TRUE(data1 != data2); } #ifdef __x86_64__ TEST_F(DataTest, LargesizeSize) { //Needs 64bit for representation. This value isn't in the size param list, because the list is also used for read/write checks. uint64_t size = static_cast(4.5L*1024*1024*1024); Data data(size); EXPECT_EQ(size, data.size()); } #else #if defined(_MSC_VER) #pragma message This is not a 64bit architecture. Large size data tests are disabled. #else #warning This is not a 64bit architecture. Large size data tests are disabled. #endif #endif TEST_F(DataTest, LoadingNonexistingFile) { TempFile file(false); // Pass false to constructor, so the tempfile is not created EXPECT_FALSE(Data::LoadFromFile(file.path())); } class DataTestWithStringParam: public DataTest, public WithParamInterface {}; INSTANTIATE_TEST_CASE_P(DataTestWithStringParam, DataTestWithStringParam, Values("", "2898B4B8A13C0F0278CCE465DB", "6FFEBAD90C0DAA2B79628F0627CE9841")); TEST_P(DataTestWithStringParam, FromAndToString) { Data data = Data::FromString(GetParam()); EXPECT_EQ(GetParam(), data.ToString()); } TEST_P(DataTestWithStringParam, ToAndFromString) { Data data = Data::FromString(GetParam()); Data data2 = Data::FromString(data.ToString()); EXPECT_EQ(data, data2); } struct MockAllocator final : public Allocator { MOCK_METHOD1(allocate, void* (size_t)); MOCK_METHOD2(free, void(void*, size_t)); }; class DataTestWithMockAllocator: public DataTest { public: char ptr_target{}; unique_ref allocator = make_unique_ref(); MockAllocator* allocator_ptr = allocator.get(); }; TEST_F(DataTestWithMockAllocator, whenCreatingNewData_thenTakesItFromAllocator) { EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target)); Data data(5, std::move(allocator)); EXPECT_EQ(&ptr_target, data.data()); } TEST_F(DataTestWithMockAllocator, whenDestructingData_thenFreesItInAllocator) { EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target)); Data data(5, std::move(allocator)); EXPECT_CALL(*allocator_ptr, free(&ptr_target, 5)).Times(1); } TEST_F(DataTestWithMockAllocator, whenMoveConstructing_thenOnlyFreesOnce) { EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target)); Data data(5, std::move(allocator)); Data data2 = std::move(data); EXPECT_CALL(*allocator_ptr, free(&ptr_target, 5)).Times(1); } TEST_F(DataTestWithMockAllocator, whenMoveAssigning_thenOnlyFreesOnce) { EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target)); Data data(5, std::move(allocator)); Data data2(3); data2 = std::move(data); EXPECT_CALL(*allocator_ptr, free(&ptr_target, 5)).Times(1); } TEST_F(DataTestWithMockAllocator, whenMoveConstructing_thenOnlyFreesWhenSecondIsDestructed) { EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target)); EXPECT_CALL(*allocator_ptr, free(_, _)).Times(0); auto data = std::make_unique(5, std::move(allocator)); Data data2 = std::move(*data); data.reset(); EXPECT_CALL(*allocator_ptr, free(&ptr_target, 5)).Times(1); } TEST_F(DataTestWithMockAllocator, whenMoveAssigning_thenOnlyFreesWhenSecondIsDestructed) { EXPECT_CALL(*allocator, allocate(5)).Times(1).WillOnce(Return(&ptr_target)); EXPECT_CALL(*allocator_ptr, free(_, _)).Times(0); auto data = std::make_unique(5, std::move(allocator)); Data data2(3); data2 = std::move(*data); data.reset(); EXPECT_CALL(*allocator_ptr, free(&ptr_target, 5)).Times(1); } test/cpp-utils/data/FixedSizeDataIncludeTest.cpp000066400000000000000000000001651347701267100222340ustar00rootroot00000000000000#include "cpp-utils/data/FixedSizeData.h" // Test the header can be included without needing additional dependenciestest/cpp-utils/data/FixedSizeDataTest.cpp000066400000000000000000000161671347701267100207410ustar00rootroot00000000000000#include "cpp-utils/data/DataFixture.h" #include "cpp-utils/data/FixedSizeData.h" #include "cpp-utils/data/Data.h" #include using ::testing::Test; using ::testing::WithParamInterface; using ::testing::Values; using std::string; using namespace cpputils; class FixedSizeDataTest: public Test { public: static constexpr size_t SIZE = 16; const string DATA1_AS_STRING = "1491BB4932A389EE14BC7090AC772972"; const string DATA2_AS_STRING = "272EE5517627CFA147A971A8E6E747E0"; const Data DATA3_AS_BINARY; const Data DATA4_AS_BINARY; FixedSizeDataTest() : DATA3_AS_BINARY(DataFixture::generate(SIZE, 1)), DATA4_AS_BINARY(DataFixture::generate(SIZE, 2)) {} template void EXPECT_DATA_EQ(const Data &expected, const FixedSizeData &actual) { EXPECT_EQ(expected.size(), SIZE); EXPECT_EQ(0, std::memcmp(expected.data(), actual.data(), SIZE)); } }; constexpr size_t FixedSizeDataTest::SIZE; TEST_F(FixedSizeDataTest, EqualsTrue) { FixedSizeData DATA1_1 = FixedSizeData::FromString(DATA1_AS_STRING); FixedSizeData DATA1_2 = FixedSizeData::FromString(DATA1_AS_STRING); EXPECT_TRUE(DATA1_1 == DATA1_2); EXPECT_TRUE(DATA1_2 == DATA1_1); } TEST_F(FixedSizeDataTest, EqualsFalse) { FixedSizeData DATA1_1 = FixedSizeData::FromString(DATA1_AS_STRING); FixedSizeData DATA2_1 = FixedSizeData::FromString(DATA2_AS_STRING); EXPECT_FALSE(DATA1_1 == DATA2_1); EXPECT_FALSE(DATA2_1 == DATA1_1); } TEST_F(FixedSizeDataTest, NotEqualsFalse) { FixedSizeData DATA1_1 = FixedSizeData::FromString(DATA1_AS_STRING); FixedSizeData DATA1_2 = FixedSizeData::FromString(DATA1_AS_STRING); EXPECT_FALSE(DATA1_1 != DATA1_2); EXPECT_FALSE(DATA1_2 != DATA1_1); } TEST_F(FixedSizeDataTest, NotEqualsTrue) { FixedSizeData DATA1_1 = FixedSizeData::FromString(DATA1_AS_STRING); FixedSizeData DATA2_1 = FixedSizeData::FromString(DATA2_AS_STRING); EXPECT_TRUE(DATA1_1 != DATA2_1); EXPECT_TRUE(DATA2_1 != DATA1_1); } class FixedSizeDataTestWithStringParam: public FixedSizeDataTest, public WithParamInterface {}; INSTANTIATE_TEST_CASE_P(FixedSizeDataTestWithStringParam, FixedSizeDataTestWithStringParam, Values("2898B4B8A13CA63CBE0F0278CCE465DB", "6FFEBAD90C0DAA2B79628F0627CE9841")); TEST_P(FixedSizeDataTestWithStringParam, FromAndToString) { FixedSizeData data = FixedSizeData::FromString(GetParam()); EXPECT_EQ(GetParam(), data.ToString()); } TEST_P(FixedSizeDataTestWithStringParam, ToAndFromString) { FixedSizeData data = FixedSizeData::FromString(GetParam()); FixedSizeData data2 = FixedSizeData::FromString(data.ToString()); EXPECT_EQ(data, data2); } class FixedSizeDataTestWithBinaryParam: public FixedSizeDataTest, public WithParamInterface { public: static const Data VALUE1; static const Data VALUE2; }; const Data FixedSizeDataTestWithBinaryParam::VALUE1(DataFixture::generate(SIZE, 3)); const Data FixedSizeDataTestWithBinaryParam::VALUE2(DataFixture::generate(SIZE, 4)); INSTANTIATE_TEST_CASE_P(FixedSizeDataTestWithBinaryParam, FixedSizeDataTestWithBinaryParam, Values(&FixedSizeDataTestWithBinaryParam::VALUE1, &FixedSizeDataTestWithBinaryParam::VALUE2)); TEST_P(FixedSizeDataTestWithBinaryParam, FromBinary) { FixedSizeData data = FixedSizeData::FromBinary(GetParam()->data()); EXPECT_DATA_EQ(*GetParam(), data); } TEST_P(FixedSizeDataTestWithBinaryParam, FromAndToBinary) { FixedSizeData data = FixedSizeData::FromBinary(GetParam()->data()); Data output(FixedSizeData::BINARY_LENGTH); data.ToBinary(output.data()); EXPECT_EQ(*GetParam(), output); } TEST_P(FixedSizeDataTestWithBinaryParam, ToAndFromBinary) { FixedSizeData data = FixedSizeData::FromBinary(GetParam()->data()); Data stored(FixedSizeData::BINARY_LENGTH); data.ToBinary(stored.data()); FixedSizeData loaded = FixedSizeData::FromBinary(stored.data()); EXPECT_EQ(data, loaded); } class FixedSizeDataTestWithParam: public FixedSizeDataTest, public WithParamInterface> {}; INSTANTIATE_TEST_CASE_P(FixedSizeDataTestWithParam, FixedSizeDataTestWithParam, Values(FixedSizeData::FromString("2898B4B8A13CA63CBE0F0278CCE465DB"), FixedSizeData::FromString("6FFEBAD90C0DAA2B79628F0627CE9841"))); TEST_P(FixedSizeDataTestWithParam, CopyConstructor) { FixedSizeData copy(GetParam()); EXPECT_EQ(GetParam(), copy); } TEST_P(FixedSizeDataTestWithParam, Take_Half) { FixedSizeData source(GetParam()); FixedSizeData taken = source.take(); EXPECT_EQ(0, std::memcmp(source.data(), taken.data(), SIZE/2)); } TEST_P(FixedSizeDataTestWithParam, Drop_Half) { FixedSizeData source(GetParam()); FixedSizeData taken = source.drop(); EXPECT_EQ(0, std::memcmp(source.data() + SIZE/2, taken.data(), SIZE/2)); } TEST_P(FixedSizeDataTestWithParam, Take_One) { FixedSizeData source(GetParam()); FixedSizeData<1> taken = source.take<1>(); EXPECT_EQ(0, std::memcmp(source.data(), taken.data(), 1)); } TEST_P(FixedSizeDataTestWithParam, Drop_One) { FixedSizeData source(GetParam()); FixedSizeData taken = source.drop<1>(); EXPECT_EQ(0, std::memcmp(source.data() + 1, taken.data(), SIZE-1)); } TEST_P(FixedSizeDataTestWithParam, Take_Nothing) { FixedSizeData source(GetParam()); FixedSizeData<0> taken = source.take<0>(); (void)taken; // silence unused variable warning } TEST_P(FixedSizeDataTestWithParam, Drop_Nothing) { FixedSizeData source(GetParam()); FixedSizeData taken = source.drop<0>(); EXPECT_EQ(0, std::memcmp(source.data(), taken.data(), SIZE)); } TEST_P(FixedSizeDataTestWithParam, Take_All) { FixedSizeData source(GetParam()); FixedSizeData taken = source.take(); EXPECT_EQ(0, std::memcmp(source.data(), taken.data(), SIZE)); } TEST_P(FixedSizeDataTestWithParam, Drop_All) { FixedSizeData source(GetParam()); FixedSizeData<0> taken = source.drop(); (void)taken; // silence unused variable warning } TEST_F(FixedSizeDataTest, CopyConstructorDoesntChangeSource) { FixedSizeData data1 = FixedSizeData::FromString(DATA1_AS_STRING); FixedSizeData data2(data1); EXPECT_EQ(DATA1_AS_STRING, data1.ToString()); (void)data2; // silence unused variable warning } TEST_P(FixedSizeDataTestWithParam, IsEqualAfterAssignment1) { FixedSizeData data2 = FixedSizeData::FromString(DATA2_AS_STRING); EXPECT_NE(GetParam(), data2); data2 = GetParam(); EXPECT_EQ(GetParam(), data2); } TEST_F(FixedSizeDataTest, AssignmentDoesntChangeSource) { FixedSizeData data1 = FixedSizeData::FromString(DATA1_AS_STRING); FixedSizeData data2 = FixedSizeData::FromString(DATA2_AS_STRING); data2 = data1; EXPECT_EQ(DATA1_AS_STRING, data1.ToString()); } // This tests that a FixedSizeData object is very lightweight // (it is meant to be kept on stack and passed around) TEST_F(FixedSizeDataTest, IsLightweightObject) { EXPECT_EQ(FixedSizeData::BINARY_LENGTH, sizeof(FixedSizeData)); } test/cpp-utils/data/SerializationHelperTest.cpp000066400000000000000000000141441347701267100222230ustar00rootroot00000000000000#include #include #include using cpputils::serialize; using cpputils::deserialize; using cpputils::deserializeWithOffset; using cpputils::Data; TEST(SerializationHelperTest, uint8) { Data data(1); serialize(data.data(), 5u); EXPECT_EQ(5u, deserialize(data.data())); } TEST(SerializationHelperTest, int8_positive) { Data data(1); serialize(data.data(), 5); EXPECT_EQ(5, deserialize(data.data())); } TEST(SerializationHelperTest, int8_negative) { Data data(1); serialize(data.data(), -5); EXPECT_EQ(-5, deserialize(data.data())); } TEST(SerializationHelperTest, uint16_aligned) { Data data(2); serialize(data.data(), 1000u); EXPECT_EQ(1000u, deserialize(data.data())); } TEST(SerializationHelperTest, uint16_unaligned) { Data data(3); serialize(data.dataOffset(1), 1000u); EXPECT_EQ(1000u, deserialize(data.dataOffset(1))); } TEST(SerializationHelperTest, int16_postive_aligned) { Data data(2); serialize(data.data(), 1000); EXPECT_EQ(1000, deserialize(data.data())); } TEST(SerializationHelperTest, int16_positive_unaligned) { Data data(3); serialize(data.dataOffset(1), 1000); EXPECT_EQ(1000, deserialize(data.dataOffset(1))); } TEST(SerializationHelperTest, int16_negative_aligned) { Data data(2); serialize(data.data(), -1000); EXPECT_EQ(-1000, deserialize(data.data())); } TEST(SerializationHelperTest, int16_negative_unaligned) { Data data(3); serialize(data.dataOffset(1), -1000); EXPECT_EQ(-1000, deserialize(data.dataOffset(1))); } TEST(SerializationHelperTest, uint32_aligned) { Data data(4); serialize(data.data(), 100000u); EXPECT_EQ(100000u, deserialize(data.data())); } TEST(SerializationHelperTest, uint32_unaligned) { Data data(5); serialize(data.dataOffset(1), 100000u); EXPECT_EQ(100000u, deserialize(data.dataOffset(1))); } TEST(SerializationHelperTest, int32_positive_aligned) { Data data(4); serialize(data.data(), 100000); EXPECT_EQ(100000, deserialize(data.data())); } TEST(SerializationHelperTest, int32_positive_unaligned) { Data data(5); serialize(data.dataOffset(1), 100000); EXPECT_EQ(100000, deserialize(data.dataOffset(1))); } TEST(SerializationHelperTest, int32_negative_aligned) { Data data(4); serialize(data.data(), -100000); EXPECT_EQ(-100000, deserialize(data.data())); } TEST(SerializationHelperTest, int32_negative_unaligned) { Data data(5); serialize(data.dataOffset(1), -100000); EXPECT_EQ(-100000, deserialize(data.dataOffset(1))); } TEST(SerializationHelperTest, uint64_aligned) { Data data(8); serialize(data.data(), 10000000000u); EXPECT_EQ(10000000000u, deserialize(data.data())); } TEST(SerializationHelperTest, uint64_unaligned) { Data data(9); serialize(data.dataOffset(1), 10000000000u); EXPECT_EQ(10000000000u, deserialize(data.dataOffset(1))); } TEST(SerializationHelperTest, int64_positive_aligned) { Data data(8); serialize(data.data(), 10000000000); EXPECT_EQ(10000000000, deserialize(data.data())); } TEST(SerializationHelperTest, int64_positive_unaligned) { Data data(9); serialize(data.dataOffset(1), 10000000000); EXPECT_EQ(10000000000, deserialize(data.dataOffset(1))); } TEST(SerializationHelperTest, int64_negative_aligned) { Data data(8); serialize(data.data(), -10000000000); EXPECT_EQ(-10000000000, deserialize(data.data())); } TEST(SerializationHelperTest, int64_negative_unaligned) { Data data(9); serialize(data.dataOffset(1), -10000000000); EXPECT_EQ(-10000000000, deserialize(data.dataOffset(1))); } TEST(SerializationHelperTest, float_aligned) { Data data(sizeof(float)); serialize(data.data(), 3.1415f); EXPECT_EQ(3.1415f, deserialize(data.data())); } TEST(SerializationHelperTest, float_unaligned) { Data data(sizeof(float) + 1); serialize(data.dataOffset(1), 3.1415f); EXPECT_EQ(3.1415f, deserialize(data.dataOffset(1))); } TEST(SerializationHelperTest, double_aligned) { Data data(sizeof(double)); serialize(data.data(), 3.1415); EXPECT_EQ(3.1415, deserialize(data.data())); } TEST(SerializationHelperTest, double_unaligned) { Data data(sizeof(double) + 1); serialize(data.dataOffset(1), 3.1415); EXPECT_EQ(3.1415, deserialize(data.dataOffset(1))); } namespace { struct DataStructure final { uint64_t v1; uint32_t v2; uint16_t v3; uint8_t v4; }; bool operator==(const DataStructure &lhs, const DataStructure &rhs) { return lhs.v1 == rhs.v1 && lhs.v2 == rhs.v2 && lhs.v3 == rhs.v3 && lhs.v4 == rhs.v4; } } TEST(SerializationHelperTest, struct_aligned) { Data data(sizeof(DataStructure)); const DataStructure fixture {10000000000u, 100000u, 1000u, 5u}; serialize(data.data(), fixture); EXPECT_EQ(fixture, deserialize(data.data())); } TEST(SerializationHelperTest, struct_unaligned) { Data data(sizeof(DataStructure) + 1); const DataStructure fixture {10000000000u, 100000u, 1000u, 5u}; serialize(data.dataOffset(1), fixture); EXPECT_EQ(fixture, deserialize(data.dataOffset(1))); } namespace { struct OneByteStruct final { uint8_t v; }; static_assert(sizeof(OneByteStruct) == 1, ""); } TEST(SerializationHelperTest, onebytestruct) { Data data(1); OneByteStruct fixture {5}; serialize(data.data(), fixture); EXPECT_EQ(fixture.v, deserialize(data.data()).v); } TEST(SerializationHelperTest, deserializeWithOffset) { Data data(5); serialize(data.dataOffset(1), 1000); EXPECT_EQ(1000, deserializeWithOffset(data.data(), 1)); } test/cpp-utils/io/000077500000000000000000000000001347701267100143745ustar00rootroot00000000000000test/cpp-utils/io/ConsoleIncludeTest.cpp000066400000000000000000000001561347701267100206500ustar00rootroot00000000000000#include "cpp-utils/io/Console.h" // Test the header can be included without needing additional dependencies test/cpp-utils/io/ConsoleTest.h000066400000000000000000000052271347701267100170150ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_TEST_IO_CONSOLETEST_H #define MESSMER_CPPUTILS_TEST_IO_CONSOLETEST_H #include #include "cpp-utils/io/IOStreamConsole.h" #include #include #include "cpp-utils/io/pipestream.h" class ConsoleThread { public: ConsoleThread(std::ostream &ostr, std::istream &istr): _console(ostr, istr) {} std::future ask(const std::string &question, const std::vector &options) { return std::async(std::launch::async, [this, question, options]() { return _console.ask(question, options); }); } std::future askYesNo(const std::string &question) { return std::async(std::launch::async, [this, question]() { return _console.askYesNo(question, true); }); } std::future askPassword(const std::string &question) { return std::async(std::launch::async, [this, question]() { return _console.askPassword(question); }); } void print(const std::string &output) { _console.print(output); } private: cpputils::IOStreamConsole _console; }; class ConsoleTest: public ::testing::Test { public: ConsoleTest(): _inputStr(), _outputStr(), _input(&_inputStr), _output(&_outputStr), _console(_output, _input) {} void EXPECT_OUTPUT_LINES(std::initializer_list lines) { for (const std::string &line : lines) { EXPECT_OUTPUT_LINE(line); } } void EXPECT_OUTPUT_LINE(const std::string &expected, char delimiter = '\n', const std::string &expected_after_delimiter = "") { std::string actual; std::getline(_output, actual, delimiter); EXPECT_EQ(expected, actual); for (char expected_char : expected_after_delimiter) { char actual_char; _output.get(actual_char); EXPECT_EQ(expected_char, actual_char); } } void sendInputLine(const std::string &line) { _input << line << "\n" << std::flush; } std::future ask(const std::string &question, const std::vector &options) { return _console.ask(question, options); } std::future askYesNo(const std::string &question) { return _console.askYesNo(question); } std::future askPassword(const std::string &question) { return _console.askPassword(question); } void print(const std::string &output) { _console.print(output); } private: cpputils::pipestream _inputStr; cpputils::pipestream _outputStr; std::iostream _input; std::iostream _output; ConsoleThread _console; }; #endif test/cpp-utils/io/ConsoleTest_Ask.cpp000066400000000000000000000123741347701267100201470ustar00rootroot00000000000000#include "ConsoleTest.h" using std::stringstream; using std::string; using std::istream; using std::ostream; class ConsoleTest_Ask: public ConsoleTest {}; TEST_F(ConsoleTest_Ask, CrashesWithoutOptions) { EXPECT_THROW( (ask("My Question?", {}).get()), std::invalid_argument ); } TEST_F(ConsoleTest_Ask, OneOption) { auto chosen = ask("My Question?", {"First Option"}); EXPECT_OUTPUT_LINES({ "My Question?", " [1] First Option" }); EXPECT_OUTPUT_LINE("Your choice [1-1]", ':', " "); sendInputLine("1"); EXPECT_EQ(0u, chosen.get()); } TEST_F(ConsoleTest_Ask, TwoOptions_ChooseFirst) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", " [1] First Option", " [2] Second Option" }); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("1"); EXPECT_EQ(0u, chosen.get()); } TEST_F(ConsoleTest_Ask, TwoOptions_ChooseSecond) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", " [1] First Option", " [2] Second Option" }); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("2"); EXPECT_EQ(1u, chosen.get()); } TEST_F(ConsoleTest_Ask, ThreeOptions_ChooseFirst) { auto chosen = ask("My Other Question?", {"1st Option", "2nd Option", "3rd Option"}); EXPECT_OUTPUT_LINES({ "My Other Question?", " [1] 1st Option", " [2] 2nd Option", " [3] 3rd Option" }); EXPECT_OUTPUT_LINE("Your choice [1-3]", ':', " "); sendInputLine("1"); EXPECT_EQ(0u, chosen.get()); } TEST_F(ConsoleTest_Ask, ThreeOptions_ChooseSecond) { auto chosen = ask("My Question?", {"1st Option", "2nd Option", "3rd Option"}); EXPECT_OUTPUT_LINES({ "My Question?", " [1] 1st Option", " [2] 2nd Option", " [3] 3rd Option" }); EXPECT_OUTPUT_LINE("Your choice [1-3]", ':', " "); sendInputLine("2"); EXPECT_EQ(1u, chosen.get()); } TEST_F(ConsoleTest_Ask, ThreeOptions_ChooseThird) { auto chosen = ask("My Question?", {"1st Option", "2nd Option", "3rd Option"}); EXPECT_OUTPUT_LINES({ "My Question?", " [1] 1st Option", " [2] 2nd Option", " [3] 3rd Option" }); EXPECT_OUTPUT_LINE("Your choice [1-3]", ':', " "); sendInputLine("3"); EXPECT_EQ(2u, chosen.get()); } TEST_F(ConsoleTest_Ask, InputWithLeadingSpaces) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", " [1] First Option", " [2] Second Option" }); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine(" 2"); EXPECT_EQ(1u, chosen.get()); } TEST_F(ConsoleTest_Ask, InputWithFollowingSpaces) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", " [1] First Option", " [2] Second Option" }); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("2 "); EXPECT_EQ(1u, chosen.get()); } TEST_F(ConsoleTest_Ask, InputWithLeadingAndFollowingSpaces) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", " [1] First Option", " [2] Second Option" }); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine(" 2 "); EXPECT_EQ(1u, chosen.get()); } TEST_F(ConsoleTest_Ask, InputEmptyLine) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", " [1] First Option", " [2] Second Option" }); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine(""); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine(" "); // empty line with space EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("2"); EXPECT_EQ(1u, chosen.get()); } TEST_F(ConsoleTest_Ask, InputWrongNumbers) { auto chosen = ask("My Question?", {"1st Option", "2nd Option"}); EXPECT_OUTPUT_LINES({ "My Question?", " [1] 1st Option", " [2] 2nd Option", }); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("0"); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("-1"); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("3"); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("1.5"); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("1,5"); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("2"); EXPECT_EQ(1u, chosen.get()); } TEST_F(ConsoleTest_Ask, InputNonNumbers) { auto chosen = ask("My Question?", {"1st Option", "2nd Option"}); EXPECT_OUTPUT_LINES({ "My Question?", " [1] 1st Option", " [2] 2nd Option", }); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("abc"); EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("3a"); // Wrong number and string attached EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("1a"); // Right number but string attached EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("a3"); // Wrong number and string attached EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("a1"); // Right number but string attached EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("2"); EXPECT_EQ(1u, chosen.get()); }test/cpp-utils/io/ConsoleTest_AskPassword.cpp000066400000000000000000000012101347701267100216550ustar00rootroot00000000000000#include "ConsoleTest.h" using std::stringstream; using std::string; using std::istream; using std::ostream; class ConsoleTest_AskPassword: public ConsoleTest {}; TEST_F(ConsoleTest_AskPassword, InputSomePassword) { auto chosen = askPassword("Please enter my password:"); EXPECT_OUTPUT_LINE("Please enter my password", ':'); sendInputLine("this is the password"); EXPECT_EQ("this is the password", chosen.get()); } TEST_F(ConsoleTest_AskPassword, InputEmptyPassword) { auto chosen = askPassword("Please enter my password:"); EXPECT_OUTPUT_LINE("Please enter my password", ':'); sendInputLine(""); EXPECT_EQ("", chosen.get()); } test/cpp-utils/io/ConsoleTest_AskYesNo.cpp000066400000000000000000000062431347701267100211230ustar00rootroot00000000000000#include "ConsoleTest.h" using std::string; class ConsoleTest_AskYesNo: public ConsoleTest { public: void EXPECT_TRUE_ON_INPUT(const string &input) { EXPECT_RESULT_ON_INPUT(true, input); } void EXPECT_FALSE_ON_INPUT(const string &input) { EXPECT_RESULT_ON_INPUT(false, input); } void EXPECT_RESULT_ON_INPUT(const bool expected, const string &input) { auto chosen = askYesNo("Are you sure blablub?"); EXPECT_OUTPUT_LINES({"Are you sure blablub?"}); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine(input); EXPECT_EQ(expected, chosen.get()); } }; TEST_F(ConsoleTest_AskYesNo, Input_Yes) { EXPECT_TRUE_ON_INPUT("Yes"); } TEST_F(ConsoleTest_AskYesNo, Input_yes) { EXPECT_TRUE_ON_INPUT("yes"); } TEST_F(ConsoleTest_AskYesNo, Input_Y) { EXPECT_TRUE_ON_INPUT("Y"); } TEST_F(ConsoleTest_AskYesNo, Input_y) { EXPECT_TRUE_ON_INPUT("y"); } TEST_F(ConsoleTest_AskYesNo, Input_No) { EXPECT_FALSE_ON_INPUT("No"); } TEST_F(ConsoleTest_AskYesNo, Input_no) { EXPECT_FALSE_ON_INPUT("no"); } TEST_F(ConsoleTest_AskYesNo, Input_N) { EXPECT_FALSE_ON_INPUT("N"); } TEST_F(ConsoleTest_AskYesNo, Input_n) { EXPECT_FALSE_ON_INPUT("n"); } TEST_F(ConsoleTest_AskYesNo, InputWithLeadingSpaces) { EXPECT_TRUE_ON_INPUT(" y"); } TEST_F(ConsoleTest_AskYesNo, InputWithFollowingSpaces) { EXPECT_TRUE_ON_INPUT("y "); } TEST_F(ConsoleTest_AskYesNo, InputWithLeadingAndFollowingSpaces) { EXPECT_TRUE_ON_INPUT(" y "); } TEST_F(ConsoleTest_AskYesNo, InputEmptyLine) { auto chosen = askYesNo("My Question?"); EXPECT_OUTPUT_LINES({"My Question?"}); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine(""); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine(" "); // empty line with space EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("y"); EXPECT_EQ(true, chosen.get()); } TEST_F(ConsoleTest_AskYesNo, WrongInput) { auto chosen = askYesNo("My Question?"); EXPECT_OUTPUT_LINES({"My Question?"}); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("0"); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("1"); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("bla"); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("Y_andsomethingelse"); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("y_andsomethingelse"); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("N_andsomethingelse"); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("n_andsomethingelse"); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("Yes_andsomethingelse"); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("yes_andsomethingelse"); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("No_andsomethingelse"); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("no_andsomethingelse"); EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); sendInputLine("y"); EXPECT_EQ(true, chosen.get()); } test/cpp-utils/io/ConsoleTest_Print.cpp000066400000000000000000000002341347701267100205150ustar00rootroot00000000000000#include "ConsoleTest.h" TEST_F(ConsoleTest, Print) { print("Bla Blub"); EXPECT_OUTPUT_LINE("Bla Blu", 'b'); // 'b' is the delimiter for reading } test/cpp-utils/io/DontEchoStdinToStdoutRAIITest.cpp000066400000000000000000000003201347701267100226130ustar00rootroot00000000000000#include #include using cpputils::DontEchoStdinToStdoutRAII; TEST(DontEchoStdinToStdoutRAIITest, DoesntCrash) { DontEchoStdinToStdoutRAII a; } test/cpp-utils/io/ProgressBarTest.cpp000066400000000000000000000030221347701267100201660ustar00rootroot00000000000000#include #include 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&) 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(); 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%"); } test/cpp-utils/lock/000077500000000000000000000000001347701267100147155ustar00rootroot00000000000000test/cpp-utils/lock/ConditionBarrierIncludeTest.cpp000066400000000000000000000001711347701267100230210ustar00rootroot00000000000000#include "cpp-utils/lock/ConditionBarrier.h" // Test the header can be included without needing additional dependencies test/cpp-utils/lock/LockPoolIncludeTest.cpp000066400000000000000000000001611347701267100213050ustar00rootroot00000000000000#include "cpp-utils/lock/LockPool.h" // Test the header can be included without needing additional dependencies test/cpp-utils/lock/MutexPoolLockIncludeTest.cpp000066400000000000000000000001661347701267100223350ustar00rootroot00000000000000#include "cpp-utils/lock/MutexPoolLock.h" // Test the header can be included without needing additional dependencies test/cpp-utils/logging/000077500000000000000000000000001347701267100154135ustar00rootroot00000000000000test/cpp-utils/logging/LoggerIncludeTest.cpp000066400000000000000000000001621347701267100215010ustar00rootroot00000000000000#include "cpp-utils/logging/Logger.h" // Test the header can be included without needing additional dependencies test/cpp-utils/logging/LoggerTest.cpp000066400000000000000000000006241347701267100202000ustar00rootroot00000000000000#include "testutils/LoggingTest.h" /* * Contains test cases for the Logger class */ using namespace cpputils::logging; using std::string; class LoggerTest: public LoggingTest {}; TEST_F(LoggerTest, IsSingleton) { ASSERT_EQ(&logger(), &logger()); } TEST_F(LoggerTest, SetLogger) { logger().setLogger(spdlog::stderr_logger_mt("MyTestLog1")); EXPECT_EQ("MyTestLog1", logger()->name()); } test/cpp-utils/logging/LoggingIncludeTest.cpp000066400000000000000000000001631347701267100216510ustar00rootroot00000000000000#include "cpp-utils/logging/logging.h" // Test the header can be included without needing additional dependencies test/cpp-utils/logging/LoggingLevelTest.cpp000066400000000000000000000107421347701267100213410ustar00rootroot00000000000000#include "testutils/LoggingTest.h" #include using namespace cpputils::logging; using std::string; class LoggingLevelTest: public LoggingTest { public: void EXPECT_DEBUG_LOG_ENABLED() { LOG(DEBUG, "My log message"); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[debug\\].*My log message.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[debug\\].*My log message.*"))); } void EXPECT_DEBUG_LOG_DISABLED() { LOG(DEBUG, "My log message"); EXPECT_EQ("", mockLogger.capturedLog()); } void EXPECT_INFO_LOG_ENABLED() { LOG(INFO, "My log message"); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[info\\].*My log message.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[info\\].*My log message.*"))); } void EXPECT_INFO_LOG_DISABLED() { LOG(INFO, "My log message"); EXPECT_EQ("", mockLogger.capturedLog()); } void EXPECT_WARNING_LOG_ENABLED() { LOG(WARN, "My log message"); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[warning\\].*My log message.*"))); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[warning\\].*My log message.*")); } void EXPECT_WARNING_LOG_DISABLED() { LOG(WARN, "My log message"); EXPECT_EQ("", mockLogger.capturedLog()); } void EXPECT_ERROR_LOG_ENABLED() { LOG(ERR, "My log message"); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[error\\].*My log message.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[error\\].*My log message.*"))); } void EXPECT_ERROR_LOG_DISABLED() { LOG(ERR, "My log message"); EXPECT_EQ("", mockLogger.capturedLog()); } }; TEST_F(LoggingLevelTest, DefaultLevelIsInfo) { setLogger(mockLogger.get()); EXPECT_DEBUG_LOG_DISABLED(); EXPECT_INFO_LOG_ENABLED(); EXPECT_WARNING_LOG_ENABLED(); EXPECT_ERROR_LOG_ENABLED(); } TEST_F(LoggingLevelTest, DEBUG_SetBeforeSettingLogger) { setLevel(DEBUG); setLogger(mockLogger.get()); EXPECT_DEBUG_LOG_ENABLED(); EXPECT_INFO_LOG_ENABLED(); EXPECT_WARNING_LOG_ENABLED(); EXPECT_ERROR_LOG_ENABLED(); } TEST_F(LoggingLevelTest, DEBUG_SetAfterSettingLogger) { setLogger(mockLogger.get()); setLevel(DEBUG); EXPECT_DEBUG_LOG_ENABLED(); EXPECT_INFO_LOG_ENABLED(); EXPECT_WARNING_LOG_ENABLED(); EXPECT_ERROR_LOG_ENABLED(); } TEST_F(LoggingLevelTest, INFO_SetBeforeSettingLogger) { setLevel(INFO); setLogger(mockLogger.get()); EXPECT_DEBUG_LOG_DISABLED(); EXPECT_INFO_LOG_ENABLED(); EXPECT_WARNING_LOG_ENABLED(); EXPECT_ERROR_LOG_ENABLED(); } TEST_F(LoggingLevelTest, INFO_SetAfterSettingLogger) { setLogger(mockLogger.get()); setLevel(INFO); EXPECT_DEBUG_LOG_DISABLED(); EXPECT_INFO_LOG_ENABLED(); EXPECT_WARNING_LOG_ENABLED(); EXPECT_ERROR_LOG_ENABLED(); } TEST_F(LoggingLevelTest, WARNING_SetBeforeSettingLogger) { setLevel(WARN); setLogger(mockLogger.get()); EXPECT_DEBUG_LOG_DISABLED(); EXPECT_INFO_LOG_DISABLED(); EXPECT_WARNING_LOG_ENABLED(); EXPECT_ERROR_LOG_ENABLED(); } TEST_F(LoggingLevelTest, WARNING_SetAfterSettingLogger) { setLogger(mockLogger.get()); setLevel(WARN); EXPECT_DEBUG_LOG_DISABLED(); EXPECT_INFO_LOG_DISABLED(); EXPECT_WARNING_LOG_ENABLED(); EXPECT_ERROR_LOG_ENABLED(); } TEST_F(LoggingLevelTest, ERROR_SetBeforeSettingLogger) { setLevel(ERR); setLogger(mockLogger.get()); EXPECT_DEBUG_LOG_DISABLED(); EXPECT_INFO_LOG_DISABLED(); EXPECT_WARNING_LOG_DISABLED(); EXPECT_ERROR_LOG_ENABLED(); } TEST_F(LoggingLevelTest, ERROR_SetAfterSettingLogger) { setLogger(mockLogger.get()); setLevel(ERR); EXPECT_DEBUG_LOG_DISABLED(); EXPECT_INFO_LOG_DISABLED(); EXPECT_WARNING_LOG_DISABLED(); EXPECT_ERROR_LOG_ENABLED(); } test/cpp-utils/logging/LoggingTest.cpp000066400000000000000000000154771347701267100203630ustar00rootroot00000000000000#include "testutils/LoggingTest.h" #include /* * Contains test cases for the following logging interface: * LOG(INFO, "My log message)" */ using namespace cpputils::logging; using std::string; TEST_F(LoggingTest, DefaultLoggerIsStderr) { string output = captureStderr([]{ LOG(INFO, "My log message"); }); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(output, MatchesRegex(".*\\[Log\\].*\\[info\\].*My log message.*")); EXPECT_TRUE(std::regex_search(output, std::regex(".*\\[Log\\].*\\[info\\].*My log message.*"))); } TEST_F(LoggingTest, SetLogger_NewLoggerIsUsed) { setLogger(spdlog::stderr_logger_mt("MyTestLog2")); string output = captureStderr([]{ LOG(INFO, "My log message"); }); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(output, MatchesRegex(".*\\[MyTestLog2\\].*\\[info\\].*My log message.*")); EXPECT_TRUE(std::regex_search(output, std::regex(".*\\[MyTestLog2\\].*\\[info\\].*My log message.*"))); } TEST_F(LoggingTest, SetNonStderrLogger_LogsToNewLogger) { setLogger(mockLogger.get()); logger()->info("My log message"); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(output, MatchesRegex(".*\\[MockLogger\\].*\\[info\\].*My log message.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[info\\].*My log message.*"))); } TEST_F(LoggingTest, SetNonStderrLogger_DoesNotLogToStderr) { setLogger(mockLogger.get()); string output = captureStderr([] { logger()->info("My log message"); }); EXPECT_EQ("", output); } TEST_F(LoggingTest, InfoLog) { setLogger(mockLogger.get()); LOG(INFO, "My log message"); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[info\\].*My log message.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[info\\].*My log message.*"))); } TEST_F(LoggingTest, WarningLog) { setLogger(mockLogger.get()); LOG(WARN, "My log message"); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[warning\\].*My log message.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[warning\\].*My log message.*"))); } TEST_F(LoggingTest, DebugLog) { setLevel(DEBUG); setLogger(mockLogger.get()); LOG(DEBUG, "My log message"); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[debug\\].*My log message.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[debug\\].*My log message.*"))); } TEST_F(LoggingTest, ErrorLog) { setLogger(mockLogger.get()); LOG(ERR, "My log message"); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[error\\].*My log message.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[error\\].*My log message.*"))); } void logAndExit(const string &message) { LOG(INFO, message); exit(1); } // fork() only forks the main thread. This test ensures that logging doesn't depend on threads that suddenly aren't // there anymore after a fork(). TEST_F(LoggingTest, LoggingAlsoWorksAfterFork) { setLogger(spdlog::stderr_logger_mt("StderrLogger")); EXPECT_EXIT( logAndExit("My log message"), ::testing::ExitedWithCode(1), "My log message" ); } TEST_F(LoggingTest, MessageIsConstChar) { setLogger(mockLogger.get()); LOG(INFO, "My log message"); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[info\\].*My log message.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[info\\].*My log message.*"))); } TEST_F(LoggingTest, MessageIsString) { setLogger(mockLogger.get()); string msg = "My log message"; LOG(INFO, msg); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[info\\].*My log message.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[info\\].*My log message.*"))); } TEST_F(LoggingTest, FormatWithStringPlaceholder) { setLogger(mockLogger.get()); string str = "placeholder"; LOG(INFO, "My log message: {}", str); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[info\\].*My log message: placeholder.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[info\\].*My log message: placeholder.*"))); } TEST_F(LoggingTest, FormatWithConstCharPlaceholder) { setLogger(mockLogger.get()); LOG(INFO, "My log message: {}", "placeholder"); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[info\\].*My log message: placeholder.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[info\\].*My log message: placeholder.*"))); } TEST_F(LoggingTest, FormatWithIntPlaceholder) { setLogger(mockLogger.get()); LOG(INFO, "My log message: {}", 4); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[info\\].*My log message: 4.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[info\\].*My log message: 4.*"))); } TEST_F(LoggingTest, FormatWithMultiplePlaceholders) { setLogger(mockLogger.get()); LOG(INFO, "My log message: {}, {}, {}", 4, "then", true); // For some reason, the following doesn't seem to work in MSVC. Possibly because of the multiline string? //EXPECT_THAT(mockLogger.capturedLog(), MatchesRegex(".*\\[MockLogger\\].*\\[info\\].*My log message: 4, then, true.*")); EXPECT_TRUE(std::regex_search(mockLogger.capturedLog(), std::regex(".*\\[MockLogger\\].*\\[info\\].*My log message: 4, then, true.*"))); } test/cpp-utils/logging/testutils/000077500000000000000000000000001347701267100174535ustar00rootroot00000000000000test/cpp-utils/logging/testutils/LoggingTest.h000066400000000000000000000024011347701267100220470ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_TEST_LOGGING_TESTUTILS_LOGGINGTEST_H #define MESSMER_CPPUTILS_TEST_LOGGING_TESTUTILS_LOGGINGTEST_H #include #include #include "cpp-utils/logging/logging.h" #include class MockLogger final { public: MockLogger(): _capturedLogData(), _sink(std::make_shared>(_capturedLogData, true)), _logger(spdlog::create("MockLogger", {_sink})) { } ~MockLogger() { spdlog::drop("MockLogger"); }; std::shared_ptr get() { return _logger; } std::string capturedLog() const { return _capturedLogData.str(); } private: std::ostringstream _capturedLogData; std::shared_ptr> _sink; std::shared_ptr _logger; }; class LoggingTest: public ::testing::Test { public: LoggingTest(): mockLogger() {} std::string captureStderr(std::function func) { testing::internal::CaptureStderr(); func(); return testing::internal::GetCapturedStderr(); } ~LoggingTest() { cpputils::logging::reset(); } MockLogger mockLogger; }; #endif test/cpp-utils/network/000077500000000000000000000000001347701267100154565ustar00rootroot00000000000000test/cpp-utils/network/CurlHttpClientTest.cpp000066400000000000000000000022671347701267100217350ustar00rootroot00000000000000#include #include #include "cpp-utils/network/CurlHttpClient.h" #include "cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h" using std::string; using namespace cpputils; // Disable these by default because they depend on network // and - even if network is available - can fail depending // on the concrete network setup (e.g. if invalid domains are // answered with an ISP page instead of HTTP error) #ifdef CRYFS_ENABLE_NETWORK_TESTS TEST(CurlHttpClientTest, InvalidProtocol) { EXPECT_EQ(none, CurlHttpClient().get("invalid://example.com")); } TEST(CurlHttpClientTest, InvalidTld) { EXPECT_EQ(none, CurlHttpClient().get("http://example.invalidtld")); } TEST(CurlHttpClientTest, InvalidDomain) { EXPECT_EQ(none, CurlHttpClient().get("http://this_is_a_not_existing_domain.com")); } TEST(CurlHttpClientTest, ValidHttp) { string content = CurlHttpClient().get("http://example.com").value(); EXPECT_THAT(content, MatchesRegex(".*Example Domain.*")); } TEST(CurlHttpClientTest, ValidHttps) { string content = CurlHttpClient().get("https://example.com").value(); EXPECT_THAT(content, MatchesRegex(".*Example Domain.*")); } #endif test/cpp-utils/network/FakeHttpClientTest.cpp000066400000000000000000000025641347701267100216760ustar00rootroot00000000000000#include #include "cpp-utils/network/FakeHttpClient.h" #include "cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h" using boost::none; using namespace cpputils; TEST(FakeHttpClientTest, Empty) { EXPECT_ANY_THROW(FakeHttpClient().get("http://example.com")); } TEST(FakeHttpClientTest, Nonexisting) { FakeHttpClient client; client.addWebsite("http://existing.com", "content"); EXPECT_ANY_THROW(client.get("http://notexisting.com")); } TEST(FakeHttpClientTest, Existing) { FakeHttpClient client; client.addWebsite("http://existing.com", "content"); EXPECT_EQ("content", client.get("http://existing.com")); } TEST(FakeHttpClientTest, TwoExisting) { FakeHttpClient client; client.addWebsite("http://firstexisting.com", "first_content"); client.addWebsite("http://secondexisting.com", "second_content"); EXPECT_EQ("first_content", client.get("http://firstexisting.com")); EXPECT_EQ("second_content", client.get("http://secondexisting.com")); EXPECT_ANY_THROW(client.get("http://notexisting.com")); } TEST(FakeHttpClientTest, Overwriting) { FakeHttpClient client; client.addWebsite("http://existing.com", "content"); client.addWebsite("http://existing.com", "new_content"); EXPECT_EQ("new_content", client.get("http://existing.com")); } test/cpp-utils/pointer/000077500000000000000000000000001347701267100154455ustar00rootroot00000000000000test/cpp-utils/pointer/cast_include_test.cpp000066400000000000000000000001601347701267100216420ustar00rootroot00000000000000#include "cpp-utils/pointer/cast.h" // Test the header can be included without needing additional dependencies test/cpp-utils/pointer/cast_test.cpp000066400000000000000000000144551347701267100201530ustar00rootroot00000000000000#include #include #include "cpp-utils/pointer/cast.h" #include "cpp-utils/pointer/unique_ref.h" #include "cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h" //TODO There is a lot of duplication here, because each test case is there twice - once for unique_ptr, once for unique_ref. Remove redundancy by using generic test cases. //TODO Then also move the unique_ref related test cases there - cast_test.cpp should only contain the unique_ptr related ones. using namespace cpputils; using std::unique_ptr; using std::make_unique; using boost::optional; using boost::none; class DestructorCallback { public: MOCK_CONST_METHOD0(call, void()); }; class Parent { public: virtual ~Parent() { } }; class Child : public Parent { public: Child(const DestructorCallback *childDestructorCallback) : _destructorCallback(childDestructorCallback) { } Child(): Child(nullptr) {} ~Child() { if (_destructorCallback != nullptr) { _destructorCallback->call(); } } private: const DestructorCallback *_destructorCallback; DISALLOW_COPY_AND_ASSIGN(Child); }; class Child2 : public Parent {}; TEST(UniquePtr_DynamicPointerMoveTest, NullPtrParentToChildCast) { unique_ptr source(nullptr); unique_ptr casted = dynamic_pointer_move(source); EXPECT_EQ(nullptr, source.get()); EXPECT_EQ(nullptr, casted.get()); } TEST(UniquePtr_DynamicPointerMoveTest, NullPtrChildToParentCast) { unique_ptr source(nullptr); unique_ptr casted = dynamic_pointer_move(source); EXPECT_EQ(nullptr, source.get()); EXPECT_EQ(nullptr, casted.get()); } TEST(UniquePtr_DynamicPointerMoveTest, NullPtrSelfCast) { unique_ptr source(nullptr); unique_ptr casted = dynamic_pointer_move(source); EXPECT_EQ(nullptr, source.get()); EXPECT_EQ(nullptr, casted.get()); } TEST(UniqueRef_DynamicPointerMoveTest, ValidParentToChildCast) { Child *obj = new Child(); unique_ref source(nullcheck(unique_ptr(obj)).value()); unique_ref casted = dynamic_pointer_move(source).value(); EXPECT_FALSE(source.is_valid()); // source lost ownership EXPECT_EQ(obj, casted.get()); } TEST(UniquePtr_DynamicPointerMoveTest, ValidParentToChildCast) { Child *obj = new Child(); unique_ptr source(obj); unique_ptr casted = dynamic_pointer_move(source); EXPECT_EQ(nullptr, source.get()); // source lost ownership EXPECT_EQ(obj, casted.get()); } TEST(UniqueRef_DynamicPointerMoveTest, InvalidParentToChildCast1) { Parent *obj = new Parent(); unique_ref source(nullcheck(unique_ptr(obj)).value()); optional> casted = dynamic_pointer_move(source); EXPECT_EQ(obj, source.get()); // source still has ownership EXPECT_EQ(none, casted); } TEST(UniquePtr_DynamicPointerMoveTest, InvalidParentToChildCast1) { Parent *obj = new Parent(); unique_ptr source(obj); unique_ptr casted = dynamic_pointer_move(source); EXPECT_EQ(obj, source.get()); // source still has ownership EXPECT_EQ(nullptr, casted.get()); } TEST(UniqueRef_DynamicPointerMoveTest, InvalidParentToChildCast2) { Child2 *obj = new Child2(); unique_ref source(nullcheck(unique_ptr(obj)).value()); optional> casted = dynamic_pointer_move(source); EXPECT_EQ(obj, source.get()); // source still has ownership EXPECT_EQ(none, casted); } TEST(UniquePtr_DynamicPointerMoveTest, InvalidParentToChildCast2) { Child2 *obj = new Child2(); unique_ptr source(obj); unique_ptr casted = dynamic_pointer_move(source); EXPECT_EQ(obj, source.get()); // source still has ownership EXPECT_EQ(nullptr, casted.get()); } TEST(UniqueRef_DynamicPointerMoveTest, ChildToParentCast) { Child *obj = new Child(); unique_ref source(nullcheck(unique_ptr(obj)).value()); unique_ref casted = dynamic_pointer_move(source).value(); EXPECT_FALSE(source.is_valid()); // source lost ownership EXPECT_EQ(obj, casted.get()); } TEST(UniquePtr_DynamicPointerMoveTest, ChildToParentCast) { Child *obj = new Child(); unique_ptr source(obj); unique_ptr casted = dynamic_pointer_move(source); EXPECT_EQ(nullptr, source.get()); // source lost ownership EXPECT_EQ(obj, casted.get()); } class UniqueRef_DynamicPointerMoveDestructorTest: public ::testing::Test { public: UniqueRef_DynamicPointerMoveDestructorTest(): childDestructorCallback() {} DestructorCallback childDestructorCallback; unique_ref createChild() { return make_unique_ref(&childDestructorCallback); } void EXPECT_CHILD_DESTRUCTOR_CALLED() { EXPECT_CALL(childDestructorCallback, call()).Times(1); } }; class UniquePtr_DynamicPointerMoveDestructorTest: public ::testing::Test { public: UniquePtr_DynamicPointerMoveDestructorTest(): childDestructorCallback() {} DestructorCallback childDestructorCallback; unique_ptr createChild() { return make_unique(&childDestructorCallback); } void EXPECT_CHILD_DESTRUCTOR_CALLED() { EXPECT_CALL(childDestructorCallback, call()).Times(1); } }; TEST_F(UniqueRef_DynamicPointerMoveDestructorTest, ChildInParentPtr) { unique_ref parent = createChild(); EXPECT_CHILD_DESTRUCTOR_CALLED(); } TEST_F(UniquePtr_DynamicPointerMoveDestructorTest, ChildInParentPtr) { unique_ptr parent = createChild(); EXPECT_CHILD_DESTRUCTOR_CALLED(); } TEST_F(UniqueRef_DynamicPointerMoveDestructorTest, ChildToParentCast) { unique_ref child = createChild(); unique_ref parent = dynamic_pointer_move(child).value(); EXPECT_CHILD_DESTRUCTOR_CALLED(); } TEST_F(UniquePtr_DynamicPointerMoveDestructorTest, ChildToParentCast) { unique_ptr child = createChild(); unique_ptr parent = dynamic_pointer_move(child); EXPECT_CHILD_DESTRUCTOR_CALLED(); } TEST_F(UniqueRef_DynamicPointerMoveDestructorTest, ParentToChildCast) { unique_ref parent = createChild(); unique_ref child = dynamic_pointer_move(parent).value(); EXPECT_CHILD_DESTRUCTOR_CALLED(); } TEST_F(UniquePtr_DynamicPointerMoveDestructorTest, ParentToChildCast) { unique_ptr parent = createChild(); unique_ptr child = dynamic_pointer_move(parent); EXPECT_CHILD_DESTRUCTOR_CALLED(); } test/cpp-utils/pointer/optional_ownership_ptr_include_test.cpp000066400000000000000000000002021347701267100255150ustar00rootroot00000000000000#include "cpp-utils/pointer/optional_ownership_ptr.h" // Test the header can be included without needing additional dependencies test/cpp-utils/pointer/optional_ownership_ptr_test.cpp000066400000000000000000000065001347701267100240210ustar00rootroot00000000000000#include #include "cpp-utils/pointer/optional_ownership_ptr.h" #include "cpp-utils/macros.h" using std::unique_ptr; using std::function; using ::testing::Test; using namespace cpputils; class TestObject { public: TestObject(function destructorListener) : _destructorListener(destructorListener) {} virtual ~TestObject() { _destructorListener(); } private: function _destructorListener; }; class TestObjectHolder { public: TestObjectHolder() : _isDestructed(false), _testObject(new TestObject([this]() {_isDestructed = true;})) { } ~TestObjectHolder() { if (!_isDestructed) { delete _testObject; _isDestructed = true; } } TestObject *get() { return _testObject; } bool isDestructed() { return _isDestructed; } private: bool _isDestructed; TestObject *_testObject; DISALLOW_COPY_AND_ASSIGN(TestObjectHolder); }; class OptionalOwnershipPointerTest: public Test { public: OptionalOwnershipPointerTest(): obj(), obj2() {} TestObjectHolder obj; TestObjectHolder obj2; }; TEST_F(OptionalOwnershipPointerTest, TestIsInitializedCorrectly) { EXPECT_FALSE(obj.isDestructed()); } TEST_F(OptionalOwnershipPointerTest, DestructsWhenItHasOwnership_UniquePtr) { { optional_ownership_ptr ptr = WithOwnership(unique_ptr(obj.get())); EXPECT_FALSE(obj.isDestructed()); UNUSED(ptr); } EXPECT_TRUE(obj.isDestructed()); } TEST_F(OptionalOwnershipPointerTest, DestructsWhenItHasOwnership_UniqueRef) { { optional_ownership_ptr ptr = WithOwnership(cpputils::nullcheck(unique_ptr(obj.get())).value()); //EXPECT_FALSE(obj.isDestructed()); //UNUSED(ptr); } //EXPECT_TRUE(obj.isDestructed()); } TEST_F(OptionalOwnershipPointerTest, DestructsWhenItHasOwnershipAfterAssignment) { { optional_ownership_ptr ptr = WithoutOwnership(obj.get()); ptr = WithOwnership(unique_ptr(obj2.get())); } EXPECT_FALSE(obj.isDestructed()); EXPECT_TRUE(obj2.isDestructed()); } TEST_F(OptionalOwnershipPointerTest, DoesntDestructWhenItDoesntHaveOwnership) { { optional_ownership_ptr ptr = WithoutOwnership(obj.get()); UNUSED(ptr); } EXPECT_FALSE(obj.isDestructed()); } TEST_F(OptionalOwnershipPointerTest, DoesntDestructWhenItDoesntHaveOwnershipAfterAssignment) { { optional_ownership_ptr ptr = WithOwnership(unique_ptr(obj.get())); ptr = WithoutOwnership(obj2.get()); EXPECT_TRUE(obj.isDestructed()); } EXPECT_FALSE(obj2.isDestructed()); } TEST_F(OptionalOwnershipPointerTest, DestructsOnReassignmentWithNull) { optional_ownership_ptr ptr = WithOwnership(unique_ptr(obj.get())); ptr = null(); EXPECT_TRUE(obj.isDestructed()); } TEST_F(OptionalOwnershipPointerTest, DoesntCrashWhenDestructingNullptr1) { optional_ownership_ptr ptr = null(); UNUSED(ptr); } TEST_F(OptionalOwnershipPointerTest, DoesntCrashWhenDestructingNullptrWithoutOwnership) { optional_ownership_ptr ptr = WithoutOwnership(static_cast(nullptr)); UNUSED(ptr); } TEST_F(OptionalOwnershipPointerTest, DoesntCrashWhenDestructingNullptrWithOwnership) { optional_ownership_ptr ptr = WithOwnership(unique_ptr(nullptr)); UNUSED(ptr); } test/cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround_include_test.cpp000066400000000000000000000002261347701267100316510ustar00rootroot00000000000000#include "cpp-utils/pointer/unique_ref_boost_optional_gtest_workaround.h" // Test the header can be included without needing additional dependencies test/cpp-utils/pointer/unique_ref_include_test.cpp000066400000000000000000000001661347701267100230600ustar00rootroot00000000000000#include "cpp-utils/pointer/unique_ref.h" // Test the header can be included without needing additional dependencies test/cpp-utils/pointer/unique_ref_test.cpp000066400000000000000000000717561347701267100213720ustar00rootroot00000000000000#include #include "cpp-utils/pointer/unique_ref.h" #include #include #include #include #include using namespace cpputils; namespace { class SomeClass0Parameters {}; class SomeClass1Parameter { public: SomeClass1Parameter(int param_): param(param_) {} int param; }; class SomeClass2Parameters { public: SomeClass2Parameters(int param1_, int param2_): param1(param1_), param2(param2_) {} int param1; int param2; }; using SomeClass = SomeClass0Parameters; struct SomeBaseClass { SomeBaseClass(int v_): v(v_) {} int v; }; struct SomeChildClass : SomeBaseClass { SomeChildClass(int v): SomeBaseClass(v) {} }; } static_assert(std::is_same::element_type>::value, "unique_ref::element_type is wrong"); static_assert(std::is_same::element_type>::value, "unique_ref::element_type is wrong"); static_assert(std::is_same::deleter_type>::value, "unique_ref::deleter_type is wrong"); TEST(MakeUniqueRefTest, Primitive) { unique_ref var = make_unique_ref(3); EXPECT_EQ(3, *var); } TEST(MakeUniqueRefTest, ClassWith0Parameters) { unique_ref var = make_unique_ref(); //Check that the type is correct EXPECT_EQ(var.get(), dynamic_cast(var.get())); } TEST(MakeUniqueRefTest, ClassWith1Parameter) { unique_ref var = make_unique_ref(5); EXPECT_EQ(5, var->param); } TEST(MakeUniqueRefTest, ClassWith2Parameters) { unique_ref var = make_unique_ref(7,2); EXPECT_EQ(7, var->param1); EXPECT_EQ(2, var->param2); } TEST(MakeUniqueRefTest, TypeIsAutoDeductible) { auto var1 = make_unique_ref(3); auto var2 = make_unique_ref(); auto var3 = make_unique_ref(2); auto var4 = make_unique_ref(2, 3); } TEST(MakeUniqueRefTest, CanAssignToUniquePtr) { std::unique_ptr var = make_unique_ref(2); EXPECT_EQ(2, *var); } TEST(MakeUniqueRefTest, CanAssignToSharedPtr) { std::shared_ptr var = make_unique_ref(2); EXPECT_EQ(2, *var); } TEST(MakeUniqueRefTest, CanAssignToBaseClassPtr) { unique_ref var = make_unique_ref(3); EXPECT_EQ(3, var->v); } TEST(MakeUniqueRefTest, CanAssignToBaseClassUniquePtr) { std::unique_ptr var = make_unique_ref(3); EXPECT_EQ(3, var->v); } TEST(MakeUniqueRefTest, CanAssignToBaseClassSharedPtr) { std::shared_ptr var = make_unique_ref(3); EXPECT_EQ(3, var->v); } TEST(NullcheckTest, givenUniquePtrToInt_withNullptr_whenNullcheckCalled_thenReturnsNone) { boost::optional> var = nullcheck(std::unique_ptr(nullptr)); EXPECT_FALSE(static_cast(var)); } TEST(NullcheckTest, givenUniquePtrToObject_withNullptr_whenNullcheckCalled_thenReturnsNone) { boost::optional> var = nullcheck(std::unique_ptr(nullptr)); EXPECT_FALSE(static_cast(var)); } TEST(NullcheckTest, givenUniquePtrToInt_withNonNullptr_whenNullcheckCalled_thenReturnsUniqueRef) { boost::optional> var = nullcheck(std::make_unique(3)); EXPECT_TRUE(static_cast(var)); EXPECT_EQ(3, **var); } TEST(NullcheckTest, givenUniquePtrToObject_withNonNullptr_whenNullcheckCalled_thenReturnsUniqueRef) { boost::optional> var = nullcheck(std::make_unique()); EXPECT_TRUE(static_cast(var)); //Check that the type is correct EXPECT_EQ(var->get(), dynamic_cast(var->get())); } TEST(NullcheckTest, givenUniquePtrToObjectWith1Parameter_withNonNullptr_whenNullcheckCalled_thenReturnsUniqueRef) { boost::optional> var = nullcheck(std::make_unique(5)); EXPECT_TRUE(static_cast(var)); EXPECT_EQ(5, (*var)->param); } TEST(NullcheckTest, givenUniquePtrToObjectWith2Parameters_withNonNullptr_whenNullcheckCalled_thenReturnsUniqueRef) { boost::optional> var = nullcheck(std::make_unique(7,2)); EXPECT_TRUE(static_cast(var)); EXPECT_EQ(7, (*var)->param1); EXPECT_EQ(2, (*var)->param2); } TEST(NullcheckTest, givenUniquePtrToInt_withNonNullptr_whenNullcheckCalled_thenCanExtractUniqueRef) { boost::optional> var = nullcheck(std::make_unique(3)); unique_ref resolved = std::move(var).value(); } TEST(NullcheckTest, givenUniquePtrToObject_withNonNullptr_whenNullcheckCalled_thenCanExtractUniqueRef) { boost::optional> var = nullcheck(std::make_unique()); unique_ref resolved = std::move(var).value(); } TEST(NullcheckTest, givenUniquePtrToInt_whenCallingNullcheck_thenTypesCanBeAutoDeduced) { auto var = nullcheck(std::make_unique(3)); auto resolved = std::move(var).value(); } TEST(NullcheckTest, givenUniquePtrToObject_whenCallingNullcheck_thenTypesCanBeAutoDeduced) { auto var = nullcheck(std::make_unique()); auto resolved = std::move(var).value(); } class UniqueRefTest: public ::testing::Test { public: template void makeInvalid(unique_ref ref) { UNUSED(ref); //ref is moved in here and then destructed } }; TEST_F(UniqueRefTest, givenUniqueRefToInt_whenCallingGet_thenReturnsValue) { unique_ref obj = make_unique_ref(3); EXPECT_EQ(3, *obj.get()); } TEST_F(UniqueRefTest, givenUniqueRefToObject_whenCallingGet_thenReturnsObject) { unique_ref obj = make_unique_ref(5); EXPECT_EQ(5, obj.get()->param); } TEST_F(UniqueRefTest, givenUniqueRefToInt_whenDereferencing_thenReturnsValue) { unique_ref obj = make_unique_ref(3); EXPECT_EQ(3, *obj); } TEST_F(UniqueRefTest, givenUniqueRefToObject_whenDereferencing_thenReturnsObject) { unique_ref obj = make_unique_ref(5); EXPECT_EQ(5, (*obj).param); } TEST_F(UniqueRefTest, givenUniqueRefToObject_whenArrowDereferencing_thenReturnsObject) { unique_ref obj = make_unique_ref(3); EXPECT_EQ(3, obj->param); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigning_thenPointsToSameObject) { unique_ref obj1 = make_unique_ref(); unique_ref obj2 = make_unique_ref(); SomeClass *obj1ptr = obj1.get(); obj2 = std::move(obj1); EXPECT_EQ(obj1ptr, obj2.get()); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigning_thenOldInstanceInvalid) { unique_ref obj1 = make_unique_ref(); unique_ref obj2 = make_unique_ref(); obj2 = std::move(obj1); EXPECT_FALSE(obj1.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigningToBaseClass_thenPointsToSameObject) { unique_ref child = make_unique_ref(3); unique_ref base = make_unique_ref(10); base = std::move(child); EXPECT_EQ(3, base->v); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigningToBaseClass_thenOldInstanceInvalid) { unique_ref obj1 = make_unique_ref(3); unique_ref obj2 = make_unique_ref(10); obj2 = std::move(obj1); EXPECT_FALSE(obj1.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigningToUniquePtr_thenPointsToSameObject) { unique_ref obj1 = make_unique_ref(); std::unique_ptr obj2 = std::make_unique(); SomeClass *obj1ptr = obj1.get(); obj2 = std::move(obj1); EXPECT_EQ(obj1ptr, obj2.get()); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigningToUniquePtr_thenOldInstanceInvalid) { unique_ref obj1 = make_unique_ref(); std::unique_ptr obj2 = std::make_unique(); obj2 = std::move(obj1); EXPECT_FALSE(obj1.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigningToBaseClassUniquePtr_thenPointsToSameObject) { unique_ref child = make_unique_ref(3); std::unique_ptr base = std::make_unique(10); base = std::move(child); EXPECT_EQ(3, base->v); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigningToBaseClassUniquePtr_thenOldInstanceInvalid) { unique_ref obj1 = make_unique_ref(3); std::unique_ptr obj2 = std::make_unique(10); obj2 = std::move(obj1); EXPECT_FALSE(obj1.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigningToSharedPtr_thenPointsToSameObject) { unique_ref obj1 = make_unique_ref(); std::shared_ptr obj2 = std::make_shared(); SomeClass *obj1ptr = obj1.get(); obj2 = std::move(obj1); EXPECT_EQ(obj1ptr, obj2.get()); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigningToSharedPtr_thenOldInstanceInvalid) { unique_ref obj1 = make_unique_ref(); std::shared_ptr obj2 = std::make_shared(); obj2 = std::move(obj1); EXPECT_FALSE(obj1.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigningToBaseClassSharedPtr_thenPointsToSameObject) { unique_ref child = make_unique_ref(3); std::shared_ptr base = std::make_shared(10); base = std::move(child); EXPECT_EQ(3, base->v); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveAssigningToBaseClassSharedPtr_thenOldInstanceInvalid) { unique_ref obj1 = make_unique_ref(3); std::shared_ptr obj2 = std::make_shared(10); obj2 = std::move(obj1); EXPECT_FALSE(obj1.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructing_thenPointsToSameObject) { unique_ref obj1 = make_unique_ref(); SomeClass *obj1ptr = obj1.get(); unique_ref obj2 = std::move(obj1); EXPECT_EQ(obj1ptr, obj2.get()); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructing_thenOldInstanceInvalid) { unique_ref obj1 = make_unique_ref(); unique_ref obj2 = std::move(obj1); EXPECT_FALSE(obj1.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructingToBaseClass_thenPointsToSameObject) { unique_ref child = make_unique_ref(3); unique_ref base = std::move(child); EXPECT_EQ(3, base->v); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructingToBaseClass_thenOldInstanceInvalid) { unique_ref child = make_unique_ref(3); unique_ref base = std::move(child); EXPECT_FALSE(child.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructingToUniquePtr_thenPointsToSameObject) { unique_ref obj1 = make_unique_ref(); SomeClass *obj1ptr = obj1.get(); std::unique_ptr obj2 = std::move(obj1); EXPECT_EQ(obj1ptr, obj2.get()); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructingToUniquePtr_thenOldInstanceInvalid) { unique_ref obj1 = make_unique_ref(); std::unique_ptr obj2 = std::move(obj1); EXPECT_FALSE(obj1.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructingToBaseClassUniquePtr_thenPointsToSameObject) { unique_ref child = make_unique_ref(3); std::unique_ptr base = std::move(child); EXPECT_EQ(3, base->v); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructingToBaseClassUniquePtr_thenOldInstanceInvalid) { unique_ref child = make_unique_ref(3); std::unique_ptr base = std::move(child); EXPECT_FALSE(child.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructingToSharedPtr_thenPointsToSameObject) { unique_ref obj1 = make_unique_ref(); SomeClass *obj1ptr = obj1.get(); std::shared_ptr obj2 = std::move(obj1); EXPECT_EQ(obj1ptr, obj2.get()); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructingToSharedPtr_thenOldInstanceInvalid) { unique_ref obj1 = make_unique_ref(); std::shared_ptr obj2 = std::move(obj1); EXPECT_FALSE(obj1.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructingToBaseClassSharedPtr_thenPointsToSameObject) { unique_ref child = make_unique_ref(3); std::shared_ptr base = std::move(child); EXPECT_EQ(3, base->v); } TEST_F(UniqueRefTest, givenUniqueRef_whenMoveConstructingToBaseClassSharedPtr_thenOldInstanceInvalid) { unique_ref child = make_unique_ref(3); std::shared_ptr base = std::move(child); EXPECT_FALSE(child.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, Swap) { unique_ref obj1 = make_unique_ref(); unique_ref obj2 = make_unique_ref(); SomeClass *obj1ptr = obj1.get(); SomeClass *obj2ptr = obj2.get(); std::swap(obj1, obj2); EXPECT_EQ(obj2ptr, obj1.get()); EXPECT_EQ(obj1ptr, obj2.get()); } TEST_F(UniqueRefTest, SwapFromInvalid) { unique_ref obj1 = make_unique_ref(); makeInvalid(std::move(obj1)); unique_ref obj2 = make_unique_ref(); SomeClass *obj2ptr = obj2.get(); std::swap(obj1, obj2); EXPECT_EQ(obj2ptr, obj1.get()); EXPECT_TRUE(obj1.is_valid()); EXPECT_FALSE(obj2.is_valid()); } TEST_F(UniqueRefTest, SwapWithInvalid) { unique_ref obj1 = make_unique_ref(); unique_ref obj2 = make_unique_ref(); makeInvalid(std::move(obj2)); SomeClass *obj1ptr = obj1.get(); std::swap(obj1, obj2); EXPECT_FALSE(obj1.is_valid()); EXPECT_TRUE(obj2.is_valid()); EXPECT_EQ(obj1ptr, obj2.get()); } TEST_F(UniqueRefTest, SwapInvalidWithInvalid) { unique_ref obj1 = make_unique_ref(); unique_ref obj2 = make_unique_ref(); makeInvalid(std::move(obj1)); makeInvalid(std::move(obj2)); std::swap(obj1, obj2); EXPECT_FALSE(obj1.is_valid()); EXPECT_FALSE(obj2.is_valid()); } TEST_F(UniqueRefTest, SwapFromRValue) { unique_ref obj1 = make_unique_ref(); unique_ref obj2 = make_unique_ref(); SomeClass *obj1ptr = obj1.get(); SomeClass *obj2ptr = obj2.get(); std::swap(std::move(obj1), obj2); EXPECT_EQ(obj2ptr, obj1.get()); // NOLINT (intentional use-after-move) EXPECT_EQ(obj1ptr, obj2.get()); } TEST_F(UniqueRefTest, SwapWithRValue) { unique_ref obj1 = make_unique_ref(); unique_ref obj2 = make_unique_ref(); SomeClass *obj1ptr = obj1.get(); SomeClass *obj2ptr = obj2.get(); std::swap(obj1, std::move(obj2)); EXPECT_EQ(obj2ptr, obj1.get()); EXPECT_EQ(obj1ptr, obj2.get()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, CanBePutInContainer_Primitive) { std::vector> vec; vec.push_back(make_unique_ref(3)); EXPECT_EQ(3, *vec[0]); } TEST_F(UniqueRefTest, CanBePutInContainer_Object) { std::vector> vec; vec.push_back(make_unique_ref(5)); EXPECT_EQ(5, vec[0]->param); } TEST_F(UniqueRefTest, CanBePutInContainer_Nullcheck) { std::vector> vec; vec.push_back(*nullcheck(std::make_unique(3))); EXPECT_EQ(3, *vec[0]); } TEST_F(UniqueRefTest, CanBePutInSet_Primitive) { std::set> set; set.insert(make_unique_ref(3)); EXPECT_EQ(3, **set.begin()); } TEST_F(UniqueRefTest, CanBePutInSet_Object) { std::set> set; set.insert(make_unique_ref(5)); EXPECT_EQ(5, (*set.begin())->param); } TEST_F(UniqueRefTest, CanBePutInSet_Nullcheck) { std::set> set; set.insert(*nullcheck(std::make_unique(3))); EXPECT_EQ(3, **set.begin()); } TEST_F(UniqueRefTest, CanBePutInUnorderedSet_Primitive) { std::unordered_set> set; set.insert(make_unique_ref(3)); EXPECT_EQ(3, **set.begin()); } TEST_F(UniqueRefTest, CanBePutInUnorderedSet_Object) { std::unordered_set> set; set.insert(make_unique_ref(5)); EXPECT_EQ(5, (*set.begin())->param); } TEST_F(UniqueRefTest, CanBePutInUnorderedSet_Nullcheck) { std::unordered_set> set; set.insert(*nullcheck(std::make_unique(3))); EXPECT_EQ(3, **set.begin()); } TEST_F(UniqueRefTest, CanBePutInMap_Primitive) { std::map, unique_ref> map; map.insert(std::make_pair(make_unique_ref(3), make_unique_ref(5))); EXPECT_EQ(3, *map.begin()->first); EXPECT_EQ(5, *map.begin()->second); } TEST_F(UniqueRefTest, CanBePutInMap_Object) { std::map, unique_ref> map; map.insert(std::make_pair(make_unique_ref(5), make_unique_ref(3))); EXPECT_EQ(5, map.begin()->first->param); EXPECT_EQ(3, map.begin()->second->param); } TEST_F(UniqueRefTest, CanBePutInMap_Nullcheck) { std::map, unique_ref> map; map.insert(std::make_pair(*nullcheck(std::make_unique(3)), *nullcheck(std::make_unique(5)))); EXPECT_EQ(3, *map.begin()->first); EXPECT_EQ(5, *map.begin()->second); } TEST_F(UniqueRefTest, CanBePutInUnorderedMap_Primitive) { std::unordered_map, unique_ref> map; map.insert(std::make_pair(make_unique_ref(3), make_unique_ref(5))); EXPECT_EQ(3, *map.begin()->first); EXPECT_EQ(5, *map.begin()->second); } TEST_F(UniqueRefTest, CanBePutInUnorderedMap_Object) { std::unordered_map, unique_ref> map; map.insert(std::make_pair(make_unique_ref(5), make_unique_ref(3))); EXPECT_EQ(5, map.begin()->first->param); EXPECT_EQ(3, map.begin()->second->param); } TEST_F(UniqueRefTest, CanBePutInUnorderedMap_Nullcheck) { std::unordered_map, unique_ref> map; map.insert(std::make_pair(*nullcheck(std::make_unique(3)), *nullcheck(std::make_unique(5)))); EXPECT_EQ(3, *map.begin()->first); EXPECT_EQ(5, *map.begin()->second); } TEST_F(UniqueRefTest, Equality_Nullptr) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(4); makeInvalid(std::move(var1)); makeInvalid(std::move(var2)); EXPECT_TRUE(var1 == var2); // NOLINT (intentional use-after-move) EXPECT_FALSE(var1 != var2); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, Nonequality) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(3); EXPECT_TRUE(var1 != var2); EXPECT_FALSE(var1 == var2); } TEST_F(UniqueRefTest, Nonequality_NullptrLeft) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(3); makeInvalid(std::move(var1)); EXPECT_TRUE(var1 != var2); // NOLINT (intentional use-after-move) EXPECT_FALSE(var1 == var2); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, Nonequality_NullptrRight) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(3); makeInvalid(std::move(var2)); EXPECT_TRUE(var1 != var2); // NOLINT (intentional use-after-move) EXPECT_FALSE(var1 == var2); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, HashIsDifferent) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(3); EXPECT_NE(std::hash>()(var1), std::hash>()(var2)); } TEST_F(UniqueRefTest, HashIsDifferent_NullptrLeft) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(3); makeInvalid(std::move(var1)); EXPECT_NE(std::hash>()(var1), std::hash>()(var2)); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, HashIsDifferent_NullptrRight) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(3); makeInvalid(std::move(var2)); EXPECT_NE(std::hash>()(var1), std::hash>()(var2)); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, HashIsSame_BothNullptr) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(3); makeInvalid(std::move(var1)); makeInvalid(std::move(var2)); EXPECT_EQ(std::hash>()(var1), std::hash>()(var2)); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, OneIsLess) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(3); EXPECT_TRUE(std::less>()(var1, var2) != std::less>()(var2, var1)); } TEST_F(UniqueRefTest, NullptrIsLess1) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(3); makeInvalid(std::move(var1)); EXPECT_TRUE(std::less>()(var1, var2)); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, NullptrIsLess2) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(3); makeInvalid(std::move(var2)); EXPECT_FALSE(std::less>()(var1, var2)); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, NullptrIsNotLessThanNullptr) { unique_ref var1 = make_unique_ref(3); unique_ref var2 = make_unique_ref(3); makeInvalid(std::move(var1)); makeInvalid(std::move(var2)); EXPECT_FALSE(std::less>()(var1, var2)); // NOLINT (intentional use-after-move) } namespace { class OnlyMoveable { public: OnlyMoveable(int value_): value(value_) {} OnlyMoveable(OnlyMoveable &&source) noexcept: value(source.value) {source.value = -1;} bool operator==(const OnlyMoveable &rhs) const { return value == rhs.value; } int value; private: OnlyMoveable(const OnlyMoveable& rhs) = delete; OnlyMoveable& operator=(const OnlyMoveable& rhs) = delete; }; } TEST_F(UniqueRefTest, AllowsDerefOnRvalue) { OnlyMoveable val = *make_unique_ref(5); EXPECT_EQ(OnlyMoveable(5), val); } namespace { class DestructableMock final { public: DestructableMock(bool* wasDestructed): wasDestructed_(wasDestructed) {} ~DestructableMock() { *wasDestructed_ = true; } private: bool* wasDestructed_; }; } TEST_F(UniqueRefTest, givenUniqueRefWithDefaultDeleter_whenDestructed_thenCallsDefaultDeleter) { bool wasDestructed = false; { auto obj = make_unique_ref(&wasDestructed); EXPECT_FALSE(wasDestructed); } EXPECT_TRUE(wasDestructed); } TEST_F(UniqueRefTest, givenUniqueRefWithDefaultDeleter_whenMoveConstructed_thenCallsDefaultDeleterAfterSecondDestructed) { bool wasDestructed = false; auto obj = make_unique_ref(&wasDestructed); { unique_ref obj2 = std::move(obj); EXPECT_FALSE(wasDestructed); } EXPECT_TRUE(wasDestructed); } TEST_F(UniqueRefTest, givenUniqueRefWithDefaultDeleter_whenMoveAssigned_thenCallDefaultDeleterAfterSecondDestructed) { bool dummy = false; bool wasDestructed = false; unique_ref obj = make_unique_ref(&wasDestructed); { unique_ref obj2 = make_unique_ref(&dummy); obj2 = std::move(obj); EXPECT_FALSE(wasDestructed); } EXPECT_TRUE(wasDestructed); } TEST_F(UniqueRefTest, givenUniqueRefWithDefaultDeleter_whenDestructCalled_thenCallsDefaultDeleter) { bool wasDestructed = false; auto obj = make_unique_ref(&wasDestructed); destruct(std::move(obj)); EXPECT_TRUE(wasDestructed); EXPECT_FALSE(obj.is_valid()); // NOLINT (intentional use-after-move) } namespace { struct SetToTrueDeleter final { void operator()(bool* ptr) { *ptr = true; } }; } TEST_F(UniqueRefTest, givenUniqueRefWithCustomDefaultConstructibleDeleter_whenDestructed_thenCallsCustomDeleter) { bool wasDestructed = false; { auto obj = nullcheck(std::unique_ptr(&wasDestructed)).value(); EXPECT_FALSE(wasDestructed); } EXPECT_TRUE(wasDestructed); } TEST_F(UniqueRefTest, givenUniqueRefWithCustomDefaultConstructibleDeleter_whenMoveConstructed_thenCallsCustomDeleterAfterSecondDestructed) { bool wasDestructed = false; unique_ref obj = nullcheck(std::unique_ptr(&wasDestructed)).value(); { unique_ref obj2 = std::move(obj); EXPECT_FALSE(wasDestructed); } EXPECT_TRUE(wasDestructed); } TEST_F(UniqueRefTest, givenUniqueRefWithCustomDefaultConstructibleDeleter_whenMoveAssigned_thenCallsCustomDeleterAfterSecondDestructed) { bool dummy = false; bool wasDestructed = false; unique_ref obj = nullcheck(std::unique_ptr(&wasDestructed)).value(); { unique_ref obj2 = nullcheck(std::unique_ptr(&dummy)).value(); obj2 = std::move(obj); EXPECT_FALSE(wasDestructed); } EXPECT_TRUE(wasDestructed); } TEST_F(UniqueRefTest, givenUniqueRefWithCustomDefaultConstructibleDeleter_whenDestructCalled_thenCallsCustomDeleter) { bool wasDestructed = false; auto obj = nullcheck(std::unique_ptr(&wasDestructed)).value(); destruct(std::move(obj)); EXPECT_TRUE(wasDestructed); EXPECT_FALSE(obj.is_valid()); // NOLINT (intentional use-after-move) } namespace { struct SetToDeleter final { SetToDeleter(int value): value_(value) {} int value_; void operator()(int* ptr) { *ptr = value_; } }; } TEST_F(UniqueRefTest, givenUniqueRefWithCustomDeleterInstance_whenDestructed_thenCallsCustomDeleterInstance) { int value = 0; { auto obj = nullcheck(std::unique_ptr(&value, SetToDeleter(4))).value(); EXPECT_EQ(0, value); } EXPECT_EQ(4, value); } TEST_F(UniqueRefTest, givenUniqueRefWithCustomDeleterInstance_whenMoveConstructed_thenCallsCustomDeleterInstanceAfterSecondDestructed) { int value = 0; unique_ref obj = nullcheck(std::unique_ptr(&value, SetToDeleter(4))).value(); { unique_ref obj2 = std::move(obj); EXPECT_EQ(0, value); } EXPECT_EQ(4, value); } TEST_F(UniqueRefTest, givenUniqueRefWithCustomDeleterInstance_whenMoveAssigned_thenCallsCustomDeleterInstanceAfterSecondDestructed) { int dummy = 0; int value = 0; unique_ref obj = nullcheck(std::unique_ptr(&value, SetToDeleter(4))).value(); { unique_ref obj2 = nullcheck(std::unique_ptr(&dummy, SetToDeleter(0))).value(); obj2 = std::move(obj); EXPECT_EQ(0, value); } EXPECT_EQ(4, value); } TEST_F(UniqueRefTest, givenUniqueRefWithCustomDeleterInstance_whenDestructCalled_thenCallsCustomDeleterInstance) { int value = 0; auto obj = nullcheck(std::unique_ptr(&value, SetToDeleter(4))).value(); destruct(std::move(obj)); EXPECT_EQ(4, value); EXPECT_FALSE(obj.is_valid()); // NOLINT (intentional use-after-move) } TEST_F(UniqueRefTest, givenUniquePtrWithCustomDeleterInstance_whenMovedToUniquePtr_thenHasSameDeleterInstance) { int dummy = 0; SetToDeleter deleter(4); auto ptr = std::unique_ptr(&dummy, deleter); auto ref = nullcheck(std::move(ptr)).value(); EXPECT_EQ(4, ref.get_deleter().value_); } TEST_F(UniqueRefTest, givenUniqueRefWithCustomDeleterInstance_whenMoveConstructing_thenHasSameDeleterInstance) { int dummy = 0; SetToDeleter deleter(4); auto ref = nullcheck(std::unique_ptr(&dummy, deleter)).value(); unique_ref ref2 = std::move(ref); EXPECT_EQ(4, ref2.get_deleter().value_); } TEST_F(UniqueRefTest, givenUniqueRefWithCustomDeleterInstance_whenMoveAssigning_thenHasSameDeleterInstance) { int dummy = 0; SetToDeleter deleter(4); auto ref = nullcheck(std::unique_ptr(&dummy, deleter)).value(); auto ref2 = nullcheck(std::unique_ptr(&dummy, SetToDeleter(0))).value(); ref2 = std::move(ref); EXPECT_EQ(4, ref2.get_deleter().value_); } TEST_F(UniqueRefTest, AllowsMoveConstructingToUniqueRefOfConst) { unique_ref a = make_unique_ref(3); unique_ref b = std::move(a); } TEST_F(UniqueRefTest, AllowsMoveAssigningToUniqueRefOfConst) { unique_ref a = make_unique_ref(3); unique_ref b = make_unique_ref(10); b = std::move(a); } test/cpp-utils/process/000077500000000000000000000000001347701267100154435ustar00rootroot00000000000000test/cpp-utils/process/SignalCatcherTest.cpp000066400000000000000000000120601347701267100215150ustar00rootroot00000000000000#include #include #include 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), "" ); } test/cpp-utils/process/SignalHandlerTest.cpp000066400000000000000000000054611347701267100215300ustar00rootroot00000000000000#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), "" ); } } test/cpp-utils/process/SubprocessTest.cpp000066400000000000000000000102061347701267100211360ustar00rootroot00000000000000#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) auto executable = get_executable().parent_path() / "cpp-utils-test_exit_status.exe"; #else auto executable = get_executable().parent_path() / "cpp-utils-test_exit_status"; #endif if (!bf::exists(executable)) { throw std::runtime_error(executable.string() + " not found."); } return executable.string() + " \"" + message + "\" " + std::to_string(status); } } TEST(SubprocessTest, CheckCall_success_output) { EXPECT_EQ("hello", Subprocess::check_call(exit_with_message_and_status("hello", 0)).output); } TEST(SubprocessTest, CheckCall_successwithemptyoutput_output) { EXPECT_EQ("", Subprocess::check_call(exit_with_message_and_status("", 0)).output); } TEST(SubprocessTest, CheckCall_success_exitcode) { EXPECT_EQ(0, Subprocess::check_call(exit_with_message_and_status("hello", 0)).exitcode); } TEST(SubprocessTest, CheckCall_successwithemptyoutput_exitcode) { EXPECT_EQ(0, Subprocess::check_call(exit_with_message_and_status("", 0)).exitcode); } TEST(SubprocessTest, CheckCall_error) { EXPECT_THROW( Subprocess::check_call(exit_with_message_and_status("", 1)), SubprocessError ); } TEST(SubprocessTest, CheckCall_error5) { EXPECT_THROW( Subprocess::check_call(exit_with_message_and_status("", 5)), SubprocessError ); } TEST(SubprocessTest, CheckCall_errorwithoutput) { EXPECT_THROW( Subprocess::check_call(exit_with_message_and_status("hello", 1)), SubprocessError ); } TEST(SubprocessTest, CheckCall_error5withoutput) { EXPECT_THROW( Subprocess::check_call(exit_with_message_and_status("hello", 5)), SubprocessError ); } TEST(SubprocessTest, Call_success_exitcode) { EXPECT_EQ(0, Subprocess::call(exit_with_message_and_status("hello", 0)).exitcode); } TEST(SubprocessTest, Call_success_output) { EXPECT_EQ("hello", Subprocess::call(exit_with_message_and_status("hello", 0)).output); } TEST(SubprocessTest, Call_error_exitcode) { EXPECT_EQ(1, Subprocess::call(exit_with_message_and_status("", 1)).exitcode); } TEST(SubprocessTest, Call_error_output) { EXPECT_EQ("", Subprocess::call(exit_with_message_and_status("", 1)).output); } TEST(SubprocessTest, Call_error5_exitcode) { EXPECT_EQ(5, Subprocess::call(exit_with_message_and_status("", 5)).exitcode); } TEST(SubprocessTest, Call_error5_output) { EXPECT_EQ("", Subprocess::call(exit_with_message_and_status("", 1)).output); } TEST(SubprocessTest, Call_errorwithoutput_output) { EXPECT_EQ("hello", Subprocess::call(exit_with_message_and_status("hello", 1)).output); } TEST(SubprocessTest, Call_errorwithoutput_exitcode) { EXPECT_EQ(1, Subprocess::call(exit_with_message_and_status("hello", 1)).exitcode); } TEST(SubprocessTest, Call_error5withoutput_output) { EXPECT_EQ("hello", Subprocess::call(exit_with_message_and_status("hello", 5)).output); } TEST(SubprocessTest, Call_error5withoutput_exitcode) { EXPECT_EQ(5, Subprocess::call(exit_with_message_and_status("hello", 5)).exitcode); } // TODO Move this test to a test suite for ThreadSystem/LoopThread #include TEST(SubprocessTest, CallFromThreadSystemThread) { cpputils::ConditionBarrier barrier; cpputils::LoopThread thread( [&barrier] () { auto result = Subprocess::check_call(exit_with_message_and_status("hello", 0)); EXPECT_EQ(0, result.exitcode); EXPECT_EQ("hello", result.output); barrier.release(); return false; // don't run loop again }, "child_thread" ); thread.start(); barrier.wait(); thread.stop(); // just to make sure it's stopped before the test exits. Returning false above should already stop it, but we don't know when exactly. thread.stop() will block until it's actually stopped. } test/cpp-utils/process/daemonize_include_test.cpp000066400000000000000000000001661347701267100226670ustar00rootroot00000000000000#include "cpp-utils/process/daemonize.h" // Test the header can be included without needing additional dependencies test/cpp-utils/process/exit_status.cpp000066400000000000000000000006251347701267100205260ustar00rootroot00000000000000// This is a small executable that prints its first argument and exits with the exit status in its second argument #include #include int main(int argc, char* argv[]) { if (argc != 3) { std::cerr << "Wrong number of arguments" << std::endl; std::abort(); } std::cout << argv[1]; int exit_status = static_cast(std::strtol(argv[2], nullptr, 10)); return exit_status; } test/cpp-utils/process/subprocess_include_test.cpp000066400000000000000000000001671347701267100231050ustar00rootroot00000000000000#include "cpp-utils/process/subprocess.h" // Test the header can be included without needing additional dependencies test/cpp-utils/random/000077500000000000000000000000001347701267100152455ustar00rootroot00000000000000test/cpp-utils/random/RandomIncludeTest.cpp000066400000000000000000000001611347701267100213330ustar00rootroot00000000000000#include "cpp-utils/random/Random.h" // Test the header can be included without needing additional dependencies test/cpp-utils/system/000077500000000000000000000000001347701267100153115ustar00rootroot00000000000000test/cpp-utils/system/EnvTest.cpp000066400000000000000000000011341347701267100174040ustar00rootroot00000000000000#include #include #include using std::string; TEST(EnvTest, SetAndGetEnv_ValueIsCorrect) { cpputils::setenv("my_key", "my_value"); EXPECT_EQ(string("my_value"), string(std::getenv("my_key"))); } TEST(EnvTest, SetAndGetEnvWithSpacedValue_ValueIsCorrect) { cpputils::setenv("my_key", "my value with spaces"); EXPECT_EQ(string("my value with spaces"), string(std::getenv("my_key"))); } TEST(EnvTest, UnsetAndGetEnv_ValueIsEmpty) { cpputils::setenv("my_key", "my_value"); cpputils::unsetenv("my_key"); EXPECT_EQ(nullptr, std::getenv("my_key")); } test/cpp-utils/system/FiletimeTest.cpp000066400000000000000000000020031347701267100204060ustar00rootroot00000000000000#include #include #include using cpputils::TempFile; using cpputils::set_filetime; using cpputils::get_filetime; TEST(FiletimeTest, SetAndGetTime_ReturnsCorrectTime) { TempFile file; struct timespec accessTime { 1535965242, 12345000 }; struct timespec modificationTime { 1435965242, 98765000 }; int retval = set_filetime(file.path().string().c_str(), accessTime, modificationTime); EXPECT_EQ(0, retval); struct timespec readAccessTime{}; struct timespec readModificationTime{}; retval = get_filetime(file.path().string().c_str(), &readAccessTime, &readModificationTime); EXPECT_EQ(0, retval); EXPECT_EQ(accessTime.tv_sec, readAccessTime.tv_sec); EXPECT_EQ(modificationTime.tv_sec, readModificationTime.tv_sec); // Apple unfortunately doesn't give us nanoseconds at all #if !defined(__APPLE__) EXPECT_EQ(accessTime.tv_nsec, readAccessTime.tv_nsec); EXPECT_EQ(modificationTime.tv_nsec, readModificationTime.tv_nsec); #endif } test/cpp-utils/system/GetTotalMemoryTest.cpp000066400000000000000000000004471347701267100215760ustar00rootroot00000000000000#include #include using cpputils::system::get_total_memory; TEST(GetTotalMemoryTest, DoesntCrash) { get_total_memory(); } TEST(GetTotalMemoryTest, IsNotZero) { uint64_t mem = get_total_memory(); EXPECT_LT(UINT64_C(0), mem); } test/cpp-utils/system/HomedirTest.cpp000066400000000000000000000046301347701267100202470ustar00rootroot00000000000000#include #include #include using cpputils::system::HomeDirectory; using cpputils::system::FakeHomeDirectoryRAII; using cpputils::system::FakeTempHomeDirectoryRAII; using cpputils::TempDir; namespace bf = boost::filesystem; TEST(HomedirTest, HomedirExists) { EXPECT_TRUE(bf::exists(HomeDirectory::get())); } TEST(HomedirTest, AppDataDirIsValid) { auto dir = HomeDirectory::getXDGDataDir(); EXPECT_FALSE(dir.empty()); EXPECT_GE(std::distance(dir.begin(), dir.end()), 2u); // has at least two components } TEST(HomedirTest, FakeHomeDirectorySetsHomedirCorrectly) { TempDir fakeHomeDir, fakeAppDataDir; FakeHomeDirectoryRAII a(fakeHomeDir.path(), fakeAppDataDir.path()); EXPECT_EQ(fakeHomeDir.path(), HomeDirectory::get()); EXPECT_EQ(fakeAppDataDir.path(), HomeDirectory::getXDGDataDir()); } TEST(HomedirTest, FakeHomeDirectoryResetsHomedirCorrectly) { bf::path actualHomeDir = HomeDirectory::get(); bf::path actualAppDataDir = HomeDirectory::getXDGDataDir(); { TempDir fakeHomeDir, fakeAppDataDir; FakeHomeDirectoryRAII a(fakeHomeDir.path(), fakeAppDataDir.path()); EXPECT_NE(actualHomeDir, HomeDirectory::get()); EXPECT_NE(actualAppDataDir, HomeDirectory::getXDGDataDir()); } EXPECT_EQ(actualHomeDir, HomeDirectory::get()); EXPECT_EQ(actualAppDataDir, HomeDirectory::getXDGDataDir()); } TEST(HomedirTest, FakeTempHomeDirectorySetsHomedirCorrectly) { bf::path actualHomeDir = HomeDirectory::get(); bf::path actualAppDataDir = HomeDirectory::getXDGDataDir(); FakeTempHomeDirectoryRAII a; EXPECT_NE(actualHomeDir, HomeDirectory::get()); EXPECT_NE(actualAppDataDir, HomeDirectory::getXDGDataDir()); } TEST(HomedirTest, FakeTempHomeDirectoryResetsHomedirCorrectly) { bf::path actualHomeDir = HomeDirectory::get(); bf::path actualAppDataDir = HomeDirectory::getXDGDataDir(); { FakeTempHomeDirectoryRAII a; EXPECT_NE(actualHomeDir, HomeDirectory::get()); EXPECT_NE(actualAppDataDir, HomeDirectory::getXDGDataDir()); } EXPECT_EQ(actualHomeDir, HomeDirectory::get()); EXPECT_EQ(actualAppDataDir, HomeDirectory::getXDGDataDir()); } TEST(HomedirTest, FakeTempHomeDirectoryUsesDifferentDirsForHomedirAndAppdataDir) { FakeTempHomeDirectoryRAII a; EXPECT_NE(HomeDirectory::get(), HomeDirectory::getXDGDataDir()); } test/cpp-utils/system/MemoryTest.cpp000066400000000000000000000007521347701267100201310ustar00rootroot00000000000000#include #include #include #include using cpputils::UnswappableAllocator; TEST(MemoryTest, LockingSmallMemoryDoesntCrash) { UnswappableAllocator allocator; void *data = allocator.allocate(5); allocator.free(data, 5); } TEST(MemoryTest, LockingLargeMemoryDoesntCrash) { UnswappableAllocator allocator; void *data = allocator.allocate(10240); allocator.free(data, 10240); } test/cpp-utils/system/PathTest.cpp000066400000000000000000000020761347701267100175560ustar00rootroot00000000000000#include #include using cpputils::path_is_just_drive_letter; #if defined(_MSC_VER) TEST(PathTest, pathIsJustDriveLetter) { EXPECT_FALSE(path_is_just_drive_letter("C")); EXPECT_TRUE(path_is_just_drive_letter("C:")); EXPECT_FALSE(path_is_just_drive_letter("C:\\")); EXPECT_FALSE(path_is_just_drive_letter("C:/")); EXPECT_FALSE(path_is_just_drive_letter("C:\\test")); EXPECT_FALSE(path_is_just_drive_letter("C:\\test\\")); EXPECT_FALSE(path_is_just_drive_letter("/")); EXPECT_FALSE(path_is_just_drive_letter("")); } #else TEST(PathTest, onNonWindowsWeDontHaveDriveLetterPaths) { EXPECT_FALSE(path_is_just_drive_letter("C")); EXPECT_FALSE(path_is_just_drive_letter("C:")); EXPECT_FALSE(path_is_just_drive_letter("C:\\")); EXPECT_FALSE(path_is_just_drive_letter("C:/")); EXPECT_FALSE(path_is_just_drive_letter("C:\\test")); EXPECT_FALSE(path_is_just_drive_letter("C:\\test\\")); EXPECT_FALSE(path_is_just_drive_letter("/")); EXPECT_FALSE(path_is_just_drive_letter("")); } #endif test/cpp-utils/system/TimeTest.cpp000066400000000000000000000106041347701267100175540ustar00rootroot00000000000000#include #include #include #include using cpputils::time::now; namespace { uint64_t _to_nanos(struct timespec time) { constexpr uint64_t nanos = UINT64_C(1000000000); return time.tv_sec * nanos + time.tv_nsec; } } TEST(TimeTest, DoesntCrash) { now(); } TEST(TimeTest, IsLaterThanYear2010) { struct timespec current_time = now(); constexpr time_t year_2010_timestamp = 1262304000; EXPECT_LT(year_2010_timestamp, current_time.tv_sec); } TEST(TimeTest, IsNondecreasing) { uint64_t time1 = _to_nanos(now()); uint64_t time2 = _to_nanos(now()); EXPECT_LE(time1, time2); } TEST(TimeTest, IsIncreasedAfterPause) { uint64_t time1 = _to_nanos(now()); std::this_thread::sleep_for(std::chrono::milliseconds(10)); uint64_t time2 = _to_nanos(now()); EXPECT_LT(time1, time2); } constexpr struct timespec time1 {1262304000, 000000000}; constexpr struct timespec time2 {1262304000, 000000001}; constexpr struct timespec time3 {1262304000, 100000000}; constexpr struct timespec time4 {1262304001, 000000001}; TEST(TimeTest, LessThan) { EXPECT_FALSE(time1 < time1); EXPECT_TRUE(time1 < time2); EXPECT_TRUE(time1 < time3); EXPECT_TRUE(time1 < time4); EXPECT_FALSE(time2 < time1); EXPECT_FALSE(time2 < time2); EXPECT_TRUE(time2 < time3); EXPECT_TRUE(time2 < time4); EXPECT_FALSE(time3 < time1); EXPECT_FALSE(time3 < time2); EXPECT_FALSE(time3 < time3); EXPECT_TRUE(time3 < time4); EXPECT_FALSE(time4 < time1); EXPECT_FALSE(time4 < time2); EXPECT_FALSE(time4 < time3); EXPECT_FALSE(time4 < time4); } TEST(TimeTest, GreaterThan) { EXPECT_FALSE(time1 > time1); EXPECT_FALSE(time1 > time2); EXPECT_FALSE(time1 > time3); EXPECT_FALSE(time1 > time4); EXPECT_TRUE(time2 > time1); EXPECT_FALSE(time2 > time2); EXPECT_FALSE(time2 > time3); EXPECT_FALSE(time2 > time4); EXPECT_TRUE(time3 > time1); EXPECT_TRUE(time3 > time2); EXPECT_FALSE(time3 > time3); EXPECT_FALSE(time3 > time4); EXPECT_TRUE(time4 > time1); EXPECT_TRUE(time4 > time2); EXPECT_TRUE(time4 > time3); EXPECT_FALSE(time4 > time4); } TEST(TimeTest, LessEquals) { EXPECT_TRUE(time1 <= time1); EXPECT_TRUE(time1 <= time2); EXPECT_TRUE(time1 <= time3); EXPECT_TRUE(time1 <= time4); EXPECT_FALSE(time2 <= time1); EXPECT_TRUE(time2 <= time2); EXPECT_TRUE(time2 <= time3); EXPECT_TRUE(time2 <= time4); EXPECT_FALSE(time3 <= time1); EXPECT_FALSE(time3 <= time2); EXPECT_TRUE(time3 <= time3); EXPECT_TRUE(time3 <= time4); EXPECT_FALSE(time4 <= time1); EXPECT_FALSE(time4 <= time2); EXPECT_FALSE(time4 <= time3); EXPECT_TRUE(time4 <= time4); } TEST(TimeTest, GreaterEquals) { EXPECT_TRUE(time1 >= time1); EXPECT_FALSE(time1 >= time2); EXPECT_FALSE(time1 >= time3); EXPECT_FALSE(time1 >= time4); EXPECT_TRUE(time2 >= time1); EXPECT_TRUE(time2 >= time2); EXPECT_FALSE(time2 >= time3); EXPECT_FALSE(time2 >= time4); EXPECT_TRUE(time3 >= time1); EXPECT_TRUE(time3 >= time2); EXPECT_TRUE(time3 >= time3); EXPECT_FALSE(time3 >= time4); EXPECT_TRUE(time4 >= time1); EXPECT_TRUE(time4 >= time2); EXPECT_TRUE(time4 >= time3); EXPECT_TRUE(time4 >= time4); } TEST(TimeTest, Equals) { EXPECT_TRUE(time1 == time1); EXPECT_FALSE(time1 == time2); EXPECT_FALSE(time1 == time3); EXPECT_FALSE(time1 == time4); EXPECT_FALSE(time2 == time1); EXPECT_TRUE(time2 == time2); EXPECT_FALSE(time2 == time3); EXPECT_FALSE(time2 == time4); EXPECT_FALSE(time3 == time1); EXPECT_FALSE(time3 == time2); EXPECT_TRUE(time3 == time3); EXPECT_FALSE(time3 == time4); EXPECT_FALSE(time4 == time1); EXPECT_FALSE(time4 == time2); EXPECT_FALSE(time4 == time3); EXPECT_TRUE(time4 == time4); } TEST(TimeTest, NotEquals) { EXPECT_FALSE(time1 != time1); EXPECT_TRUE(time1 != time2); EXPECT_TRUE(time1 != time3); EXPECT_TRUE(time1 != time4); EXPECT_TRUE(time2 != time1); EXPECT_FALSE(time2 != time2); EXPECT_TRUE(time2 != time3); EXPECT_TRUE(time2 != time4); EXPECT_TRUE(time3 != time1); EXPECT_TRUE(time3 != time2); EXPECT_FALSE(time3 != time3); EXPECT_TRUE(time3 != time4); EXPECT_TRUE(time4 != time1); EXPECT_TRUE(time4 != time2); EXPECT_TRUE(time4 != time3); EXPECT_FALSE(time4 != time4); } test/cpp-utils/tempfile/000077500000000000000000000000001347701267100155725ustar00rootroot00000000000000test/cpp-utils/tempfile/TempDirIncludeTest.cpp000066400000000000000000000001641347701267100220070ustar00rootroot00000000000000#include "cpp-utils/tempfile/TempDir.h" // Test the header can be included without needing additional dependencies test/cpp-utils/tempfile/TempDirTest.cpp000066400000000000000000000022321347701267100205010ustar00rootroot00000000000000#include #include "cpp-utils/tempfile/TempDir.h" #include using ::testing::Test; using std::ofstream; using namespace cpputils; namespace bf = boost::filesystem; class TempDirTest: public Test { public: void EXPECT_ENTRY_COUNT(int expected, const bf::path &path) { int actual = CountEntries(path); EXPECT_EQ(expected, actual); } int CountEntries(const bf::path &path) { int count = 0; for (bf::directory_iterator iter(path); iter != bf::directory_iterator(); ++iter) { ++count; } return count; } void CreateFile(const bf::path &path) { ofstream file(path.string().c_str()); } }; TEST_F(TempDirTest, DirIsCreated) { TempDir dir; EXPECT_TRUE(bf::exists(dir.path())); EXPECT_TRUE(bf::is_directory(dir.path())); } TEST_F(TempDirTest, DirIsCreatedEmpty) { TempDir dir; EXPECT_ENTRY_COUNT(0, dir.path()); } TEST_F(TempDirTest, DirIsWriteable) { TempDir dir; CreateFile(dir.path() / "myfile"); EXPECT_ENTRY_COUNT(1, dir.path()); } TEST_F(TempDirTest, DirIsDeletedAfterUse) { bf::path dirpath; { TempDir dir; dirpath = dir.path(); } EXPECT_FALSE(bf::exists(dirpath)); } test/cpp-utils/tempfile/TempFileIncludeTest.cpp000066400000000000000000000001651347701267100221510ustar00rootroot00000000000000#include "cpp-utils/tempfile/TempFile.h" // Test the header can be included without needing additional dependencies test/cpp-utils/tempfile/TempFileTest.cpp000066400000000000000000000053211347701267100206440ustar00rootroot00000000000000#include #include "cpp-utils/tempfile/TempFile.h" #include "cpp-utils/tempfile/TempDir.h" #include using ::testing::Test; using std::ifstream; using std::ofstream; using namespace cpputils; namespace bf = boost::filesystem; class TempFileTest: public Test { public: TempFileTest(): tempdir(), filepath_sample(tempdir.path() / "myfile") {} TempDir tempdir; bf::path filepath_sample; void CreateFile(const bf::path &path) { ofstream file(path.string().c_str()); } }; TEST_F(TempFileTest, FileIsCreated) { TempFile file; EXPECT_TRUE(bf::exists(file.path())); EXPECT_TRUE(bf::is_regular_file(file.path())); } TEST_F(TempFileTest, FileIsReadable) { TempFile file; ifstream opened(file.path().string().c_str()); EXPECT_TRUE(opened.good()); } TEST_F(TempFileTest, FileIsCreatedEmpty) { TempFile file; ifstream opened(file.path().string().c_str()); opened.get(); EXPECT_TRUE(opened.eof()); } TEST_F(TempFileTest, FileIsWriteable) { TempFile file; ofstream opened(file.path().string().c_str()); EXPECT_TRUE(opened.good()); } TEST_F(TempFileTest, FileIsDeletedAfterUse) { bf::path filepath; { TempFile file; filepath = file.path(); } EXPECT_FALSE(bf::exists(filepath)); } TEST_F(TempFileTest, DontCreateFileSpecified_FileIsNotCreated) { TempFile file(false); EXPECT_FALSE(bf::exists(file.path())); } TEST_F(TempFileTest, DontCreateFileSpecified_FileIsCreatable) { TempFile file(false); CreateFile(file.path()); EXPECT_TRUE(bf::exists(file.path())); } TEST_F(TempFileTest, DontCreateFileSpecified_FileIsDeletedAfterUse) { bf::path filepath; { TempFile file(false); CreateFile(file.path()); filepath = file.path(); } EXPECT_FALSE(bf::exists(filepath)); } TEST_F(TempFileTest, PathGiven_FileIsCreatedAtGivenPath) { TempFile file(filepath_sample); EXPECT_EQ(filepath_sample, file.path()); } TEST_F(TempFileTest, PathGiven_FileIsCreatedAndAccessible) { TempFile file(filepath_sample); EXPECT_TRUE(bf::exists(filepath_sample)); } TEST_F(TempFileTest, PathGiven_FileIsDeletedAfterUse) { { TempFile file(filepath_sample); } EXPECT_FALSE(bf::exists(filepath_sample)); } TEST_F(TempFileTest, PathGiven_DontCreateFileSpecified_FileIsNotCreated) { TempFile file(filepath_sample, false); EXPECT_FALSE(bf::exists(filepath_sample)); } TEST_F(TempFileTest, PathGiven_DontCreateFileSpecified_FileIsCreatable) { TempFile file(filepath_sample, false); CreateFile(filepath_sample); EXPECT_TRUE(bf::exists(filepath_sample)); } TEST_F(TempFileTest, PathGiven_DontCreateFileSpecified_FileIsDeletedAfterUse) { { TempFile file(filepath_sample, false); CreateFile(filepath_sample); } EXPECT_FALSE(bf::exists(filepath_sample)); } test/cpp-utils/thread/000077500000000000000000000000001347701267100152345ustar00rootroot00000000000000test/cpp-utils/thread/LeftRightTest.cpp000066400000000000000000000153111347701267100204710ustar00rootroot00000000000000#include #include #include using cpputils::LeftRight; using std::vector; TEST(LeftRightTest, givenInt_whenWritingAndReading_thenChangesArePresent) { LeftRight 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> obj; obj.write([] (auto& obj) {obj.push_back(5);}); vector read = obj.read([] (auto& obj) {return obj;}); EXPECT_EQ((vector{5}), read); obj.write([] (auto& obj) {obj.push_back(6);}); read = obj.read([] (auto& obj) {return obj;}); EXPECT_EQ((vector{5, 6}), read); } TEST(LeftRightTest, givenVector_whenWritingReturnsValue_thenValueIsReturned) { LeftRight> obj; auto a = obj.write([] (auto&) -> int {return 5;}); static_assert(std::is_same::value, ""); EXPECT_EQ(5, a); } TEST(LeftRightTest, readsCanBeConcurrent) { LeftRight obj; std::atomic 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 obj; std::atomic reader_running{false}; std::atomic 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 obj; std::atomic writer_running{false}; std::atomic 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 obj; std::atomic first_writer_started{false}; std::atomic 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 obj; EXPECT_THROW( obj.read([](auto&) {throw MyException();}), MyException ); } TEST(LeftRightTest, whenWriteThrowsException_thenThrowsThrough) { LeftRight obj; EXPECT_THROW( obj.write([](auto&) {throw MyException();}), MyException ); } TEST(LeftRightTest, givenInt_whenWriteThrowsExceptionOnFirstCall_thenResetsToOldState) { LeftRight 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); } // note: each write is executed twice, on the foreground and background copy. // We need to test a thrown exception in either call is handled correctly. TEST(LeftRightTest, givenInt_whenWriteThrowsExceptionOnSecondCall_thenKeepsNewState) { LeftRight obj; obj.write([](auto& obj) {obj = 5;}); bool write_called = false; EXPECT_THROW( obj.write([&](auto& obj) { obj = 6; if (write_called) { // this is the second time the write callback is executed throw MyException(); } else { write_called = true; } }), MyException ); // check reading it returns new value int read = obj.read([] (auto& obj) {return obj;}); EXPECT_EQ(6, 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(6, read); } TEST(LeftRightTest, givenVector_whenWriteThrowsException_thenResetsToOldState) { LeftRight> 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 read = obj.read([] (auto& obj) {return obj;}); EXPECT_EQ((vector{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{5}), read); } test/cpp-utils/thread/debugging_test.cpp000066400000000000000000000040541347701267100207350ustar00rootroot00000000000000#include #include #include #include using namespace cpputils; using std::string; TEST(ThreadDebuggingTest_ThreadName, givenMainThread_whenSettingAndGetting_thenDoesntCrash) { set_thread_name("my_thread_name"); 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; bool child_didnt_crash = false; std::thread child([&] { set_thread_name("my_thread_name"); get_thread_name(); child_didnt_crash = true; nameIsChecked.wait(); }); get_thread_name(&child); nameIsChecked.release(); // getting the name of a not-running thread would cause errors, so let's make sure we only exit after getting the name child.join(); EXPECT_TRUE(child_didnt_crash); } TEST(ThreadDebuggingTest_ThreadName, givenChildThread_whenGettingFromOutside_thenIsCorrect) { ConditionBarrier nameIsSet; ConditionBarrier nameIsChecked; 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); nameIsChecked.release(); child.join(); } #endif test/cpp-utils/value_type/000077500000000000000000000000001347701267100161425ustar00rootroot00000000000000test/cpp-utils/value_type/ValueTypeTest.cpp000066400000000000000000000343121347701267100214270ustar00rootroot00000000000000#include #include #include #include #include // TODO Test with MovableOnly underlying type // TODO Test that move constructing/assigning actually moves the underlying // TODO Test that noexcept flags are set correctly using cpputils::value_type::IdValueType; using cpputils::value_type::OrderedIdValueType; using cpputils::value_type::QuantityValueType; using cpputils::value_type::FlagsValueType; namespace { struct MyIdValueType : IdValueType { constexpr explicit MyIdValueType(int64_t val): IdValueType(val) {} }; struct MyOrderedIdValueType : OrderedIdValueType { constexpr explicit MyOrderedIdValueType(int64_t val): OrderedIdValueType(val) {} }; struct MyQuantityValueType : QuantityValueType { constexpr explicit MyQuantityValueType(int64_t val): QuantityValueType(val) {} }; struct MyFlagsValueType : FlagsValueType { constexpr explicit MyFlagsValueType(int64_t val): FlagsValueType(val) {} }; } DEFINE_HASH_FOR_VALUE_TYPE(MyIdValueType); DEFINE_HASH_FOR_VALUE_TYPE(MyOrderedIdValueType); DEFINE_HASH_FOR_VALUE_TYPE(MyQuantityValueType); DEFINE_HASH_FOR_VALUE_TYPE(MyFlagsValueType); namespace { /** * Tests for IdValueType */ template struct IdValueTypeTest_constexpr_test { static constexpr Type test_constructor = Type(5); static_assert(Type(5) == test_constructor, ""); static constexpr Type test_copy_constructor = test_constructor; static_assert(Type(5) == test_copy_constructor, ""); #if !defined(_MSC_VER) // These aren't evaluated at compile time on MSVC :( static constexpr Type test_copy_assignment = (Type(4) = test_copy_constructor); static_assert(test_copy_assignment == Type(5), ""); static constexpr Type test_move_assignment = (Type(4) = Type(3)); static_assert(test_move_assignment == Type(3), ""); #endif static_assert(Type(5) == Type(5), ""); static_assert(!(Type(5) != Type(5)), ""); static constexpr bool success = true; }; static_assert(IdValueTypeTest_constexpr_test::success, ""); static_assert(IdValueTypeTest_constexpr_test::success, ""); static_assert(IdValueTypeTest_constexpr_test::success, ""); static_assert(IdValueTypeTest_constexpr_test::success, ""); namespace IdValueTypeTest_constexpr_test_extras { // For some reason, MSVC crashes when these are part of IdValueTypeTest_constexpr_test. // so let's define them separately. static_assert(!(MyIdValueType(5) == MyIdValueType(6)), ""); static_assert(MyIdValueType(5) != MyIdValueType(6), ""); static_assert(!(MyOrderedIdValueType(5) == MyOrderedIdValueType(6)), ""); static_assert(MyOrderedIdValueType(5) != MyOrderedIdValueType(6), ""); static_assert(!(MyQuantityValueType(5) == MyQuantityValueType(6)), ""); static_assert(MyQuantityValueType(5) != MyQuantityValueType(6), ""); static_assert(!(MyFlagsValueType(5) == MyFlagsValueType(6)), ""); static_assert(MyFlagsValueType(5) != MyFlagsValueType(6), ""); } template class IdValueTypeTest : public testing::Test { }; using IdValueTypeTest_types = testing::Types; TYPED_TEST_CASE(IdValueTypeTest, IdValueTypeTest_types); TYPED_TEST(IdValueTypeTest, Equality) { TypeParam obj1(4); TypeParam obj2(4); TypeParam obj3(5); EXPECT_TRUE(obj1 == obj2); EXPECT_TRUE(obj2 == obj1);; EXPECT_FALSE(obj1 == obj3); EXPECT_FALSE(obj3 == obj1); EXPECT_FALSE(obj1 != obj2); EXPECT_FALSE(obj2 != obj1); EXPECT_TRUE(obj1 != obj3); EXPECT_TRUE(obj3 != obj1); } TYPED_TEST(IdValueTypeTest, Constructor) { TypeParam obj(4); EXPECT_TRUE(obj == TypeParam(4)); } TYPED_TEST(IdValueTypeTest, CopyConstructor) { TypeParam obj(2); TypeParam obj2(obj); EXPECT_TRUE(obj2 == TypeParam(2)); EXPECT_TRUE(obj == obj2); } TYPED_TEST(IdValueTypeTest, MoveConstructor) { TypeParam obj(2); TypeParam obj2(std::move(obj)); EXPECT_TRUE(obj2 == TypeParam(2)); } TYPED_TEST(IdValueTypeTest, CopyAssignment) { TypeParam obj(3); TypeParam obj2(2); obj2 = obj; EXPECT_TRUE(obj2 == TypeParam(3)); EXPECT_TRUE(obj == obj2); } TYPED_TEST(IdValueTypeTest, CopyAssignment_Return) { TypeParam obj(3); TypeParam obj2(2); EXPECT_TRUE((obj2 = obj) == TypeParam(3)); } TYPED_TEST(IdValueTypeTest, MoveAssignment) { TypeParam obj(3); TypeParam obj2(2); obj2 = std::move(obj); EXPECT_TRUE(obj2 == TypeParam(3)); } TYPED_TEST(IdValueTypeTest, MoveAssignment_Return) { TypeParam obj(3); TypeParam obj2(2); EXPECT_TRUE((obj2 = std::move(obj)) == TypeParam(3)); } TYPED_TEST(IdValueTypeTest, Hash) { TypeParam obj(3); TypeParam obj2(3); EXPECT_EQ(std::hash()(obj), std::hash()(obj2)); } TYPED_TEST(IdValueTypeTest, UnorderedSet) { std::unordered_set set; set.insert(TypeParam(3)); EXPECT_EQ(1u, set.count(TypeParam(3))); } /** * Tests for OrderedIdValueType */ template struct OrderedIdValueTypeTest_constexpr_test { static_assert(!(Type(4) < Type(3)), ""); static_assert(!(Type(3) < Type(3)), ""); static_assert(!(Type(3) > Type(4)), ""); static_assert(!(Type(3) > Type(3)), ""); static_assert(Type(3) <= Type(4), ""); static_assert(Type(3) <= Type(3), ""); static_assert(Type(4) >= Type(3), ""); static_assert(Type(3) >= Type(3), ""); static constexpr bool success = true; }; static_assert(OrderedIdValueTypeTest_constexpr_test::success, ""); static_assert(OrderedIdValueTypeTest_constexpr_test::success, ""); namespace OrderedIdValueTypeTest_constexpr_test_extras { // For some reason, MSVC crashes when these are part of IdValueTypeTest_constexpr_test. // so let's define them separately. static_assert(MyOrderedIdValueType(3) < MyOrderedIdValueType(4), ""); static_assert(MyOrderedIdValueType(4) > MyOrderedIdValueType(3), ""); static_assert(!(MyOrderedIdValueType(4) <= MyOrderedIdValueType(3)), ""); static_assert(!(MyOrderedIdValueType(3) >= MyOrderedIdValueType(4)), ""); static_assert(MyQuantityValueType(3) < MyQuantityValueType(4), ""); static_assert(MyQuantityValueType(4) > MyQuantityValueType(3), ""); static_assert(!(MyQuantityValueType(4) <= MyQuantityValueType(3)), ""); static_assert(!(MyQuantityValueType(3) >= MyQuantityValueType(4)), ""); } template class OrderedIdValueTypeTest : public testing::Test {}; using OrderedIdValueTypeTest_types = testing::Types; TYPED_TEST_CASE(OrderedIdValueTypeTest, OrderedIdValueTypeTest_types); TYPED_TEST(OrderedIdValueTypeTest, LessThan) { TypeParam a(3); TypeParam b(3); TypeParam c(4); EXPECT_FALSE(a < a); EXPECT_FALSE(a < b); EXPECT_TRUE(a < c); EXPECT_FALSE(b < a); EXPECT_FALSE(b < b); EXPECT_TRUE(b < c); EXPECT_FALSE(c < a); EXPECT_FALSE(c < b); EXPECT_FALSE(c < c); } TYPED_TEST(OrderedIdValueTypeTest, GreaterThan) { TypeParam a(3); TypeParam b(3); TypeParam c(4); EXPECT_FALSE(a > a); EXPECT_FALSE(a > b); EXPECT_FALSE(a > c); EXPECT_FALSE(b > a); EXPECT_FALSE(b > b); EXPECT_FALSE(b > c); EXPECT_TRUE(c > a); EXPECT_TRUE(c > b); EXPECT_FALSE(c > c); } TYPED_TEST(OrderedIdValueTypeTest, LessOrEqualThan) { TypeParam a(3); TypeParam b(3); TypeParam c(4); EXPECT_TRUE(a <= a); EXPECT_TRUE(a <= b); EXPECT_TRUE(a <= c); EXPECT_TRUE(b <= a); EXPECT_TRUE(b <= b); EXPECT_TRUE(b <= c); EXPECT_FALSE(c <= a); EXPECT_FALSE(c <= b); EXPECT_TRUE(c <= c); } TYPED_TEST(OrderedIdValueTypeTest, GreaterOrEqualThan) { TypeParam a(3); TypeParam b(3); TypeParam c(4); EXPECT_TRUE(a >= a); EXPECT_TRUE(a >= b); EXPECT_FALSE(a >= c); EXPECT_TRUE(b >= a); EXPECT_TRUE(b >= b); EXPECT_FALSE(b >= c); EXPECT_TRUE(c >= a); EXPECT_TRUE(c >= b); EXPECT_TRUE(c >= c); } TYPED_TEST(OrderedIdValueTypeTest, Set) { std::set set; set.insert(TypeParam(3)); EXPECT_EQ(1u, set.count(TypeParam(3))); } /** * Tests for QuantityValueType */ namespace QuantityValueTypeTest_constexpr_test { static_assert(++MyQuantityValueType(3) == MyQuantityValueType(4), ""); static_assert(MyQuantityValueType(3)++ == MyQuantityValueType(3), ""); static_assert(--MyQuantityValueType(3) == MyQuantityValueType(2), ""); static_assert(MyQuantityValueType(3)-- == MyQuantityValueType(3), ""); static_assert((MyQuantityValueType(3) += MyQuantityValueType(2)) == MyQuantityValueType(5), ""); static_assert((MyQuantityValueType(3) -= MyQuantityValueType(2)) == MyQuantityValueType(1), ""); static_assert((MyQuantityValueType(3) *= 2) == MyQuantityValueType(6), ""); static_assert((MyQuantityValueType(6) /= 2) == MyQuantityValueType(3), ""); static_assert((MyQuantityValueType(7) /= 3) == MyQuantityValueType(2), ""); static_assert((MyQuantityValueType(7) %= 3) == MyQuantityValueType(1), ""); static_assert(MyQuantityValueType(3) + MyQuantityValueType(2) == MyQuantityValueType(5), ""); static_assert(MyQuantityValueType(3) - MyQuantityValueType(2) == MyQuantityValueType(1), ""); static_assert(MyQuantityValueType(3) * 2 == MyQuantityValueType(6), ""); static_assert(2 * MyQuantityValueType(3) == MyQuantityValueType(6), ""); static_assert(MyQuantityValueType(6) / 2 == MyQuantityValueType(3), ""); static_assert(MyQuantityValueType(6) / MyQuantityValueType(2) == 3, ""); static_assert(MyQuantityValueType(7) / 3 == MyQuantityValueType(2), ""); static_assert(MyQuantityValueType(7) / MyQuantityValueType(3) == 2, ""); static_assert(MyQuantityValueType(7) % 3 == MyQuantityValueType(1), ""); static_assert(MyQuantityValueType(7) % MyQuantityValueType(3) == 1, ""); }; template class QuantityValueTypeTest : public testing::Test {}; using QuantityValueTypeTest_types = testing::Types; TYPED_TEST_CASE(QuantityValueTypeTest, QuantityValueTypeTest_types); TYPED_TEST(QuantityValueTypeTest, PreIncrement) { TypeParam a(3); EXPECT_EQ(TypeParam(4), ++a); EXPECT_EQ(TypeParam(4), a); } TYPED_TEST(QuantityValueTypeTest, PostIncrement) { TypeParam a(3); EXPECT_EQ(TypeParam(3), a++); EXPECT_EQ(TypeParam(4), a); } TYPED_TEST(QuantityValueTypeTest, PreDecrement) { TypeParam a(3); EXPECT_EQ(TypeParam(2), --a); EXPECT_EQ(TypeParam(2), a); } TYPED_TEST(QuantityValueTypeTest, PostDecrement) { TypeParam a(3); EXPECT_EQ(TypeParam(3), a--); EXPECT_EQ(TypeParam(2), a); } TYPED_TEST(QuantityValueTypeTest, AddAssignment) { TypeParam a(3); EXPECT_EQ(TypeParam(5), a += TypeParam(2)); EXPECT_EQ(TypeParam(5), a); } TYPED_TEST(QuantityValueTypeTest, SubAssignment) { TypeParam a(3); EXPECT_EQ(TypeParam(1), a -= TypeParam(2)); EXPECT_EQ(TypeParam(1), a); } TYPED_TEST(QuantityValueTypeTest, MulAssignment) { TypeParam a(3); EXPECT_EQ(TypeParam(6), a *= 2); EXPECT_EQ(TypeParam(6), a); } TYPED_TEST(QuantityValueTypeTest, DivScalarAssignment) { TypeParam a(6); EXPECT_EQ(TypeParam(3), a /= 2); EXPECT_EQ(TypeParam(3), a); } TYPED_TEST(QuantityValueTypeTest, DivScalarWithRemainderAssignment) { TypeParam a(7); EXPECT_EQ(TypeParam(2), a /= 3); EXPECT_EQ(TypeParam(2), a); } TYPED_TEST(QuantityValueTypeTest, ModScalarAssignment) { TypeParam a(7); EXPECT_EQ(TypeParam(1), a %= 3); EXPECT_EQ(TypeParam(1), a); } TYPED_TEST(QuantityValueTypeTest, Add) { EXPECT_EQ(TypeParam(5), TypeParam(3) + TypeParam(2)); } TYPED_TEST(QuantityValueTypeTest, Sub) { EXPECT_EQ(TypeParam(1), TypeParam(3) - TypeParam(2)); } TYPED_TEST(QuantityValueTypeTest, Mul1) { EXPECT_EQ(TypeParam(6), TypeParam(3) * 2); } TYPED_TEST(QuantityValueTypeTest, Mul2) { EXPECT_EQ(TypeParam(6), 2 * TypeParam(3)); } TYPED_TEST(QuantityValueTypeTest, DivScalar) { EXPECT_EQ(TypeParam(3), TypeParam(6) / 2); } TYPED_TEST(QuantityValueTypeTest, DivValue) { EXPECT_EQ(3, TypeParam(6) / TypeParam(2)); } TYPED_TEST(QuantityValueTypeTest, DivScalarWithRemainder) { EXPECT_EQ(TypeParam(2), TypeParam(7) / 3); } TYPED_TEST(QuantityValueTypeTest, DivValueWithRemainder) { EXPECT_EQ(2, TypeParam(7) / TypeParam(3)); } TYPED_TEST(QuantityValueTypeTest, ModScalar) { EXPECT_EQ(TypeParam(1), TypeParam(7) % 3); } TYPED_TEST(QuantityValueTypeTest, ModValue) { EXPECT_EQ(1, TypeParam(7) % TypeParam(3)); } /** * Tests for FlagsValueType */ namespace FlagsValueTypeTest_constexpr_test { static_assert(~MyFlagsValueType(3) != MyFlagsValueType(3), ""); static_assert(~~MyFlagsValueType(3) == MyFlagsValueType(3), ""); static_assert(~MyFlagsValueType(3) == MyFlagsValueType(~3), ""); static_assert((MyFlagsValueType(3) & MyFlagsValueType(5)) == MyFlagsValueType(3 & 5), ""); static_assert((MyFlagsValueType(3) | MyFlagsValueType(5)) == MyFlagsValueType(3 | 5), ""); static_assert((MyFlagsValueType(3) ^ MyFlagsValueType(5)) == MyFlagsValueType(3 ^ 5), ""); static_assert((MyFlagsValueType(3) &= MyFlagsValueType(5)) == MyFlagsValueType(3 & 5), ""); static_assert((MyFlagsValueType(3) |= MyFlagsValueType(5)) == MyFlagsValueType(3 | 5), ""); static_assert((MyFlagsValueType(3) ^= MyFlagsValueType(5)) == MyFlagsValueType(3 ^ 5), ""); } template class FlagsValueTypeTest : public testing::Test {}; using FlagsValueType_types = testing::Types; TYPED_TEST_CASE(FlagsValueTypeTest, FlagsValueType_types); TYPED_TEST(FlagsValueTypeTest, Invert) { TypeParam a(3); TypeParam b(~3); EXPECT_EQ(b, ~a); a = ~a; EXPECT_EQ(b, a); } TYPED_TEST(FlagsValueTypeTest, And) { TypeParam a(3); TypeParam b(5); TypeParam c(3 & 5); EXPECT_EQ(c, a & b); EXPECT_EQ(c, b &= a); EXPECT_EQ(c, b); } TYPED_TEST(FlagsValueTypeTest, Or) { TypeParam a(3); TypeParam b(5); TypeParam c(3 | 5); EXPECT_EQ(c, a | b); EXPECT_EQ(c, b |= a); EXPECT_EQ(c, b); } TYPED_TEST(FlagsValueTypeTest, Xor) { TypeParam a(3); TypeParam b(5); TypeParam c(3 ^ 5); EXPECT_EQ(c, a ^ b); EXPECT_EQ(c, b ^= a); EXPECT_EQ(c, b); } } test/cryfs-cli/000077500000000000000000000000001347701267100137405ustar00rootroot00000000000000test/cryfs-cli/CMakeLists.txt000066400000000000000000000012331347701267100164770ustar00rootroot00000000000000project (cryfs-cli-test) set(SOURCES CallAfterTimeoutTest.cpp testutils/CliTest.cpp CliTest_Setup.cpp CliTest_WrongEnvironment.cpp program_options/UtilsTest.cpp program_options/ProgramOptionsTest.cpp program_options/ParserTest.cpp CliTest_ShowingHelp.cpp EnvironmentTest.cpp VersionCheckerTest.cpp CliTest_IntegrityCheck.cpp CryfsUnmountTest.cpp ) add_executable(${PROJECT_NAME} ${SOURCES}) 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}) target_activate_cpp14(${PROJECT_NAME}) test/cryfs-cli/CallAfterTimeoutTest.cpp000066400000000000000000000037261347701267100205200ustar00rootroot00000000000000#include #include #include #include using cpputils::unique_ref; using cpputils::make_unique_ref; using boost::chrono::milliseconds; using boost::chrono::minutes; using boost::this_thread::sleep_for; using namespace cryfs_cli; class CallAfterTimeoutTest : public ::testing::Test { public: CallAfterTimeoutTest(): called(false) {} unique_ref callAfterTimeout(milliseconds timeout) { return make_unique_ref(timeout, [this] {called = true;}, "test"); } std::atomic called; }; TEST_F(CallAfterTimeoutTest, NoReset_1) { auto obj = callAfterTimeout(milliseconds(100)); sleep_for(milliseconds(50)); EXPECT_FALSE(called); sleep_for(milliseconds(100)); EXPECT_TRUE(called); } TEST_F(CallAfterTimeoutTest, NoReset_2) { auto obj = callAfterTimeout(milliseconds(200)); sleep_for(milliseconds(150)); EXPECT_FALSE(called); sleep_for(milliseconds(100)); EXPECT_TRUE(called); } TEST_F(CallAfterTimeoutTest, DoesntCallTwice) { auto obj = callAfterTimeout(milliseconds(50)); // Wait until it was called while(!called) { sleep_for(milliseconds(10)); } EXPECT_TRUE(called); // Test that it isn't called again called = false; sleep_for(milliseconds(150)); EXPECT_FALSE(called); } TEST_F(CallAfterTimeoutTest, OneReset) { auto obj = callAfterTimeout(milliseconds(200)); sleep_for(milliseconds(125)); obj->resetTimer(); sleep_for(milliseconds(125)); EXPECT_FALSE(called); sleep_for(milliseconds(125)); EXPECT_TRUE(called); } TEST_F(CallAfterTimeoutTest, TwoResets) { auto obj = callAfterTimeout(milliseconds(200)); sleep_for(milliseconds(100)); obj->resetTimer(); sleep_for(milliseconds(125)); obj->resetTimer(); sleep_for(milliseconds(125)); EXPECT_FALSE(called); sleep_for(milliseconds(125)); EXPECT_TRUE(called); } test/cryfs-cli/CliTest_IntegrityCheck.cpp000066400000000000000000000150761347701267100210200ustar00rootroot00000000000000#include "testutils/CliTest.h" #include #include #include #include #include #include #include using std::vector; using std::string; using cryfs::CryConfig; using cryfs::CryConfigFile; using cryfs::ErrorCode; using cryfs::CryKeyProvider; using cpputils::Data; using cpputils::EncryptionKey; using cpputils::SCrypt; using cpputils::TempDir; namespace bf = boost::filesystem; namespace { void writeFile(const bf::path& filename, const string& content) { std::ofstream file(filename.c_str(), std::ios::trunc); file << content; ASSERT(file.good(), "Failed writing file to file system"); } bool readingFileIsSuccessful(const bf::path& filename) { std::ifstream file(filename.c_str()); std::string content; file >> content; // just read a little bit so we have a file access return file.good(); } void recursive_copy(const bf::path &src, const bf::path &dst) { if (bf::exists(dst)) { throw std::runtime_error(dst.generic_string() + " already exists"); } if (bf::is_directory(src)) { bf::create_directories(dst); for (auto& item : bf::directory_iterator(src)) { recursive_copy(item.path(), dst / item.path().filename()); } } else if (bf::is_regular_file(src)) { bf::copy_file(src, dst); } else { throw std::runtime_error(dst.generic_string() + " neither dir nor file"); } } class FakeCryKeyProvider final : public CryKeyProvider { EncryptionKey requestKeyForExistingFilesystem(size_t keySize, const Data &kdfParameters) override { return SCrypt(SCrypt::TestSettings).deriveExistingKey(keySize, "pass", kdfParameters); } KeyResult requestKeyForNewFilesystem(size_t keySize) override { auto derived = SCrypt(SCrypt::TestSettings).deriveNewKey(keySize, "pass"); return { std::move(derived.key), std::move(derived.kdfParameters) }; } }; class CliTest_IntegrityCheck : public CliTest { public: void modifyFilesystemId() { FakeCryKeyProvider keyProvider; auto configFile = CryConfigFile::load(basedir / "cryfs.config", &keyProvider).value(); configFile.config()->SetFilesystemId(CryConfig::FilesystemID::FromString("0123456789ABCDEF0123456789ABCDEF")); configFile.save(); } void modifyFilesystemKey() { FakeCryKeyProvider keyProvider; auto configFile = CryConfigFile::load(basedir / "cryfs.config", &keyProvider).value(); configFile.config()->SetEncryptionKey("0123456789ABCDEF0123456789ABCDEF"); configFile.save(); } }; TEST_F(CliTest_IntegrityCheck, givenIncorrectFilesystemId_thenFails) { vector args{basedir.string().c_str(), mountdir.string().c_str(), "--cipher", "aes-256-gcm", "-f"}; //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that EXPECT_RUN_SUCCESS(args, mountdir); modifyFilesystemId(); EXPECT_RUN_ERROR( args, "Error 20: The filesystem id in the config file is different to the last time we loaded a filesystem from this basedir.", ErrorCode::FilesystemIdChanged ); } TEST_F(CliTest_IntegrityCheck, givenIncorrectFilesystemKey_thenFails) { vector args{basedir.string().c_str(), mountdir.string().c_str(), "--cipher", "aes-256-gcm", "-f"}; //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that EXPECT_RUN_SUCCESS(args, mountdir); modifyFilesystemKey(); EXPECT_RUN_ERROR( args, "Error 21: The filesystem encryption key differs from the last time we loaded this filesystem. Did an attacker replace the file system?", ErrorCode::EncryptionKeyChanged ); } // TODO Also enable this TEST_F(CliTest_IntegrityCheck, givenFilesystemWithRolledBackBasedir_whenMounting_thenFails) { vector args{basedir.string().c_str(), mountdir.string().c_str(), "--cipher", "aes-256-gcm", "-f"}; //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS/EXPECT_RUN_ERROR can handle that // create a filesystem with one file EXPECT_RUN_SUCCESS(args, mountdir, [&] { writeFile(mountdir / "myfile", "hello world"); }); // backup the base directory TempDir backup; recursive_copy(basedir, backup.path() / "basedir"); // modify the file system contents EXPECT_RUN_SUCCESS(args, mountdir, [&] { writeFile(mountdir / "myfile", "hello world 2"); }); // roll back base directory bf::remove_all(basedir); recursive_copy(backup.path() / "basedir", basedir); // error code is success because it unmounts normally EXPECT_RUN_ERROR(args, "Integrity violation detected. Unmounting.", ErrorCode::IntegrityViolation, [&] { EXPECT_FALSE(readingFileIsSuccessful(mountdir / "myfile")); }); // Test it doesn't mount anymore now because it's marked with an integrity violation EXPECT_RUN_ERROR(args, "There was an integrity violation detected. Preventing any further access to the file system.", ErrorCode::IntegrityViolationOnPreviousRun); } TEST_F(CliTest_IntegrityCheck, whenRollingBackBasedirWhileMounted_thenUnmounts) { vector args{basedir.string().c_str(), mountdir.string().c_str(), "--cipher", "aes-256-gcm", "-f"}; //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS/EXPECT_RUN_ERROR can handle that // create a filesystem with one file EXPECT_RUN_SUCCESS(args, mountdir, [&] { writeFile(mountdir / "myfile", "hello world"); }); // backup the base directory TempDir backup; recursive_copy(basedir, backup.path() / "basedir"); EXPECT_RUN_ERROR(args, "Integrity violation detected. Unmounting.", ErrorCode::IntegrityViolation, [&] { // modify the file system contents writeFile(mountdir / "myfile", "hello world 2"); ASSERT(readingFileIsSuccessful(mountdir / "myfile"), ""); // just to make sure reading usually works // wait for cache timeout (i.e. flush file system to disk) constexpr auto cache_timeout = blockstore::caching::CachingBlockStore2::MAX_LIFETIME_SEC + cryfs::cachingfsblobstore::CachingFsBlobStore::MAX_LIFETIME_SEC; boost::this_thread::sleep_for(boost::chrono::seconds(static_cast(std::ceil(cache_timeout * 3)))); // roll back base directory bf::remove_all(basedir); recursive_copy(backup.path() / "basedir", basedir); // expect reading now fails EXPECT_FALSE(readingFileIsSuccessful(mountdir / "myfile")); }); // Test it doesn't mount anymore now because it's marked with an integrity violation EXPECT_RUN_ERROR(args, "There was an integrity violation detected. Preventing any further access to the file system.", ErrorCode::IntegrityViolationOnPreviousRun); } } test/cryfs-cli/CliTest_Setup.cpp000066400000000000000000000037241347701267100172010ustar00rootroot00000000000000#include "testutils/CliTest.h" using cpputils::TempFile; //Tests that cryfs is correctly setup according to the CLI parameters specified using CliTest_Setup = CliTest; TEST_F(CliTest_Setup, NoSpecialOptions) { //Specify --cipher parameter to make it non-interactive //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that EXPECT_RUN_SUCCESS({basedir.string().c_str(), mountdir.string().c_str(), "--cipher", "aes-256-gcm", "-f"}, mountdir); } TEST_F(CliTest_Setup, NotexistingLogfileGiven) { TempFile notexisting_logfile(false); //Specify --cipher parameter to make it non-interactive //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that EXPECT_RUN_SUCCESS({basedir.string().c_str(), mountdir.string().c_str(), "-f", "--cipher", "aes-256-gcm", "--logfile", notexisting_logfile.path().string().c_str()}, mountdir); //TODO Expect logfile is used (check logfile content) } TEST_F(CliTest_Setup, ExistingLogfileGiven) { //Specify --cipher parameter to make it non-interactive //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that EXPECT_RUN_SUCCESS({basedir.string().c_str(), mountdir.string().c_str(), "-f", "--cipher", "aes-256-gcm", "--logfile", logfile.path().string().c_str()}, mountdir); //TODO Expect logfile is used (check logfile content) } TEST_F(CliTest_Setup, ConfigfileGiven) { //Specify --cipher parameter to make it non-interactive //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that EXPECT_RUN_SUCCESS({basedir.string().c_str(), mountdir.string().c_str(), "-f", "--cipher", "aes-256-gcm", "--config", configfile.path().string().c_str()}, mountdir); } TEST_F(CliTest_Setup, FuseOptionGiven) { //Specify --cipher parameter to make it non-interactive //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that EXPECT_RUN_SUCCESS({basedir.string().c_str(), mountdir.string().c_str(), "-f", "--cipher", "aes-256-gcm", "--", "-f"}, mountdir); }test/cryfs-cli/CliTest_ShowingHelp.cpp000066400000000000000000000020321347701267100203170ustar00rootroot00000000000000#include "testutils/CliTest.h" using CliTest_ShowingHelp = CliTest; using cryfs::ErrorCode; TEST_F(CliTest_ShowingHelp, HelpLongOption) { EXPECT_EXIT_WITH_HELP_MESSAGE({"--help"}, "", ErrorCode::Success); } TEST_F(CliTest_ShowingHelp, HelpLongOptionTogetherWithOtherOptions) { EXPECT_EXIT_WITH_HELP_MESSAGE({basedir.string().c_str(), mountdir.string().c_str(), "--help"}, "", ErrorCode::Success); } TEST_F(CliTest_ShowingHelp, HelpShortOption) { EXPECT_EXIT_WITH_HELP_MESSAGE({"-h"}, "", ErrorCode::Success); } TEST_F(CliTest_ShowingHelp, HelpShortOptionTogetherWithOtherOptions) { EXPECT_EXIT_WITH_HELP_MESSAGE({basedir.string().c_str(), mountdir.string().c_str(), "-h"}, "", ErrorCode::Success); } TEST_F(CliTest_ShowingHelp, MissingAllOptions) { EXPECT_EXIT_WITH_HELP_MESSAGE({}, "Please specify a base directory", ErrorCode::InvalidArguments); } TEST_F(CliTest_ShowingHelp, MissingDir) { EXPECT_EXIT_WITH_HELP_MESSAGE({basedir.string().c_str()}, "Please specify a mount directory", ErrorCode::InvalidArguments); } test/cryfs-cli/CliTest_WrongEnvironment.cpp000066400000000000000000000245721347701267100214260ustar00rootroot00000000000000#include "testutils/CliTest.h" #include namespace bf = boost::filesystem; using ::testing::Values; using ::testing::WithParamInterface; using ::testing::Return; using ::testing::_; using std::vector; using cpputils::TempFile; using cryfs::ErrorCode; struct TestConfig { bool externalConfigfile; bool logIsNotStderr; bool runningInForeground; }; //Tests what happens if cryfs is run in the wrong environment, i.e. with a base directory that doesn't exist or similar class CliTest_WrongEnvironment: public CliTest, public WithParamInterface { public: void SetAllPermissions(const bf::path &dir) { bf::permissions(dir, bf::owner_write|bf::owner_read|bf::owner_exe); } void SetNoReadPermission(const bf::path &dir) { bf::permissions(dir, bf::owner_write|bf::owner_exe); } void SetNoWritePermission(const bf::path &dir) { bf::permissions(dir, bf::owner_read|bf::owner_exe); } void SetNoExePermission(const bf::path &dir) { bf::permissions(dir, bf::owner_read|bf::owner_write); } void SetNoPermission(const bf::path &dir) { bf::permissions(dir, bf::no_perms); } void Test_Run_Success() { EXPECT_RUN_SUCCESS(args(), mountdir); } void Test_Run_Error(const char *expectedError, cryfs::ErrorCode errorCode) { EXPECT_RUN_ERROR( args(), expectedError, errorCode ); } vector args() { vector result = {basedir.string(), mountdir.string()}; if (GetParam().externalConfigfile) { result.push_back("--config"); result.push_back(configfile.path().string()); } if (GetParam().logIsNotStderr) { result.push_back("--logfile"); result.push_back(logfile.path().string()); } if (GetParam().runningInForeground) { result.push_back("-f"); } // Test case should be non-interactive, so don't ask for cipher. result.push_back("--cipher"); result.push_back("aes-256-gcm"); return result; } }; INSTANTIATE_TEST_CASE_P(DefaultParams, CliTest_WrongEnvironment, Values(TestConfig({false, false, false}))); INSTANTIATE_TEST_CASE_P(ExternalConfigfile, CliTest_WrongEnvironment, Values(TestConfig({true, false, false}))); INSTANTIATE_TEST_CASE_P(LogIsNotStderr, CliTest_WrongEnvironment, Values(TestConfig({false, true, false}))); INSTANTIATE_TEST_CASE_P(ExternalConfigfile_LogIsNotStderr, CliTest_WrongEnvironment, Values(TestConfig({true, true, false}))); INSTANTIATE_TEST_CASE_P(RunningInForeground, CliTest_WrongEnvironment, Values(TestConfig({false, false, true}))); INSTANTIATE_TEST_CASE_P(RunningInForeground_ExternalConfigfile, CliTest_WrongEnvironment, Values(TestConfig({true, false, true}))); INSTANTIATE_TEST_CASE_P(RunningInForeground_LogIsNotStderr, CliTest_WrongEnvironment, Values(TestConfig({false, true, true}))); INSTANTIATE_TEST_CASE_P(RunningInForeground_ExternalConfigfile_LogIsNotStderr, CliTest_WrongEnvironment, Values(TestConfig({true, true, true}))); //Counter-Test. Test that it doesn't fail if we call it without an error condition. TEST_P(CliTest_WrongEnvironment, NoErrorCondition) { if (!GetParam().runningInForeground) {return;} // TODO Make this work also if run in background (see CliTest::EXPECT_RUN_SUCCESS) Test_Run_Success(); } TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir) { mountdir = basedir; Test_Run_Error("Error 18: base directory can't be inside the mount directory", ErrorCode::BaseDirInsideMountDir); } bf::path make_relative(const bf::path &path) { bf::path result; bf::path cwd = bf::current_path(); for(auto iter = ++cwd.begin(); iter!=cwd.end(); ++iter) { result /= ".."; } result /= path.relative_path(); return result; } TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir_MountDirRelative) { mountdir = make_relative(basedir); Test_Run_Error("Error 18: base directory can't be inside the mount directory", ErrorCode::BaseDirInsideMountDir); } TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir_BaseDirRelative) { mountdir = basedir; basedir = make_relative(basedir); Test_Run_Error("Error 18: base directory can't be inside the mount directory", ErrorCode::BaseDirInsideMountDir); } TEST_P(CliTest_WrongEnvironment, MountDirIsBaseDir_BothRelative) { basedir = make_relative(basedir); mountdir = basedir; Test_Run_Error("Error 18: base directory can't be inside the mount directory", ErrorCode::BaseDirInsideMountDir); } TEST_P(CliTest_WrongEnvironment, BaseDir_DoesntExist) { _basedir.remove(); // ON_CALL and not EXPECT_CALL, because this is a death test (i.e. it is forked) and gmock EXPECT_CALL in fork children don't report to parents. ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(false)); Test_Run_Error("Error 16: base directory not found", ErrorCode::InaccessibleBaseDir); } TEST_P(CliTest_WrongEnvironment, BaseDir_DoesntExist_Noninteractive) { _basedir.remove(); // We can't set an EXPECT_CALL().Times(0), because this is a death test (i.e. it is forked) and gmock EXPECT_CALL in fork children don't report to parents. // So we set a default answer that shouldn't crash and check it's not called by checking that it crashes. ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(true)); cpputils::setenv("CRYFS_FRONTEND", "noninteractive"); Test_Run_Error("Error 16: base directory not found", ErrorCode::InaccessibleBaseDir); cpputils::unsetenv("CRYFS_FRONTEND"); } TEST_P(CliTest_WrongEnvironment, BaseDir_DoesntExist_Create) { if (!GetParam().runningInForeground) {return;} // TODO Make this work also if run in background (see CliTest::EXPECT_RUN_SUCCESS) _basedir.remove(); ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(true)); Test_Run_Success(); EXPECT_TRUE(bf::exists(_basedir.path()) && bf::is_directory(_basedir.path())); } TEST_P(CliTest_WrongEnvironment, BaseDir_IsNotDirectory) { TempFile basedirfile; basedir = basedirfile.path(); Test_Run_Error("Error 16: base directory is not a directory", ErrorCode::InaccessibleBaseDir); } TEST_P(CliTest_WrongEnvironment, BaseDir_AllPermissions) { if (!GetParam().runningInForeground) {return;} // TODO Make this work also if run in background (see CliTest::EXPECT_RUN_SUCCESS) //Counter-Test. Test it doesn't fail if permissions are there. SetAllPermissions(basedir); Test_Run_Success(); } // boost::filesystem doesn't set permissions on Windows correctly #if !defined(_MSC_VER) TEST_P(CliTest_WrongEnvironment, BaseDir_NoReadPermission) { SetNoReadPermission(basedir); Test_Run_Error("Error 16: Could not read from base directory", ErrorCode::InaccessibleBaseDir); } TEST_P(CliTest_WrongEnvironment, BaseDir_NoExePermission) { SetNoExePermission(basedir); Test_Run_Error("Error 16: Could not write to base directory", ErrorCode::InaccessibleBaseDir); } TEST_P(CliTest_WrongEnvironment, BaseDir_NoWritePermission) { SetNoWritePermission(basedir); Test_Run_Error("Error 16: Could not write to base directory", ErrorCode::InaccessibleBaseDir); } TEST_P(CliTest_WrongEnvironment, BaseDir_NoPermission) { SetNoPermission(basedir); Test_Run_Error("Error 16: Could not write to base directory", ErrorCode::InaccessibleBaseDir); } #endif TEST_P(CliTest_WrongEnvironment, MountDir_DoesntExist) { _mountdir.remove(); // ON_CALL and not EXPECT_CALL, because this is a death test (i.e. it is forked) and gmock EXPECT_CALL in fork children don't report to parents. ON_CALL(*console, askYesNo("Could not find mount directory. Do you want to create it?", _)).WillByDefault(Return(false)); Test_Run_Error("mount directory not found", ErrorCode::InaccessibleMountDir); } TEST_P(CliTest_WrongEnvironment, MountDir_DoesntExist_Noninteractive) { _mountdir.remove(); // We can't set an EXPECT_CALL().Times(0), because this is a death test (i.e. it is forked) and gmock EXPECT_CALL in fork children don't report to parents. // So we set a default answer that shouldn't crash and check it's not called by checking that it crashes. ON_CALL(*console, askYesNo("Could not find base directory. Do you want to create it?", _)).WillByDefault(Return(true)); cpputils::setenv("CRYFS_FRONTEND", "noninteractive"); Test_Run_Error("mount directory not found", ErrorCode::InaccessibleMountDir); cpputils::unsetenv("CRYFS_FRONTEND"); } TEST_P(CliTest_WrongEnvironment, MountDir_DoesntExist_Create) { if (!GetParam().runningInForeground) {return;} // TODO Make this work also if run in background (see CliTest::EXPECT_RUN_SUCCESS) _mountdir.remove(); ON_CALL(*console, askYesNo("Could not find mount directory. Do you want to create it?", _)).WillByDefault(Return(true)); Test_Run_Success(); EXPECT_TRUE(bf::exists(_mountdir.path()) && bf::is_directory(_mountdir.path())); } TEST_P(CliTest_WrongEnvironment, MountDir_IsNotDirectory) { TempFile mountdirfile; mountdir = mountdirfile.path(); Test_Run_Error("Error 17: mount directory is not a directory", ErrorCode::InaccessibleMountDir); } TEST_P(CliTest_WrongEnvironment, MountDir_AllPermissions) { if (!GetParam().runningInForeground) {return;} // TODO Make this work also if run in background (see CliTest::EXPECT_RUN_SUCCESS) //Counter-Test. Test it doesn't fail if permissions are there. SetAllPermissions(mountdir); Test_Run_Success(); } // boost::filesystem doesn't set permissions on Windows correctly #if !defined(_MSC_VER) TEST_P(CliTest_WrongEnvironment, MountDir_NoReadPermission) { SetNoReadPermission(mountdir); Test_Run_Error("Error 17: Could not read from mount directory", ErrorCode::InaccessibleMountDir); } TEST_P(CliTest_WrongEnvironment, MountDir_NoExePermission) { SetNoExePermission(mountdir); Test_Run_Error("Error 17: Could not write to mount directory", ErrorCode::InaccessibleMountDir); } TEST_P(CliTest_WrongEnvironment, MountDir_NoWritePermission) { SetNoWritePermission(mountdir); Test_Run_Error("Error 17: Could not write to mount directory", ErrorCode::InaccessibleMountDir); } TEST_P(CliTest_WrongEnvironment, MountDir_NoPermission) { SetNoPermission(mountdir); Test_Run_Error("Error 17: Could not write to mount directory", ErrorCode::InaccessibleMountDir); } #endiftest/cryfs-cli/CryfsUnmountTest.cpp000066400000000000000000000016741347701267100177700ustar00rootroot00000000000000#include "testutils/CliTest.h" #include #include using CliTest_Unmount = CliTest; namespace bf = boost::filesystem; namespace { void unmount(const bf::path& mountdir) { std::vector _args = {"cryfs-unmount", mountdir.string().c_str()}; cryfs_unmount::Cli().main(2, _args.data()); } TEST_F(CliTest_Unmount, givenMountedFilesystem_whenUnmounting_thenSucceeds) { // we're passing in boost::none as mountdir so EXPECT_RUN_SUCCESS doesn't unmount itself. // if the unmount we're calling here in the onMounted callback wouldn't work, EXPECT_RUN_SUCCESS // would never return and this would be a deadlock. EXPECT_RUN_SUCCESS({basedir.string().c_str(), mountdir.string().c_str(), "-f"}, boost::none, [this] () { unmount(mountdir); }); } // TODO Test calling with invalid args, valid '--version' or '--help' args, with a non-mounted mountdir and a nonexisting mountdir. } test/cryfs-cli/EnvironmentTest.cpp000066400000000000000000000056531347701267100176210ustar00rootroot00000000000000#include #include #include #include #include using namespace cryfs_cli; using std::string; using boost::optional; using boost::none; #if defined(_MSC_VER) constexpr const char* some_local_state_dir = "C:/my/local/state/dir"; #else constexpr const char* some_local_state_dir = "/my/local/state/dir"; #endif namespace bf = boost::filesystem; class EnvironmentTest : public ::testing::Test { public: // WithEnv sets an environment variable while it is in scope. // Once it leaves scope, the environment is reset. class WithEnv { public: WithEnv(const string &key, const string &value): _key(key) , _oldValue(none) { char *oldValue = std::getenv(key.c_str()); if (nullptr != oldValue) { _oldValue = string(oldValue); } cpputils::setenv(key.c_str(), value.c_str()); } ~WithEnv() { if (none == _oldValue) { cpputils::unsetenv(_key.c_str()); } else { cpputils::setenv(_key.c_str(), _oldValue->c_str()); } } private: string _key; optional _oldValue; }; }; TEST_F(EnvironmentTest, Noninteractive_Unset) { EXPECT_FALSE(Environment::isNoninteractive()); } TEST_F(EnvironmentTest, Noninteractive_Set) { WithEnv env("CRYFS_FRONTEND", "noninteractive"); EXPECT_TRUE(Environment::isNoninteractive()); } TEST_F(EnvironmentTest, Noninteractive_SetToOtherValue) { WithEnv env("CRYFS_FRONTEND", "someotherfrontend"); EXPECT_FALSE(Environment::isNoninteractive()); } TEST_F(EnvironmentTest, NoUpdateCheck_Unset) { EXPECT_FALSE(Environment::noUpdateCheck()); } TEST_F(EnvironmentTest, NoUpdateCheck_Set) { WithEnv env("CRYFS_NO_UPDATE_CHECK", "true"); EXPECT_TRUE(Environment::noUpdateCheck()); } TEST_F(EnvironmentTest, NoUpdateCheck_SetToOtherValue) { WithEnv env("CRYFS_NO_UPDATE_CHECK", "someothervalue"); // No matter what the value is, setting the environment variable says we don't do update checks. EXPECT_TRUE(Environment::noUpdateCheck()); } TEST_F(EnvironmentTest, LocalStateDir_NotSet) { EXPECT_EQ(Environment::defaultLocalStateDir(), Environment::localStateDir()); } TEST_F(EnvironmentTest, LocalStateDir_Set) { WithEnv env("CRYFS_LOCAL_STATE_DIR", some_local_state_dir); EXPECT_EQ(some_local_state_dir, Environment::localStateDir().string()); } TEST_F(EnvironmentTest, LocalStateDir_ConvertsRelativeToAbsolutePath_WithDot) { WithEnv env("CRYFS_LOCAL_STATE_DIR", "./dir"); EXPECT_EQ((bf::current_path() / "./dir").string(), Environment::localStateDir().string()); } TEST_F(EnvironmentTest, LocalStateDir_ConvertsRelativeToAbsolutePath_WithoutDot) { WithEnv env("CRYFS_LOCAL_STATE_DIR", "dir"); EXPECT_EQ((bf::current_path() / "dir").string(), Environment::localStateDir().string()); } test/cryfs-cli/VersionCheckerTest.cpp000066400000000000000000000110671347701267100202230ustar00rootroot00000000000000#include #include #include #include using std::string; using cpputils::FakeHttpClient; using cpputils::unique_ref; using cpputils::make_unique_ref; using boost::none; using namespace cryfs_cli; class VersionCheckerTest: public ::testing::Test { public: unique_ref versionChecker() { return make_unique_ref(_http.get()); } void setVersionInfo(const string &versionInfo) { _http->addWebsite("https://www.cryfs.org/version_info.json", versionInfo); } private: unique_ref _http = make_unique_ref(); }; TEST_F(VersionCheckerTest, NewestVersion_NoInternet) { EXPECT_EQ(none, versionChecker()->newestVersion()); } TEST_F(VersionCheckerTest, SecurityWarningFor_NoInternet) { EXPECT_EQ(none, versionChecker()->securityWarningFor("0.8")); } TEST_F(VersionCheckerTest, NewestVersion_NoWarnings_1) { setVersionInfo("{\"version_info\":{\"current\":\"0.8.2\"}}"); EXPECT_EQ("0.8.2", versionChecker()->newestVersion().value()); } TEST_F(VersionCheckerTest, NewestVersion_NoWarnings_2) { setVersionInfo("{\"version_info\":{\"current\":\"0.8.3\"}}"); EXPECT_EQ("0.8.3", versionChecker()->newestVersion().value()); } TEST_F(VersionCheckerTest, NewestVersion_EmptyWarnings) { setVersionInfo("{\"version_info\":{\"current\":\"0.8.2\"},\"warnings\":{}}"); EXPECT_EQ("0.8.2", versionChecker()->newestVersion().value()); } TEST_F(VersionCheckerTest, NewestVersion_WarningsOtherVersion) { setVersionInfo("{\"version_info\":{\"current\":\"0.8.2\"},\"warnings\":{\"0.8.1\": \"warning\"}}"); EXPECT_EQ("0.8.2", versionChecker()->newestVersion().value()); } TEST_F(VersionCheckerTest, NewestVersion_WarningsSameVersion) { setVersionInfo("{\"version_info\":{\"current\":\"0.8.2\"},\"warnings\":{\"0.8.2\": \"warning\"}}"); EXPECT_EQ("0.8.2", versionChecker()->newestVersion().value()); } TEST_F(VersionCheckerTest, NewestVersion_WarningsSameAndOtherVersion) { setVersionInfo("{\"version_info\":{\"current\":\"0.8.2\"},\"warnings\":{\"0.8.1\": \"warning1\", \"0.8.2\": \"warning2\", \"0.8.3\": \"warning3\"}}"); EXPECT_EQ("0.8.2", versionChecker()->newestVersion().value()); } TEST_F(VersionCheckerTest, NewestVersion_BlankVersionInfo) { setVersionInfo(""); EXPECT_EQ(none, versionChecker()->newestVersion()); } TEST_F(VersionCheckerTest, NewestVersion_EmptyVersionInfo) { setVersionInfo("{}"); EXPECT_EQ(none, versionChecker()->newestVersion()); } TEST_F(VersionCheckerTest, NewestVersion_InvalidVersionInfo) { setVersionInfo("invalid-json"); EXPECT_EQ(none, versionChecker()->newestVersion()); } TEST_F(VersionCheckerTest, NewestVersion_MissingKey) { setVersionInfo("{\"warnings\":{}"); EXPECT_EQ(none, versionChecker()->newestVersion()); } TEST_F(VersionCheckerTest, SecurityWarningFor_NoWarnings) { setVersionInfo("{\"version_info\":{\"current\":\"0.8.2\"}}"); EXPECT_EQ(none, versionChecker()->securityWarningFor("0.8.2")); } TEST_F(VersionCheckerTest, SecurityWarningFor_EmptyWarnings) { setVersionInfo("{\"version_info\":{\"current\":\"0.8.2\"},\"warnings\":{}}"); EXPECT_EQ(none, versionChecker()->securityWarningFor("0.8.2")); } TEST_F(VersionCheckerTest, SecurityWarningFor_WarningsOtherVersion) { setVersionInfo("{\"version_info\":{\"current\":\"0.8.2\"},\"warnings\":{\"0.8.1\": \"warning\"}}"); EXPECT_EQ(none, versionChecker()->securityWarningFor("0.8.2")); } TEST_F(VersionCheckerTest, SecurityWarningFor_WarningsSameVersion) { setVersionInfo("{\"version_info\":{\"current\":\"0.8.2\"},\"warnings\":{\"0.8.2\": \"warning\"}}"); EXPECT_EQ("warning", versionChecker()->securityWarningFor("0.8.2").value()); } TEST_F(VersionCheckerTest, SecurityWarningFor_WarningsSameAndOtherVersion) { setVersionInfo("{\"version_info\":{\"current\":\"0.8.2\"},\"warnings\":{\"0.8.1\": \"warning1\", \"0.8.2\": \"warning2\", \"0.8.3\": \"warning3\"}}"); EXPECT_EQ("warning2", versionChecker()->securityWarningFor("0.8.2").value()); } TEST_F(VersionCheckerTest, SecurityWarningFor_BlankVersionInfo) { setVersionInfo(""); EXPECT_EQ(none, versionChecker()->securityWarningFor("0.8.2")); } TEST_F(VersionCheckerTest, SecurityWarningFor_EmptyVersionInfo) { setVersionInfo("{}"); EXPECT_EQ(none, versionChecker()->securityWarningFor("0.8.2")); } TEST_F(VersionCheckerTest, SecurityWarningFor_InvalidVersionInfo) { setVersionInfo("invalid-json"); EXPECT_EQ(none, versionChecker()->securityWarningFor("0.8.2")); } test/cryfs-cli/program_options/000077500000000000000000000000001347701267100171625ustar00rootroot00000000000000test/cryfs-cli/program_options/ParserTest.cpp000066400000000000000000000251671347701267100217750ustar00rootroot00000000000000#include "testutils/ProgramOptionsTestBase.h" #include #include #include #include #include #include using namespace cryfs; using namespace cryfs_cli::program_options; using std::vector; using std::string; using boost::none; namespace bf = boost::filesystem; using cpputils::CaptureStderrRAII; #if !defined(_MSC_VER) constexpr const char *basedir = "/home/user/baseDir"; constexpr const char *mountdir = "/home/user/mountDir"; constexpr const char *logfile = "/home/user/logfile"; constexpr const char *configfile = "/home/user/configfile"; #else constexpr const char *basedir = "C:\\basedir"; constexpr const char *mountdir = "C:\\mountdir"; constexpr const char *logfile = "C:\\logfile"; constexpr const char *configfile = "C:\\configfile"; #endif class ProgramOptionsParserTest: public ProgramOptionsTestBase { public: ProgramOptions parse(std::initializer_list options) { vector _options = options; return Parser(_options.size(), _options.data()).parse(CryCiphers::supportedCipherNames()); } }; TEST_F(ProgramOptionsParserTest, MissingAllOptions) { CaptureStderrRAII captureStderr; try { parse({"./myExecutable"}); EXPECT_TRUE(false); // expect throws } catch (const CryfsException& e) { EXPECT_EQ(ErrorCode::InvalidArguments, e.errorCode()); captureStderr.EXPECT_MATCHES("Usage:"); // expect show usage information } } TEST_F(ProgramOptionsParserTest, MissingDir) { CaptureStderrRAII captureStderr; try { parse({"./myExecutable", basedir}); EXPECT_TRUE(false); // expect throw } catch (const CryfsException& e) { EXPECT_EQ(ErrorCode::InvalidArguments, e.errorCode()); captureStderr.EXPECT_MATCHES("Usage:"); // expect show usage information } } TEST_F(ProgramOptionsParserTest, HelpLongOption) { CaptureStderrRAII captureStderr; try { parse({"./myExecutable", "--help"}); EXPECT_TRUE(false); // expect throw } catch (const CryfsException& e) { EXPECT_EQ(ErrorCode::Success, e.errorCode()); captureStderr.EXPECT_MATCHES("Usage:"); // expect show usage information } } TEST_F(ProgramOptionsParserTest, HelpShortOption) { CaptureStderrRAII captureStderr; try { parse({"./myExecutable", "-h"}); EXPECT_TRUE(false); // expect throw } catch (const CryfsException& e) { EXPECT_EQ(ErrorCode::Success, e.errorCode()); captureStderr.EXPECT_MATCHES("Usage:"); // expect show usage information } } TEST_F(ProgramOptionsParserTest, ShowCiphers) { CaptureStderrRAII captureStderr; try { parse({"./myExecutable", "--show-ciphers"}); EXPECT_TRUE(false); // expect throw } catch (const CryfsException& e) { EXPECT_EQ(ErrorCode::Success, e.errorCode()); captureStderr.EXPECT_MATCHES("aes-256-gcm"); // expect show ciphers } } TEST_F(ProgramOptionsParserTest, BaseDir_Absolute) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir}); EXPECT_EQ(basedir, options.baseDir()); } TEST_F(ProgramOptionsParserTest, Basedir_Relative) { ProgramOptions options = parse({"./myExecutable", "baseDir", mountdir}); EXPECT_EQ(bf::current_path() / "baseDir", options.baseDir()); } TEST_F(ProgramOptionsParserTest, MountDir_Absolute) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir}); EXPECT_EQ(mountdir, options.mountDir()); } TEST_F(ProgramOptionsParserTest, MountDir_Relative) { ProgramOptions options = parse({"./myExecutable", basedir, "mountDir"}); EXPECT_EQ(bf::current_path() / "mountDir", options.mountDir()); } TEST_F(ProgramOptionsParserTest, Foreground_False) { ProgramOptions options = parse({"./myExecutable", basedir, "mountdir"}); EXPECT_FALSE(options.foreground()); } TEST_F(ProgramOptionsParserTest, Foreground_True) { ProgramOptions options = parse({"./myExecutable", "-f", basedir, "mountdir"}); EXPECT_TRUE(options.foreground()); } TEST_F(ProgramOptionsParserTest, AllowFilesystemUpgrade_False) { ProgramOptions options = parse({"./myExecutable", basedir, "mountdir"}); EXPECT_FALSE(options.allowFilesystemUpgrade()); } TEST_F(ProgramOptionsParserTest, AllowFilesystemUpgrade_True) { ProgramOptions options = parse({"./myExecutable", "--allow-filesystem-upgrade", basedir, "mountdir"}); EXPECT_TRUE(options.allowFilesystemUpgrade()); } TEST_F(ProgramOptionsParserTest, LogfileGiven) { ProgramOptions options = parse({"./myExecutable", basedir, "--logfile", logfile, mountdir}); EXPECT_EQ(logfile, options.logFile().value()); } TEST_F(ProgramOptionsParserTest, LogfileGiven_RelativePath) { ProgramOptions options = parse({"./myExecutable", basedir, "--logfile", "mylogfile", mountdir}); EXPECT_EQ(bf::current_path() / "mylogfile", options.logFile().value()); } TEST_F(ProgramOptionsParserTest, LogfileNotGiven) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir}); EXPECT_EQ(none, options.logFile()); } TEST_F(ProgramOptionsParserTest, ConfigfileGiven) { ProgramOptions options = parse({"./myExecutable", basedir, "--config", configfile, mountdir}); EXPECT_EQ(configfile, options.configFile().value()); } TEST_F(ProgramOptionsParserTest, ConfigfileGiven_RelativePath) { ProgramOptions options = parse({"./myExecutable", basedir, "--config", "myconfigfile", mountdir}); EXPECT_EQ(bf::current_path() / "myconfigfile", options.configFile().value()); } TEST_F(ProgramOptionsParserTest, ConfigfileNotGiven) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir}); EXPECT_EQ(none, options.configFile()); } TEST_F(ProgramOptionsParserTest, CipherGiven) { ProgramOptions options = parse({"./myExecutable", basedir, "--cipher", "aes-256-gcm", mountdir}); EXPECT_EQ("aes-256-gcm", options.cipher().value()); } TEST_F(ProgramOptionsParserTest, CipherNotGiven) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir}); EXPECT_EQ(none, options.cipher()); } TEST_F(ProgramOptionsParserTest, InvalidCipher) { try { parse({"./myExecutable", basedir, "--cipher", "invalid-cipher", mountdir}); EXPECT_TRUE(false); // expect throw } catch (const CryfsException& e) { EXPECT_EQ(ErrorCode::InvalidArguments, e.errorCode()); EXPECT_THAT(e.what(), testing::MatchesRegex(".*Invalid cipher: invalid-cipher.*")); } } TEST_F(ProgramOptionsParserTest, UnmountAfterIdleMinutesGiven) { ProgramOptions options = parse({"./myExecutable", basedir, "--unmount-idle", "10", mountdir}); EXPECT_EQ(10, options.unmountAfterIdleMinutes().value()); } TEST_F(ProgramOptionsParserTest, UnmountAfterIdleMinutesGiven_Float) { ProgramOptions options = parse({"./myExecutable", basedir, "--unmount-idle", "0.5", mountdir}); EXPECT_EQ(0.5, options.unmountAfterIdleMinutes().value()); } TEST_F(ProgramOptionsParserTest, UnmountAfterIdleMinutesNotGiven) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir}); EXPECT_EQ(none, options.unmountAfterIdleMinutes()); } TEST_F(ProgramOptionsParserTest, BlocksizeGiven) { ProgramOptions options = parse({"./myExecutable", basedir, "--blocksize", "10240", mountdir}); EXPECT_EQ(10240u, options.blocksizeBytes().value()); } TEST_F(ProgramOptionsParserTest, BlocksizeNotGiven) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir}); EXPECT_EQ(none, options.blocksizeBytes()); } TEST_F(ProgramOptionsParserTest, MissingBlockIsIntegrityViolationGiven_True) { ProgramOptions options = parse({"./myExecutable", basedir, "--missing-block-is-integrity-violation", "true", mountdir}); EXPECT_TRUE(options.missingBlockIsIntegrityViolation().value()); } TEST_F(ProgramOptionsParserTest, MissingBlockIsIntegrityViolationGiven_False) { ProgramOptions options = parse({"./myExecutable", basedir, "--missing-block-is-integrity-violation", "false", mountdir}); EXPECT_FALSE(options.missingBlockIsIntegrityViolation().value()); } TEST_F(ProgramOptionsParserTest, AllowIntegrityViolations_True) { ProgramOptions options = parse({"./myExecutable", basedir, "--allow-integrity-violations", mountdir}); EXPECT_TRUE(options.allowIntegrityViolations()); } TEST_F(ProgramOptionsParserTest, AllowIntegrityViolations_False) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir}); EXPECT_FALSE(options.allowIntegrityViolations()); } TEST_F(ProgramOptionsParserTest, MissingBlockIsIntegrityViolationNotGiven) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir}); EXPECT_EQ(none, options.missingBlockIsIntegrityViolation()); } TEST_F(ProgramOptionsParserTest, FuseOptionGiven) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir, "--", "-f"}); EXPECT_EQ(basedir, options.baseDir()); EXPECT_EQ(mountdir, options.mountDir()); EXPECT_VECTOR_EQ({"-f"}, options.fuseOptions()); } TEST_F(ProgramOptionsParserTest, FuseOptionGiven_Empty) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir, "--"}); EXPECT_EQ(basedir, options.baseDir()); EXPECT_EQ(mountdir, options.mountDir()); EXPECT_VECTOR_EQ({}, options.fuseOptions()); } TEST_F(ProgramOptionsParserTest, FuseOptionNotGiven) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir}); EXPECT_EQ(basedir, options.baseDir()); EXPECT_EQ(mountdir, options.mountDir()); EXPECT_VECTOR_EQ({}, options.fuseOptions()); } TEST_F(ProgramOptionsParserTest, DirectFuseOptionsGiven_AfterPositionalOptions) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir, "-o", "my_opt"}); EXPECT_VECTOR_EQ({"-o", "my_opt"}, options.fuseOptions()); } TEST_F(ProgramOptionsParserTest, DirectFuseOptionsGiven_BeforePositionalOptions) { ProgramOptions options = parse({"./myExecutable", "-o", "my_opt", basedir, mountdir}); EXPECT_VECTOR_EQ({"-o", "my_opt"}, options.fuseOptions()); } TEST_F(ProgramOptionsParserTest, DirectFuseOptionsGiven_BeforeAndAfterPositionalOptions) { ProgramOptions options = parse({"./myExecutable", "-o", "first", "-o", "second", basedir, "-o", "third", "-o", "fourth", mountdir, "-o", "fifth", "-o", "sixth"}); EXPECT_VECTOR_EQ({"-o", "first", "-o", "second", "-o", "third", "-o", "fourth", "-o", "fifth", "-o", "sixth"}, options.fuseOptions()); } TEST_F(ProgramOptionsParserTest, DirectAndIndirectFuseOptionsGiven) { ProgramOptions options = parse({"./myExecutable", basedir, mountdir, "-o", "my_opt", "--", "-o", "other_opt"}); EXPECT_VECTOR_EQ({"-o", "other_opt", "-o", "my_opt"}, options.fuseOptions()); } test/cryfs-cli/program_options/ProgramOptionsTest.cpp000066400000000000000000000140761347701267100235210ustar00rootroot00000000000000#include "testutils/ProgramOptionsTestBase.h" #include #include using namespace cryfs_cli::program_options; using boost::none; using boost::optional; using std::ostream; using std::string; namespace bf = boost::filesystem; // This is needed for google test to work with boost::optional namespace boost { template<> inline ostream& operator<< , bf::path>(ostream &stream, const optional &path) { if (path == none) { return stream << "none"; } return stream << *path; } } class ProgramOptionsTest: public ProgramOptionsTestBase {}; TEST_F(ProgramOptionsTest, BaseDir) { ProgramOptions testobj("/home/user/mydir", "", none, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ("/home/user/mydir", testobj.baseDir()); } TEST_F(ProgramOptionsTest, MountDir) { ProgramOptions testobj("", "/home/user/mydir", none, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ("/home/user/mydir", testobj.mountDir()); } TEST_F(ProgramOptionsTest, ConfigfileNone) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(none, testobj.configFile()); } TEST_F(ProgramOptionsTest, ConfigfileSome) { ProgramOptions testobj("", "", bf::path("/home/user/configfile"), true, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ("/home/user/configfile", testobj.configFile().get()); } TEST_F(ProgramOptionsTest, ForegroundFalse) { ProgramOptions testobj("", "", none, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_FALSE(testobj.foreground()); } TEST_F(ProgramOptionsTest, ForegroundTrue) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_TRUE(testobj.foreground()); } TEST_F(ProgramOptionsTest, AllowFilesystemUpgradeFalse) { ProgramOptions testobj("", "", none, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_FALSE(testobj.allowFilesystemUpgrade()); } TEST_F(ProgramOptionsTest, AllowFilesystemUpgradeTrue) { ProgramOptions testobj("", "", none, false, true, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_TRUE(testobj.allowFilesystemUpgrade()); } TEST_F(ProgramOptionsTest, LogfileNone) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(none, testobj.logFile()); } TEST_F(ProgramOptionsTest, LogfileSome) { ProgramOptions testobj("", "", none, true, false, false, none, bf::path("logfile"), none, none, false, none, {"./myExecutable"}); EXPECT_EQ("logfile", testobj.logFile().get()); } TEST_F(ProgramOptionsTest, UnmountAfterIdleMinutesNone) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(none, testobj.unmountAfterIdleMinutes()); } TEST_F(ProgramOptionsTest, UnmountAfterIdleMinutesSome) { ProgramOptions testobj("", "", none, true, false, false, 10, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(10, testobj.unmountAfterIdleMinutes().get()); } TEST_F(ProgramOptionsTest, CipherNone) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(none, testobj.cipher()); } TEST_F(ProgramOptionsTest, CipherSome) { ProgramOptions testobj("", "", none, true, false, false, none, none, string("aes-256-gcm"), none, false, none, {"./myExecutable"}); EXPECT_EQ("aes-256-gcm", testobj.cipher().get()); } TEST_F(ProgramOptionsTest, BlocksizeBytesNone) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(none, testobj.blocksizeBytes()); } TEST_F(ProgramOptionsTest, BlocksizeBytesSome) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, 10*1024, false, none, {"./myExecutable"}); EXPECT_EQ(10*1024u, testobj.blocksizeBytes().get()); } TEST_F(ProgramOptionsTest, MissingBlockIsIntegrityViolationTrue) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, none, false, true, {"./myExecutable"}); EXPECT_TRUE(testobj.missingBlockIsIntegrityViolation().value()); } TEST_F(ProgramOptionsTest, MissingBlockIsIntegrityViolationFalse) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, none, false, false, {"./myExecutable"}); EXPECT_FALSE(testobj.missingBlockIsIntegrityViolation().value()); } TEST_F(ProgramOptionsTest, MissingBlockIsIntegrityViolationNone) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(none, testobj.missingBlockIsIntegrityViolation()); } TEST_F(ProgramOptionsTest, AllowIntegrityViolationsFalse) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_FALSE(testobj.allowIntegrityViolations()); } TEST_F(ProgramOptionsTest, AllowIntegrityViolationsTrue) { ProgramOptions testobj("", "", none, true, false, false, none, none, none, none, true, none, {"./myExecutable"}); EXPECT_TRUE(testobj.allowIntegrityViolations()); } TEST_F(ProgramOptionsTest, EmptyFuseOptions) { ProgramOptions testobj("/rootDir", "/home/user/mydir", none, false, false, false, none, none, none, none, false, none, {}); //Fuse should have the mount dir as first parameter EXPECT_VECTOR_EQ({}, testobj.fuseOptions()); } TEST_F(ProgramOptionsTest, SomeFuseOptions) { ProgramOptions testobj("/rootDir", "/home/user/mydir", none, false, false, false, none, none, none, none, false, none, {"-f", "--longoption"}); //Fuse should have the mount dir as first parameter EXPECT_VECTOR_EQ({"-f", "--longoption"}, testobj.fuseOptions()); } test/cryfs-cli/program_options/UtilsTest.cpp000066400000000000000000000126761347701267100216420ustar00rootroot00000000000000#include "testutils/ProgramOptionsTestBase.h" #include using namespace cryfs_cli::program_options; using std::pair; using std::vector; using std::string; class ProgramOptionsUtilsTest: public ProgramOptionsTestBase {}; TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_ZeroOptions) { vector input = {"./executableName"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName"}, result.first); EXPECT_VECTOR_EQ({}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_OneShortOption) { vector input = {"./executableName", "-j"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName", "-j"}, result.first); EXPECT_VECTOR_EQ({}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_OneLongOption) { vector input = {"./executableName", "--myoption"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName", "--myoption"}, result.first); EXPECT_VECTOR_EQ({}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_OnePositionalOption) { vector input = {"./executableName", "mypositionaloption"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName", "mypositionaloption"}, result.first); EXPECT_VECTOR_EQ({}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_OneShortOption_DoubleDash) { vector input = {"./executableName", "-j", "--"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName", "-j"}, result.first); EXPECT_VECTOR_EQ({}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_OneLongOption_DoubleDash) { vector input = {"./executableName", "--myoption", "--"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName", "--myoption"}, result.first); EXPECT_VECTOR_EQ({}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_OnePositionalOption_DoubleDash) { vector input = {"./executableName", "mypositionaloption", "--"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName", "mypositionaloption"}, result.first); EXPECT_VECTOR_EQ({}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_DoubleDash_OneShortOption) { vector input = {"./executableName", "--", "-a"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName"}, result.first); EXPECT_VECTOR_EQ({"-a"}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_DoubleDash_OneLongOption) { vector input = {"./executableName", "--", "--myoption"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName"}, result.first); EXPECT_VECTOR_EQ({"--myoption"}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_DoubleDash_OnePositionalOption) { vector input = {"./executableName", "--", "mypositionaloption"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName"}, result.first); EXPECT_VECTOR_EQ({"mypositionaloption"}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_OneShortOption_DoubleDash_OneShortOption) { vector input = {"./executableName", "-j", "--", "-a"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName", "-j"}, result.first); EXPECT_VECTOR_EQ({"-a"}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_OneLongOption_DoubleDash_OneLongOption) { vector input = {"./executableName", "--myoption", "--", "--myotheroption"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName", "--myoption"}, result.first); EXPECT_VECTOR_EQ({"--myotheroption"}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_OnePositionalOption_DoubleDash_OnePositionalOption) { vector input = {"./executableName", "mypositionaloption", "--", "otherpositionaloption"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName", "mypositionaloption"}, result.first); EXPECT_VECTOR_EQ({"otherpositionaloption"}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_MoreOptions) { vector input = {"./executableName", "mypositionaloption", "myotherpositionaloption", "-j", "--alpha", "--", "filename", "--beta", "-j3"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName", "mypositionaloption", "myotherpositionaloption", "-j", "--alpha"}, result.first); EXPECT_VECTOR_EQ({"filename", "--beta", "-j3"}, result.second); } TEST_F(ProgramOptionsUtilsTest, SplitAtDoubleDash_RealisticCryfsOptions) { vector input = {"./executableName", "rootDir", "mountDir", "--", "-f"}; pair,vector> result = splitAtDoubleDash(input); EXPECT_VECTOR_EQ({"./executableName", "rootDir", "mountDir"}, result.first); EXPECT_VECTOR_EQ({"-f"}, result.second); } test/cryfs-cli/program_options/testutils/000077500000000000000000000000001347701267100212225ustar00rootroot00000000000000test/cryfs-cli/program_options/testutils/ProgramOptionsTestBase.h000066400000000000000000000011131347701267100260050ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_TEST_PROGRAMOPTIONS_PROGRAMOPTIONSTEST_H #define MESSMER_CRYFS_TEST_PROGRAMOPTIONS_PROGRAMOPTIONSTEST_H #include class ProgramOptionsTestBase: public ::testing::Test { public: void EXPECT_VECTOR_EQ(std::initializer_list expected, const std::vector &actual) { std::vector expectedVec(expected); ASSERT_EQ(expectedVec.size(), actual.size()); for(size_t i = 0; i < expectedVec.size(); ++i) { EXPECT_EQ(expectedVec[i], actual[i]); } } }; #endif test/cryfs-cli/testutils/000077500000000000000000000000001347701267100160005ustar00rootroot00000000000000test/cryfs-cli/testutils/CliTest.cpp000066400000000000000000000000251347701267100200500ustar00rootroot00000000000000#include "CliTest.h" test/cryfs-cli/testutils/CliTest.h000066400000000000000000000157431347701267100175320ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_TEST_CLI_TESTUTILS_CLITEST_H #define MESSMER_CRYFS_TEST_CLI_TESTUTILS_CLITEST_H #if defined(_MSC_VER) #include #include #endif #include #include #include #include #include #include #include #include #include #include #include "../../cryfs/testutils/MockConsole.h" #include "../../cryfs/testutils/TestWithFakeHomeDirectory.h" #include #include #include #include #include class CliTest : public ::testing::Test, TestWithFakeHomeDirectory { public: CliTest(): _basedir(), _mountdir(), basedir(_basedir.path()), mountdir(_mountdir.path()), logfile(), configfile(false), console(std::make_shared()) {} cpputils::TempDir _basedir; cpputils::TempDir _mountdir; boost::filesystem::path basedir; boost::filesystem::path mountdir; cpputils::TempFile logfile; cpputils::TempFile configfile; std::shared_ptr console; cpputils::unique_ref _httpClient() { cpputils::unique_ref httpClient = cpputils::make_unique_ref(); httpClient->addWebsite("https://www.cryfs.org/version_info.json", "{\"version_info\":{\"current\":\"0.8.5\"}}"); return httpClient; } int run(const std::vector& args, std::function onMounted) { std::vector _args; _args.reserve(args.size() + 1); _args.emplace_back("cryfs"); for (const std::string& arg : args) { _args.emplace_back(arg.c_str()); } auto &keyGenerator = cpputils::Random::PseudoRandom(); ON_CALL(*console, askPassword(testing::StrEq("Password: "))).WillByDefault(testing::Return("pass")); ON_CALL(*console, askPassword(testing::StrEq("Confirm Password: "))).WillByDefault(testing::Return("pass")); // Run Cryfs return cryfs_cli::Cli(keyGenerator, cpputils::SCrypt::TestSettings, console).main(_args.size(), _args.data(), _httpClient(), std::move(onMounted)); } void EXPECT_EXIT_WITH_HELP_MESSAGE(const std::vector& args, const std::string &message, cryfs::ErrorCode errorCode) { EXPECT_RUN_ERROR(args, "Usage:[^\\x00]*"+message, errorCode); } void EXPECT_RUN_ERROR(const std::vector& args, const std::string& message, cryfs::ErrorCode errorCode, std::function onMounted = [] {}) { FilesystemOutput filesystem_output = run_filesystem(args, boost::none, std::move(onMounted)); EXPECT_EQ(exitCode(errorCode), filesystem_output.exit_code); if (!std::regex_search(filesystem_output.stderr_, std::regex(message))) { std::cerr << filesystem_output.stderr_ << std::endl; EXPECT_TRUE(false); } } void EXPECT_RUN_SUCCESS(const std::vector& args, const boost::optional &mountDir, std::function onMounted = [] {}) { //TODO Make this work when run in background ASSERT(std::find(args.begin(), args.end(), string("-f")) != args.end(), "Currently only works if run in foreground"); FilesystemOutput filesystem_output = run_filesystem(args, mountDir, std::move(onMounted)); EXPECT_EQ(0, filesystem_output.exit_code); if (!std::regex_search(filesystem_output.stdout_, std::regex("Mounting filesystem"))) { std::cerr << filesystem_output.stdout_ << std::endl; EXPECT_TRUE(false); } } struct FilesystemOutput final { int exit_code; std::string stdout_; std::string stderr_; }; static void _unmount(const boost::filesystem::path &mountDir) { fspp::fuse::Fuse::unmount(mountDir, true); } FilesystemOutput run_filesystem(const std::vector& args, boost::optional mountDirForUnmounting, std::function onMounted) { testing::internal::CaptureStdout(); testing::internal::CaptureStderr(); bool exited = false; cpputils::ConditionBarrier isMountedOrFailedBarrier; std::future exit_code = std::async(std::launch::async, [&] { int exit_code = run(args, [&] { isMountedOrFailedBarrier.release(); }); // just in case it fails, we also want to release the barrier. // if it succeeds, this will release it a second time, which doesn't hurt. exited = true; isMountedOrFailedBarrier.release(); return exit_code; }); std::future on_mounted_success = std::async(std::launch::async, [&] { isMountedOrFailedBarrier.wait(); if (exited) { // file system already exited on its own, this indicates an error. It should have stayed mounted. // while the exit_code from run() will signal an error in this case, we didn't encounter another // error in the onMounted future, so return true here. return true; } // now we know the filesystem stayed online, so we can call the onMounted callback onMounted(); // and unmount it afterwards if (mountDirForUnmounting.is_initialized()) { _unmount(*mountDirForUnmounting); } return true; }); if(std::future_status::ready != on_mounted_success.wait_for(std::chrono::seconds(1000))) { testing::internal::GetCapturedStdout(); // stop capturing stdout testing::internal::GetCapturedStderr(); // stop capturing stderr std::cerr << "onMounted thread (e.g. used for unmount) didn't finish" << std::endl; // The std::future destructor of a future created with std::async blocks until the future is ready. // so, instead of causing a deadlock, rather abort exit(EXIT_FAILURE); } EXPECT_TRUE(on_mounted_success.get()); // this also re-throws any potential exceptions if(std::future_status::ready != exit_code.wait_for(std::chrono::seconds(1000))) { testing::internal::GetCapturedStdout(); // stop capturing stdout testing::internal::GetCapturedStderr(); // stop capturing stderr std::cerr << "Filesystem thread didn't finish" << std::endl; // The std::future destructor of a future created with std::async blocks until the future is ready. // so, instead of causing a deadlock, rather abort exit(EXIT_FAILURE); } return { exit_code.get(), // this also re-throws any potential exceptions testing::internal::GetCapturedStdout(), testing::internal::GetCapturedStderr() }; } }; #endif test/cryfs/000077500000000000000000000000001347701267100131735ustar00rootroot00000000000000test/cryfs/CMakeLists.txt000066400000000000000000000020571347701267100157370ustar00rootroot00000000000000project (cryfs-test) set(SOURCES config/crypto/CryConfigEncryptorFactoryTest.cpp config/crypto/outer/OuterConfigTest.cpp config/crypto/outer/OuterEncryptorTest.cpp config/crypto/inner/ConcreteInnerEncryptorTest.cpp config/crypto/inner/InnerConfigTest.cpp config/crypto/CryConfigEncryptorTest.cpp config/CompatibilityTest.cpp config/CryConfigCreatorTest.cpp config/CryConfigFileTest.cpp config/CryConfigTest.cpp config/CryCipherTest.cpp config/CryConfigLoaderTest.cpp config/CryConfigConsoleTest.cpp config/CryPasswordBasedKeyProviderTest.cpp config/CryPresetPasswordBasedKeyProviderTest.cpp filesystem/CryFsTest.cpp filesystem/CryNodeTest.cpp filesystem/FileSystemTest.cpp localstate/LocalStateMetadataTest.cpp localstate/BasedirMetadataTest.cpp ) add_executable(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} my-gtest-main googletest cryfs) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) test/cryfs/config/000077500000000000000000000000001347701267100144405ustar00rootroot00000000000000test/cryfs/config/CompatibilityTest.cpp000066400000000000000000000154031347701267100206200ustar00rootroot00000000000000#include "cpp-utils/crypto/cryptopp_byte.h" #include #include #include #include #include #include #include #include #include #include "../testutils/MockConsole.h" using cpputils::Data; using cpputils::AES256_GCM; using cpputils::Serpent128_CFB; using cpputils::TempFile; using cpputils::make_unique_ref; using cpputils::SCrypt; using namespace cryfs; // Test that config files created with (old) versions of cryfs are still loadable. // The config files created with these cryfs versions are included in the test cases in hex code. // To make tests run fast, the config files should be created using SCrypt::TestSettings. class CryConfigCompatibilityTest: public ::testing::Test { public: TempFile file; CryConfigFile loadConfigFromHex(const string &configFileContentHex) { storeHexToFile(configFileContentHex); CryPresetPasswordBasedKeyProvider keyProvider("mypassword", make_unique_ref(SCrypt::DefaultSettings)); return CryConfigFile::load(file.path(), &keyProvider).value(); } private: void storeHexToFile(const string &hex) { hexToBinary(hex).StoreToFile(file.path()); } Data hexToBinary(const string &hex) { ASSERT(hex.size()%2 == 0, "Hex codes need to have two characters per byte"); Data result(hex.size()/2); { CryptoPP::StringSource _1(hex, true, new CryptoPP::HexDecoder( new CryptoPP::ArraySink(static_cast(result.data()), result.size()) ) ); } return result; } }; #ifndef CRYFS_NO_COMPATIBILITY TEST_F(CryConfigCompatibilityTest, v0_8_1_with_aes_256_gcm) { auto config = loadConfigFromHex("63727966732e636f6e6669673b303b736372797074000004000000000000010000000100000020000000000000000af023e55f804ad303c1dbdf6a2b2bed8cc1ab3d0b2f3312c073628dc041e6f3b92f54fad63a1d4e0ad47a33e65e08080dd4e5bdec0a95fe777705ad68e88eabcb4b91d4f25c32e3e44d6e893421c9efa6d4b2c56d66d0546a410de489b04160b276184ebe7dd77840ce6e414f829bb4399451e055d3406261b4faf5fb52f27e21823f8073df255f96b37edf2c2e58ecee21ae82e3883341024aff326cc4c0929f0ed90473511cbe801d1e34899b3cdb9556477be0127c35375967bf7c8392ad4d30d81479c7923900f532b195b71cff868fa1643d1f9e0a0d7996260691d7b5b8ef3c319c2f2303f822eac389e1d6822e7ee57df60c922fa10fbc7e5970e723df2fdb2a6529c94ea1d2507f55cb9bc3fecab77bf3102e96c6675a618b54bb18e632dd99d3d47fa9e24392535abc8c76f891df76c2193233d6c93851966f69def3f53108be83b30980c98e377186da71297a1b61df76e9f138a3998577a0c486452f1b6a2d89b1e62d78695ac6a062d5a4cf5972de7bc2775fd27d25337f0666250f19d6fe50fe762371befa26de6a9299d7df362780f59ccc475e28c71cc5af9e817e4e2160005393ec7fb385d467e0915e7169934ea986141101c9e6b9c62b8819c51a4422e7fe58e24c3293c3f12173a4c480334cb67c9313df667c65802fb778e20f5a4d6cfd07fffb35c2ae8fdf235882bf0e6371125509872e73bd1bc3faca72948db76d8179e08bf3391bafdede591d5623240ba072ada17c2444116bf8c08ee32367eb86eda02ecfecc625b4bc974428b8f5024df430c352b9849f48774bac1455e7364f7660abc039d2a1134cde596a1e7c3a1a8d68591e8640a2d308e2ea5f03874ba1fa8e9ffec2d1bc1541d124adcd3e9f4ed23343e2148629317e6304c63c2a5ceeeb572246393c8e50196728e0b604aa9e2d49e65680cc53ee343645412239f82500ef3d70a6b6e3335ef8cbcfe4fd46b84f634f6fe96cd1b79207a2120ac2bad668bcdbc377b98448c9afbbda9fe8164dbb48aaa68b0aaa90e52bc419ea2ea1f5e12bb649831af12f1ef14765f312640e22c5508531bd46e3ab3a7f922a91fb83332104609e385d61d3778bc5acb03bee522ea5b791d27026fd589ec2b32f04caaf2ef2effbd50e12430ef42631fdabd061966fde1489b9a1eff3fe40b295d75e36af19ae0e69559b9c81b3bc47b35fef5c7038b83db6729aac522efee897a44895804ffbeb11317a78586f8a3511905514eee86d7cf0a395f5f0af521c72542e544ff359a69150a5e64f00b020d48c20d5716331c3b0ac314eeceaadf8b73516b4273b48046b5c3a3980bade0cc25baf54afb27755d155d4b94fa556deb30d58b1d920cbe01b13479135ff84d6a9345a0d418075ecc66bd10d8d2a07963450256897018f54b2a88fffe9383913565823dbfb71568f5d9af93e542937ecde25c2d4407ad964ece5c09a1f81a57c18a8bb2aa8d9d062415903c98752d70ad7175d0d30b391da77459e9a68a69825b3bb4061bc2504789fc395"); EXPECT_EQ(AES256_GCM::NAME, config.config()->Cipher()); EXPECT_EQ("4A70AD8AC565A5123586D0928B496465", config.config()->RootBlob()); } TEST_F(CryConfigCompatibilityTest, v0_8_1_with_serpent_128_cfb) { auto config = loadConfigFromHex("63727966732e636f6e6669673b303b736372797074000004000000000000010000000100000020000000000000006496611b94011d03433181e83e80c19888a5607392c2117cd377251978fbbf8d46716bd90dbae3c1728b40e7dc086ee97b8a641f1ea6cc31ccda9a1e4a7a1bf4b6ca3d1d0075acba94017c034e87dfad048c9448df24ea7b89b14d60c9f9e0162cfeb420a85759aaa011232cff99e81b25c456cb13690d5cc735e61ef9b01800961a90488073c5320c14a562f0eccd32383d144476d72d9c61733fb3561f5187c2d6069ce7ff4c43df4fc3f7c1d63df239d05a2b377f70816fb31f7a65d8fa667cd14b62a67e2d67adbf841860af2abed9d4c839c510c7fac7cd58210238830a4926917d663500dd3dc2395845297b3726c00228cb08a2bde648d2e9bc2a3e8655f86bc2f5c2b327e71c75b92894dd43b56dfefeeea598fbae9782dca64cc9676df3a954a9ecedbe3f8fe16e790aeec2ac2336984c8fcce74bfebf310d6fdcc0882ee7f2bdd32928aa53b09084e8035ec235ebe971a0b141e5fcab6ce2207f0f399e683ff4994ed638194cc7d3891019a2a674d14180114494e21270e4234a51e28661d4ac908d4f2b2d7c94ffcb74df8c9f08fb68d9a7167a05fae370e041689cce5fb13fdf3ff14863f6c882e6d302eafe5348362c026853a3717756cefb4657a721efab95f6d949b508a98de86885fe37b2f16f1792411ff47c1074e2c2d301e6401f251854b28c329040c8e55184402648bc79710536390b0f99acd2400734a81e8bd573d9a96971a5ef195d85babbe1092e271a69a146d2d4114b855c89b710908d4a584e598b6dd186709d956fbd8b13b2ba3e9115c7d75416b7ac0e6d9dde83d367af00fa8dae54f03079cdf5fa1bac35ff3fbf6440f4fa41d06278fd8a6c3edc80d4ceee900975db2a345d8eda0ce0260d389a8336134a20945c1dde16e475ae537a4407da7f389dce79b439d0d81fa915a7dc4cb018e0d1bd806101ff53970731876850255dd5b7d78bf817112bc74ebae39ecae0aa1555e2a9e61660f4ba373086492e0561efb3bfa98399f899d6909415f19963cef3dc6d154056403ed1f475cbc78981cc949c23686bd71b420ed7ce654578d322f1eeec7413c77c017479f2e19b0c5ebb25ad76e1af8ec3a6b6ba3e092af8ddd624dfd1fccb4a61cc77f5c001fafdd7d2cc6c7b418c6dac97e840bc027ef9d4b237eebc2d1255bc0f796c3de78e31a9ae346734801e0f51dc47f8279164ab35bad3b06a5f74356deec0296c350aae4da560b8d77782195621dca054703433037f28cac2603cdefe52903f25013ad4cf4f29e0e413f89f950d26b4ef129aabf78abf9acae252bc60db373cfe3963fc87e9532056f2f2a7f9f1ddd1a3cf9d0078a5c9cccdcc60714de967556032283075424446ccda9f62a5e31f2b5efbc2256c742e4a300d5bc797e90b3a65fdffe3c5f22f5a885903331dbbb031e51411facb4e80e1728c5b14c9fce196d7d5f1dfeca2fe9b877f45fc1f1c8ec09dd5335eb89accf38fe0e6dbbe77a59c96a49830ce5279f17f4897c64e2aeaec127d8d06adcefa7ac800c1e8"); EXPECT_EQ(Serpent128_CFB::NAME, config.config()->Cipher()); EXPECT_EQ("5821ED3B0739C7EDB6A09590232EA75B", config.config()->RootBlob()); } #endif test/cryfs/config/CryCipherTest.cpp000066400000000000000000000150461347701267100177020ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include using namespace cryfs; using namespace blockstore::encrypted; using namespace blockstore::inmemory; using namespace blockstore; using std::initializer_list; using std::string; using std::vector; using std::find; using boost::none; using testing::MatchesRegex; using namespace cpputils; class CryCipherTest : public ::testing::Test { public: void EXPECT_FINDS_CORRECT_CIPHERS(initializer_list ciphers) { for (const string & cipher : ciphers) { EXPECT_FINDS_CORRECT_CIPHER(cipher); } } void EXPECT_FINDS_CORRECT_CIPHER(const string &cipherName) { EXPECT_EQ(cipherName, CryCiphers::find(cipherName).cipherName()); } template void EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE(const string &cipherName) { const auto &actualCipher = CryCiphers::find(cipherName); Data dataFixture = DataFixture::generate(1024); string encKey = ExpectedCipher::EncryptionKey::CreateKey(Random::PseudoRandom(), ExpectedCipher::KEYSIZE).ToString(); _EXPECT_ENCRYPTS_WITH_ACTUAL_BLOCKSTORE_DECRYPTS_CORRECTLY_WITH_EXPECTED_BLOCKSTORE(actualCipher, encKey, std::move(dataFixture)); } template void _EXPECT_ENCRYPTS_WITH_ACTUAL_BLOCKSTORE_DECRYPTS_CORRECTLY_WITH_EXPECTED_BLOCKSTORE(const CryCipher &actualCipher, const std::string &encKey, Data dataFixture) { blockstore::BlockId blockId = blockstore::BlockId::Random(); Data encrypted = _encryptUsingEncryptedBlockStoreWithCipher(actualCipher, encKey, blockId, dataFixture.copy()); Data decrypted = _decryptUsingEncryptedBlockStoreWithCipher(encKey, blockId, std::move(encrypted)); EXPECT_EQ(dataFixture, decrypted); } Data _encryptUsingEncryptedBlockStoreWithCipher(const CryCipher &cipher, const std::string &encKey, const blockstore::BlockId &blockId, Data data) { unique_ref _baseStore = make_unique_ref(); InMemoryBlockStore2 *baseStore = _baseStore.get(); unique_ref encryptedStore = cipher.createEncryptedBlockstore(std::move(_baseStore), encKey); bool created = encryptedStore->tryCreate(blockId, data); EXPECT_TRUE(created); return _loadBlock(baseStore, blockId); } template Data _decryptUsingEncryptedBlockStoreWithCipher(const std::string &encKey, const blockstore::BlockId &blockId, Data data) { unique_ref baseStore = make_unique_ref(); bool created = baseStore->tryCreate(blockId, data); EXPECT_TRUE(created); EncryptedBlockStore2 encryptedStore(std::move(baseStore), Cipher::EncryptionKey::FromString(encKey)); return _loadBlock(&encryptedStore, blockId); } Data _loadBlock(BlockStore2 *store, const blockstore::BlockId &blockId) { return store->load(blockId).value(); } }; TEST_F(CryCipherTest, FindsCorrectCipher) { EXPECT_FINDS_CORRECT_CIPHERS({ "aes-256-gcm", "aes-256-cfb", "aes-256-gcm", "aes-256-cfb", "twofish-256-gcm", "twofish-256-cfb", "twofish-256-gcm", "twofish-256-cfb", "serpent-256-gcm", "serpent-256-cfb", "serpent-256-gcm", "serpent-256-cfb", "cast-256-gcm", "cast-256-cfb", #if CRYPTOPP_VERSION != 564 "mars-448-gcm", "mars-448-cfb", #endif "mars-256-gcm", "mars-256-cfb", "mars-256-gcm", "mars-256-cfb" }); } TEST_F(CryCipherTest, CreatesCorrectEncryptedBlockStore) { EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("aes-256-gcm"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("aes-256-cfb"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("aes-128-gcm"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("aes-128-cfb"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("twofish-256-gcm"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("twofish-256-cfb"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("twofish-128-gcm"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("twofish-128-cfb"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("serpent-256-gcm"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("serpent-256-cfb"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("serpent-128-gcm"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("serpent-128-cfb"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("cast-256-gcm"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("cast-256-cfb"); #if CRYPTOPP_VERSION != 564 EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("mars-448-gcm"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("mars-448-cfb"); #endif EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("mars-256-gcm"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("mars-256-cfb"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("mars-128-gcm"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("mars-128-cfb"); } TEST_F(CryCipherTest, SupportedCipherNamesContainsACipher) { vector supportedCipherNames = CryCiphers::supportedCipherNames(); EXPECT_NE(supportedCipherNames.end(), find(supportedCipherNames.begin(), supportedCipherNames.end(), "aes-256-gcm")); } TEST_F(CryCipherTest, ThereIsACipherWithoutWarning) { EXPECT_EQ(none, CryCiphers::find("aes-256-gcm").warning()); } TEST_F(CryCipherTest, ThereIsACipherWithIntegrityWarning) { EXPECT_THAT(CryCiphers::find("aes-256-cfb").warning().value(), MatchesRegex(".*integrity.*")); } #if CRYPTOPP_VERSION != 564 TEST_F(CryCipherTest, EncryptionKeyHasCorrectSize_448) { EXPECT_EQ(Mars448_GCM::STRING_KEYSIZE, CryCiphers::find("mars-448-gcm").createKey(Random::PseudoRandom()).size()); } #endif TEST_F(CryCipherTest, EncryptionKeyHasCorrectSize_256) { EXPECT_EQ(AES256_GCM::STRING_KEYSIZE, CryCiphers::find("aes-256-gcm").createKey(Random::PseudoRandom()).size()); } TEST_F(CryCipherTest, EncryptionKeyHasCorrectSize_128) { EXPECT_EQ(AES128_GCM::STRING_KEYSIZE, CryCiphers::find("aes-128-gcm").createKey(Random::PseudoRandom()).size()); } test/cryfs/config/CryConfigConsoleTest.cpp000066400000000000000000000111031347701267100212060ustar00rootroot00000000000000#include #include #include #include #include #include #include "../testutils/MockConsole.h" using namespace cryfs; using boost::optional; using boost::none; using cpputils::NoninteractiveConsole; using std::string; using std::shared_ptr; using std::make_shared; using ::testing::_; using ::testing::NiceMock; using ::testing::Return; using ::testing::ValuesIn; using ::testing::HasSubstr; using ::testing::UnorderedElementsAreArray; using ::testing::WithParamInterface; class CryConfigConsoleTest: public ::testing::Test { public: CryConfigConsoleTest() : console(make_shared>()), cryconsole(console), noninteractiveCryconsole(make_shared(console)) { } shared_ptr> console; CryConfigConsole cryconsole; CryConfigConsole noninteractiveCryconsole; }; class CryConfigConsoleTest_Cipher: public CryConfigConsoleTest {}; #define EXPECT_ASK_FOR_CIPHER() \ EXPECT_CALL(*console, askYesNo("Use default settings?", _)).Times(1).WillOnce(Return(false)); \ EXPECT_CALL(*console, ask(HasSubstr("block cipher"), UnorderedElementsAreArray(CryCiphers::supportedCipherNames()))).Times(1) #define EXPECT_ASK_FOR_BLOCKSIZE() \ EXPECT_CALL(*console, askYesNo("Use default settings?", _)).Times(1).WillOnce(Return(false)); \ EXPECT_CALL(*console, ask(HasSubstr("block size"), _)).Times(1) #define EXPECT_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION() \ EXPECT_CALL(*console, askYesNo("Use default settings?", _)).Times(1).WillOnce(Return(false)); \ EXPECT_CALL(*console, askYesNo(HasSubstr("missing block"), _)).Times(1) TEST_F(CryConfigConsoleTest_Cipher, AsksForCipher) { EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseAnyCipher()); cryconsole.askCipher(); } TEST_F(CryConfigConsoleTest_Cipher, ChooseDefaultCipher) { EXPECT_CALL(*console, askYesNo("Use default settings?", _)).Times(1).WillOnce(Return(true)); EXPECT_CALL(*console, ask(HasSubstr("block cipher"), _)).Times(0); string cipher = cryconsole.askCipher(); EXPECT_EQ(CryConfigConsole::DEFAULT_CIPHER, cipher); } TEST_F(CryConfigConsoleTest_Cipher, ChooseDefaultCipherWhenNoninteractiveEnvironment) { EXPECT_CALL(*console, askYesNo(HasSubstr("default"), _)).Times(0); EXPECT_CALL(*console, ask(HasSubstr("block cipher"), _)).Times(0); string cipher = noninteractiveCryconsole.askCipher(); EXPECT_EQ(CryConfigConsole::DEFAULT_CIPHER, cipher); } TEST_F(CryConfigConsoleTest_Cipher, AsksForBlocksize) { EXPECT_ASK_FOR_BLOCKSIZE().WillOnce(Return(0)); cryconsole.askBlocksizeBytes(); } TEST_F(CryConfigConsoleTest_Cipher, AsksForMissingBlockIsIntegrityViolation) { EXPECT_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION().WillOnce(Return(true)); cryconsole.askMissingBlockIsIntegrityViolation(); } TEST_F(CryConfigConsoleTest_Cipher, ChooseDefaultBlocksizeWhenNoninteractiveEnvironment) { EXPECT_CALL(*console, askYesNo(HasSubstr("default"), _)).Times(0); EXPECT_CALL(*console, ask(HasSubstr("block size"), _)).Times(0); uint32_t blocksize = noninteractiveCryconsole.askBlocksizeBytes(); EXPECT_EQ(CryConfigConsole::DEFAULT_BLOCKSIZE_BYTES, blocksize); } class CryConfigConsoleTest_Cipher_Choose: public CryConfigConsoleTest_Cipher, public ::testing::WithParamInterface { public: string cipherName = GetParam(); optional cipherWarning = CryCiphers::find(cipherName).warning(); void EXPECT_DONT_SHOW_WARNING() { EXPECT_CALL(*console, askYesNo(_, _)).Times(0); } void EXPECT_SHOW_WARNING(const string &warning) { EXPECT_CALL(*console, askYesNo(HasSubstr(warning), _)).WillOnce(Return(true)); } }; TEST_P(CryConfigConsoleTest_Cipher_Choose, ChoosesCipherCorrectly) { if (cipherWarning == none) { EXPECT_DONT_SHOW_WARNING(); } else { EXPECT_SHOW_WARNING(*cipherWarning); } EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseCipher(cipherName)); string chosenCipher = cryconsole.askCipher(); EXPECT_EQ(cipherName, chosenCipher); } INSTANTIATE_TEST_CASE_P(CryConfigConsoleTest_Cipher_Choose, CryConfigConsoleTest_Cipher_Choose, ValuesIn(CryCiphers::supportedCipherNames())); test/cryfs/config/CryConfigCreatorTest.cpp000066400000000000000000000225401347701267100212120ustar00rootroot00000000000000#include #include #include #include #include #include "../testutils/MockConsole.h" #include "../testutils/TestWithFakeHomeDirectory.h" #include #include #include using namespace cryfs; using boost::none; using cpputils::NoninteractiveConsole; using std::string; using std::shared_ptr; using std::make_shared; using ::testing::_; using ::testing::Return; using ::testing::HasSubstr; using ::testing::UnorderedElementsAreArray; using ::testing::NiceMock; #define EXPECT_ASK_TO_USE_DEFAULT_SETTINGS() \ EXPECT_CALL(*console, askYesNo("Use default settings?", true)).Times(1) #define EXPECT_DOES_NOT_ASK_TO_USE_DEFAULT_SETTINGS() \ EXPECT_CALL(*console, askYesNo("Use default settings?", true)).Times(0) #define EXPECT_ASK_FOR_CIPHER() \ EXPECT_CALL(*console, ask(HasSubstr("block cipher"), UnorderedElementsAreArray(CryCiphers::supportedCipherNames()))).Times(1) #define EXPECT_DOES_NOT_ASK_FOR_CIPHER() \ EXPECT_CALL(*console, ask(HasSubstr("block cipher"), _)).Times(0) #define EXPECT_ASK_FOR_BLOCKSIZE() \ EXPECT_CALL(*console, ask(HasSubstr("block size"), _)).Times(1) #define EXPECT_DOES_NOT_ASK_FOR_BLOCKSIZE() \ EXPECT_CALL(*console, ask(HasSubstr("block size"), _)).Times(0) #define EXPECT_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION() \ EXPECT_CALL(*console, askYesNo(HasSubstr("missing block"), false)).Times(1) #define EXPECT_DOES_NOT_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION() \ EXPECT_CALL(*console, askYesNo(HasSubstr("missing block"), false)).Times(0) #define IGNORE_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION() \ EXPECT_CALL(*console, askYesNo(HasSubstr("missing block"), false)) class CryConfigCreatorTest: public ::testing::Test, TestWithFakeHomeDirectory { public: CryConfigCreatorTest() : console(make_shared>()), tempLocalStateDir(), localStateDir(tempLocalStateDir.path()), creator(console, cpputils::Random::PseudoRandom(), localStateDir), noninteractiveCreator(make_shared(console), cpputils::Random::PseudoRandom(), localStateDir) { EXPECT_CALL(*console, ask(HasSubstr("block cipher"), _)).WillRepeatedly(ChooseAnyCipher()); EXPECT_CALL(*console, ask(HasSubstr("block size"), _)).WillRepeatedly(Return(0)); } shared_ptr> console; cpputils::TempDir tempLocalStateDir; LocalStateDir localStateDir; CryConfigCreator creator; CryConfigCreator noninteractiveCreator; void AnswerNoToDefaultSettings() { EXPECT_ASK_TO_USE_DEFAULT_SETTINGS().WillOnce(Return(false)); } void AnswerYesToDefaultSettings() { EXPECT_ASK_TO_USE_DEFAULT_SETTINGS().WillOnce(Return(true)); } }; TEST_F(CryConfigCreatorTest, DoesAskForCipherIfNotSpecified) { AnswerNoToDefaultSettings(); IGNORE_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseAnyCipher()); CryConfig config = creator.create(none, none, none, false).config; } TEST_F(CryConfigCreatorTest, DoesNotAskForCipherIfSpecified) { AnswerNoToDefaultSettings(); IGNORE_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); EXPECT_DOES_NOT_ASK_FOR_CIPHER(); CryConfig config = creator.create(string("aes-256-gcm"), none, none, false).config; } TEST_F(CryConfigCreatorTest, DoesNotAskForCipherIfUsingDefaultSettings) { AnswerYesToDefaultSettings(); EXPECT_DOES_NOT_ASK_FOR_CIPHER(); CryConfig config = creator.create(none, none, none, false).config; } TEST_F(CryConfigCreatorTest, DoesNotAskForCipherIfNoninteractive) { EXPECT_DOES_NOT_ASK_TO_USE_DEFAULT_SETTINGS(); EXPECT_DOES_NOT_ASK_FOR_CIPHER(); CryConfig config = noninteractiveCreator.create(none, none, none, false).config; } TEST_F(CryConfigCreatorTest, DoesAskForBlocksizeIfNotSpecified) { AnswerNoToDefaultSettings(); IGNORE_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); EXPECT_ASK_FOR_BLOCKSIZE().WillOnce(Return(1)); CryConfig config = creator.create(none, none, none, false).config; } TEST_F(CryConfigCreatorTest, DoesNotAskForBlocksizeIfSpecified) { AnswerNoToDefaultSettings(); IGNORE_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); EXPECT_DOES_NOT_ASK_FOR_BLOCKSIZE(); CryConfig config = creator.create(none, 10*1024u, none, false).config; } TEST_F(CryConfigCreatorTest, DoesNotAskForBlocksizeIfNoninteractive) { EXPECT_DOES_NOT_ASK_TO_USE_DEFAULT_SETTINGS(); EXPECT_DOES_NOT_ASK_FOR_BLOCKSIZE(); CryConfig config = noninteractiveCreator.create(none, none, none, false).config; } TEST_F(CryConfigCreatorTest, DoesNotAskForBlocksizeIfUsingDefaultSettings) { AnswerYesToDefaultSettings(); EXPECT_DOES_NOT_ASK_FOR_BLOCKSIZE(); CryConfig config = creator.create(none, none, none, false).config; } TEST_F(CryConfigCreatorTest, DoesAskWhetherMissingBlocksAreIntegrityViolationsIfNotSpecified) { AnswerNoToDefaultSettings(); EXPECT_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION().WillOnce(Return(true)); CryConfig config = creator.create(none, none, none, false).config; } TEST_F(CryConfigCreatorTest, DoesNotAskWhetherMissingBlocksAreIntegrityViolationsIfSpecified_True) { AnswerNoToDefaultSettings(); EXPECT_DOES_NOT_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); CryConfig config = creator.create(none, none, true, false).config; } TEST_F(CryConfigCreatorTest, DoesNotAskWhetherMissingBlocksAreIntegrityViolationsIfSpecified_False) { AnswerNoToDefaultSettings(); EXPECT_DOES_NOT_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); CryConfig config = creator.create(none, none, false, false).config; } TEST_F(CryConfigCreatorTest, DoesNotAskWhetherMissingBlocksAreIntegrityViolationsIfNoninteractive) { EXPECT_DOES_NOT_ASK_TO_USE_DEFAULT_SETTINGS(); EXPECT_DOES_NOT_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); CryConfig config = noninteractiveCreator.create(none, none, none, false).config; } TEST_F(CryConfigCreatorTest, DoesNotAskWhetherMissingBlocksAreIntegrityViolationsIfUsingDefaultSettings) { AnswerYesToDefaultSettings(); EXPECT_DOES_NOT_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); CryConfig config = creator.create(none, none, none, false).config; } TEST_F(CryConfigCreatorTest, ChoosesEmptyRootBlobId) { AnswerNoToDefaultSettings(); IGNORE_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); CryConfig config = creator.create(none, none, none, false).config; EXPECT_EQ("", config.RootBlob()); // This tells CryFS to create a new root blob } #if CRYPTOPP_VERSION != 564 TEST_F(CryConfigCreatorTest, ChoosesValidEncryptionKey_448) { AnswerNoToDefaultSettings(); IGNORE_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseCipher("mars-448-gcm")); CryConfig config = creator.create(none, none, none, false).config; cpputils::Mars448_GCM::EncryptionKey::FromString(config.EncryptionKey()); // This crashes if invalid } #endif TEST_F(CryConfigCreatorTest, ChoosesValidEncryptionKey_256) { AnswerNoToDefaultSettings(); IGNORE_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseCipher("aes-256-gcm")); CryConfig config = creator.create(none, none, none, false).config; cpputils::AES256_GCM::EncryptionKey::FromString(config.EncryptionKey()); // This crashes if invalid } TEST_F(CryConfigCreatorTest, ChoosesValidEncryptionKey_128) { AnswerNoToDefaultSettings(); IGNORE_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION(); EXPECT_ASK_FOR_CIPHER().WillOnce(ChooseCipher("aes-128-gcm")); CryConfig config = creator.create(none, none, none, false).config; cpputils::AES128_GCM::EncryptionKey::FromString(config.EncryptionKey()); // This crashes if invalid } TEST_F(CryConfigCreatorTest, DoesNotAskForAnythingIfEverythingIsSpecified) { EXPECT_DOES_NOT_ASK_TO_USE_DEFAULT_SETTINGS(); EXPECT_DOES_NOT_ASK_FOR_CIPHER(); CryConfig config = noninteractiveCreator.create(string("aes-256-gcm"), 10*1024u, none, false).config; } TEST_F(CryConfigCreatorTest, SetsCorrectCreatedWithVersion) { CryConfig config = noninteractiveCreator.create(none, none, none, false).config; EXPECT_EQ(gitversion::VersionString(), config.CreatedWithVersion()); } TEST_F(CryConfigCreatorTest, SetsCorrectLastOpenedWithVersion) { CryConfig config = noninteractiveCreator.create(none, none, none, false).config; EXPECT_EQ(gitversion::VersionString(), config.CreatedWithVersion()); } TEST_F(CryConfigCreatorTest, SetsCorrectVersion) { CryConfig config = noninteractiveCreator.create(none, none, none, false).config; EXPECT_EQ(CryConfig::FilesystemFormatVersion, config.Version()); } //TODO Add test cases ensuring that the values entered are correctly taken test/cryfs/config/CryConfigFileTest.cpp000066400000000000000000000153631347701267100204770ustar00rootroot00000000000000#include #include #include #include "../testutils/FakeCryKeyProvider.h" using namespace cryfs; using cpputils::TempFile; using std::string; using boost::optional; using boost::none; using cpputils::Data; namespace bf = boost::filesystem; //gtest/boost::optional workaround for working with optional namespace boost { inline std::ostream &operator<<(std::ostream &out, const CryConfigFile &file) { UNUSED(file); out << "ConfigFile()"; return out; } } #include class CryConfigFileTest: public ::testing::Test { public: CryConfigFileTest(): file(false) {} TempFile file; CryConfig Config() { CryConfig result; result.SetCipher("aes-256-gcm"); return result; } CryConfigFile CreateAndLoadEmpty(unsigned char keySeed = 0) { Create(Config(), keySeed); return Load().value(); } void Create(CryConfig cfg, unsigned int keySeed = 0) { FakeCryKeyProvider keyProvider(keySeed); CryConfigFile::create(file.path(), std::move(cfg), &keyProvider); } optional Load(unsigned int keySeed = 0) { FakeCryKeyProvider keyProvider(keySeed); return CryConfigFile::load(file.path(), &keyProvider); } void CreateWithCipher(const string &cipher) { return CreateWithCipher(cipher, file); } void CreateWithCipher(const string &cipher, const TempFile &tempFile) { CryConfig cfg; cfg.SetCipher(cipher); FakeCryKeyProvider keyProvider(0); CryConfigFile::create(tempFile.path(), std::move(cfg), &keyProvider); } }; TEST_F(CryConfigFileTest, DoesntLoadIfWrongPassword) { const unsigned char pw1 = 0; const unsigned char pw2 = 1; Create(Config(), pw1); auto loaded = Load(pw2); EXPECT_EQ(none, loaded); } TEST_F(CryConfigFileTest, RootBlob_Init) { CryConfigFile created = CreateAndLoadEmpty(); EXPECT_EQ("", created.config()->RootBlob()); } TEST_F(CryConfigFileTest, RootBlob_CreateAndLoad) { CryConfig cfg = Config(); cfg.SetRootBlob("rootblobid"); Create(std::move(cfg)); CryConfigFile loaded = Load().value(); EXPECT_EQ("rootblobid", loaded.config()->RootBlob()); } TEST_F(CryConfigFileTest, RootBlob_SaveAndLoad) { CryConfigFile created = CreateAndLoadEmpty(); created.config()->SetRootBlob("rootblobid"); created.save(); CryConfigFile loaded = Load().value(); EXPECT_EQ("rootblobid", loaded.config()->RootBlob()); } TEST_F(CryConfigFileTest, EncryptionKey_Init) { CryConfigFile created = CreateAndLoadEmpty(); EXPECT_EQ("", created.config()->EncryptionKey()); } TEST_F(CryConfigFileTest, EncryptionKey_CreateAndLoad) { CryConfig cfg = Config(); cfg.SetEncryptionKey("encryptionkey"); Create(std::move(cfg)); CryConfigFile loaded = Load().value(); EXPECT_EQ("encryptionkey", loaded.config()->EncryptionKey()); } TEST_F(CryConfigFileTest, EncryptionKey_SaveAndLoad) { CryConfigFile created = CreateAndLoadEmpty(); created.config()->SetEncryptionKey("encryptionkey"); created.save(); CryConfigFile loaded = Load().value(); EXPECT_EQ("encryptionkey", loaded.config()->EncryptionKey()); } TEST_F(CryConfigFileTest, Cipher_Init) { CryConfigFile created = CreateAndLoadEmpty(); EXPECT_EQ("aes-256-gcm", created.config()->Cipher()); } TEST_F(CryConfigFileTest, Cipher_CreateAndLoad) { CryConfig cfg = Config(); cfg.SetCipher("twofish-128-cfb"); Create(std::move(cfg)); CryConfigFile loaded = Load().value(); EXPECT_EQ("twofish-128-cfb", loaded.config()->Cipher()); } TEST_F(CryConfigFileTest, Cipher_SaveAndLoad) { CryConfigFile created = CreateAndLoadEmpty(); created.config()->SetCipher("twofish-128-cfb"); created.save(); CryConfigFile loaded = Load().value(); EXPECT_EQ("twofish-128-cfb", loaded.config()->Cipher()); } TEST_F(CryConfigFileTest, Version_Init) { CryConfigFile created = CreateAndLoadEmpty(); EXPECT_EQ("", created.config()->Version()); } TEST_F(CryConfigFileTest, Version_CreateAndLoad) { CryConfig cfg = Config(); cfg.SetVersion("0.9.2"); Create(std::move(cfg)); CryConfigFile loaded = Load().value(); EXPECT_EQ("0.9.2", loaded.config()->Version()); } TEST_F(CryConfigFileTest, Version_SaveAndLoad) { CryConfigFile created = CreateAndLoadEmpty(); created.config()->SetVersion("0.9.2"); created.save(); CryConfigFile loaded = Load().value(); EXPECT_EQ("0.9.2", loaded.config()->Version()); } TEST_F(CryConfigFileTest, CreatedWithVersion_Init) { CryConfigFile created = CreateAndLoadEmpty(); EXPECT_EQ("", created.config()->Version()); } TEST_F(CryConfigFileTest, CreatedWithVersion_CreateAndLoad) { CryConfig cfg = Config(); cfg.SetCreatedWithVersion("0.9.2"); Create(std::move(cfg)); CryConfigFile loaded = Load().value(); EXPECT_EQ("0.9.2", loaded.config()->CreatedWithVersion()); } TEST_F(CryConfigFileTest, CreatedWithVersion_SaveAndLoad) { CryConfigFile created = CreateAndLoadEmpty(); created.config()->SetCreatedWithVersion("0.9.2"); created.save(); CryConfigFile loaded = Load().value(); EXPECT_EQ("0.9.2", loaded.config()->CreatedWithVersion()); } //Test that the encrypted config file has the same size, no matter how big the plaintext config data. TEST_F(CryConfigFileTest, ConfigFileHasFixedSize) { TempFile file1(false); TempFile file2(false); //It is important to have different cipher name lengths here, because they're on the outer encryption level. //So this ensures that there also is a padding happening on the outer encryption level. CreateWithCipher("aes-128-gcm", file1); // Short cipher name and short key CreateWithCipher("twofish-256-cfb", file2); // Long cipher name and long key EXPECT_EQ(bf::file_size(file1.path()), bf::file_size(file2.path())); } TEST_F(CryConfigFileTest, CanSaveAndLoadModififedCipher) { CreateWithCipher("aes-256-gcm"); CryConfigFile created = Load().value(); EXPECT_EQ("aes-256-gcm", created.config()->Cipher()); created.config()->SetCipher("twofish-128-cfb"); created.save(); CryConfigFile loaded = Load().value(); EXPECT_EQ("twofish-128-cfb", loaded.config()->Cipher()); } TEST_F(CryConfigFileTest, FailsIfConfigFileIsEncryptedWithACipherDifferentToTheOneSpecifiedByTheUser) { constexpr unsigned char keySeed = 0; FakeCryKeyProvider keyProvider(keySeed); auto encryptor = CryConfigEncryptorFactory::deriveNewKey(&keyProvider); auto config = Config(); config.SetCipher("aes-256-gcm"); Data encrypted = encryptor->encrypt(config.save(), "aes-256-cfb"); encrypted.StoreToFile(file.path()); auto loaded = Load(keySeed); EXPECT_EQ(none, loaded); } test/cryfs/config/CryConfigLoaderTest.cpp000066400000000000000000000343351347701267100210260ustar00rootroot00000000000000#include #include #include #include "../testutils/MockConsole.h" #include "../testutils/TestWithFakeHomeDirectory.h" #include #include #include #include #include #include #include #include using cpputils::TempFile; using cpputils::SCrypt; using cpputils::DataFixture; using cpputils::Data; using cpputils::NoninteractiveConsole; using cpputils::make_unique_ref; using cpputils::Console; using cpputils::unique_ref; using cryfs::CryPresetPasswordBasedKeyProvider; using boost::optional; using boost::none; using std::string; using std::ostream; using std::shared_ptr; using std::make_shared; using ::testing::Return; using ::testing::HasSubstr; using ::testing::_; using namespace cryfs; // This is needed for google test namespace boost { inline ostream &operator<<(ostream &stream, const CryConfigFile &) { return stream << "CryConfigFile()"; } } namespace cryfs { inline ostream &operator<<(ostream &stream, const CryConfigLoader::ConfigLoadResult &) { return stream << "ConfigLoadResult()"; } } #include class FakeRandomGenerator final : public cpputils::RandomGenerator { public: FakeRandomGenerator(Data output) : _output(std::move(output)) {} void _get(void *target, size_t bytes) override { ASSERT_EQ(_output.size(), bytes); std::memcpy(target, _output.data(), bytes); } private: Data _output; }; class CryConfigLoaderTest: public ::testing::Test, public TestWithMockConsole, TestWithFakeHomeDirectory { public: unique_ref keyProvider(const string& password) { return make_unique_ref(password, make_unique_ref(SCrypt::TestSettings)); } CryConfigLoaderTest(): file(false), tempLocalStateDir(), localStateDir(tempLocalStateDir.path()) { console = mockConsole(); } CryConfigLoader loader(const string &password, bool noninteractive, const optional &cipher = none) { auto _console = noninteractive ? shared_ptr(make_shared(console)) : shared_ptr(console); return CryConfigLoader(_console, cpputils::Random::PseudoRandom(), keyProvider(password), localStateDir, cipher, none, none); } CryConfigFile Create(const string &password = "mypassword", const optional &cipher = none, bool noninteractive = false) { EXPECT_FALSE(file.exists()); return loader(password, noninteractive, cipher).loadOrCreate(file.path(), false, false).value().configFile; } optional Load(const string &password = "mypassword", const optional &cipher = none, bool noninteractive = false, bool allowFilesystemUpgrade = false) { EXPECT_TRUE(file.exists()); auto loadResult = loader(password, noninteractive, cipher).loadOrCreate(file.path(), allowFilesystemUpgrade, false); if (loadResult == none) { return none; } return std::move(loadResult->configFile); } void CreateWithRootBlob(const string &rootBlob, const string &password = "mypassword") { auto cfg = loader(password, false).loadOrCreate(file.path(), false, false).value().configFile; cfg.config()->SetRootBlob(rootBlob); cfg.save(); } void CreateWithCipher(const string &cipher, const string &password = "mypassword") { auto cfg = loader(password, false).loadOrCreate(file.path(), false, false).value().configFile; cfg.config()->SetCipher(cipher); cfg.save(); } void CreateWithEncryptionKey(const string &encKey, const string &password = "mypassword") { FakeRandomGenerator generator(Data::FromString(encKey)); auto loader = CryConfigLoader(console, generator, keyProvider(password), localStateDir, none, none, none); ASSERT_NE(boost::none, loader.loadOrCreate(file.path(), false, false)); } void ChangeEncryptionKey(const string &encKey, const string& password = "mypassword") { auto cfg = CryConfigFile::load(file.path(), keyProvider(password).get()).value(); cfg.config()->SetEncryptionKey(encKey); cfg.save(); } void CreateWithVersion(const string &version, const string& formatVersion, const string &password = "mypassword") { auto cfg = loader(password, false).loadOrCreate(file.path(), false, false).value().configFile; cfg.config()->SetVersion(formatVersion); cfg.config()->SetLastOpenedWithVersion(version); cfg.config()->SetCreatedWithVersion(version); cfg.save(); } void CreateWithFilesystemID(const CryConfig::FilesystemID &filesystemId, const string &password = "mypassword") { auto cfg = loader(password, false).loadOrCreate(file.path(), false, false).value().configFile; cfg.config()->SetFilesystemId(filesystemId); cfg.save(); } void ChangeFilesystemID(const CryConfig::FilesystemID &filesystemId, const string& password = "mypassword") { auto cfg = CryConfigFile::load(file.path(), keyProvider(password).get()).value(); cfg.config()->SetFilesystemId(filesystemId); cfg.save(); } string olderVersion() { auto versionInfo = gitversion::Parser::parse(CryConfig::FilesystemFormatVersion); string olderVersion; if (std::stol(versionInfo.minorVersion) > 0) { olderVersion = versionInfo.majorVersion + "." + std::to_string(std::stol(versionInfo.minorVersion) - 1) + ".9"; } else { olderVersion = std::to_string(std::stol(versionInfo.majorVersion) - 1) + "." + versionInfo.minorVersion; } assert(gitversion::VersionCompare::isOlderThan(olderVersion, CryConfig::FilesystemFormatVersion)); return olderVersion; } string newerVersion() { string newerVersion = gitversion::MajorVersion()+"."+std::to_string(std::stol(gitversion::MinorVersion())+2); EXPECT_TRUE(gitversion::VersionCompare::isOlderThan(CryConfig::FilesystemFormatVersion, newerVersion)) << "Format Version " << CryConfig::FilesystemFormatVersion << " should be older than Git Version " << newerVersion; return newerVersion; } std::shared_ptr console; TempFile file; cpputils::TempDir tempLocalStateDir; LocalStateDir localStateDir; }; TEST_F(CryConfigLoaderTest, CreatesNewIfNotExisting) { EXPECT_FALSE(file.exists()); Create(); EXPECT_TRUE(file.exists()); } TEST_F(CryConfigLoaderTest, DoesntCrashIfExisting) { Create(); Load(); } TEST_F(CryConfigLoaderTest, DoesntLoadIfWrongPassword) { Create("mypassword"); auto loaded = Load("mypassword2"); EXPECT_EQ(none, loaded); } TEST_F(CryConfigLoaderTest, DoesntLoadIfDifferentCipher) { Create("mypassword", string("aes-256-gcm"), false); try { Load("mypassword", string("aes-256-cfb"), false); EXPECT_TRUE(false); // Should throw exception } catch (const std::runtime_error &e) { EXPECT_EQ(string("Filesystem uses aes-256-gcm cipher and not aes-256-cfb as specified."), e.what()); } } TEST_F(CryConfigLoaderTest, DoesntLoadIfDifferentCipher_Noninteractive) { Create("mypassword", string("aes-256-gcm"), true); try { Load("mypassword", string("aes-256-cfb"), true); EXPECT_TRUE(false); // Should throw exception } catch (const std::runtime_error &e) { EXPECT_EQ(string("Filesystem uses aes-256-gcm cipher and not aes-256-cfb as specified."), e.what()); } } TEST_F(CryConfigLoaderTest, DoesLoadIfSameCipher) { Create("mypassword", string("aes-256-gcm")); Load("mypassword", string("aes-256-gcm")); } TEST_F(CryConfigLoaderTest, DoesLoadIfSameCipher_Noninteractive) { Create("mypassword", string("aes-128-gcm"), true); Load("mypassword", string("aes-128-gcm"), true); } TEST_F(CryConfigLoaderTest, RootBlob_Load) { CreateWithRootBlob("rootblobid"); auto loaded = Load().value(); EXPECT_EQ("rootblobid", loaded.config()->RootBlob()); } TEST_F(CryConfigLoaderTest, RootBlob_Create) { auto created = Create(); EXPECT_EQ("", created.config()->RootBlob()); } TEST_F(CryConfigLoaderTest, EncryptionKey_Load) { CreateWithEncryptionKey("3B4682CF22F3CA199E385729B9F3CA19D325229E385729B9443CA19D325229E3"); auto loaded = Load().value(); EXPECT_EQ("3B4682CF22F3CA199E385729B9F3CA19D325229E385729B9443CA19D325229E3", loaded.config()->EncryptionKey()); } TEST_F(CryConfigLoaderTest, EncryptionKey_Load_whenKeyChanged_thenFails) { CreateWithEncryptionKey("3B4682CF22F3CA199E385729B9F3CA19D325229E385729B9443CA19D325229E3"); ChangeEncryptionKey("3B4682CF22F3CA199E385729B9F3CA19D325229E385729B9443CA19D325229E4"); EXPECT_THROW( Load(), std::runtime_error ); } TEST_F(CryConfigLoaderTest, EncryptionKey_Create) { auto created = Create(); //aes-256-gcm is the default cipher chosen by mockConsole() cpputils::AES256_GCM::EncryptionKey::FromString(created.config()->EncryptionKey()); // This crashes if key is invalid } TEST_F(CryConfigLoaderTest, Cipher_Load) { CreateWithCipher("twofish-128-cfb"); auto loaded = Load().value(); EXPECT_EQ("twofish-128-cfb", loaded.config()->Cipher()); } TEST_F(CryConfigLoaderTest, Cipher_Create) { auto created = Create(); //aes-256-gcm is the default cipher chosen by mockConsole() EXPECT_EQ("aes-256-gcm", created.config()->Cipher()); } TEST_F(CryConfigLoaderTest, Version_Load) { CreateWithVersion("0.9.4", "0.9.4"); auto loaded = Load().value(); EXPECT_EQ(CryConfig::FilesystemFormatVersion, loaded.config()->Version()); EXPECT_EQ(gitversion::VersionString(), loaded.config()->LastOpenedWithVersion()); EXPECT_EQ("0.9.4", loaded.config()->CreatedWithVersion()); } TEST_F(CryConfigLoaderTest, Version_Load_IsStoredAndNotOnlyOverwrittenInMemoryOnLoad) { CreateWithVersion("0.9.4", "0.9.4", "mypassword"); Load().value(); auto configFile = CryConfigFile::load(file.path(), keyProvider("mypassword").get()).value(); EXPECT_EQ(CryConfig::FilesystemFormatVersion, configFile.config()->Version()); EXPECT_EQ(gitversion::VersionString(), configFile.config()->LastOpenedWithVersion()); EXPECT_EQ("0.9.4", configFile.config()->CreatedWithVersion()); } TEST_F(CryConfigLoaderTest, Version_Create) { auto created = Create(); EXPECT_EQ(CryConfig::FilesystemFormatVersion, created.config()->Version()); EXPECT_EQ(gitversion::VersionString(), created.config()->LastOpenedWithVersion()); EXPECT_EQ(gitversion::VersionString(), created.config()->CreatedWithVersion()); } TEST_F(CryConfigLoaderTest, FilesystemID_Load) { auto fixture = DataFixture::generateFixedSize(); CreateWithFilesystemID(fixture); auto loaded = Load().value(); EXPECT_EQ(fixture, loaded.config()->FilesystemId()); } TEST_F(CryConfigLoaderTest, FilesystemID_Create) { auto created = Create(); EXPECT_NE(CryConfig::FilesystemID::Null(), created.config()->FilesystemId()); } TEST_F(CryConfigLoaderTest, AsksWhenLoadingNewerFilesystem_AnswerYes) { EXPECT_CALL(*console, askYesNo(HasSubstr("should not be opened with older versions"), false)).Times(1).WillOnce(Return(true)); string version = newerVersion(); CreateWithVersion(version, version); EXPECT_NE(boost::none, Load()); } TEST_F(CryConfigLoaderTest, AsksWhenLoadingNewerFilesystem_AnswerNo) { EXPECT_CALL(*console, askYesNo(HasSubstr("should not be opened with older versions"), false)).Times(1).WillOnce(Return(false)); string version = newerVersion(); CreateWithVersion(version, version); try { Load(); EXPECT_TRUE(false); // expect throw } catch (const std::runtime_error &e) { EXPECT_THAT(e.what(), HasSubstr("Please update your CryFS version.")); } } TEST_F(CryConfigLoaderTest, AsksWhenMigratingOlderFilesystem) { EXPECT_CALL(*console, askYesNo(HasSubstr("Do you want to attempt a migration now?"), false)).Times(1).WillOnce(Return(true)); string version = olderVersion(); CreateWithVersion(version, version); EXPECT_NE(boost::none, Load()); } TEST_F(CryConfigLoaderTest, DoesNotAskForMigrationWhenCorrectVersion) { EXPECT_CALL(*console, askYesNo(HasSubstr("Do you want to attempt a migration now?"), _)).Times(0); CreateWithVersion(gitversion::VersionString(), CryConfig::FilesystemFormatVersion); EXPECT_NE(boost::none, Load()); } TEST_F(CryConfigLoaderTest, DontMigrateWhenAnsweredNo) { EXPECT_CALL(*console, askYesNo(HasSubstr("Do you want to attempt a migration now?"), false)).Times(1).WillOnce(Return(false)); string version = olderVersion(); CreateWithVersion(version, version); try { Load(); EXPECT_TRUE(false); // expect throw } catch (const std::runtime_error &e) { EXPECT_THAT(e.what(), HasSubstr("It has to be migrated.")); } } TEST_F(CryConfigLoaderTest, MyClientIdIsIndeterministic) { TempFile file1(false); TempFile file2(false); uint32_t myClientId = loader("mypassword", true).loadOrCreate(file1.path(), false, false).value().myClientId; EXPECT_NE(myClientId, loader("mypassword", true).loadOrCreate(file2.path(), false, false).value().myClientId); } TEST_F(CryConfigLoaderTest, MyClientIdIsLoadedCorrectly) { TempFile file(false); uint32_t myClientId = loader("mypassword", true).loadOrCreate(file.path(), false, false).value().myClientId; EXPECT_EQ(myClientId, loader("mypassword", true).loadOrCreate(file.path(), false, false).value().myClientId); } TEST_F(CryConfigLoaderTest, DoesNotAskForMigrationWhenUpgradesAllowedByProgramArguments_NoninteractiveMode) { EXPECT_CALL(*console, askYesNo(HasSubstr("migrate"), _)).Times(0); string version = olderVersion(); CreateWithVersion(version, version); EXPECT_NE(boost::none, Load("mypassword", none, true, true)); } TEST_F(CryConfigLoaderTest, DoesNotAskForMigrationWhenUpgradesAllowedByProgramArguments_InteractiveMode) { EXPECT_CALL(*console, askYesNo(HasSubstr("migrate"), _)).Times(0); string version = olderVersion(); CreateWithVersion(version, version); EXPECT_NE(boost::none, Load("mypassword", none, false, true)); } test/cryfs/config/CryConfigTest.cpp000066400000000000000000000136101347701267100176700ustar00rootroot00000000000000#include #include #include #include using namespace cryfs; using cpputils::Data; using cpputils::DataFixture; using boost::none; class CryConfigTest: public ::testing::Test { public: CryConfig cfg; CryConfig SaveAndLoad(CryConfig cfg) { Data configData = cfg.save(); return CryConfig::load(configData); } }; TEST_F(CryConfigTest, RootBlob_Init) { EXPECT_EQ("", cfg.RootBlob()); } TEST_F(CryConfigTest, RootBlob) { cfg.SetRootBlob("rootblobid"); EXPECT_EQ("rootblobid", cfg.RootBlob()); } TEST_F(CryConfigTest, RootBlob_AfterMove) { cfg.SetRootBlob("rootblobid"); CryConfig moved = std::move(cfg); EXPECT_EQ("rootblobid", moved.RootBlob()); } TEST_F(CryConfigTest, RootBlob_AfterSaveAndLoad) { cfg.SetRootBlob("rootblobid"); CryConfig loaded = SaveAndLoad(std::move(cfg)); EXPECT_EQ("rootblobid", loaded.RootBlob()); } TEST_F(CryConfigTest, EncryptionKey_Init) { EXPECT_EQ("", cfg.EncryptionKey()); } TEST_F(CryConfigTest, EncryptionKey) { cfg.SetEncryptionKey("enckey"); EXPECT_EQ("enckey", cfg.EncryptionKey()); } TEST_F(CryConfigTest, EncryptionKey_AfterMove) { cfg.SetEncryptionKey("enckey"); CryConfig moved = std::move(cfg); EXPECT_EQ("enckey", moved.EncryptionKey()); } TEST_F(CryConfigTest, EncryptionKey_AfterSaveAndLoad) { cfg.SetEncryptionKey("enckey"); CryConfig loaded = SaveAndLoad(std::move(cfg)); EXPECT_EQ("enckey", loaded.EncryptionKey()); } TEST_F(CryConfigTest, Cipher_Init) { EXPECT_EQ("", cfg.Cipher()); } TEST_F(CryConfigTest, Cipher) { cfg.SetCipher("mycipher"); EXPECT_EQ("mycipher", cfg.Cipher()); } TEST_F(CryConfigTest, Cipher_AfterMove) { cfg.SetCipher("mycipher"); CryConfig moved = std::move(cfg); EXPECT_EQ("mycipher", moved.Cipher()); } TEST_F(CryConfigTest, Cipher_AfterSaveAndLoad) { cfg.SetCipher("mycipher"); CryConfig loaded = SaveAndLoad(std::move(cfg)); EXPECT_EQ("mycipher", loaded.Cipher()); } TEST_F(CryConfigTest, Version_Init) { EXPECT_EQ("", cfg.Version()); } TEST_F(CryConfigTest, Version) { cfg.SetVersion("0.9.1"); EXPECT_EQ("0.9.1", cfg.Version()); } TEST_F(CryConfigTest, Version_AfterMove) { cfg.SetVersion("0.9.1"); CryConfig moved = std::move(cfg); EXPECT_EQ("0.9.1", moved.Version()); } TEST_F(CryConfigTest, Version_AfterSaveAndLoad) { cfg.SetVersion("0.9.2"); CryConfig loaded = SaveAndLoad(std::move(cfg)); EXPECT_EQ("0.9.2", loaded.Version()); } TEST_F(CryConfigTest, CreatedWithVersion_Init) { EXPECT_EQ("", cfg.CreatedWithVersion()); } TEST_F(CryConfigTest, CreatedWithVersion) { cfg.SetCreatedWithVersion("0.9.3"); EXPECT_EQ("0.9.3", cfg.CreatedWithVersion()); } TEST_F(CryConfigTest, CreatedWithVersion_AfterMove) { cfg.SetCreatedWithVersion("0.9.3"); CryConfig moved = std::move(cfg); EXPECT_EQ("0.9.3", moved.CreatedWithVersion()); } TEST_F(CryConfigTest, CreatedWithVersion_AfterSaveAndLoad) { cfg.SetCreatedWithVersion("0.9.3"); CryConfig loaded = SaveAndLoad(std::move(cfg)); EXPECT_EQ("0.9.3", loaded.CreatedWithVersion()); } TEST_F(CryConfigTest, BlocksizeBytes_Init) { EXPECT_EQ(0u, cfg.BlocksizeBytes()); } TEST_F(CryConfigTest, BlocksizeBytes) { cfg.SetBlocksizeBytes(4*1024*1024); EXPECT_EQ(4*1024*1024u, cfg.BlocksizeBytes()); } TEST_F(CryConfigTest, BlocksizeBytes_AfterMove) { cfg.SetBlocksizeBytes(32*1024); CryConfig moved = std::move(cfg); EXPECT_EQ(32*1024u, moved.BlocksizeBytes()); } TEST_F(CryConfigTest, BlocksizeBytes_AfterSaveAndLoad) { cfg.SetBlocksizeBytes(10*1024); CryConfig loaded = SaveAndLoad(std::move(cfg)); EXPECT_EQ(10*1024u, loaded.BlocksizeBytes()); } TEST_F(CryConfigTest, FilesystemID_Init) { EXPECT_EQ(CryConfig::FilesystemID::Null(), cfg.FilesystemId()); } TEST_F(CryConfigTest, FilesystemID) { auto fixture = DataFixture::generateFixedSize(); cfg.SetFilesystemId(fixture); EXPECT_EQ(fixture, cfg.FilesystemId()); } TEST_F(CryConfigTest, FilesystemID_AfterMove) { auto fixture = DataFixture::generateFixedSize(); cfg.SetFilesystemId(fixture); CryConfig moved = std::move(cfg); EXPECT_EQ(fixture, moved.FilesystemId()); } TEST_F(CryConfigTest, FilesystemID_AfterSaveAndLoad) { auto fixture = DataFixture::generateFixedSize(); cfg.SetFilesystemId(fixture); CryConfig loaded = SaveAndLoad(std::move(cfg)); EXPECT_EQ(fixture, loaded.FilesystemId()); } TEST_F(CryConfigTest, ExclusiveClientId_Init) { EXPECT_EQ(none, cfg.ExclusiveClientId()); } TEST_F(CryConfigTest, ExclusiveClientId_Some) { cfg.SetExclusiveClientId(0x12345678u); EXPECT_EQ(0x12345678u, cfg.ExclusiveClientId().value()); } TEST_F(CryConfigTest, ExclusiveClientId_None) { cfg.SetExclusiveClientId(0x12345678u); cfg.SetExclusiveClientId(none); EXPECT_EQ(none, cfg.ExclusiveClientId()); } TEST_F(CryConfigTest, ExclusiveClientId_Some_AfterMove) { cfg.SetExclusiveClientId(0x12345678u); CryConfig moved = std::move(cfg); EXPECT_EQ(0x12345678u, moved.ExclusiveClientId().value()); } TEST_F(CryConfigTest, ExclusiveClientId_None_AfterMove) { cfg.SetExclusiveClientId(0x12345678u); cfg.SetExclusiveClientId(none); CryConfig moved = std::move(cfg); EXPECT_EQ(none, moved.ExclusiveClientId()); } TEST_F(CryConfigTest, ExclusiveClientId_Some_AfterSaveAndLoad) { cfg.SetExclusiveClientId(0x12345678u); CryConfig loaded = SaveAndLoad(std::move(cfg)); EXPECT_EQ(0x12345678u, loaded.ExclusiveClientId().value()); } TEST_F(CryConfigTest, ExclusiveClientId_None_AfterSaveAndLoad) { cfg.SetExclusiveClientId(none); CryConfig loaded = SaveAndLoad(std::move(cfg)); EXPECT_EQ(none, loaded.ExclusiveClientId()); } test/cryfs/config/CryPasswordBasedKeyProviderTest.cpp000066400000000000000000000062571347701267100234210ustar00rootroot00000000000000#include #include #include "../testutils/MockConsole.h" #include using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::EncryptionKey; using cpputils::PasswordBasedKDF; using cpputils::Data; using cpputils::DataFixture; using std::shared_ptr; using std::make_shared; using std::string; using cryfs::CryPasswordBasedKeyProvider; using testing::Return; using testing::Invoke; using testing::Eq; using testing::StrEq; using testing::NiceMock; using testing::_; class MockCallable { public: MOCK_METHOD0(call, std::string()); }; class MockKDF : public PasswordBasedKDF { public: MOCK_METHOD3(deriveExistingKey, EncryptionKey(size_t keySize, const string& password, const Data& kdfParameters)); MOCK_METHOD2(deriveNewKey, KeyResult(size_t keySize, const string& password)); }; class CryPasswordBasedKeyProviderTest : public ::testing::Test { public: CryPasswordBasedKeyProviderTest() : mockConsole(make_shared>()) , askPasswordForNewFilesystem() , askPasswordForExistingFilesystem() , kdf_(make_unique_ref()) , kdf(kdf_.get()) , keyProvider(mockConsole, [this] () {return askPasswordForExistingFilesystem.call();}, [this] () {return askPasswordForNewFilesystem.call(); }, std::move(kdf_)) {} shared_ptr> mockConsole; MockCallable askPasswordForNewFilesystem; MockCallable askPasswordForExistingFilesystem; unique_ref kdf_; MockKDF* kdf; CryPasswordBasedKeyProvider keyProvider; }; TEST_F(CryPasswordBasedKeyProviderTest, requestKeyForNewFilesystem) { constexpr size_t keySize = 512; constexpr const char* password = "mypassword"; const EncryptionKey key = EncryptionKey::FromString(DataFixture::generate(keySize).ToString()); const Data kdfParameters = DataFixture::generate(100); EXPECT_CALL(askPasswordForNewFilesystem, call()).Times(1).WillOnce(Return(password)); EXPECT_CALL(askPasswordForExistingFilesystem, call()).Times(0); EXPECT_CALL(*kdf, deriveNewKey(Eq(keySize), StrEq(password))).Times(1).WillOnce(Invoke([&] (auto, auto) {return PasswordBasedKDF::KeyResult{key, kdfParameters.copy()};})); auto returned_key = keyProvider.requestKeyForNewFilesystem(keySize); EXPECT_EQ(key.ToString(), returned_key.key.ToString()); EXPECT_EQ(kdfParameters, returned_key.kdfParameters); } TEST_F(CryPasswordBasedKeyProviderTest, requestKeyForExistingFilesystem) { constexpr size_t keySize = 512; constexpr const char* password = "mypassword"; const EncryptionKey key = EncryptionKey::FromString(DataFixture::generate(keySize).ToString()); const Data kdfParameters = DataFixture::generate(100); EXPECT_CALL(askPasswordForNewFilesystem, call()).Times(0); EXPECT_CALL(askPasswordForExistingFilesystem, call()).Times(1).WillOnce(Return(password)); EXPECT_CALL(*kdf, deriveExistingKey(Eq(keySize), StrEq(password), _)).Times(1).WillOnce(Invoke([&] (auto, auto, const auto& kdfParams) { EXPECT_EQ(kdfParameters, kdfParams); return key; })); EncryptionKey returned_key = keyProvider.requestKeyForExistingFilesystem(keySize, kdfParameters); EXPECT_EQ(key.ToString(), returned_key.ToString()); } test/cryfs/config/CryPresetPasswordBasedKeyProviderTest.cpp000066400000000000000000000044631347701267100246010ustar00rootroot00000000000000#include #include #include "../testutils/MockConsole.h" #include using cpputils::make_unique_ref; using cpputils::EncryptionKey; using cpputils::PasswordBasedKDF; using cpputils::Data; using cpputils::DataFixture; using std::string; using cryfs::CryPresetPasswordBasedKeyProvider; using testing::Invoke; using testing::Eq; using testing::StrEq; using testing::_; class MockKDF : public PasswordBasedKDF { public: MOCK_METHOD3(deriveExistingKey, EncryptionKey(size_t keySize, const string& password, const Data& kdfParameters)); MOCK_METHOD2(deriveNewKey, KeyResult(size_t keySize, const string& password)); }; TEST(CryPresetPasswordBasedKeyProviderTest, requestKeyForNewFilesystem) { constexpr size_t keySize = 512; constexpr const char* password = "mypassword"; const EncryptionKey key = EncryptionKey::FromString(DataFixture::generate(keySize).ToString()); auto kdf = make_unique_ref(); const Data kdfParameters = DataFixture::generate(100); EXPECT_CALL(*kdf, deriveNewKey(Eq(keySize), StrEq(password))).Times(1).WillOnce(Invoke([&] (auto, auto) {return PasswordBasedKDF::KeyResult{key, kdfParameters.copy()};})); CryPresetPasswordBasedKeyProvider keyProvider(password, std::move(kdf)); auto returned_key = keyProvider.requestKeyForNewFilesystem(keySize); EXPECT_EQ(key.ToString(), returned_key.key.ToString()); EXPECT_EQ(kdfParameters, returned_key.kdfParameters); } TEST(CryPresetPasswordBasedKeyProviderTest, requestKeyForExistingFilesystem) { constexpr size_t keySize = 512; constexpr const char* password = "mypassword"; const EncryptionKey key = EncryptionKey::FromString(DataFixture::generate(keySize).ToString()); auto kdf = make_unique_ref(); const Data kdfParameters = DataFixture::generate(100); EXPECT_CALL(*kdf, deriveExistingKey(Eq(keySize), StrEq(password), _)).Times(1).WillOnce(Invoke([&] (auto, auto, const auto& kdfParams) { EXPECT_EQ(kdfParameters, kdfParams); return key; })); CryPresetPasswordBasedKeyProvider keyProvider(password, std::move(kdf)); EncryptionKey returned_key = keyProvider.requestKeyForExistingFilesystem(keySize, kdfParameters); EXPECT_EQ(key.ToString(), returned_key.ToString()); } test/cryfs/config/crypto/000077500000000000000000000000001347701267100157605ustar00rootroot00000000000000test/cryfs/config/crypto/CryConfigEncryptorFactoryTest.cpp000066400000000000000000000055401347701267100244510ustar00rootroot00000000000000#include #include #include #include #include "../../testutils/FakeCryKeyProvider.h" using cpputils::AES256_GCM; using cpputils::Data; using cpputils::DataFixture; using boost::none; using std::ostream; using namespace cryfs; // This is needed for google test namespace boost { inline ostream &operator<<(ostream &stream, const CryConfigEncryptor::Decrypted &) { return stream << "CryConfigEncryptor::Decrypted()"; } } #include class CryConfigEncryptorFactoryTest: public ::testing::Test { public: }; TEST_F(CryConfigEncryptorFactoryTest, EncryptAndDecrypt_SameEncryptor) { FakeCryKeyProvider keyProvider; auto encryptor = CryConfigEncryptorFactory::deriveNewKey(&keyProvider); Data encrypted = encryptor->encrypt(DataFixture::generate(400), AES256_GCM::NAME); auto decrypted = encryptor->decrypt(encrypted).value(); EXPECT_EQ(DataFixture::generate(400), decrypted.data); } TEST_F(CryConfigEncryptorFactoryTest, EncryptAndDecrypt_NewEncryptor) { FakeCryKeyProvider keyProvider1(1); auto encryptor = CryConfigEncryptorFactory::deriveNewKey(&keyProvider1); Data encrypted = encryptor->encrypt(DataFixture::generate(400), AES256_GCM::NAME); FakeCryKeyProvider keyProvider2(1); auto loadedEncryptor = CryConfigEncryptorFactory::loadExistingKey(encrypted, &keyProvider2).value(); auto decrypted = loadedEncryptor->decrypt(encrypted).value(); EXPECT_EQ(DataFixture::generate(400), decrypted.data); } TEST_F(CryConfigEncryptorFactoryTest, DoesntDecryptWithWrongKey) { FakeCryKeyProvider keyProvider1(1); auto encryptor = CryConfigEncryptorFactory::deriveNewKey(&keyProvider1); Data encrypted = encryptor->encrypt(DataFixture::generate(400), AES256_GCM::NAME); FakeCryKeyProvider keyProvider2(2); auto loadedEncryptor = CryConfigEncryptorFactory::loadExistingKey(encrypted, &keyProvider2).value(); auto decrypted = loadedEncryptor->decrypt(encrypted); EXPECT_EQ(none, decrypted); } TEST_F(CryConfigEncryptorFactoryTest, DoesntDecryptWithWrongKey_EmptyData) { FakeCryKeyProvider keyProvider1(1); auto encryptor = CryConfigEncryptorFactory::deriveNewKey(&keyProvider1); Data encrypted = encryptor->encrypt(Data(0), AES256_GCM::NAME); FakeCryKeyProvider keyProvider2(2); auto loadedEncryptor = CryConfigEncryptorFactory::loadExistingKey(encrypted, &keyProvider2).value(); auto decrypted = loadedEncryptor->decrypt(encrypted); EXPECT_EQ(none, decrypted); } TEST_F(CryConfigEncryptorFactoryTest, DoesntDecryptInvalidData) { FakeCryKeyProvider keyProvider; auto loadedEncryptor = CryConfigEncryptorFactory::loadExistingKey(Data(0), &keyProvider); EXPECT_EQ(none, loadedEncryptor); } test/cryfs/config/crypto/CryConfigEncryptorTest.cpp000066400000000000000000000123101347701267100231120ustar00rootroot00000000000000#include #include #include #include using std::ostream; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::DataFixture; using cpputils::Data; using cpputils::EncryptionKey; using cpputils::AES128_CFB; using cpputils::AES256_GCM; using cpputils::Twofish256_GCM; using cpputils::Twofish128_CFB; using cpputils::serialize; using cpputils::deserialize; using boost::none; using namespace cryfs; // This is needed for google test namespace boost { inline ostream &operator<<(ostream &stream, const CryConfigEncryptor::Decrypted &) { return stream << "CryConfigEncryptor::Decrypted()"; } } #include class CryConfigEncryptorTest: public ::testing::Test { public: unique_ref makeEncryptor() { return make_unique_ref(_derivedKey(), _kdfParameters()); } Data changeInnerCipherFieldTo(Data data, const string &newCipherName) { InnerConfig innerConfig = _decryptInnerConfig(data); innerConfig.cipherName = newCipherName; return _encryptInnerConfig(innerConfig); } private: EncryptionKey _derivedKey() { return EncryptionKey::FromString( DataFixture::generateFixedSize(3).ToString() ); } Data _kdfParameters() { return DataFixture::generate(128, 2); } unique_ref _outerEncryptor() { auto outerKey = _derivedKey().take(CryConfigEncryptor::OuterKeySize); return make_unique_ref(outerKey, _kdfParameters()); } InnerConfig _decryptInnerConfig(const Data &data) { OuterConfig outerConfig = OuterConfig::deserialize(data).value(); Data serializedInnerConfig = _outerEncryptor()->decrypt(outerConfig).value(); return InnerConfig::deserialize(serializedInnerConfig).value(); } Data _encryptInnerConfig(const InnerConfig &innerConfig) { Data serializedInnerConfig = innerConfig.serialize(); OuterConfig outerConfig = _outerEncryptor()->encrypt(serializedInnerConfig); return outerConfig.serialize(); } }; TEST_F(CryConfigEncryptorTest, EncryptAndDecrypt_Data_AES) { auto encryptor = makeEncryptor(); Data encrypted = encryptor->encrypt(DataFixture::generate(400), AES256_GCM::NAME); auto decrypted = encryptor->decrypt(encrypted).value(); EXPECT_EQ(DataFixture::generate(400), decrypted.data); } TEST_F(CryConfigEncryptorTest, EncryptAndDecrypt_Data_Twofish) { auto encryptor = makeEncryptor(); Data encrypted = encryptor->encrypt(DataFixture::generate(400), Twofish128_CFB::NAME); auto decrypted = encryptor->decrypt(encrypted).value(); EXPECT_EQ(DataFixture::generate(400), decrypted.data); } TEST_F(CryConfigEncryptorTest, EncryptAndDecrypt_Cipher_AES) { auto encryptor = makeEncryptor(); Data encrypted = encryptor->encrypt(DataFixture::generate(400), AES256_GCM::NAME); auto decrypted = encryptor->decrypt(encrypted).value(); EXPECT_EQ(AES256_GCM::NAME, decrypted.cipherName); } TEST_F(CryConfigEncryptorTest, EncryptAndDecrypt_Cipher_Twofish) { auto encryptor = makeEncryptor(); Data encrypted = encryptor->encrypt(DataFixture::generate(400), Twofish128_CFB::NAME); auto decrypted = encryptor->decrypt(encrypted).value(); EXPECT_EQ(Twofish128_CFB::NAME, decrypted.cipherName); } TEST_F(CryConfigEncryptorTest, EncryptAndDecrypt_EmptyData) { auto encryptor = makeEncryptor(); Data encrypted = encryptor->encrypt(Data(0), AES256_GCM::NAME); auto decrypted = encryptor->decrypt(encrypted).value(); EXPECT_EQ(Data(0), decrypted.data); } TEST_F(CryConfigEncryptorTest, InvalidCiphertext) { auto encryptor = makeEncryptor(); Data encrypted = encryptor->encrypt(DataFixture::generate(400), AES256_GCM::NAME); serialize(encrypted.data(), deserialize(encrypted.data()) + 1); //Modify ciphertext auto decrypted = encryptor->decrypt(encrypted); EXPECT_EQ(none, decrypted); } TEST_F(CryConfigEncryptorTest, DoesntEncryptWhenTooLarge) { auto encryptor = makeEncryptor(); EXPECT_THROW( encryptor->encrypt(DataFixture::generate(2000), AES256_GCM::NAME), std::runtime_error ); } TEST_F(CryConfigEncryptorTest, EncryptionIsFixedSize) { auto encryptor = makeEncryptor(); Data encrypted1 = encryptor->encrypt(DataFixture::generate(100), AES128_CFB::NAME); Data encrypted2 = encryptor->encrypt(DataFixture::generate(200), Twofish256_GCM::NAME); Data encrypted3 = encryptor->encrypt(Data(0), AES256_GCM::NAME); EXPECT_EQ(encrypted1.size(), encrypted2.size()); EXPECT_EQ(encrypted1.size(), encrypted3.size()); } TEST_F(CryConfigEncryptorTest, SpecifiedInnerCipherIsUsed) { //Tests that it can't be decrypted if the inner cipher field stores the wrong cipher auto encryptor = makeEncryptor(); Data encrypted = encryptor->encrypt(DataFixture::generate(400), AES256_GCM::NAME); encrypted = changeInnerCipherFieldTo(std::move(encrypted), Twofish256_GCM::NAME); auto decrypted = encryptor->decrypt(encrypted); EXPECT_EQ(none, decrypted); } test/cryfs/config/crypto/inner/000077500000000000000000000000001347701267100170735ustar00rootroot00000000000000test/cryfs/config/crypto/inner/ConcreteInnerEncryptorTest.cpp000066400000000000000000000065531347701267100251140ustar00rootroot00000000000000#include #include #include #include using std::ostream; using boost::none; using cpputils::Data; using cpputils::DataFixture; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::AES256_GCM; using cpputils::AES256_CFB; using cpputils::Twofish128_CFB; using cpputils::serialize; using cpputils::deserialize; using namespace cryfs; // This is needed for google test namespace boost { inline ostream &operator<<(ostream &stream, const Data &) { return stream << "cpputils::Data()"; } } #include class ConcreteInnerEncryptorTest : public ::testing::Test { public: template unique_ref makeInnerEncryptor() { auto key = Cipher::EncryptionKey::FromString( DataFixture::generateFixedSize().ToString() ); return make_unique_ref>(key); } }; TEST_F(ConcreteInnerEncryptorTest, EncryptAndDecrypt_AES) { auto encryptor = makeInnerEncryptor(); InnerConfig encrypted = encryptor->encrypt(DataFixture::generate(200)); Data decrypted = encryptor->decrypt(encrypted).value(); EXPECT_EQ(DataFixture::generate(200), decrypted); } TEST_F(ConcreteInnerEncryptorTest, EncryptAndDecrypt_Twofish) { auto encryptor = makeInnerEncryptor(); InnerConfig encrypted = encryptor->encrypt(DataFixture::generate(200)); Data decrypted = encryptor->decrypt(encrypted).value(); EXPECT_EQ(DataFixture::generate(200), decrypted); } TEST_F(ConcreteInnerEncryptorTest, EncryptAndDecrypt_EmptyData) { auto encryptor = makeInnerEncryptor(); InnerConfig encrypted = encryptor->encrypt(Data(0)); Data decrypted = encryptor->decrypt(encrypted).value(); EXPECT_EQ(Data(0), decrypted); } TEST_F(ConcreteInnerEncryptorTest, DoesntDecryptWithWrongCipherName) { auto encryptor = makeInnerEncryptor(); InnerConfig encrypted = encryptor->encrypt(Data(0)); encrypted.cipherName = AES256_CFB::NAME; auto decrypted = encryptor->decrypt(encrypted); EXPECT_EQ(none, decrypted); } TEST_F(ConcreteInnerEncryptorTest, InvalidCiphertext) { auto encryptor = makeInnerEncryptor(); InnerConfig encrypted = encryptor->encrypt(DataFixture::generate(200)); serialize(encrypted.encryptedConfig.data(), deserialize(encrypted.encryptedConfig.data()) + 1); //Modify ciphertext auto decrypted = encryptor->decrypt(encrypted); EXPECT_EQ(none, decrypted); } TEST_F(ConcreteInnerEncryptorTest, DoesntEncryptWhenTooLarge) { auto encryptor = makeInnerEncryptor(); EXPECT_THROW( encryptor->encrypt(DataFixture::generate(2000)), std::runtime_error ); } TEST_F(ConcreteInnerEncryptorTest, EncryptionIsFixedSize) { auto encryptor = makeInnerEncryptor(); InnerConfig encrypted1 = encryptor->encrypt(DataFixture::generate(100)); InnerConfig encrypted2 = encryptor->encrypt(DataFixture::generate(200)); InnerConfig encrypted3 = encryptor->encrypt(Data(0)); EXPECT_EQ(encrypted1.encryptedConfig.size(), encrypted2.encryptedConfig.size()); EXPECT_EQ(encrypted1.encryptedConfig.size(), encrypted3.encryptedConfig.size()); } test/cryfs/config/crypto/inner/InnerConfigTest.cpp000066400000000000000000000035331347701267100226440ustar00rootroot00000000000000#include #include #include using cpputils::Data; using cpputils::DataFixture; using boost::none; using std::ostream; using namespace cryfs; // This is needed for google test namespace boost { ostream &operator<<(ostream &stream, const InnerConfig &config) { return stream << "InnerConfig(" << config.cipherName << ", [data])"; } } #include TEST(InnerConfigTest, SomeValues) { Data serialized = InnerConfig{"myciphername", DataFixture::generate(1024)}.serialize(); InnerConfig deserialized = InnerConfig::deserialize(serialized).value(); EXPECT_EQ("myciphername", deserialized.cipherName); EXPECT_EQ(DataFixture::generate(1024), deserialized.encryptedConfig); } TEST(InnerConfigTest, DataEmpty) { Data serialized = InnerConfig{"myciphername", Data(0)}.serialize(); InnerConfig deserialized = InnerConfig::deserialize(serialized).value(); EXPECT_EQ("myciphername", deserialized.cipherName); EXPECT_EQ(Data(0), deserialized.encryptedConfig); } TEST(InnerConfigTest, CipherNameEmpty) { Data serialized = InnerConfig{"", DataFixture::generate(1024)}.serialize(); InnerConfig deserialized = InnerConfig::deserialize(serialized).value(); EXPECT_EQ("", deserialized.cipherName); EXPECT_EQ(DataFixture::generate(1024), deserialized.encryptedConfig); } TEST(InnerConfigTest, DataAndCipherNameEmpty) { Data serialized = InnerConfig{"", Data(0)}.serialize(); InnerConfig deserialized = InnerConfig::deserialize(serialized).value(); EXPECT_EQ("", deserialized.cipherName); EXPECT_EQ(Data(0), deserialized.encryptedConfig); } TEST(InnerConfigTest, InvalidSerialization) { auto deserialized = InnerConfig::deserialize(DataFixture::generate(1024)); EXPECT_EQ(none, deserialized); } test/cryfs/config/crypto/outer/000077500000000000000000000000001347701267100171165ustar00rootroot00000000000000test/cryfs/config/crypto/outer/OuterConfigTest.cpp000066400000000000000000000040311347701267100227040ustar00rootroot00000000000000#include #include #include using cpputils::Data; using cpputils::DataFixture; using boost::none; using std::ostream; using namespace cryfs; // This is needed for google test namespace boost { ostream &operator<<(ostream &stream, const OuterConfig &) { return stream << "OuterConfig()"; } } #include class OuterConfigTest: public ::testing::Test { public: Data kdfParameters() { return DataFixture::generate(128, 2); } }; TEST_F(OuterConfigTest, SomeValues) { Data serialized = OuterConfig{kdfParameters(), DataFixture::generate(1024), false}.serialize(); OuterConfig deserialized = OuterConfig::deserialize(serialized).value(); EXPECT_EQ(kdfParameters(), deserialized.kdfParameters); EXPECT_EQ(DataFixture::generate(1024), deserialized.encryptedInnerConfig); } TEST_F(OuterConfigTest, DataEmpty) { Data serialized = OuterConfig{kdfParameters(), Data(0), false}.serialize(); OuterConfig deserialized = OuterConfig::deserialize(serialized).value(); EXPECT_EQ(kdfParameters(), deserialized.kdfParameters); EXPECT_EQ(Data(0), deserialized.encryptedInnerConfig); } TEST_F(OuterConfigTest, KeyConfigEmpty) { Data serialized = OuterConfig{Data(0), DataFixture::generate(1024), false}.serialize(); OuterConfig deserialized = OuterConfig::deserialize(serialized).value(); EXPECT_EQ(Data(0), deserialized.kdfParameters); EXPECT_EQ(DataFixture::generate(1024), deserialized.encryptedInnerConfig); } TEST_F(OuterConfigTest, DataAndKeyConfigEmpty) { Data serialized = OuterConfig{Data(0), Data(0), false}.serialize(); OuterConfig deserialized = OuterConfig::deserialize(serialized).value(); EXPECT_EQ(Data(0), deserialized.kdfParameters); EXPECT_EQ(Data(0), deserialized.encryptedInnerConfig); } TEST_F(OuterConfigTest, InvalidSerialization) { auto deserialized = OuterConfig::deserialize(DataFixture::generate(1024)); EXPECT_EQ(none, deserialized); } test/cryfs/config/crypto/outer/OuterEncryptorTest.cpp000066400000000000000000000052211347701267100234660ustar00rootroot00000000000000#include #include #include #include using std::ostream; using boost::none; using cpputils::Data; using cpputils::DataFixture; using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::serialize; using cpputils::deserialize; using namespace cryfs; // This is needed for google test namespace boost { inline ostream &operator<<(ostream &stream, const Data &) { return stream << "cpputils::Data()"; } } #include class OuterEncryptorTest : public ::testing::Test { public: Data kdfParameters() { return DataFixture::generate(128); } unique_ref makeOuterEncryptor() { auto key = OuterEncryptor::Cipher::EncryptionKey::FromString( DataFixture::generateFixedSize().ToString() ); return make_unique_ref(key, kdfParameters()); } }; TEST_F(OuterEncryptorTest, EncryptAndDecrypt) { auto encryptor = makeOuterEncryptor(); OuterConfig encrypted = encryptor->encrypt(DataFixture::generate(200)); Data decrypted = encryptor->decrypt(encrypted).value(); EXPECT_EQ(DataFixture::generate(200), decrypted); } TEST_F(OuterEncryptorTest, EncryptAndDecrypt_EmptyData) { auto encryptor = makeOuterEncryptor(); OuterConfig encrypted = encryptor->encrypt(Data(0)); Data decrypted = encryptor->decrypt(encrypted).value(); EXPECT_EQ(Data(0), decrypted); } TEST_F(OuterEncryptorTest, InvalidCiphertext) { auto encryptor = makeOuterEncryptor(); OuterConfig encrypted = encryptor->encrypt(DataFixture::generate(200)); serialize(encrypted.encryptedInnerConfig.data(), deserialize(encrypted.encryptedInnerConfig.data()) + 1); //Modify ciphertext auto decrypted = encryptor->decrypt(encrypted); EXPECT_EQ(none, decrypted); } TEST_F(OuterEncryptorTest, DoesntEncryptWhenTooLarge) { auto encryptor = makeOuterEncryptor(); EXPECT_THROW( encryptor->encrypt(DataFixture::generate(2000)), std::runtime_error ); } TEST_F(OuterEncryptorTest, EncryptionIsFixedSize) { auto encryptor = makeOuterEncryptor(); OuterConfig encrypted1 = encryptor->encrypt(DataFixture::generate(200)); OuterConfig encrypted2 = encryptor->encrypt(DataFixture::generate(700)); OuterConfig encrypted3 = encryptor->encrypt(Data(0)); EXPECT_EQ(encrypted1.encryptedInnerConfig.size(), encrypted2.encryptedInnerConfig.size()); EXPECT_EQ(encrypted1.encryptedInnerConfig.size(), encrypted3.encryptedInnerConfig.size()); } test/cryfs/filesystem/000077500000000000000000000000001347701267100153575ustar00rootroot00000000000000test/cryfs/filesystem/CryFsTest.cpp000066400000000000000000000057141347701267100177600ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "../testutils/MockConsole.h" #include #include #include #include "../testutils/TestWithFakeHomeDirectory.h" #include //TODO (whole project) Make constructors explicit when implicit construction not needed using ::testing::Test; using std::make_shared; using cpputils::TempDir; using cpputils::TempFile; using cpputils::make_unique_ref; using cpputils::unique_ref; using cpputils::Random; using cpputils::SCrypt; using cpputils::Data; using cpputils::NoninteractiveConsole; using blockstore::ondisk::OnDiskBlockStore2; using boost::none; using cryfs::CryPresetPasswordBasedKeyProvider; namespace bf = boost::filesystem; using namespace cryfs; namespace { class CryFsTest: public Test, public TestWithMockConsole, public TestWithFakeHomeDirectory { public: CryFsTest(): tempLocalStateDir(), localStateDir(tempLocalStateDir.path()), rootdir(), config(false) { } CryConfigFile loadOrCreateConfig() { auto keyProvider = make_unique_ref("mypassword", make_unique_ref(SCrypt::TestSettings)); return CryConfigLoader(make_shared(mockConsole()), Random::PseudoRandom(), std::move(keyProvider), localStateDir, none, none, none).loadOrCreate(config.path(), false, false).value().configFile; } unique_ref blockStore() { return make_unique_ref(rootdir.path()); } cpputils::TempDir tempLocalStateDir; LocalStateDir localStateDir; TempDir rootdir; TempFile config; }; auto failOnIntegrityViolation() { return [] { EXPECT_TRUE(false); }; } TEST_F(CryFsTest, CreatedRootdirIsLoadableAfterClosing) { { CryDevice dev(loadOrCreateConfig(), blockStore(), localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); } CryDevice dev(loadOrCreateConfig(), blockStore(), localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); auto rootDir = dev.LoadDir(bf::path("/")); rootDir.value()->children(); } TEST_F(CryFsTest, LoadingFilesystemDoesntModifyConfigFile) { { CryDevice dev(loadOrCreateConfig(), blockStore(), localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); } Data configAfterCreating = Data::LoadFromFile(config.path()).value(); { CryDevice dev(loadOrCreateConfig(), blockStore(), localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); } Data configAfterLoading = Data::LoadFromFile(config.path()).value(); EXPECT_EQ(configAfterCreating, configAfterLoading); } } test/cryfs/filesystem/CryNodeTest.cpp000066400000000000000000000072151347701267100202730ustar00rootroot00000000000000#include #include "testutils/CryTestBase.h" #include #include #include using cpputils::unique_ref; using cpputils::dynamic_pointer_move; using namespace cryfs; namespace bf = boost::filesystem; // Many generic (black box) test cases for FsppNode are covered in Fspp fstest. // This class adds some tests that need insight into how CryFS works. class CryNodeTest : public ::testing::Test, public CryTestBase { public: static constexpr fspp::mode_t MODE_PUBLIC = fspp::mode_t() .addUserReadFlag().addUserWriteFlag().addUserExecFlag() .addGroupReadFlag().addGroupWriteFlag().addGroupExecFlag() .addOtherReadFlag().addOtherWriteFlag().addOtherExecFlag(); unique_ref CreateFile(const bf::path &path) { auto parentDir = device().LoadDir(path.parent_path()).value(); parentDir->createAndOpenFile(path.filename().string(), MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); auto file = device().Load(path).value(); return dynamic_pointer_move(file).value(); } unique_ref CreateDir(const bf::path &path) { auto _parentDir = device().Load(path.parent_path()).value(); auto parentDir = dynamic_pointer_move(_parentDir).value(); parentDir->createDir(path.filename().string(), MODE_PUBLIC, fspp::uid_t(0), fspp::gid_t(0)); auto createdDir = device().Load(path).value(); return dynamic_pointer_move(createdDir).value(); } unique_ref CreateSymlink(const bf::path &path) { auto _parentDir = device().Load(path.parent_path()).value(); auto parentDir = dynamic_pointer_move(_parentDir).value(); parentDir->createSymlink(path.filename().string(), "/target", fspp::uid_t(0), fspp::gid_t(0)); auto createdSymlink = device().Load(path).value(); return dynamic_pointer_move(createdSymlink).value(); } }; constexpr fspp::mode_t CryNodeTest::MODE_PUBLIC; TEST_F(CryNodeTest, Rename_DoesntLeaveBlocksOver) { auto node = CreateFile("/oldname"); EXPECT_EQ(2u, device().numBlocks()); // In the beginning, there is two blocks (the root block and the created file). If that is not true anymore, we'll have to adapt the test case. node->rename("/newname"); EXPECT_EQ(2u, device().numBlocks()); // Still same number of blocks } // TODO Add similar test cases (i.e. checking number of blocks) for other situations in rename, and also for other operations (e.g. deleting files). TEST_F(CryNodeTest, Rename_Overwrite_DoesntLeaveBlocksOver) { auto node = CreateFile("/oldname"); CreateFile("/newexistingname"); EXPECT_EQ(3u, device().numBlocks()); // In the beginning, there is three blocks (the root block and the two created files). If that is not true anymore, we'll have to adapt the test case. node->rename("/newexistingname"); EXPECT_EQ(2u, device().numBlocks()); // Only the blocks of one file are left } TEST_F(CryNodeTest, Rename_UpdatesParentPointers_File) { this->CreateDir("/mydir"); auto node = this->CreateFile("/oldname"); node->rename("/mydir/newname"); EXPECT_TRUE(node->checkParentPointer()); } TEST_F(CryNodeTest, Rename_UpdatesParentPointers_Dir) { this->CreateDir("/mydir"); auto node = this->CreateDir("/oldname"); node->rename("/mydir/newname"); EXPECT_TRUE(node->checkParentPointer()); } TEST_F(CryNodeTest, Rename_UpdatesParentPointers_Symlink) { this->CreateDir("/mydir"); auto node = this->CreateSymlink("/oldname"); node->rename("/mydir/newname"); EXPECT_TRUE(node->checkParentPointer()); } test/cryfs/filesystem/FileSystemTest.cpp000066400000000000000000000036651347701267100210210ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "../testutils/MockConsole.h" #include "../testutils/TestWithFakeHomeDirectory.h" using cpputils::unique_ref; using cpputils::make_unique_ref; using cpputils::Random; using cpputils::SCrypt; using cpputils::NoninteractiveConsole; using fspp::Device; using boost::none; using std::make_shared; using blockstore::inmemory::InMemoryBlockStore2; using cryfs::CryPresetPasswordBasedKeyProvider; using namespace cryfs; namespace { auto failOnIntegrityViolation() { return [] { EXPECT_TRUE(false); }; } class CryFsTestFixture: public FileSystemTestFixture, public TestWithMockConsole, public TestWithFakeHomeDirectory { public: CryFsTestFixture() // Don't create config tempfile yet : tempLocalStateDir(), localStateDir(tempLocalStateDir.path()), configFile(false) {} unique_ref createDevice() override { auto blockStore = cpputils::make_unique_ref(); auto _console = make_shared(mockConsole()); auto keyProvider = make_unique_ref("mypassword", make_unique_ref(SCrypt::TestSettings)); auto config = CryConfigLoader(_console, Random::PseudoRandom(), std::move(keyProvider), localStateDir, none, none, none) .loadOrCreate(configFile.path(), false, false).value(); return make_unique_ref(std::move(config.configFile), std::move(blockStore), localStateDir, config.myClientId, false, false, failOnIntegrityViolation()); } cpputils::TempDir tempLocalStateDir; LocalStateDir localStateDir; cpputils::TempFile configFile; }; FSPP_ADD_FILESYTEM_TESTS(CryFS, CryFsTestFixture); } test/cryfs/filesystem/testutils/000077500000000000000000000000001347701267100174175ustar00rootroot00000000000000test/cryfs/filesystem/testutils/CryTestBase.h000066400000000000000000000034511347701267100217630ustar00rootroot00000000000000#ifndef MESSMER_CRYFS_TEST_CRYFS_FILESYSTEM_CRYTESTBASE_H #define MESSMER_CRYFS_TEST_CRYFS_FILESYSTEM_CRYTESTBASE_H #include #include #include #include #include #include "../../testutils/TestWithFakeHomeDirectory.h" #include "../../testutils/MockConsole.h" inline auto failOnIntegrityViolation() { return [] { EXPECT_TRUE(false); }; } class CryTestBase : public TestWithFakeHomeDirectory { public: CryTestBase(): _tempLocalStateDir(), _localStateDir(_tempLocalStateDir.path()), _configFile(false), _device(nullptr) { auto fakeBlockStore = cpputils::make_unique_ref(); _device = std::make_unique(configFile(), std::move(fakeBlockStore), _localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); } cryfs::CryConfigFile configFile() { cryfs::CryConfig config; config.SetCipher("aes-256-gcm"); config.SetEncryptionKey(cpputils::AES256_GCM::EncryptionKey::CreateKey(cpputils::Random::PseudoRandom(), cpputils::AES256_GCM::KEYSIZE).ToString()); config.SetBlocksizeBytes(10240); cryfs::CryPresetPasswordBasedKeyProvider keyProvider("mypassword", cpputils::make_unique_ref(cpputils::SCrypt::TestSettings)); return cryfs::CryConfigFile::create(_configFile.path(), std::move(config), &keyProvider); } cryfs::CryDevice &device() { return *_device; } private: cpputils::TempDir _tempLocalStateDir; cryfs::LocalStateDir _localStateDir; cpputils::TempFile _configFile; std::unique_ptr _device; }; #endif test/cryfs/localstate/000077500000000000000000000000001347701267100153265ustar00rootroot00000000000000test/cryfs/localstate/BasedirMetadataTest.cpp000066400000000000000000000056541347701267100217160ustar00rootroot00000000000000#include #include #include #include #include #include "../testutils/TestWithFakeHomeDirectory.h" using cpputils::TempDir; using cryfs::BasedirMetadata; using std::ofstream; namespace bf = boost::filesystem; using FilesystemID = cryfs::CryConfig::FilesystemID ; class BasedirMetadataTest : public ::testing::Test, TestWithFakeHomeDirectory { public: TempDir tempLocalStateDir; cryfs::LocalStateDir localStateDir; TempDir tempdir; bf::path basedir1; bf::path basedir2; const FilesystemID id1; const FilesystemID id2; BasedirMetadataTest() : tempLocalStateDir() , localStateDir(tempLocalStateDir.path()) , tempdir() , basedir1(tempdir.path() / "my/basedir") , basedir2(tempdir.path() / "my/other/basedir") , id1(FilesystemID::FromString("1491BB4932A389EE14BC7090AC772972")) , id2(FilesystemID::FromString("A1491BB493214BC7090C772972A389EE")) { // Create basedirs so bf::canonical() works bf::create_directories(basedir1); bf::create_directories(basedir2); } }; TEST_F(BasedirMetadataTest, givenEmptyState_whenCalled_thenSucceeds) { EXPECT_TRUE(BasedirMetadata::load(localStateDir).filesystemIdForBasedirIsCorrect(basedir1, id1)); } TEST_F(BasedirMetadataTest, givenStateWithBasedir_whenCalledForDifferentBasedir_thenSucceeds) { BasedirMetadata::load(localStateDir).updateFilesystemIdForBasedir(basedir2, id1).save(); EXPECT_TRUE(BasedirMetadata::load(localStateDir).filesystemIdForBasedirIsCorrect(basedir1, id1)); } TEST_F(BasedirMetadataTest, givenStateWithBasedir_whenCalledWithSameId_thenSucceeds) { BasedirMetadata::load(localStateDir).updateFilesystemIdForBasedir(basedir1, id1).save(); EXPECT_TRUE(BasedirMetadata::load(localStateDir).filesystemIdForBasedirIsCorrect(basedir1, id1)); } TEST_F(BasedirMetadataTest, givenStateWithBasedir_whenCalledWithDifferentId_thenFails) { BasedirMetadata::load(localStateDir).updateFilesystemIdForBasedir(basedir1, id2).save(); EXPECT_FALSE(BasedirMetadata::load(localStateDir).filesystemIdForBasedirIsCorrect(basedir1, id1)); } TEST_F(BasedirMetadataTest, givenStateWithUpdatedBasedir_whenCalledWithSameId_thenSucceeds) { BasedirMetadata::load(localStateDir).updateFilesystemIdForBasedir(basedir1, id2).save(); BasedirMetadata::load(localStateDir).updateFilesystemIdForBasedir(basedir1, id1).save(); EXPECT_TRUE(BasedirMetadata::load(localStateDir).filesystemIdForBasedirIsCorrect(basedir1, id1)); } TEST_F(BasedirMetadataTest, givenStateWithUpdatedBasedir_whenCalledWithDifferentId_thenFails) { BasedirMetadata::load(localStateDir).updateFilesystemIdForBasedir(basedir1, id2).save(); BasedirMetadata::load(localStateDir).updateFilesystemIdForBasedir(basedir1, id1).save(); EXPECT_FALSE(BasedirMetadata::load(localStateDir).filesystemIdForBasedirIsCorrect(basedir1, id2)); } test/cryfs/localstate/LocalStateMetadataTest.cpp000066400000000000000000000040141347701267100223650ustar00rootroot00000000000000#include #include #include #include #include using cpputils::TempDir; using cpputils::Data; using cryfs::LocalStateMetadata; using cpputils::DataFixture; using std::ofstream; class LocalStateMetadataTest : public ::testing::Test { public: TempDir stateDir; TempDir stateDir2; }; TEST_F(LocalStateMetadataTest, myClientId_ValueIsConsistent) { LocalStateMetadata metadata1 = LocalStateMetadata::loadOrGenerate(stateDir.path(), Data(0), false); LocalStateMetadata metadata2 = LocalStateMetadata::loadOrGenerate(stateDir.path(), Data(0), false); EXPECT_EQ(metadata1.myClientId(), metadata2.myClientId()); } TEST_F(LocalStateMetadataTest, myClientId_ValueIsRandomForNewClient) { LocalStateMetadata metadata1 = LocalStateMetadata::loadOrGenerate(stateDir.path(), Data(0), false); LocalStateMetadata metadata2 = LocalStateMetadata::loadOrGenerate(stateDir2.path(), Data(0), false); EXPECT_NE(metadata1.myClientId(), metadata2.myClientId()); } #ifndef CRYFS_NO_COMPATIBILITY TEST_F(LocalStateMetadataTest, myClientId_TakesLegacyValueIfSpecified) { ofstream file((stateDir.path() / "myClientId").string()); file << 12345u; file.close(); LocalStateMetadata metadata = LocalStateMetadata::loadOrGenerate(stateDir.path(), Data(0), false); EXPECT_EQ(12345u, metadata.myClientId()); } #endif TEST_F(LocalStateMetadataTest, encryptionKeyHash_whenLoadingWithSameKey_thenDoesntCrash) { LocalStateMetadata::loadOrGenerate(stateDir.path(), DataFixture::generate(1024), false); LocalStateMetadata::loadOrGenerate(stateDir.path(), DataFixture::generate(1024), false); } TEST_F(LocalStateMetadataTest, encryptionKeyHash_whenLoadingWithDifferentKey_thenCrashes) { LocalStateMetadata::loadOrGenerate(stateDir.path(), DataFixture::generate(1024, 1), false); EXPECT_THROW( LocalStateMetadata::loadOrGenerate(stateDir.path(), DataFixture::generate(1024, 2), false), std::runtime_error ); } test/cryfs/testutils/000077500000000000000000000000001347701267100152335ustar00rootroot00000000000000test/cryfs/testutils/FakeCryKeyProvider.h000066400000000000000000000023471347701267100211220ustar00rootroot00000000000000#pragma once #ifndef CRYFS_FAKECRYKEYPROVIDER_H #define CRYFS_FAKECRYKEYPROVIDER_H #include #include class FakeCryKeyProvider final : public cryfs::CryKeyProvider { private: static constexpr const unsigned char KDF_TEST_PARAMETERS = 5; // test value to check that kdf parameters are passed in correctly public: FakeCryKeyProvider(unsigned char keySeed = 0): _keySeed(keySeed) {} cpputils::EncryptionKey requestKeyForExistingFilesystem(size_t keySize, const cpputils::Data& kdfParameters) override { ASSERT(kdfParameters.size() == 1 && *reinterpret_cast(kdfParameters.data()) == KDF_TEST_PARAMETERS, "Wrong kdf parameters"); return cpputils::EncryptionKey::FromString(cpputils::DataFixture::generate(keySize, _keySeed).ToString()); } KeyResult requestKeyForNewFilesystem(size_t keySize) override { cpputils::Data kdfParameters(sizeof(unsigned char)); *reinterpret_cast(kdfParameters.data()) = KDF_TEST_PARAMETERS; auto key = requestKeyForExistingFilesystem(keySize, kdfParameters); return KeyResult{ std::move(key), std::move(kdfParameters) }; } private: unsigned char _keySeed; }; #endif test/cryfs/testutils/MockConsole.h000066400000000000000000000022031347701267100176150ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_TEST_TESTUTILS_MOCKCONSOLE_H #define MESSMER_CRYFS_TEST_TESTUTILS_MOCKCONSOLE_H #include #include class MockConsole: public cpputils::Console { public: MOCK_METHOD1(print, void(const std::string&)); MOCK_METHOD2(ask, unsigned int(const std::string&, const std::vector&)); MOCK_METHOD2(askYesNo, bool(const std::string&, bool)); MOCK_METHOD1(askPassword, std::string(const std::string&)); }; ACTION_P(ChooseCipher, cipherName) { return std::find(arg1.begin(), arg1.end(), cipherName) - arg1.begin(); } #define ChooseAnyCipher() ChooseCipher("aes-256-gcm") class TestWithMockConsole { public: // Return a console that chooses a valid cryfs setting static std::shared_ptr mockConsole() { auto console = std::make_shared<::testing::NiceMock>(); EXPECT_CALL(*console, ask(::testing::_, ::testing::_)).WillRepeatedly(ChooseCipher("aes-256-gcm")); EXPECT_CALL(*console, askYesNo(::testing::_, ::testing::_)).WillRepeatedly(::testing::Return(true)); return console; } }; #endif test/cryfs/testutils/MockCryKeyProvider.h000066400000000000000000000007001347701267100211340ustar00rootroot00000000000000#pragma once #ifndef CRYFS_MOCKCRYKEYPROVIDER_H #define CRYFS_MOCKCRYKEYPROVIDER_H #include #include class MockCryKeyProvider: public cryfs::CryKeyProvider { public: MOCK_METHOD2(requestKeyForExistingFilesystem, cpputils::EncryptionKey(size_t keySize, const cpputils::Data& kdfParameters)); MOCK_METHOD1(requestKeyForNewFilesystem, cryfs::CryKeyProvider::KeyResult(size_t keySize)); }; #endif test/cryfs/testutils/TestWithFakeHomeDirectory.h000066400000000000000000000005261347701267100224470ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_TEST_TESTUTILS_TESTWITHFAKEHOMEDIRECTORY_H #define MESSMER_CRYFS_TEST_TESTUTILS_TESTWITHFAKEHOMEDIRECTORY_H #include #include class TestWithFakeHomeDirectory { private: cpputils::system::FakeTempHomeDirectoryRAII fakeHomeDirRAII; }; #endif test/fspp/000077500000000000000000000000001347701267100130155ustar00rootroot00000000000000test/fspp/CMakeLists.txt000066400000000000000000000106761347701267100155670ustar00rootroot00000000000000project (fspp-test) set(SOURCES testutils/FuseTest.cpp testutils/FuseThread.cpp testutils/InMemoryFile.cpp impl/FuseOpenFileListTest.cpp impl/IdListTest.cpp fuse/lstat/FuseLstatReturnUidTest.cpp fuse/lstat/testutils/FuseLstatTest.cpp fuse/lstat/FuseLstatReturnCtimeTest.cpp fuse/lstat/FuseLstatReturnGidTest.cpp fuse/lstat/FuseLstatPathParameterTest.cpp fuse/lstat/FuseLstatReturnNlinkTest.cpp fuse/lstat/FuseLstatReturnModeTest.cpp fuse/lstat/FuseLstatReturnAtimeTest.cpp fuse/lstat/FuseLstatErrorTest.cpp fuse/lstat/FuseLstatReturnMtimeTest.cpp fuse/lstat/FuseLstatReturnSizeTest.cpp fuse/read/FuseReadFileDescriptorTest.cpp fuse/read/testutils/FuseReadTest.cpp fuse/read/FuseReadOverflowTest.cpp fuse/read/FuseReadErrorTest.cpp fuse/read/FuseReadReturnedDataTest.cpp fuse/flush/testutils/FuseFlushTest.cpp fuse/flush/FuseFlushErrorTest.cpp fuse/flush/FuseFlushFileDescriptorTest.cpp fuse/rename/testutils/FuseRenameTest.cpp fuse/rename/FuseRenameErrorTest.cpp fuse/rename/FuseRenameFilenameTest.cpp fuse/utimens/testutils/FuseUtimensTest.cpp fuse/utimens/FuseUtimensErrorTest.cpp fuse/utimens/FuseUtimensFilenameTest.cpp fuse/utimens/FuseUtimensTimeParameterTest.cpp fuse/unlink/testutils/FuseUnlinkTest.cpp fuse/unlink/FuseUnlinkErrorTest.cpp fuse/unlink/FuseUnlinkFilenameTest.cpp fuse/ftruncate/testutils/FuseFTruncateTest.cpp fuse/ftruncate/FuseFTruncateFileDescriptorTest.cpp fuse/ftruncate/FuseFTruncateSizeTest.cpp fuse/ftruncate/FuseFTruncateErrorTest.cpp fuse/fstat/testutils/FuseFstatTest.cpp fuse/fstat/FuseFstatParameterTest.cpp fuse/fstat/FuseFstatErrorTest.cpp fuse/truncate/FuseTruncateSizeTest.cpp fuse/truncate/testutils/FuseTruncateTest.cpp fuse/truncate/FuseTruncateErrorTest.cpp fuse/truncate/FuseTruncateFilenameTest.cpp fuse/statfs/FuseStatfsReturnFilesTest.cpp fuse/statfs/FuseStatfsReturnFfreeTest.cpp fuse/statfs/FuseStatfsReturnNamemaxTest.cpp fuse/statfs/testutils/FuseStatfsTest.cpp fuse/statfs/FuseStatfsReturnBsizeTest.cpp fuse/statfs/FuseStatfsErrorTest.cpp fuse/statfs/FuseStatfsReturnBfreeTest.cpp fuse/statfs/FuseStatfsReturnBlocksTest.cpp fuse/statfs/FuseStatfsReturnBavailTest.cpp fuse/closeFile/FuseCloseTest.cpp fuse/fsync/testutils/FuseFsyncTest.cpp fuse/fsync/FuseFsyncFileDescriptorTest.cpp fuse/fsync/FuseFsyncErrorTest.cpp fuse/openFile/testutils/FuseOpenTest.cpp fuse/openFile/FuseOpenFilenameTest.cpp fuse/openFile/FuseOpenFlagsTest.cpp fuse/openFile/FuseOpenFileDescriptorTest.cpp fuse/openFile/FuseOpenErrorTest.cpp fuse/access/FuseAccessFilenameTest.cpp fuse/access/testutils/FuseAccessTest.cpp fuse/access/FuseAccessModeTest.cpp fuse/access/FuseAccessErrorTest.cpp fuse/BasicFuseTest.cpp fuse/rmdir/testutils/FuseRmdirTest.cpp fuse/rmdir/FuseRmdirErrorTest.cpp fuse/rmdir/FuseRmdirDirnameTest.cpp fuse/fdatasync/testutils/FuseFdatasyncTest.cpp fuse/fdatasync/FuseFdatasyncErrorTest.cpp fuse/fdatasync/FuseFdatasyncFileDescriptorTest.cpp fuse/mkdir/testutils/FuseMkdirTest.cpp fuse/mkdir/FuseMkdirErrorTest.cpp fuse/mkdir/FuseMkdirModeTest.cpp fuse/mkdir/FuseMkdirDirnameTest.cpp fuse/write/FuseWriteErrorTest.cpp fuse/write/testutils/FuseWriteTest.cpp fuse/write/FuseWriteOverflowTest.cpp fuse/write/FuseWriteFileDescriptorTest.cpp fuse/write/FuseWriteDataTest.cpp fuse/readDir/testutils/FuseReadDirTest.cpp fuse/readDir/FuseReadDirDirnameTest.cpp fuse/readDir/FuseReadDirErrorTest.cpp fuse/readDir/FuseReadDirReturnTest.cpp fuse/createAndOpenFile/FuseCreateAndOpenFilenameTest.cpp fuse/createAndOpenFile/testutils/FuseCreateAndOpenTest.cpp fuse/createAndOpenFile/FuseCreateAndOpenFlagsTest.cpp fuse/createAndOpenFile/FuseCreateAndOpenFileDescriptorTest.cpp fuse/createAndOpenFile/FuseCreateAndOpenErrorTest.cpp fuse/FilesystemTest.cpp fs_interface/NodeTest.cpp fs_interface/FileTest.cpp fs_interface/DirTest.cpp fs_interface/DeviceTest.cpp fs_interface/OpenFileTest.cpp testutils/OpenFileHandle.cpp testutils/OpenFileHandle.h) add_executable(${PROJECT_NAME} ${SOURCES}) 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}) target_activate_cpp14(${PROJECT_NAME}) test/fspp/fs_interface/000077500000000000000000000000001347701267100154455ustar00rootroot00000000000000test/fspp/fs_interface/DeviceTest.cpp000066400000000000000000000002221347701267100202040ustar00rootroot00000000000000/* * Tests that the header can be included without needing additional header includes as dependencies. */ #include "fspp/fs_interface/Device.h" test/fspp/fs_interface/DirTest.cpp000066400000000000000000000002171347701267100175270ustar00rootroot00000000000000/* * Tests that the header can be included without needing additional header includes as dependencies. */ #include "fspp/fs_interface/Dir.h" test/fspp/fs_interface/FileTest.cpp000066400000000000000000000002201347701267100176620ustar00rootroot00000000000000/* * Tests that the header can be included without needing additional header includes as dependencies. */ #include "fspp/fs_interface/File.h" test/fspp/fs_interface/NodeTest.cpp000066400000000000000000000002201347701267100176700ustar00rootroot00000000000000/* * Tests that the header can be included without needing additional header includes as dependencies. */ #include "fspp/fs_interface/Node.h" test/fspp/fs_interface/OpenFileTest.cpp000066400000000000000000000002241347701267100205100ustar00rootroot00000000000000/* * Tests that the header can be included without needing additional header includes as dependencies. */ #include "fspp/fs_interface/OpenFile.h" test/fspp/fuse/000077500000000000000000000000001347701267100137575ustar00rootroot00000000000000test/fspp/fuse/BasicFuseTest.cpp000066400000000000000000000004411347701267100171660ustar00rootroot00000000000000#include "../testutils/FuseTest.h" using namespace fspp::fuse; using namespace fspp::fuse; typedef FuseTest BasicFuseTest; //This test case simply checks whether a filesystem can be setup and teardown without crashing. TEST_F(BasicFuseTest, setupAndTearDown) { auto fs = TestFS(); } test/fspp/fuse/FilesystemTest.cpp000066400000000000000000000002161347701267100174460ustar00rootroot00000000000000/* * Tests that the header can be included without needing additional header includes as dependencies. */ #include "fspp/fuse/Filesystem.h" test/fspp/fuse/access/000077500000000000000000000000001347701267100152205ustar00rootroot00000000000000test/fspp/fuse/access/FuseAccessErrorTest.cpp000066400000000000000000000014761347701267100216320ustar00rootroot00000000000000#include "testutils/FuseAccessTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::StrEq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::AtLeast; using namespace fspp::fuse; class FuseAccessErrorTest: public FuseAccessTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseAccessErrorTest, FuseAccessErrorTest, Values(EACCES, ELOOP, ENAMETOOLONG, ENOENT, ENOTDIR, EROFS, EFAULT, EINVAL, EIO, ENOMEM, ETXTBSY)); TEST_P(FuseAccessErrorTest, ReturnedErrorIsCorrect) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, access(StrEq(FILENAME), _)) .Times(AtLeast(1)).WillRepeatedly(Throw(FuseErrnoException(GetParam()))); int error = AccessFileReturnError(FILENAME, 0); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/access/FuseAccessFilenameTest.cpp000066400000000000000000000016421347701267100222540ustar00rootroot00000000000000#include "testutils/FuseAccessTest.h" using ::testing::_; using ::testing::StrEq; using ::testing::Return; class FuseAccessFilenameTest: public FuseAccessTest { }; TEST_F(FuseAccessFilenameTest, AccessFile) { ReturnIsFileOnLstat("/myfile"); EXPECT_CALL(*fsimpl, access(StrEq("/myfile"), _)) .Times(1).WillOnce(Return()); AccessFile("/myfile", 0); } TEST_F(FuseAccessFilenameTest, AccessFileNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsFileOnLstat("/mydir/myfile"); EXPECT_CALL(*fsimpl, access(StrEq("/mydir/myfile"), _)) .Times(1).WillOnce(Return()); AccessFile("/mydir/myfile", 0); } TEST_F(FuseAccessFilenameTest, AccessFileNested2) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); ReturnIsFileOnLstat("/mydir/mydir2/myfile"); EXPECT_CALL(*fsimpl, access(StrEq("/mydir/mydir2/myfile"), _)) .Times(1).WillOnce(Return()); AccessFile("/mydir/mydir2/myfile", 0); } test/fspp/fuse/access/FuseAccessModeTest.cpp000066400000000000000000000011221347701267100214110ustar00rootroot00000000000000#include "testutils/FuseAccessTest.h" using ::testing::StrEq; using ::testing::Return; using ::testing::WithParamInterface; using ::testing::Values; class FuseAccessModeTest: public FuseAccessTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseAccessModeTest, FuseAccessModeTest, Values(0, F_OK, R_OK, W_OK, X_OK, R_OK|W_OK, W_OK|X_OK, R_OK|X_OK, R_OK|W_OK|X_OK)); TEST_P(FuseAccessModeTest, AccessFile) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, access(StrEq(FILENAME), GetParam())) .Times(1).WillOnce(Return()); AccessFile(FILENAME, GetParam()); } test/fspp/fuse/access/testutils/000077500000000000000000000000001347701267100172605ustar00rootroot00000000000000test/fspp/fuse/access/testutils/FuseAccessTest.cpp000066400000000000000000000006761347701267100226610ustar00rootroot00000000000000#include "FuseAccessTest.h" void FuseAccessTest::AccessFile(const char *filename, int mode) { int error = AccessFileReturnError(filename, mode); EXPECT_EQ(0, error); } int FuseAccessTest::AccessFileReturnError(const char *filename, int mode) { auto fs = TestFS(); auto realpath = fs->mountDir() / filename; int retval = ::access(realpath.string().c_str(), mode); if (retval == 0) { return 0; } else { return errno; } } test/fspp/fuse/access/testutils/FuseAccessTest.h000066400000000000000000000006141347701267100223160ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_ACCESS_TESTUTILS_FUSEACCESSTEST_H_ #define MESSMER_FSPP_TEST_FUSE_ACCESS_TESTUTILS_FUSEACCESSTEST_H_ #include "../../../testutils/FuseTest.h" class FuseAccessTest: public FuseTest { public: const char *FILENAME = "/myfile"; void AccessFile(const char *filename, int mode); int AccessFileReturnError(const char *filename, int mode); }; #endif test/fspp/fuse/closeFile/000077500000000000000000000000001347701267100156645ustar00rootroot00000000000000test/fspp/fuse/closeFile/FuseCloseTest.cpp000066400000000000000000000062051347701267100211230ustar00rootroot00000000000000#include "../../testutils/FuseTest.h" #include "../../testutils/OpenFileHandle.h" #include using ::testing::WithParamInterface; using ::testing::Values; using std::string; using std::mutex; using std::unique_lock; using std::condition_variable; using std::chrono::duration; using std::chrono::seconds; using cpputils::unique_ref; using cpputils::make_unique_ref; // The fuse behaviour is: For each open(), there will be exactly one call to release(). // Directly before this call to release(), flush() will be called. After flush() returns, // the ::close() syscall (in the process using the filesystem) returns. So the fuse release() call is // called asynchronously afterwards. Errors have to be returned in the implementation of flush(). // Citing FUSE spec: // 1) Flush is called on each close() of a file descriptor. // 2) Filesystems shouldn't assume that flush will always be called after some writes, or that if will be called at all. // I can't get these sentences together. For the test cases here, I go with the first one and assume that // flush() will ALWAYS be called on a file close. class Barrier { public: Barrier(): m(), cv(), finished(false) {} template void WaitAtMost(const duration &atMost) { unique_lock lock(m); if (!finished) { cv.wait_for(lock, atMost, [this] () {return finished;}); } } void Release() { unique_lock lock(m); finished = true; cv.notify_all(); } private: mutex m; condition_variable cv; bool finished; }; class FuseCloseTest: public FuseTest, public WithParamInterface { public: const string FILENAME = "/myfile"; void OpenAndCloseFile(const string &filename) { auto fs = TestFS(); auto fd = OpenFile(fs.get(), filename); CloseFile(std::move(fd)); } unique_ref OpenFile(const TempTestFS *fs, const string &filename) { auto real_path = fs->mountDir() / filename; auto fd = make_unique_ref(real_path.string().c_str(), O_RDONLY); EXPECT_GE(fd->fd(), 0) << "Opening file failed"; return fd; } void CloseFile(unique_ref fd) { int retval = ::close(fd->fd()); EXPECT_EQ(0, retval); fd->release(); // don't try closing it again } }; INSTANTIATE_TEST_CASE_P(FuseCloseTest, FuseCloseTest, Values(0, 1, 2, 100, 1024*1024*1024)); //TODO Figure out what's wrong and enable this test //Disabled, because it is flaky. libfuse seems to not send the release() event sometimes. /*TEST_P(FuseCloseTest, CloseFile) { Barrier barrier; ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, openFile(StrEq(FILENAME), _)).WillOnce(Return(GetParam())); { //InSequence fileCloseSequence; EXPECT_CALL(*fsimpl, flush(Eq(GetParam()))).Times(1); EXPECT_CALL(*fsimpl, closeFile(Eq(GetParam()))).Times(1).WillOnce(Invoke([&barrier] (int) { // Release the waiting lock at the end of this test case, because the fuse release() came in now. barrier.Release(); })); } OpenAndCloseFile(FILENAME); // Wait, until fuse release() was called, so we can check for the function call expectation. barrier.WaitAtMost(seconds(10)); }*/ test/fspp/fuse/createAndOpenFile/000077500000000000000000000000001347701267100172675ustar00rootroot00000000000000test/fspp/fuse/createAndOpenFile/FuseCreateAndOpenErrorTest.cpp000066400000000000000000000025561347701267100251500ustar00rootroot00000000000000#include "testutils/FuseCreateAndOpenTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Return; using ::testing::Throw; using ::testing::StrEq; using ::testing::_; using namespace fspp::fuse; class FuseCreateAndOpenErrorTest: public FuseCreateAndOpenTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseCreateAndOpenErrorTest, FuseCreateAndOpenErrorTest, Values(EACCES, EDQUOT, EEXIST, EFAULT, EFBIG, EINTR, EOVERFLOW, EINVAL, EISDIR, ELOOP, EMFILE, ENAMETOOLONG, ENFILE, ENODEV, ENOENT, ENOMEM, ENOSPC, ENOTDIR, ENXIO, EOPNOTSUPP, EPERM, EROFS, ETXTBSY, EWOULDBLOCK, EBADF, ENOTDIR)); TEST_F(FuseCreateAndOpenErrorTest, ReturnNoError) { ReturnDoesntExistOnLstat(FILENAME); EXPECT_CALL(*fsimpl, createAndOpenFile(StrEq(FILENAME), _, _, _)).Times(1).WillOnce(Return(1)); //For the syscall to succeed, we also need to give an fstat implementation. ReturnIsFileOnFstat(1); int error = CreateAndOpenFileReturnError(FILENAME, O_RDONLY); EXPECT_EQ(0, error); } TEST_P(FuseCreateAndOpenErrorTest, ReturnError) { ReturnDoesntExistOnLstat(FILENAME); EXPECT_CALL(*fsimpl, createAndOpenFile(StrEq(FILENAME), _, _, _)).Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = CreateAndOpenFileReturnError(FILENAME, O_RDONLY); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/createAndOpenFile/FuseCreateAndOpenFileDescriptorTest.cpp000066400000000000000000000031561347701267100267720ustar00rootroot00000000000000#include "testutils/FuseCreateAndOpenTest.h" using ::testing::_; using ::testing::StrEq; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Return; using cpputils::unique_ref; using cpputils::make_unique_ref; class FuseCreateAndOpenFileDescriptorTest: public FuseCreateAndOpenTest, public WithParamInterface { public: void CreateAndOpenAndReadFile(const char *filename) { auto fs = TestFS(); auto fd = CreateAndOpenFile(fs.get(), filename); ReadFile(fd->fd()); } private: unique_ref CreateAndOpenFile(const TempTestFS *fs, const char *filename) { auto realpath = fs->mountDir() / filename; auto fd = make_unique_ref(realpath.string().c_str(), O_RDONLY | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH); EXPECT_GE(fd->fd(), 0) << "Creating file failed"; return fd; } void ReadFile(int fd) { uint8_t buf; int retval = ::read(fd, &buf, 1); EXPECT_EQ(1, retval) << "Reading file failed"; } }; INSTANTIATE_TEST_CASE_P(FuseCreateAndOpenFileDescriptorTest, FuseCreateAndOpenFileDescriptorTest, Values(0, 2, 5, 1000, 1024*1024*1024)); TEST_P(FuseCreateAndOpenFileDescriptorTest, TestReturnedFileDescriptor) { ReturnDoesntExistOnLstat(FILENAME); EXPECT_CALL(*fsimpl, createAndOpenFile(StrEq(FILENAME), _, _, _)) .Times(1).WillOnce(Return(GetParam())); EXPECT_CALL(*fsimpl, read(GetParam(), _, _, _)).Times(1).WillOnce(Return(fspp::num_bytes_t(1))); //For the syscall to succeed, we also need to give an fstat implementation. ReturnIsFileOnFstatWithSize(GetParam(), fspp::num_bytes_t(1)); CreateAndOpenAndReadFile(FILENAME); } test/fspp/fuse/createAndOpenFile/FuseCreateAndOpenFilenameTest.cpp000066400000000000000000000026201347701267100255670ustar00rootroot00000000000000#include "testutils/FuseCreateAndOpenTest.h" using ::testing::_; using ::testing::StrEq; using ::testing::Return; class FuseCreateAndOpenFilenameTest: public FuseCreateAndOpenTest { public: }; TEST_F(FuseCreateAndOpenFilenameTest, CreateAndOpenFile) { ReturnDoesntExistOnLstat("/myfile"); EXPECT_CALL(*fsimpl, createAndOpenFile(StrEq("/myfile"), _, _, _)) .Times(1).WillOnce(Return(0)); //For the syscall to succeed, we also need to give an fstat implementation. ReturnIsFileOnFstat(0); CreateAndOpenFile("/myfile", O_RDONLY); } TEST_F(FuseCreateAndOpenFilenameTest, CreateAndOpenFileNested) { ReturnIsDirOnLstat("/mydir"); ReturnDoesntExistOnLstat("/mydir/myfile"); EXPECT_CALL(*fsimpl, createAndOpenFile(StrEq("/mydir/myfile"), _, _, _)) .Times(1).WillOnce(Return(0)); //For the syscall to succeed, we also need to give an fstat implementation. ReturnIsFileOnFstat(0); CreateAndOpenFile("/mydir/myfile", O_RDONLY); } TEST_F(FuseCreateAndOpenFilenameTest, CreateAndOpenFileNested2) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); ReturnDoesntExistOnLstat("/mydir/mydir2/myfile"); EXPECT_CALL(*fsimpl, createAndOpenFile(StrEq("/mydir/mydir2/myfile"), _, _, _)) .Times(1).WillOnce(Return(0)); //For the syscall to succeed, we also need to give an fstat implementation. ReturnIsFileOnFstat(0); CreateAndOpenFile("/mydir/mydir2/myfile", O_RDONLY); } test/fspp/fuse/createAndOpenFile/FuseCreateAndOpenFlagsTest.cpp000066400000000000000000000014321347701267100251030ustar00rootroot00000000000000#include "testutils/FuseCreateAndOpenTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseCreateAndOpenFlagsTest: public FuseCreateAndOpenTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseCreateAndOpenFlagsTest, FuseCreateAndOpenFlagsTest, Values(O_RDWR, O_RDONLY, O_WRONLY)); //TODO Disabled because it doesn't seem to work. Fuse doesn't seem to pass flags to create(). Why? /*TEST_P(FuseCreateAndOpenFlagsTest, testFlags) { ReturnDoesntExistOnLstat(FILENAME); EXPECT_CALL(*fsimpl, createAndOpenFile(StrEq(FILENAME), OpenFlagsEq(GetParam()), _, _)) .Times(1).WillOnce(Return(0)); //For the syscall to succeed, we also need to give an fstat implementation. ReturnIsFileOnFstat(0); CreateAndOpenFile(FILENAME, GetParam()); }*/ test/fspp/fuse/createAndOpenFile/testutils/000077500000000000000000000000001347701267100213275ustar00rootroot00000000000000test/fspp/fuse/createAndOpenFile/testutils/FuseCreateAndOpenTest.cpp000066400000000000000000000016061347701267100261710ustar00rootroot00000000000000#include "FuseCreateAndOpenTest.h" using cpputils::unique_ref; using cpputils::make_unique_ref; void FuseCreateAndOpenTest::CreateAndOpenFile(const std::string &filename, int flags) { auto fs = TestFS(); auto fd = CreateAndOpenFileAllowErrors(fs.get(), filename, flags); EXPECT_GE(fd->fd(), 0) << "Opening file failed"; } int FuseCreateAndOpenTest::CreateAndOpenFileReturnError(const std::string &filename, int flags) { auto fs = TestFS(); auto fd = CreateAndOpenFileAllowErrors(fs.get(), filename, flags); return fd->errorcode(); } unique_ref FuseCreateAndOpenTest::CreateAndOpenFileAllowErrors(const TempTestFS *fs, const std::string &filename, int flags) { auto real_path = fs->mountDir() / filename; auto fd = make_unique_ref(real_path.string().c_str(), flags | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); return fd; } test/fspp/fuse/createAndOpenFile/testutils/FuseCreateAndOpenTest.h000066400000000000000000000013541347701267100256360ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_CREATEANDOPENFILE_TESTUTILS_FUSECREATEANDOPENTEST_H_ #define MESSMER_FSPP_TEST_FUSE_CREATEANDOPENFILE_TESTUTILS_FUSECREATEANDOPENTEST_H_ #include "../../../testutils/FuseTest.h" #include "../../../testutils/OpenFileHandle.h" class FuseCreateAndOpenTest: public FuseTest { public: const char *FILENAME = "/myfile"; void CreateAndOpenFile(const std::string& FILENAME, int flags); int CreateAndOpenFileReturnError(const std::string& FILENAME, int flags); private: cpputils::unique_ref CreateAndOpenFileAllowErrors(const TempTestFS *fs, const std::string &filename, int flags); }; MATCHER_P(OpenFlagsEq, expectedFlags, "") { return expectedFlags == (O_ACCMODE & arg); } #endif test/fspp/fuse/fdatasync/000077500000000000000000000000001347701267100157335ustar00rootroot00000000000000test/fspp/fuse/fdatasync/FuseFdatasyncErrorTest.cpp000066400000000000000000000013301347701267100230450ustar00rootroot00000000000000#include "testutils/FuseFdatasyncTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseFdatasyncErrorTest: public FuseFdatasyncTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseFdatasyncErrorTest, FuseFdatasyncErrorTest, Values(EBADF, EIO, EROFS, EINVAL)); TEST_P(FuseFdatasyncErrorTest, ReturnedErrorIsCorrect) { ReturnIsFileOnLstat(FILENAME); OnOpenReturnFileDescriptor(FILENAME, 0); EXPECT_CALL(*fsimpl, fdatasync(0)) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = FdatasyncFileReturnError(FILENAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/fdatasync/FuseFdatasyncFileDescriptorTest.cpp000066400000000000000000000013271347701267100247000ustar00rootroot00000000000000#include "testutils/FuseFdatasyncTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using ::testing::Return; using namespace fspp::fuse; class FuseFdatasyncFileDescriptorTest: public FuseFdatasyncTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseFdatasyncFileDescriptorTest, FuseFdatasyncFileDescriptorTest, Values(0,1,10,1000,1024*1024*1024)); TEST_P(FuseFdatasyncFileDescriptorTest, FileDescriptorIsCorrect) { ReturnIsFileOnLstat(FILENAME); OnOpenReturnFileDescriptor(FILENAME, GetParam()); EXPECT_CALL(*fsimpl, fdatasync(Eq(GetParam()))) .Times(1).WillOnce(Return()); FdatasyncFile(FILENAME); } test/fspp/fuse/fdatasync/testutils/000077500000000000000000000000001347701267100177735ustar00rootroot00000000000000test/fspp/fuse/fdatasync/testutils/FuseFdatasyncTest.cpp000066400000000000000000000016241347701267100241010ustar00rootroot00000000000000#include "FuseFdatasyncTest.h" #include using cpputils::unique_ref; using cpputils::make_unique_ref; void FuseFdatasyncTest::FdatasyncFile(const char *filename) { int error = FdatasyncFileReturnError(filename); EXPECT_EQ(0, error); } int FuseFdatasyncTest::FdatasyncFileReturnError(const char *filename) { auto fs = TestFS(); auto fd = OpenFile(fs.get(), filename); #ifdef F_FULLFSYNC // This is MacOSX, which doesn't know fdatasync int retval = fcntl(fd->fd(), F_FULLFSYNC); #else int retval = ::fdatasync(fd->fd()); #endif if (retval != -1) { return 0; } else { return errno; } } unique_ref FuseFdatasyncTest::OpenFile(const TempTestFS *fs, const char *filename) { auto realpath = fs->mountDir() / filename; auto fd = make_unique_ref(realpath.string().c_str(), O_RDWR); EXPECT_GE(fd->fd(), 0) << "Error opening file"; return fd; } test/fspp/fuse/fdatasync/testutils/FuseFdatasyncTest.h000066400000000000000000000010431347701267100235410ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_FDATASYNC_TESTUTILS_FUSEFDATASYNCTEST_H_ #define MESSMER_FSPP_TEST_FUSE_FDATASYNC_TESTUTILS_FUSEFDATASYNCTEST_H_ #include "../../../testutils/FuseTest.h" #include "../../../testutils/OpenFileHandle.h" class FuseFdatasyncTest: public FuseTest { public: const char *FILENAME = "/myfile"; void FdatasyncFile(const char *filename); int FdatasyncFileReturnError(const char *filename); private: cpputils::unique_ref OpenFile(const TempTestFS *fs, const char *filename); }; #endif test/fspp/fuse/flush/000077500000000000000000000000001347701267100151005ustar00rootroot00000000000000test/fspp/fuse/flush/FuseFlushErrorTest.cpp000066400000000000000000000021211347701267100213560ustar00rootroot00000000000000#include "testutils/FuseFlushTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::WithParamInterface; using ::testing::StrEq; using ::testing::Eq; using ::testing::Return; using ::testing::Throw; using ::testing::Values; using ::testing::_; using fspp::fuse::FuseErrnoException; class FuseFlushErrorTest: public FuseFlushTest, public WithParamInterface { }; 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); EXPECT_CALL(*fsimpl, openFile(StrEq(FILENAME), _)).WillOnce(Return(GetParam())); EXPECT_CALL(*fsimpl, flush(Eq(GetParam()))).Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); auto fs = TestFS(); auto fd = OpenFile(fs.get(), FILENAME); int close_result = ::close(fd->fd()); EXPECT_EQ(GetParam(), errno); EXPECT_EQ(-1, close_result); fd->release(); // don't close it again } test/fspp/fuse/flush/FuseFlushFileDescriptorTest.cpp000066400000000000000000000026241347701267100232130ustar00rootroot00000000000000#include "testutils/FuseFlushTest.h" using ::testing::_; using ::testing::StrEq; using ::testing::Eq; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Return; using std::string; // The fuse behaviour is: For each open(), there will be exactly one call to release(). // Directly before this call to release(), flush() will be called. After flush() returns, // the ::close() syscall (in the process using the filesystem) returns. So the fuse release() call is // called asynchronously afterwards. Errors have to be returned in the implementation of flush(). // Citing FUSE spec: // 1) Flush is called on each close() of a file descriptor. // 2) Filesystems shouldn't assume that flush will always be called after some writes, or that if will be called at all. // I can't get these sentences together. For the test cases here, I go with the first one and assume that // flush() will ALWAYS be called on a file close. class FuseFlushFileDescriptorTest: public FuseFlushTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseFlushFileDescriptorTest, FuseFlushFileDescriptorTest, Values(0, 1, 2, 100, 1024*1024*1024)); TEST_P(FuseFlushFileDescriptorTest, FlushOnCloseFile) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, openFile(StrEq(FILENAME), _)).WillOnce(Return(GetParam())); EXPECT_CALL(*fsimpl, flush(Eq(GetParam()))).Times(1); OpenAndCloseFile(FILENAME); } test/fspp/fuse/flush/testutils/000077500000000000000000000000001347701267100171405ustar00rootroot00000000000000test/fspp/fuse/flush/testutils/FuseFlushTest.cpp000066400000000000000000000012621347701267100224110ustar00rootroot00000000000000#include "FuseFlushTest.h" using cpputils::unique_ref; using cpputils::make_unique_ref; void FuseFlushTest::OpenAndCloseFile(const std::string &filename) { auto fs = TestFS(); auto fd = OpenFile(fs.get(), filename); CloseFile(fd->fd()); fd->release(); // don't try to close it again } unique_ref FuseFlushTest::OpenFile(const TempTestFS *fs, const std::string &filename) { auto real_path = fs->mountDir() / filename; auto fd = make_unique_ref(real_path.string().c_str(), O_RDONLY); EXPECT_GE(fd->fd(), 0) << "Opening file failed"; return fd; } void FuseFlushTest::CloseFile(int fd) { int retval = ::close(fd); EXPECT_EQ(0, retval); } test/fspp/fuse/flush/testutils/FuseFlushTest.h000066400000000000000000000010011347701267100220450ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_FLUSH_TESTUTILS_FUSEFLUSHTEST_H_ #define MESSMER_FSPP_TEST_FUSE_FLUSH_TESTUTILS_FUSEFLUSHTEST_H_ #include "../../../testutils/FuseTest.h" #include "../../../testutils/OpenFileHandle.h" class FuseFlushTest: public FuseTest { public: const std::string FILENAME = "/myfile"; void OpenAndCloseFile(const std::string &filename); cpputils::unique_ref OpenFile(const TempTestFS *fs, const std::string &filename); void CloseFile(int fd); }; #endif test/fspp/fuse/fstat/000077500000000000000000000000001347701267100151005ustar00rootroot00000000000000test/fspp/fuse/fstat/FuseFstatErrorTest.cpp000066400000000000000000000026071347701267100213670ustar00rootroot00000000000000#include "testutils/FuseFstatTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using ::testing::Throw; using namespace fspp::fuse; // Cite from FUSE documentation on the fgetattr function: // "Currently this is only called after the create() method if that is implemented (see above). // Later it may be called for invocations of fstat() too." // So we need to issue a create to get our fstat called. class FuseFstatErrorTest: public FuseFstatTest, public WithParamInterface { public: /*unique_ref CreateFileAllowErrors(const TempTestFS *fs, const std::string &filename) { auto real_path = fs->mountDir() / filename; return make_unique_ref(real_path.string().c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); }*/ }; INSTANTIATE_TEST_CASE_P(FuseFstatErrorTest, FuseFstatErrorTest, Values(EACCES, EBADF, EFAULT, ELOOP, ENAMETOOLONG, ENOENT, ENOMEM, ENOTDIR, EOVERFLOW)); TEST_P(FuseFstatErrorTest, ReturnedErrorCodeIsCorrect) { ReturnDoesntExistOnLstat(FILENAME); OnCreateAndOpenReturnFileDescriptor(FILENAME, 0); EXPECT_CALL(*fsimpl, fstat(Eq(0), _)).Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); auto fs = TestFS(); int error = CreateFileReturnError(fs.get(), FILENAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/fstat/FuseFstatParameterTest.cpp000066400000000000000000000020651347701267100222140ustar00rootroot00000000000000#include "testutils/FuseFstatTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using namespace fspp::fuse; // Cite from FUSE documentation on the fgetattr function: // "Currently this is only called after the create() method if that is implemented (see above). // Later it may be called for invocations of fstat() too." // So we need to issue a create to get our fstat called. class FuseFstatParameterTest: public FuseFstatTest, public WithParamInterface { public: void CallFstat(const char *filename) { auto fs = TestFS(); CreateFile(fs.get(), filename); } }; INSTANTIATE_TEST_CASE_P(FuseFstatParameterTest, FuseFstatParameterTest, Values(0,1,10,1000,1024*1024*1024)); TEST_P(FuseFstatParameterTest, FileDescriptorIsCorrect) { ReturnDoesntExistOnLstat(FILENAME); OnCreateAndOpenReturnFileDescriptor(FILENAME, GetParam()); EXPECT_CALL(*fsimpl, fstat(Eq(GetParam()), _)).Times(1).WillOnce(ReturnIsFileFstat); CallFstat(FILENAME); } test/fspp/fuse/fstat/README000066400000000000000000000005731347701267100157650ustar00rootroot00000000000000Cite from FUSE documentation on the fgetattr function: Currently this is only called after the create() method if that is implemented (see above). Later it may be called for invocations of fstat() too. So we need to issue a create to get our fstat called. Since fstat is currently only called after create, we can't call it directly. So we can't test the returned values.test/fspp/fuse/fstat/testutils/000077500000000000000000000000001347701267100171405ustar00rootroot00000000000000test/fspp/fuse/fstat/testutils/FuseFstatTest.cpp000066400000000000000000000020541347701267100224110ustar00rootroot00000000000000#include "FuseFstatTest.h" using ::testing::StrEq; using ::testing::_; using ::testing::Return; using cpputils::unique_ref; using cpputils::make_unique_ref; unique_ref FuseFstatTest::CreateFile(const TempTestFS *fs, const std::string &filename) { auto fd = CreateFileAllowErrors(fs, filename); EXPECT_GE(fd->fd(), 0) << "Opening file failed"; return fd; } int FuseFstatTest::CreateFileReturnError(const TempTestFS *fs, const std::string &filename) { auto fd = CreateFileAllowErrors(fs, filename); return fd->errorcode(); } unique_ref FuseFstatTest::CreateFileAllowErrors(const TempTestFS *fs, const std::string &filename) { auto real_path = fs->mountDir() / filename; auto fd = make_unique_ref(real_path.string().c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); return fd; } void FuseFstatTest::OnCreateAndOpenReturnFileDescriptor(const char *filename, int descriptor) { EXPECT_CALL(*fsimpl, createAndOpenFile(StrEq(filename), _, _, _)).Times(1).WillOnce(Return(descriptor)); } test/fspp/fuse/fstat/testutils/FuseFstatTest.h000066400000000000000000000012441347701267100220560ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_FSTAT_TESTUTILS_FUSEFSTATTEST_H_ #define MESSMER_FSPP_TEST_FUSE_FSTAT_TESTUTILS_FUSEFSTATTEST_H_ #include "../../../testutils/FuseTest.h" #include "../../../testutils/OpenFileHandle.h" class FuseFstatTest: public FuseTest { public: cpputils::unique_ref CreateFile(const TempTestFS *fs, const std::string &filename); int CreateFileReturnError(const TempTestFS *fs, const std::string &filename); void OnCreateAndOpenReturnFileDescriptor(const char *filename, int descriptor); private: cpputils::unique_ref CreateFileAllowErrors(const TempTestFS *fs, const std::string &filename); }; #endif test/fspp/fuse/fsync/000077500000000000000000000000001347701267100151015ustar00rootroot00000000000000test/fspp/fuse/fsync/FuseFsyncErrorTest.cpp000066400000000000000000000012701347701267100213640ustar00rootroot00000000000000#include "testutils/FuseFsyncTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseFsyncErrorTest: public FuseFsyncTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseFsyncErrorTest, FuseFsyncErrorTest, Values(EBADF, EIO, EROFS, EINVAL)); TEST_P(FuseFsyncErrorTest, ReturnedErrorIsCorrect) { ReturnIsFileOnLstat(FILENAME); OnOpenReturnFileDescriptor(FILENAME, 0); EXPECT_CALL(*fsimpl, fsync(0)) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = FsyncFileReturnError(FILENAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/fsync/FuseFsyncFileDescriptorTest.cpp000066400000000000000000000012671347701267100232170ustar00rootroot00000000000000#include "testutils/FuseFsyncTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using ::testing::Return; using namespace fspp::fuse; class FuseFsyncFileDescriptorTest: public FuseFsyncTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseFsyncFileDescriptorTest, FuseFsyncFileDescriptorTest, Values(0,1,10,1000,1024*1024*1024)); TEST_P(FuseFsyncFileDescriptorTest, FileDescriptorIsCorrect) { ReturnIsFileOnLstat(FILENAME); OnOpenReturnFileDescriptor(FILENAME, GetParam()); EXPECT_CALL(*fsimpl, fsync(Eq(GetParam()))) .Times(1).WillOnce(Return()); FsyncFile(FILENAME); } test/fspp/fuse/fsync/testutils/000077500000000000000000000000001347701267100171415ustar00rootroot00000000000000test/fspp/fuse/fsync/testutils/FuseFsyncTest.cpp000066400000000000000000000013411347701267100224110ustar00rootroot00000000000000#include "FuseFsyncTest.h" using cpputils::unique_ref; using cpputils::make_unique_ref; void FuseFsyncTest::FsyncFile(const char *filename) { int error = FsyncFileReturnError(filename); EXPECT_EQ(0, error); } int FuseFsyncTest::FsyncFileReturnError(const char *filename) { auto fs = TestFS(); auto fd = OpenFile(fs.get(), filename); int retval = ::fsync(fd->fd()); if (retval == 0) { return 0; } else { return errno; } } unique_ref FuseFsyncTest::OpenFile(const TempTestFS *fs, const char *filename) { auto realpath = fs->mountDir() / filename; auto fd = make_unique_ref(realpath.string().c_str(), O_RDWR); EXPECT_GE(fd->fd(), 0) << "Error opening file"; return fd; } test/fspp/fuse/fsync/testutils/FuseFsyncTest.h000066400000000000000000000010071347701267100220550ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_FSYNC_TESTUTILS_FUSEFSYNCTEST_H_ #define MESSMER_FSPP_TEST_FUSE_FSYNC_TESTUTILS_FUSEFSYNCTEST_H_ #include "../../../testutils/FuseTest.h" #include "../../../testutils/OpenFileHandle.h" class FuseFsyncTest: public FuseTest { public: const char *FILENAME = "/myfile"; void FsyncFile(const char *filename); int FsyncFileReturnError(const char *filename); private: cpputils::unique_ref OpenFile(const TempTestFS *fs, const char *filename); }; #endif test/fspp/fuse/ftruncate/000077500000000000000000000000001347701267100157525ustar00rootroot00000000000000test/fspp/fuse/ftruncate/FuseFTruncateErrorTest.cpp000066400000000000000000000016721347701267100230540ustar00rootroot00000000000000#include "testutils/FuseFTruncateTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseFTruncateErrorTest: public FuseFTruncateTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseFTruncateErrorTest, FuseFTruncateErrorTest, Values(EACCES, EFAULT, EFBIG, EINTR, EINVAL, EIO, EISDIR, ELOOP, ENAMETOOLONG, ENOENT, ENOTDIR, EPERM, EROFS, ETXTBSY, EBADF)); TEST_P(FuseFTruncateErrorTest, ReturnedErrorIsCorrect) { ReturnIsFileOnLstat(FILENAME); OnOpenReturnFileDescriptor(FILENAME, 0); EXPECT_CALL(*fsimpl, ftruncate(0, _)) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); //Needed to make ::ftruncate system call return successfully ReturnIsFileOnFstat(0); int error = FTruncateFileReturnError(FILENAME, fspp::num_bytes_t(0)); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/ftruncate/FuseFTruncateFileDescriptorTest.cpp000066400000000000000000000015461347701267100247010ustar00rootroot00000000000000#include "testutils/FuseFTruncateTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using ::testing::Return; using namespace fspp::fuse; class FuseFTruncateFileDescriptorTest: public FuseFTruncateTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseFTruncateFileDescriptorTest, FuseFTruncateFileDescriptorTest, Values(0,1,10,1000,1024*1024*1024)); TEST_P(FuseFTruncateFileDescriptorTest, FileDescriptorIsCorrect) { ReturnIsFileOnLstat(FILENAME); OnOpenReturnFileDescriptor(FILENAME, GetParam()); EXPECT_CALL(*fsimpl, ftruncate(Eq(GetParam()), _)) .Times(1).WillOnce(Return()); //Needed to make ::ftruncate system call return successfully ReturnIsFileOnFstat(GetParam()); FTruncateFile(FILENAME, fspp::num_bytes_t(0)); } test/fspp/fuse/ftruncate/FuseFTruncateSizeTest.cpp000066400000000000000000000015151347701267100226710ustar00rootroot00000000000000#include "testutils/FuseFTruncateTest.h" using ::testing::Eq; using ::testing::Return; using ::testing::WithParamInterface; using ::testing::Values; class FuseFTruncateSizeTest: public FuseFTruncateTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseFTruncateSizeTest, FuseFTruncateSizeTest, Values( fspp::num_bytes_t(0), fspp::num_bytes_t(1), fspp::num_bytes_t(10), fspp::num_bytes_t(1024), fspp::num_bytes_t(1024*1024*1024))); TEST_P(FuseFTruncateSizeTest, FTruncateFile) { ReturnIsFileOnLstat(FILENAME); OnOpenReturnFileDescriptor(FILENAME, 0); EXPECT_CALL(*fsimpl, ftruncate(Eq(0), GetParam())) .Times(1).WillOnce(Return()); //Needed to make ::ftruncate system call return successfully ReturnIsFileOnFstat(0); FTruncateFile(FILENAME, fspp::num_bytes_t(GetParam())); } test/fspp/fuse/ftruncate/testutils/000077500000000000000000000000001347701267100200125ustar00rootroot00000000000000test/fspp/fuse/ftruncate/testutils/FuseFTruncateTest.cpp000066400000000000000000000015051347701267100240750ustar00rootroot00000000000000#include "FuseFTruncateTest.h" using cpputils::unique_ref; using cpputils::make_unique_ref; void FuseFTruncateTest::FTruncateFile(const char *filename, fspp::num_bytes_t size) { int error = FTruncateFileReturnError(filename, size); EXPECT_EQ(0, error); } int FuseFTruncateTest::FTruncateFileReturnError(const char *filename, fspp::num_bytes_t size) { auto fs = TestFS(); auto fd = OpenFile(fs.get(), filename); int retval = ::ftruncate(fd->fd(), size.value()); if (0 == retval) { return 0; } else { return errno; } } unique_ref FuseFTruncateTest::OpenFile(const TempTestFS *fs, const char *filename) { auto realpath = fs->mountDir() / filename; auto fd = make_unique_ref(realpath.string().c_str(), O_RDWR); EXPECT_GE(fd->fd(), 0) << "Error opening file"; return fd; } test/fspp/fuse/ftruncate/testutils/FuseFTruncateTest.h000066400000000000000000000011231347701267100235360ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_FTRUNCATE_TESTUTILS_FUSEFTRUNCATETEST_H_ #define MESSMER_FSPP_TEST_FUSE_FTRUNCATE_TESTUTILS_FUSEFTRUNCATETEST_H_ #include "../../../testutils/FuseTest.h" #include "../../../testutils/OpenFileHandle.h" class FuseFTruncateTest: public FuseTest { public: const char *FILENAME = "/myfile"; void FTruncateFile(const char *filename, fspp::num_bytes_t size); int FTruncateFileReturnError(const char *filename, fspp::num_bytes_t size); private: cpputils::unique_ref OpenFile(const TempTestFS *fs, const char *filename); }; #endif test/fspp/fuse/lstat/000077500000000000000000000000001347701267100151065ustar00rootroot00000000000000test/fspp/fuse/lstat/FuseLstatErrorTest.cpp000066400000000000000000000017711347701267100214040ustar00rootroot00000000000000#include "testutils/FuseLstatTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::StrEq; using ::testing::_; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::AtLeast; using fspp::fuse::FuseErrnoException; class FuseLstatErrorTest: public FuseLstatTest, public WithParamInterface { public: }; INSTANTIATE_TEST_CASE_P(LstatErrorCodes, FuseLstatErrorTest, Values(EACCES, EBADF, EFAULT, ELOOP, ENAMETOOLONG, ENOENT, ENOMEM, ENOTDIR, EOVERFLOW, EINVAL, ENOTDIR)); TEST_F(FuseLstatErrorTest, ReturnNoError) { EXPECT_CALL(*fsimpl, lstat(StrEq(FILENAME), _)).Times(AtLeast(1)).WillRepeatedly(ReturnIsFile); errno = 0; int error = LstatPathReturnError(FILENAME); EXPECT_EQ(0, error); } TEST_P(FuseLstatErrorTest, ReturnError) { EXPECT_CALL(*fsimpl, lstat(StrEq(FILENAME), _)).Times(AtLeast(1)).WillRepeatedly(Throw(FuseErrnoException(GetParam()))); int error = LstatPathReturnError(FILENAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/lstat/FuseLstatPathParameterTest.cpp000066400000000000000000000024551347701267100230500ustar00rootroot00000000000000#include "testutils/FuseLstatTest.h" using ::testing::_; using ::testing::StrEq; using ::testing::AtLeast; class FuseLstatPathParameterTest: public FuseLstatTest { }; TEST_F(FuseLstatPathParameterTest, PathParameterIsCorrectRoot) { EXPECT_CALL(*fsimpl, lstat(StrEq("/"), _)).Times(AtLeast(1)).WillRepeatedly(ReturnIsDir); LstatPath("/"); } TEST_F(FuseLstatPathParameterTest, PathParameterIsCorrectSimpleFile) { EXPECT_CALL(*fsimpl, lstat(StrEq("/myfile"), _)).Times(AtLeast(1)).WillRepeatedly(ReturnIsFile); LstatPath("/myfile"); } TEST_F(FuseLstatPathParameterTest, PathParameterIsCorrectSimpleDir) { EXPECT_CALL(*fsimpl, lstat(StrEq("/mydir"), _)).Times(AtLeast(1)).WillRepeatedly(ReturnIsDir); LstatPath("/mydir/"); } TEST_F(FuseLstatPathParameterTest, PathParameterIsCorrectNestedFile) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); EXPECT_CALL(*fsimpl, lstat(StrEq("/mydir/mydir2/myfile"), _)).Times(AtLeast(1)).WillRepeatedly(ReturnIsFile); LstatPath("/mydir/mydir2/myfile"); } TEST_F(FuseLstatPathParameterTest, PathParameterIsCorrectNestedDir) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); EXPECT_CALL(*fsimpl, lstat(StrEq("/mydir/mydir2/mydir3"), _)).Times(AtLeast(1)).WillRepeatedly(ReturnIsDir); LstatPath("/mydir/mydir2/mydir3/"); } test/fspp/fuse/lstat/FuseLstatReturnAtimeTest.cpp000066400000000000000000000020011347701267100225350ustar00rootroot00000000000000#include "testutils/FuseLstatReturnTest.h" #include using ::testing::WithParamInterface; using ::testing::Values; class FuseLstatReturnATimeTest: public FuseLstatReturnTest, public WithParamInterface { private: void set(fspp::fuse::STAT *stat, time_t value) override { stat->st_atim.tv_sec = value; stat->st_atim.tv_nsec = 0; } }; INSTANTIATE_TEST_CASE_P(FuseLstatReturnATimeTest, FuseLstatReturnATimeTest, Values( 0, 100, 1416496809, // current timestamp as of writing the test 32503680000 // needs a 64bit timestamp )); TEST_P(FuseLstatReturnATimeTest, ReturnedFileAtimeIsCorrect) { fspp::fuse::STAT result = CallFileLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_atim.tv_sec); EXPECT_EQ(0, result.st_atim.tv_nsec); } TEST_P(FuseLstatReturnATimeTest, ReturnedDirAtimeIsCorrect) { fspp::fuse::STAT result = CallDirLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_atim.tv_sec); EXPECT_EQ(0, result.st_atim.tv_nsec); } test/fspp/fuse/lstat/FuseLstatReturnCtimeTest.cpp000066400000000000000000000020011347701267100225370ustar00rootroot00000000000000#include "testutils/FuseLstatReturnTest.h" #include using ::testing::WithParamInterface; using ::testing::Values; class FuseLstatReturnCtimeTest: public FuseLstatReturnTest, public WithParamInterface { private: void set(fspp::fuse::STAT *stat, time_t value) override { stat->st_ctim.tv_sec = value; stat->st_ctim.tv_nsec = 0; } }; INSTANTIATE_TEST_CASE_P(FuseLstatReturnCtimeTest, FuseLstatReturnCtimeTest, Values( 0, 100, 1416496809, // current timestamp as of writing the test 32503680000 // needs a 64bit timestamp )); TEST_P(FuseLstatReturnCtimeTest, ReturnedFileCtimeIsCorrect) { fspp::fuse::STAT result = CallFileLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_ctim.tv_sec); EXPECT_EQ(0, result.st_ctim.tv_nsec); } TEST_P(FuseLstatReturnCtimeTest, ReturnedDirCtimeIsCorrect) { fspp::fuse::STAT result = CallDirLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_ctim.tv_sec); EXPECT_EQ(0, result.st_ctim.tv_nsec); } test/fspp/fuse/lstat/FuseLstatReturnGidTest.cpp000066400000000000000000000013401347701267100222060ustar00rootroot00000000000000#include "testutils/FuseLstatReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseLstatReturnGidTest: public FuseLstatReturnTest, public WithParamInterface { private: void set(fspp::fuse::STAT *stat, gid_t value) override { stat->st_gid = value; } }; INSTANTIATE_TEST_CASE_P(FuseLstatReturnGidTest, FuseLstatReturnGidTest, Values( 0, 10 )); TEST_P(FuseLstatReturnGidTest, ReturnedFileGidIsCorrect) { fspp::fuse::STAT result = CallFileLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_gid); } TEST_P(FuseLstatReturnGidTest, ReturnedDirGidIsCorrect) { fspp::fuse::STAT result = CallDirLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_gid); } test/fspp/fuse/lstat/FuseLstatReturnModeTest.cpp000066400000000000000000000014311347701267100223700ustar00rootroot00000000000000#include "testutils/FuseLstatReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseLstatReturnModeTest: public FuseLstatTest, public WithParamInterface { public: fspp::fuse::STAT CallLstatWithValue(mode_t mode) { return CallLstatWithImpl([mode] (fspp::fuse::STAT *stat) { stat->st_mode = mode; }); } }; INSTANTIATE_TEST_CASE_P(FuseLstatReturnModeTest, FuseLstatReturnModeTest, Values( S_IFREG, S_IFDIR, S_IFREG | S_IRUSR | S_IWGRP | S_IXOTH, // a file with some access bits set S_IFDIR | S_IWUSR | S_IXGRP | S_IROTH // a dir with some access bits set )); TEST_P(FuseLstatReturnModeTest, ReturnedModeIsCorrect) { fspp::fuse::STAT result = CallLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_mode); } test/fspp/fuse/lstat/FuseLstatReturnMtimeTest.cpp000066400000000000000000000020011347701267100225510ustar00rootroot00000000000000#include "testutils/FuseLstatReturnTest.h" #include using ::testing::WithParamInterface; using ::testing::Values; class FuseLstatReturnMtimeTest: public FuseLstatReturnTest, public WithParamInterface { private: void set(fspp::fuse::STAT *stat, time_t value) override { stat->st_mtim.tv_sec = value; stat->st_mtim.tv_nsec = 0; } }; INSTANTIATE_TEST_CASE_P(FuseLstatReturnMtimeTest, FuseLstatReturnMtimeTest, Values( 0, 100, 1416496809, // current timestamp as of writing the test 32503680000 // needs a 64bit timestamp )); TEST_P(FuseLstatReturnMtimeTest, ReturnedFileMtimeIsCorrect) { fspp::fuse::STAT result = CallFileLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_mtim.tv_sec); EXPECT_EQ(0, result.st_mtim.tv_nsec); } TEST_P(FuseLstatReturnMtimeTest, ReturnedDirMtimeIsCorrect) { fspp::fuse::STAT result = CallDirLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_mtim.tv_sec); EXPECT_EQ(0, result.st_mtim.tv_nsec); } test/fspp/fuse/lstat/FuseLstatReturnNlinkTest.cpp000066400000000000000000000014111347701267100225550ustar00rootroot00000000000000#include "testutils/FuseLstatReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseLstatReturnNlinkTest: public FuseLstatReturnTest, public WithParamInterface { private: void set(fspp::fuse::STAT *stat, nlink_t value) override { stat->st_nlink = value; } }; INSTANTIATE_TEST_CASE_P(FuseLstatReturnNlinkTest, FuseLstatReturnNlinkTest, Values( 1, 2, 5, 100 )); TEST_P(FuseLstatReturnNlinkTest, ReturnedFileNlinkIsCorrect) { fspp::fuse::STAT result = CallDirLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_nlink); } TEST_P(FuseLstatReturnNlinkTest, ReturnedDirNlinkIsCorrect) { fspp::fuse::STAT result = CallDirLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_nlink); } test/fspp/fuse/lstat/FuseLstatReturnSizeTest.cpp000066400000000000000000000016341347701267100224230ustar00rootroot00000000000000#include "testutils/FuseLstatReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseLstatReturnSizeTest: public FuseLstatReturnTest, public WithParamInterface { private: void set(fspp::fuse::STAT *stat, fspp::num_bytes_t value) override { stat->st_size = value.value(); } }; INSTANTIATE_TEST_CASE_P(FuseLstatReturnSizeTest, FuseLstatReturnSizeTest, Values( fspp::num_bytes_t(0), fspp::num_bytes_t(1), fspp::num_bytes_t(4096), fspp::num_bytes_t(1024*1024*1024) )); TEST_P(FuseLstatReturnSizeTest, ReturnedFileSizeIsCorrect) { fspp::fuse::STAT result = CallDirLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), fspp::num_bytes_t(result.st_size)); } TEST_P(FuseLstatReturnSizeTest, ReturnedDirSizeIsCorrect) { fspp::fuse::STAT result = CallDirLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), fspp::num_bytes_t(result.st_size)); } test/fspp/fuse/lstat/FuseLstatReturnUidTest.cpp000066400000000000000000000013401347701267100222240ustar00rootroot00000000000000#include "testutils/FuseLstatReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseLstatReturnUidTest: public FuseLstatReturnTest, public WithParamInterface { private: void set(fspp::fuse::STAT *stat, uid_t value) override { stat->st_uid = value; } }; INSTANTIATE_TEST_CASE_P(FuseLstatReturnUidTest, FuseLstatReturnUidTest, Values( 0, 10 )); TEST_P(FuseLstatReturnUidTest, ReturnedFileUidIsCorrect) { fspp::fuse::STAT result = CallFileLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_uid); } TEST_P(FuseLstatReturnUidTest, ReturnedDirUidIsCorrect) { fspp::fuse::STAT result = CallDirLstatWithValue(GetParam()); EXPECT_EQ(GetParam(), result.st_uid); } test/fspp/fuse/lstat/testutils/000077500000000000000000000000001347701267100171465ustar00rootroot00000000000000test/fspp/fuse/lstat/testutils/FuseLstatReturnTest.h000066400000000000000000000032361347701267100232750ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_LSTAT_TESTUTILS_FUSELSTATRETURNTEST_H_ #define MESSMER_FSPP_TEST_FUSE_LSTAT_TESTUTILS_FUSELSTATRETURNTEST_H_ #include "FuseLstatTest.h" // This class offers test helpers for testing (fspp::fuse::STAT) entries. We return them from // our mock filesystem, set up a temporary filesystem, call lstat syscall on it, and // then check the return value. template class FuseLstatReturnTest: public FuseLstatTest { public: // Set the specified (fspp::fuse::STAT) entry to the given value, and test whether it is correctly returned from the syscall. // The CallFile[...] version tests it on a file node of the filesystem, the CallDir[...] version on a dir node. fspp::fuse::STAT CallFileLstatWithValue(Property value); fspp::fuse::STAT CallDirLstatWithValue(Property value); private: std::function SetPropertyImpl(Property value); // Override this function to specify, how to set the specified (fspp::fuse::STAT) entry on the passed (fspp::fuse::STAT *) object. virtual void set(fspp::fuse::STAT *stat, Property value) = 0; }; template fspp::fuse::STAT FuseLstatReturnTest::CallFileLstatWithValue(Property value) { return CallFileLstatWithImpl(SetPropertyImpl(value)); } template fspp::fuse::STAT FuseLstatReturnTest::CallDirLstatWithValue(Property value) { return CallDirLstatWithImpl(SetPropertyImpl(value)); } template std::function FuseLstatReturnTest::SetPropertyImpl(Property value) { return [this, value] (fspp::fuse::STAT *stat) { set(stat, value); }; } #endif test/fspp/fuse/lstat/testutils/FuseLstatTest.cpp000066400000000000000000000034211347701267100224240ustar00rootroot00000000000000#include "FuseLstatTest.h" using std::function; using ::testing::StrEq; using ::testing::_; using ::testing::Invoke; void FuseLstatTest::LstatPath(const std::string &path) { fspp::fuse::STAT dummy{}; LstatPath(path, &dummy); } int FuseLstatTest::LstatPathReturnError(const std::string &path) { fspp::fuse::STAT dummy{}; return LstatPathReturnError(path, &dummy); } void FuseLstatTest::LstatPath(const std::string &path, fspp::fuse::STAT *result) { int error = LstatPathReturnError(path, result); EXPECT_EQ(0, error) << "lstat syscall failed. errno: " << error; } int FuseLstatTest::LstatPathReturnError(const std::string &path, fspp::fuse::STAT *result) { auto fs = TestFS(); auto realpath = fs->mountDir() / path; int retval = ::lstat(realpath.string().c_str(), result); if (retval == 0) { return 0; } else { return errno; } } fspp::fuse::STAT FuseLstatTest::CallFileLstatWithImpl(function implementation) { return CallLstatWithModeAndImpl(S_IFREG, implementation); } fspp::fuse::STAT FuseLstatTest::CallDirLstatWithImpl(function implementation) { return CallLstatWithModeAndImpl(S_IFDIR, implementation); } fspp::fuse::STAT FuseLstatTest::CallLstatWithImpl(function implementation) { EXPECT_CALL(*fsimpl, lstat(StrEq(FILENAME), _)).WillRepeatedly(Invoke([implementation](const char*, fspp::fuse::STAT *stat) { implementation(stat); })); fspp::fuse::STAT result{}; LstatPath(FILENAME, &result); return result; } fspp::fuse::STAT FuseLstatTest::CallLstatWithModeAndImpl(mode_t mode, function implementation) { return CallLstatWithImpl([mode, implementation] (fspp::fuse::STAT *stat) { stat->st_mode = mode; implementation(stat); }); } test/fspp/fuse/lstat/testutils/FuseLstatTest.h000066400000000000000000000040111347701267100220650ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_LSTAT_TESTUTILS_FUSELSTATTEST_H_ #define MESSMER_FSPP_TEST_FUSE_LSTAT_TESTUTILS_FUSELSTATTEST_H_ #include #include #include #include "../../../testutils/FuseTest.h" // This class offers some utility functions for testing lstat(). class FuseLstatTest: public FuseTest { protected: const char *FILENAME = "/myfile"; // Set up a temporary filesystem (using the fsimpl mock in FuseTest as filesystem implementation) // and call the lstat syscall on the given (filesystem-relative) path. void LstatPath(const std::string &path); // Same as LstatPath above, but also return the result of the lstat syscall. void LstatPath(const std::string &path, fspp::fuse::STAT *result); // These two functions are the same as LstatPath above, but they don't fail the test when the lstat syscall // crashes. Instead, they return the value of errno after calling ::lstat. int LstatPathReturnError(const std::string &path); int LstatPathReturnError(const std::string &path, fspp::fuse::STAT *result); // You can specify an implementation, which can modify the (fspp::fuse::STAT *) result, // our fuse mock filesystem implementation will then return this to fuse on an lstat call. // This functions then set up a temporary filesystem with this mock, call lstat on a filesystem node // and return the (fspp::fuse::STAT) returned from an lstat syscall to this filesystem. fspp::fuse::STAT CallLstatWithImpl(std::function implementation); // These two functions are like CallLstatWithImpl, but they also modify the (fspp::fuse::STAT).st_mode // field, so the node accessed is specified to be a file/directory. fspp::fuse::STAT CallFileLstatWithImpl(std::function implementation); fspp::fuse::STAT CallDirLstatWithImpl(std::function implementation); private: fspp::fuse::STAT CallLstatWithModeAndImpl(mode_t mode, std::function implementation); }; #endif test/fspp/fuse/mkdir/000077500000000000000000000000001347701267100150655ustar00rootroot00000000000000test/fspp/fuse/mkdir/FuseMkdirDirnameTest.cpp000066400000000000000000000024541347701267100216270ustar00rootroot00000000000000#include "testutils/FuseMkdirTest.h" using ::testing::_; using ::testing::StrEq; class FuseMkdirDirnameTest: public FuseMkdirTest { }; TEST_F(FuseMkdirDirnameTest, Mkdir) { ReturnDoesntExistOnLstat("/mydir"); EXPECT_CALL(*fsimpl, mkdir(StrEq("/mydir"), _, _, _)) // After mkdir was called, lstat should return that it is a dir. // This is needed to make the ::mkdir() syscall pass. .Times(1).WillOnce(FromNowOnReturnIsDirOnLstat()); Mkdir("/mydir", 0); } TEST_F(FuseMkdirDirnameTest, MkdirNested) { ReturnIsDirOnLstat("/mydir"); ReturnDoesntExistOnLstat("/mydir/mysubdir"); EXPECT_CALL(*fsimpl, mkdir(StrEq("/mydir/mysubdir"), _, _, _)) // After mkdir was called, lstat should return that it is a dir. // This is needed to make the ::mkdir() syscall pass. .Times(1).WillOnce(FromNowOnReturnIsDirOnLstat()); Mkdir("/mydir/mysubdir", 0); } TEST_F(FuseMkdirDirnameTest, MkdirNested2) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); ReturnDoesntExistOnLstat("/mydir/mydir2/mydir3"); EXPECT_CALL(*fsimpl, mkdir(StrEq("/mydir/mydir2/mydir3"), _, _, _)) // After mkdir was called, lstat should return that it is a dir. // This is needed to make the ::mkdir() syscall pass. .Times(1).WillOnce(FromNowOnReturnIsDirOnLstat()); Mkdir("/mydir/mydir2/mydir3", 0); } test/fspp/fuse/mkdir/FuseMkdirErrorTest.cpp000066400000000000000000000020511347701267100213320ustar00rootroot00000000000000#include "testutils/FuseMkdirTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::StrEq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseMkdirErrorTest: public FuseMkdirTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseMkdirErrorTest, FuseMkdirErrorTest, Values(EACCES, EDQUOT, EEXIST, EFAULT, ELOOP, EMLINK, ENAMETOOLONG, ENOENT, ENOMEM, ENOSPC, ENOTDIR, EPERM, EROFS, EBADF)); TEST_F(FuseMkdirErrorTest, NoError) { ReturnDoesntExistOnLstat(DIRNAME); EXPECT_CALL(*fsimpl, mkdir(StrEq(DIRNAME), _, _, _)) .Times(1).WillOnce(FromNowOnReturnIsDirOnLstat()); int error = MkdirReturnError(DIRNAME, 0); EXPECT_EQ(0, error); } TEST_P(FuseMkdirErrorTest, ReturnedErrorIsCorrect) { ReturnDoesntExistOnLstat(DIRNAME); EXPECT_CALL(*fsimpl, mkdir(StrEq(DIRNAME), _, _, _)) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = MkdirReturnError(DIRNAME, 0); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/mkdir/FuseMkdirModeTest.cpp000066400000000000000000000011171347701267100211270ustar00rootroot00000000000000#include "testutils/FuseMkdirTest.h" using ::testing::_; using ::testing::StrEq; using ::testing::WithParamInterface; using ::testing::Values; class FuseMkdirModeTest: public FuseMkdirTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseMkdirModeTest, FuseMkdirModeTest, Values(0, S_IRUSR, S_IRGRP, S_IXOTH, S_IRUSR|S_IRGRP|S_IROTH|S_IRGRP)); TEST_P(FuseMkdirModeTest, Mkdir) { ReturnDoesntExistOnLstat(DIRNAME); EXPECT_CALL(*fsimpl, mkdir(StrEq(DIRNAME), GetParam(), _, _)) .Times(1).WillOnce(FromNowOnReturnIsDirOnLstat()); Mkdir(DIRNAME, GetParam()); } test/fspp/fuse/mkdir/testutils/000077500000000000000000000000001347701267100171255ustar00rootroot00000000000000test/fspp/fuse/mkdir/testutils/FuseMkdirTest.cpp000066400000000000000000000012551347701267100223650ustar00rootroot00000000000000#include "FuseMkdirTest.h" using ::testing::Action; using ::testing::Invoke; void FuseMkdirTest::Mkdir(const char *dirname, mode_t mode) { int error = MkdirReturnError(dirname, mode); EXPECT_EQ(0, error); } int FuseMkdirTest::MkdirReturnError(const char *dirname, mode_t mode) { auto fs = TestFS(); auto realpath = fs->mountDir() / dirname; int retval = ::mkdir(realpath.string().c_str(), mode); if (retval == 0) { return 0; } else { return errno; } } Action FuseMkdirTest::FromNowOnReturnIsDirOnLstat() { return Invoke([this](const char *dirname, mode_t, uid_t, gid_t) { ReturnIsDirOnLstat(dirname); }); } test/fspp/fuse/mkdir/testutils/FuseMkdirTest.h000066400000000000000000000007341347701267100220330ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_MKDIR_TESTUTILS_FUSEMKDIRTEST_H_ #define MESSMER_FSPP_TEST_FUSE_MKDIR_TESTUTILS_FUSEMKDIRTEST_H_ #include "../../../testutils/FuseTest.h" class FuseMkdirTest: public FuseTest { public: const char *DIRNAME = "/mydir"; void Mkdir(const char *dirname, mode_t mode); int MkdirReturnError(const char *dirname, mode_t mode); ::testing::Action FromNowOnReturnIsDirOnLstat(); }; #endif test/fspp/fuse/openFile/000077500000000000000000000000001347701267100155205ustar00rootroot00000000000000test/fspp/fuse/openFile/FuseOpenErrorTest.cpp000066400000000000000000000022301347701267100216170ustar00rootroot00000000000000#include "testutils/FuseOpenTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Return; using ::testing::Throw; using ::testing::StrEq; using ::testing::_; using namespace fspp::fuse; class FuseOpenErrorTest: public FuseOpenTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseOpenErrorTest, FuseOpenErrorTest, Values(EACCES, EDQUOT, EEXIST, EFAULT, EFBIG, EINTR, EOVERFLOW, EINVAL, EISDIR, ELOOP, EMFILE, ENAMETOOLONG, ENFILE, ENODEV, ENOENT, ENOMEM, ENOSPC, ENOTDIR, ENXIO, EOPNOTSUPP, EPERM, EROFS, ETXTBSY, EWOULDBLOCK, EBADF, ENOTDIR)); TEST_F(FuseOpenErrorTest, ReturnNoError) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, openFile(StrEq(FILENAME), _)).Times(1).WillOnce(Return(1)); errno = 0; int error = OpenFileReturnError(FILENAME, O_RDONLY); EXPECT_EQ(0, error); } TEST_P(FuseOpenErrorTest, ReturnError) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, openFile(StrEq(FILENAME), _)).Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = OpenFileReturnError(FILENAME, O_RDONLY); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/openFile/FuseOpenFileDescriptorTest.cpp000066400000000000000000000025501347701267100234510ustar00rootroot00000000000000#include "testutils/FuseOpenTest.h" using ::testing::_; using ::testing::StrEq; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Return; using cpputils::unique_ref; using cpputils::make_unique_ref; class FuseOpenFileDescriptorTest: public FuseOpenTest, public WithParamInterface { public: void OpenAndReadFile(const char *filename) { auto fs = TestFS(); auto fd = OpenFile(fs.get(), filename); ReadFile(fd->fd()); } private: unique_ref OpenFile(const TempTestFS *fs, const char *filename) { auto realpath = fs->mountDir() / filename; auto fd = make_unique_ref(realpath.string().c_str(), O_RDONLY); EXPECT_GE(fd->fd(), 0) << "Opening file failed"; return fd; } void ReadFile(int fd) { uint8_t buf; int retval = ::read(fd, &buf, 1); EXPECT_EQ(1, retval) << "Reading file failed"; } }; INSTANTIATE_TEST_CASE_P(FuseOpenFileDescriptorTest, FuseOpenFileDescriptorTest, Values(0, 2, 5, 1000, 1024*1024*1024)); TEST_P(FuseOpenFileDescriptorTest, TestReturnedFileDescriptor) { ReturnIsFileOnLstatWithSize(FILENAME, fspp::num_bytes_t(1)); EXPECT_CALL(*fsimpl, openFile(StrEq(FILENAME), _)) .Times(1).WillOnce(Return(GetParam())); EXPECT_CALL(*fsimpl, read(GetParam(), _, _, _)).Times(1).WillOnce(Return(fspp::num_bytes_t(1))); OpenAndReadFile(FILENAME); } test/fspp/fuse/openFile/FuseOpenFilenameTest.cpp000066400000000000000000000016601347701267100222540ustar00rootroot00000000000000#include "testutils/FuseOpenTest.h" using ::testing::_; using ::testing::StrEq; using ::testing::Return; class FuseOpenFilenameTest: public FuseOpenTest { public: }; TEST_F(FuseOpenFilenameTest, OpenFile) { ReturnIsFileOnLstat("/myfile"); EXPECT_CALL(*fsimpl, openFile(StrEq("/myfile"), _)) .Times(1).WillOnce(Return(0)); OpenFile("/myfile", O_RDONLY); } TEST_F(FuseOpenFilenameTest, OpenFileNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsFileOnLstat("/mydir/myfile"); EXPECT_CALL(*fsimpl, openFile(StrEq("/mydir/myfile"), _)) .Times(1).WillOnce(Return(0)); OpenFile("/mydir/myfile", O_RDONLY); } TEST_F(FuseOpenFilenameTest, OpenFileNested2) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); ReturnIsFileOnLstat("/mydir/mydir2/myfile"); EXPECT_CALL(*fsimpl, openFile(StrEq("/mydir/mydir2/myfile"), _)) .Times(1).WillOnce(Return(0)); OpenFile("/mydir/mydir2/myfile", O_RDONLY); } test/fspp/fuse/openFile/FuseOpenFlagsTest.cpp000066400000000000000000000010461347701267100215660ustar00rootroot00000000000000#include "testutils/FuseOpenTest.h" using ::testing::StrEq; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Return; class FuseOpenFlagsTest: public FuseOpenTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseOpenFlagsTest, FuseOpenFlagsTest, Values(O_RDWR, O_RDONLY, O_WRONLY)); TEST_P(FuseOpenFlagsTest, testFlags) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, openFile(StrEq(FILENAME), OpenFlagsEq(GetParam()))) .Times(1).WillOnce(Return(0)); OpenFile(FILENAME, GetParam()); } test/fspp/fuse/openFile/testutils/000077500000000000000000000000001347701267100175605ustar00rootroot00000000000000test/fspp/fuse/openFile/testutils/FuseOpenTest.cpp000066400000000000000000000012551347701267100226530ustar00rootroot00000000000000#include "FuseOpenTest.h" using cpputils::unique_ref; using cpputils::make_unique_ref; void FuseOpenTest::OpenFile(const char *filename, int flags) { auto fs = TestFS(); auto fd = OpenFileAllowError(fs.get(), filename, flags); EXPECT_GE(fd->fd(), 0); } int FuseOpenTest::OpenFileReturnError(const char *filename, int flags) { auto fs = TestFS(); auto fd = OpenFileAllowError(fs.get(), filename, flags); return fd->errorcode(); } unique_ref FuseOpenTest::OpenFileAllowError(const TempTestFS *fs, const char *filename, int flags) { auto realpath = fs->mountDir() / filename; return make_unique_ref(realpath.string().c_str(), flags); } test/fspp/fuse/openFile/testutils/FuseOpenTest.h000066400000000000000000000012161347701267100223150ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_OPENFILE_TESTUTILS_FUSEOPENTEST_H_ #define MESSMER_FSPP_TEST_FUSE_OPENFILE_TESTUTILS_FUSEOPENTEST_H_ #include "../../../testutils/FuseTest.h" #include "../../../testutils/OpenFileHandle.h" class FuseOpenTest: public FuseTest { public: const char *FILENAME = "/myfile"; void OpenFile(const char *FILENAME, int flags); int OpenFileReturnError(const char *FILENAME, int flags); private: cpputils::unique_ref OpenFileAllowError(const TempTestFS *fs, const char *FILENAME, int flags); }; MATCHER_P(OpenFlagsEq, expectedFlags, "") { return expectedFlags == (O_ACCMODE & arg); } #endif test/fspp/fuse/read/000077500000000000000000000000001347701267100146725ustar00rootroot00000000000000test/fspp/fuse/read/FuseReadErrorTest.cpp000066400000000000000000000044441347701267100207540ustar00rootroot00000000000000#include "testutils/FuseReadTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using ::testing::Ne; using ::testing::Invoke; using ::testing::Throw; using namespace fspp::fuse; class FuseReadErrorTest: public FuseReadTest, public WithParamInterface { public: fspp::num_bytes_t FILESIZE = fspp::num_bytes_t(64*1024*1024); fspp::num_bytes_t READCOUNT = fspp::num_bytes_t(32*1024*1024); void SetUp() override { //Make the file size big enough that fuse should issue at least two reads ReturnIsFileOnLstatWithSize(FILENAME, FILESIZE); OnOpenReturnFileDescriptor(FILENAME, 0); } }; INSTANTIATE_TEST_CASE_P(FuseReadErrorTest, FuseReadErrorTest, Values(EAGAIN, EBADF, EFAULT, EINTR, EINVAL, EIO, EISDIR, EOVERFLOW, ESPIPE, ENXIO)); TEST_P(FuseReadErrorTest, ReturnErrorOnFirstReadCall) { EXPECT_CALL(*fsimpl, read(0, _, _, _)) .WillRepeatedly(Throw(FuseErrnoException(GetParam()))); char *buf = new char[READCOUNT.value()]; auto retval = ReadFileReturnError(FILENAME, buf, READCOUNT, fspp::num_bytes_t(0)); EXPECT_EQ(GetParam(), retval.error); delete[] buf; } TEST_P(FuseReadErrorTest, ReturnErrorOnSecondReadCall) { // The first read request is from the beginning of the file and works, but the later ones fail. // We store the number of bytes the first call could successfully read and check later that our // read syscall returns exactly this number of bytes fspp::num_bytes_t successfullyReadBytes = fspp::num_bytes_t(-1); EXPECT_CALL(*fsimpl, read(0, _, _, Eq(fspp::num_bytes_t(0)))) .Times(1) .WillOnce(Invoke([&successfullyReadBytes](int, void*, fspp::num_bytes_t count, fspp::num_bytes_t) { // Store the number of successfully read bytes successfullyReadBytes = count; return count; })); EXPECT_CALL(*fsimpl, read(0, _, _, Ne(fspp::num_bytes_t(0)))) .WillRepeatedly(Throw(FuseErrnoException(GetParam()))); char *buf = new char[READCOUNT.value()]; auto retval = ReadFileReturnError(FILENAME, buf, READCOUNT, fspp::num_bytes_t(0)); EXPECT_EQ(0, retval.error); EXPECT_EQ(successfullyReadBytes, retval.read_bytes); // Check that we're getting the number of successfully read bytes (the first read call) returned delete[] buf; } test/fspp/fuse/read/FuseReadFileDescriptorTest.cpp000066400000000000000000000014621347701267100225760ustar00rootroot00000000000000#include "testutils/FuseReadTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using namespace fspp::fuse; class FuseReadFileDescriptorTest: public FuseReadTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseReadFileDescriptorTest, FuseReadFileDescriptorTest, Values(0,1,10,1000,1024*1024*1024)); TEST_P(FuseReadFileDescriptorTest, FileDescriptorIsCorrect) { ReturnIsFileOnLstatWithSize(FILENAME, fspp::num_bytes_t(1)); OnOpenReturnFileDescriptor(FILENAME, GetParam()); EXPECT_CALL(*fsimpl, read(Eq(GetParam()), _, _, _)) .Times(1).WillOnce(ReturnSuccessfulRead); std::array buf{}; ReadFile(FILENAME, buf.data(), fspp::num_bytes_t(1), fspp::num_bytes_t(0)); } test/fspp/fuse/read/FuseReadOverflowTest.cpp000066400000000000000000000024371347701267100214660ustar00rootroot00000000000000#include "testutils/FuseReadTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using namespace fspp::fuse; class FuseReadOverflowTest: public FuseReadTest { public: static constexpr fspp::num_bytes_t FILESIZE = fspp::num_bytes_t(1000); static constexpr fspp::num_bytes_t READSIZE = fspp::num_bytes_t(2000); static constexpr fspp::num_bytes_t OFFSET = fspp::num_bytes_t(500); void SetUp() override { ReturnIsFileOnLstatWithSize(FILENAME, FILESIZE); OnOpenReturnFileDescriptor(FILENAME, 0); EXPECT_CALL(*fsimpl, read(0, _, _, _)).WillRepeatedly(ReturnSuccessfulReadRegardingSize(FILESIZE)); } }; constexpr fspp::num_bytes_t FuseReadOverflowTest::FILESIZE; constexpr fspp::num_bytes_t FuseReadOverflowTest::READSIZE; constexpr fspp::num_bytes_t FuseReadOverflowTest::OFFSET; TEST_F(FuseReadOverflowTest, ReadMoreThanFileSizeFromBeginning) { std::array buf{}; auto retval = ReadFileReturnError(FILENAME, buf.data(), READSIZE, fspp::num_bytes_t(0)); EXPECT_EQ(FILESIZE, retval.read_bytes); } TEST_F(FuseReadOverflowTest, ReadMoreThanFileSizeFromMiddle) { std::array buf{}; auto retval = ReadFileReturnError(FILENAME, buf.data(), READSIZE, OFFSET); EXPECT_EQ(FILESIZE-OFFSET, retval.read_bytes); } test/fspp/fuse/read/FuseReadReturnedDataTest.cpp000066400000000000000000000062441347701267100222450ustar00rootroot00000000000000#include #include #include "../../testutils/InMemoryFile.h" #include "testutils/FuseReadTest.h" #include #include "fspp/fs_interface/FuseErrnoException.h" #include #include using ::testing::_; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Combine; using ::testing::Invoke; using ::testing::Action; using std::tuple; using std::get; using cpputils::Data; using cpputils::DataFixture; using namespace fspp::fuse; // We can't test the count or size parameter directly, because fuse doesn't pass them 1:1. // It usually asks to read bigger blocks (probably does some caching). // But we can test that the data returned from the ::read syscall is the correct data region. struct TestData { TestData(): count(0), offset(0), additional_bytes_at_end_of_file(0) {} TestData(const tuple &data): count(get<0>(data)), offset(get<1>(data)), additional_bytes_at_end_of_file(get<2>(data)) {} fspp::num_bytes_t count; fspp::num_bytes_t offset; //How many more bytes does the file have after the read block? fspp::num_bytes_t additional_bytes_at_end_of_file; fspp::num_bytes_t fileSize() { return count + offset + additional_bytes_at_end_of_file; } }; // The testcase creates random data in memory, offers a mock read() implementation to read from this // memory region and check methods to check for data equality of a region. class FuseReadReturnedDataTest: public FuseReadTest, public WithParamInterface> { public: std::unique_ptr testFile; TestData testData; FuseReadReturnedDataTest() : testFile(nullptr), testData(GetParam()) { testFile = std::make_unique(DataFixture::generate(testData.fileSize().value())); ReturnIsFileOnLstatWithSize(FILENAME, testData.fileSize()); OnOpenReturnFileDescriptor(FILENAME, 0); EXPECT_CALL(*fsimpl, read(0, _, _, _)) .WillRepeatedly(ReadFromFile); } // This read() mock implementation reads from the stored virtual file (testFile). Action ReadFromFile = Invoke([this](int, void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) { return testFile->read(buf, count, offset); }); }; INSTANTIATE_TEST_CASE_P(FuseReadReturnedDataTest, FuseReadReturnedDataTest, Combine( Values(fspp::num_bytes_t(0), fspp::num_bytes_t(1), fspp::num_bytes_t(10), fspp::num_bytes_t(1000), fspp::num_bytes_t(1024), fspp::num_bytes_t(10*1024*1024)), Values(fspp::num_bytes_t(0), fspp::num_bytes_t(1), fspp::num_bytes_t(10), fspp::num_bytes_t(1024), fspp::num_bytes_t(10*1024*1024)), Values(fspp::num_bytes_t(0), fspp::num_bytes_t(1), fspp::num_bytes_t(10), fspp::num_bytes_t(1024), fspp::num_bytes_t(10*1024*1024)) )); TEST_P(FuseReadReturnedDataTest, ReturnedDataRangeIsCorrect) { Data buf(testData.count.value()); ReadFile(FILENAME, buf.data(), testData.count, testData.offset); EXPECT_TRUE(testFile->fileContentEquals(buf, testData.offset)); } test/fspp/fuse/read/testutils/000077500000000000000000000000001347701267100167325ustar00rootroot00000000000000test/fspp/fuse/read/testutils/FuseReadTest.cpp000066400000000000000000000017671347701267100220070ustar00rootroot00000000000000#include "FuseReadTest.h" using cpputils::make_unique_ref; using cpputils::unique_ref; void FuseReadTest::ReadFile(const char *filename, void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) { auto retval = ReadFileReturnError(filename, buf, count, offset); EXPECT_EQ(0, retval.error); EXPECT_EQ(count, retval.read_bytes); } FuseReadTest::ReadError FuseReadTest::ReadFileReturnError(const char *filename, void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) { auto fs = TestFS(); auto fd = OpenFile(fs.get(), filename); ReadError result{}; errno = 0; result.read_bytes = fspp::num_bytes_t(::pread(fd->fd(), buf, count.value(), offset.value())); result.error = errno; return result; } unique_ref FuseReadTest::OpenFile(const TempTestFS *fs, const char *filename) { auto realpath = fs->mountDir() / filename; auto fd = make_unique_ref(realpath.string().c_str(), O_RDONLY); EXPECT_GE(fd->fd(), 0) << "Error opening file"; return fd; } test/fspp/fuse/read/testutils/FuseReadTest.h000066400000000000000000000024551347701267100214470ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_READ_TESTUTILS_FUSEREADTEST_H_ #define MESSMER_FSPP_TEST_FUSE_READ_TESTUTILS_FUSEREADTEST_H_ #include "../../../testutils/FuseTest.h" #include "../../../testutils/OpenFileHandle.h" class FuseReadTest: public FuseTest { public: const char *FILENAME = "/myfile"; struct ReadError { int error{}; fspp::num_bytes_t read_bytes; }; void ReadFile(const char *filename, void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset); ReadError ReadFileReturnError(const char *filename, void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset); ::testing::Action ReturnSuccessfulRead = ::testing::Invoke([](int, void *, fspp::num_bytes_t count, fspp::num_bytes_t) { return count; }); ::testing::Action ReturnSuccessfulReadRegardingSize(fspp::num_bytes_t filesize) { return ::testing::Invoke([filesize](int, void *, fspp::num_bytes_t count, fspp::num_bytes_t offset) { fspp::num_bytes_t ableToReadCount = std::min(count, filesize - offset); return ableToReadCount; }); } private: cpputils::unique_ref OpenFile(const TempTestFS *fs, const char *filename); }; #endif test/fspp/fuse/readDir/000077500000000000000000000000001347701267100153315ustar00rootroot00000000000000test/fspp/fuse/readDir/FuseReadDirDirnameTest.cpp000066400000000000000000000020541347701267100223330ustar00rootroot00000000000000#include "testutils/FuseReadDirTest.h" using ::testing::StrEq; using std::string; class FuseReadDirDirnameTest: public FuseReadDirTest { public: }; TEST_F(FuseReadDirDirnameTest, ReadRootDir) { EXPECT_CALL(*fsimpl, readDir(StrEq("/"))) .Times(1).WillOnce(ReturnDirEntries({})); ReadDir("/"); } TEST_F(FuseReadDirDirnameTest, ReadDir) { ReturnIsDirOnLstat("/mydir"); EXPECT_CALL(*fsimpl, readDir(StrEq("/mydir"))) .Times(1).WillOnce(ReturnDirEntries({})); ReadDir("/mydir"); } TEST_F(FuseReadDirDirnameTest, ReadDirNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); EXPECT_CALL(*fsimpl, readDir(StrEq("/mydir/mydir2"))) .Times(1).WillOnce(ReturnDirEntries({})); ReadDir("/mydir/mydir2"); } TEST_F(FuseReadDirDirnameTest, ReadDirNested2) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); ReturnIsDirOnLstat("/mydir/mydir2/mydir3"); EXPECT_CALL(*fsimpl, readDir(StrEq("/mydir/mydir2/mydir3"))) .Times(1).WillOnce(ReturnDirEntries({})); ReadDir("/mydir/mydir2/mydir3"); } test/fspp/fuse/readDir/FuseReadDirErrorTest.cpp000066400000000000000000000021341347701267100220440ustar00rootroot00000000000000#include "testutils/FuseReadDirTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::StrEq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using std::string; using namespace fspp::fuse; class FuseReadDirErrorTest: public FuseReadDirTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseReadDirErrorTest, FuseReadDirErrorTest, Values(EACCES, EBADF, EMFILE, ENFILE, ENOMEM, ENOTDIR, EFAULT, EINVAL)); //TODO On ENOENT, libfuse doesn't return the ENOENT error, but returns a success response with an empty directory. Why? TEST_F(FuseReadDirErrorTest, NoError) { ReturnIsDirOnLstat(DIRNAME); EXPECT_CALL(*fsimpl, readDir(StrEq(DIRNAME))) .Times(1).WillOnce(ReturnDirEntries({})); int error = ReadDirReturnError(DIRNAME); EXPECT_EQ(0, error); } TEST_P(FuseReadDirErrorTest, ReturnedErrorCodeIsCorrect) { ReturnIsDirOnLstat(DIRNAME); EXPECT_CALL(*fsimpl, readDir(StrEq(DIRNAME))) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = ReadDirReturnError(DIRNAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/readDir/FuseReadDirReturnTest.cpp000066400000000000000000000041621347701267100222350ustar00rootroot00000000000000#include "testutils/FuseReadDirTest.h" #include #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::StrEq; using ::testing::WithParamInterface; using ::testing::Values; using cpputils::unique_ref; using cpputils::make_unique_ref; using std::vector; using std::string; using namespace fspp::fuse; unique_ref> LARGE_DIR(int num_entries) { auto result = make_unique_ref>(); result->reserve(num_entries); for(int i=0; ipush_back("File "+std::to_string(i)+" file"); } return result; } class FuseReadDirReturnTest: public FuseReadDirTest, public WithParamInterface> { public: void testDirEntriesAreCorrect(const vector &direntries) { ReturnIsDirOnLstat(DIRNAME); EXPECT_CALL(*fsimpl, readDir(StrEq(DIRNAME))) .Times(1).WillOnce(ReturnDirEntries(direntries)); auto returned_dir_entries = ReadDir(DIRNAME); EXPECT_EQ(direntries, *returned_dir_entries); } }; INSTANTIATE_TEST_CASE_P(FuseReadDirReturnTest, FuseReadDirReturnTest, Values( vector({}), vector({"oneentry"}), vector({"twoentries_1", "twoentries_2"}), vector({"file1", "file with spaces"}), vector({"file1", ".dotfile"}) )); TEST_P(FuseReadDirReturnTest, ReturnedDirEntriesAreCorrect) { testDirEntriesAreCorrect(GetParam()); } // If using this with GTest Value-Parametrized TEST_P, it breaks some other unrelated tests // (probably because it is doing a lot of construction work on the start of the test program) TEST_F(FuseReadDirReturnTest, ReturnedDirEntriesAreCorrect_LargeDir1000) { auto direntries = LARGE_DIR(1000); testDirEntriesAreCorrect(*direntries); } // If using this with GTest Value-Parametrized TEST_P, it breaks some other unrelated tests // (probably because it is doing a lot of construction work on the start of the test program) // DISABLED, because it uses a lot of memory TEST_F(FuseReadDirReturnTest, DISABLED_ReturnedDirEntriesAreCorrect_LargeDir1000000) { auto direntries = LARGE_DIR(1000000); testDirEntriesAreCorrect(*direntries); } test/fspp/fuse/readDir/testutils/000077500000000000000000000000001347701267100173715ustar00rootroot00000000000000test/fspp/fuse/readDir/testutils/FuseReadDirTest.cpp000066400000000000000000000046111347701267100230740ustar00rootroot00000000000000#include "FuseReadDirTest.h" using cpputils::unique_ref; using cpputils::make_unique_ref; using std::vector; using std::string; using ::testing::Action; using ::testing::Return; unique_ref> FuseReadDirTest::ReadDir(const char *dirname) { auto fs = TestFS(); DIR *dir = openDir(fs.get(), dirname); auto result = make_unique_ref>(); readDirEntries(dir, result.get()); closeDir(dir); return result; } int FuseReadDirTest::ReadDirReturnError(const char *dirname) { auto fs = TestFS(); errno = 0; DIR *dir = openDirAllowError(fs.get(), dirname); EXPECT_EQ(errno!=0, dir==nullptr) << "errno should exactly be != 0 if opendir returned nullptr"; if (errno != 0) { return errno; } auto result = make_unique_ref>(); int error = readDirEntriesAllowError(dir, result.get()); closeDir(dir); return error; } DIR *FuseReadDirTest::openDir(TempTestFS *fs, const char *dirname) { DIR *dir = openDirAllowError(fs, dirname); EXPECT_NE(nullptr, dir) << "Opening directory failed"; return dir; } DIR *FuseReadDirTest::openDirAllowError(TempTestFS *fs, const char *dirname) { auto realpath = fs->mountDir() / dirname; return ::opendir(realpath.string().c_str()); } void FuseReadDirTest::readDirEntries(DIR *dir, vector *result) { int error = readDirEntriesAllowError(dir, result); EXPECT_EQ(0, error); } int FuseReadDirTest::readDirEntriesAllowError(DIR *dir, vector *result) { struct dirent *entry = nullptr; int error = readNextDirEntryAllowError(dir, &entry); if (error != 0) { return error; } while(entry != nullptr) { result->push_back(entry->d_name); int error = readNextDirEntryAllowError(dir, &entry); if (error != 0) { return error; } } return 0; } int FuseReadDirTest::readNextDirEntryAllowError(DIR *dir, struct dirent **result) { errno = 0; *result = ::readdir(dir); return errno; } void FuseReadDirTest::closeDir(DIR *dir) { int retval = ::closedir(dir); EXPECT_EQ(0, retval) << "Closing dir failed"; } Action*(const char*)> FuseReadDirTest::ReturnDirEntries(vector entries) { vector *direntries = new vector(entries.size(), fspp::Dir::Entry(fspp::Dir::EntryType::FILE, "")); for(size_t i = 0; i < entries.size(); ++i) { (*direntries)[i].name = entries[i]; } return Return(direntries); } test/fspp/fuse/readDir/testutils/FuseReadDirTest.h000066400000000000000000000016751347701267100225500ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_READDIR_TESTUTILS_FUSEREADDIRTEST_H_ #define MESSMER_FSPP_TEST_FUSE_READDIR_TESTUTILS_FUSEREADDIRTEST_H_ #include "../../../testutils/FuseTest.h" #include #include "fspp/fs_interface/Dir.h" class FuseReadDirTest: public FuseTest { public: const char *DIRNAME = "/mydir"; cpputils::unique_ref> ReadDir(const char *dirname); int ReadDirReturnError(const char *dirname); static ::testing::Action*(const char*)> ReturnDirEntries(std::vector entries); private: DIR *openDir(TempTestFS *fs, const char *dirname); DIR *openDirAllowError(TempTestFS *fs, const char *dirname); void readDirEntries(DIR *dir, std::vector *result); int readDirEntriesAllowError(DIR *dir, std::vector *result); int readNextDirEntryAllowError(DIR *dir, struct dirent **result); void closeDir(DIR *dir); }; #endif test/fspp/fuse/rename/000077500000000000000000000000001347701267100152265ustar00rootroot00000000000000test/fspp/fuse/rename/FuseRenameErrorTest.cpp000066400000000000000000000016071347701267100216420ustar00rootroot00000000000000#include "testutils/FuseRenameTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::StrEq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseRenameErrorTest: public FuseRenameTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseRenameErrorTest, FuseRenameErrorTest, Values(EACCES, EBUSY, EDQUOT, EFAULT, EINVAL, EISDIR, ELOOP, EMLINK, ENAMETOOLONG, ENOENT, ENOMEM, ENOSPC, ENOTDIR, ENOTEMPTY, EEXIST, EPERM, EROFS, EXDEV, EBADF, ENOTDIR)); TEST_P(FuseRenameErrorTest, ReturnedErrorIsCorrect) { ReturnIsFileOnLstat(FILENAME1); ReturnDoesntExistOnLstat(FILENAME2); EXPECT_CALL(*fsimpl, rename(StrEq(FILENAME1), StrEq(FILENAME2))) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = RenameReturnError(FILENAME1, FILENAME2); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/rename/FuseRenameFilenameTest.cpp000066400000000000000000000107561347701267100222760ustar00rootroot00000000000000#include "testutils/FuseRenameTest.h" using ::testing::StrEq; using ::testing::Return; class FuseRenameFilenameTest: public FuseRenameTest { }; TEST_F(FuseRenameFilenameTest, RenameFileRootToRoot) { ReturnIsFileOnLstat("/myfile"); ReturnDoesntExistOnLstat("/myrenamedfile"); EXPECT_CALL(*fsimpl, rename(StrEq("/myfile"), StrEq("/myrenamedfile"))) .Times(1).WillOnce(Return()); Rename("/myfile", "/myrenamedfile"); } TEST_F(FuseRenameFilenameTest, RenameFileRootToNested) { ReturnIsFileOnLstat("/myfile"); ReturnIsDirOnLstat("/mydir"); ReturnDoesntExistOnLstat("/mydir/myrenamedfile"); EXPECT_CALL(*fsimpl, rename(StrEq("/myfile"), StrEq("/mydir/myrenamedfile"))) .Times(1).WillOnce(Return()); Rename("/myfile", "/mydir/myrenamedfile"); } TEST_F(FuseRenameFilenameTest, RenameFileNestedToRoot) { ReturnDoesntExistOnLstat("/myrenamedfile"); ReturnIsDirOnLstat("/mydir"); ReturnIsFileOnLstat("/mydir/myfile"); EXPECT_CALL(*fsimpl, rename(StrEq("/mydir/myfile"), StrEq("/myrenamedfile"))) .Times(1).WillOnce(Return()); Rename("/mydir/myfile", "/myrenamedfile"); } TEST_F(FuseRenameFilenameTest, RenameFileNestedToNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsFileOnLstat("/mydir/myfile"); ReturnDoesntExistOnLstat("/mydir/myrenamedfile"); EXPECT_CALL(*fsimpl, rename(StrEq("/mydir/myfile"), StrEq("/mydir/myrenamedfile"))) .Times(1).WillOnce(Return()); Rename("/mydir/myfile", "/mydir/myrenamedfile"); } TEST_F(FuseRenameFilenameTest, RenameFileNestedToNested2) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); ReturnIsFileOnLstat("/mydir/mydir2/myfile"); ReturnDoesntExistOnLstat("/mydir/mydir2/myrenamedfile"); EXPECT_CALL(*fsimpl, rename(StrEq("/mydir/mydir2/myfile"), StrEq("/mydir/mydir2/myrenamedfile"))) .Times(1).WillOnce(Return()); Rename("/mydir/mydir2/myfile", "/mydir/mydir2/myrenamedfile"); } TEST_F(FuseRenameFilenameTest, RenameFileNestedToNested_DifferentFolder) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir2"); ReturnIsFileOnLstat("/mydir/myfile"); ReturnDoesntExistOnLstat("/mydir2/myrenamedfile"); EXPECT_CALL(*fsimpl, rename(StrEq("/mydir/myfile"), StrEq("/mydir2/myrenamedfile"))) .Times(1).WillOnce(Return()); Rename("/mydir/myfile", "/mydir2/myrenamedfile"); } TEST_F(FuseRenameFilenameTest, RenameDirRootToRoot) { ReturnIsDirOnLstat("/mydir"); ReturnDoesntExistOnLstat("/myrenameddir"); EXPECT_CALL(*fsimpl, rename(StrEq("/mydir"), StrEq("/myrenameddir"))) .Times(1).WillOnce(Return()); Rename("/mydir", "/myrenameddir"); } TEST_F(FuseRenameFilenameTest, RenameDirRootToNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/myrootdir"); ReturnDoesntExistOnLstat("/myrootdir/myrenameddir"); EXPECT_CALL(*fsimpl, rename(StrEq("/mydir"), StrEq("/myrootdir/myrenameddir"))) .Times(1).WillOnce(Return()); Rename("/mydir", "/myrootdir/myrenameddir"); } TEST_F(FuseRenameFilenameTest, RenameDirNestedToRoot) { ReturnDoesntExistOnLstat("/myrenameddir"); ReturnIsDirOnLstat("/myrootdir"); ReturnIsDirOnLstat("/myrootdir/mydir"); EXPECT_CALL(*fsimpl, rename(StrEq("/myrootdir/mydir"), StrEq("/myrenameddir"))) .Times(1).WillOnce(Return()); Rename("/myrootdir/mydir", "/myrenameddir"); } TEST_F(FuseRenameFilenameTest, RenameDirNestedToNested) { ReturnIsDirOnLstat("/myrootdir"); ReturnIsDirOnLstat("/myrootdir/mydir"); ReturnDoesntExistOnLstat("/myrootdir/myrenameddir"); EXPECT_CALL(*fsimpl, rename(StrEq("/myrootdir/mydir"), StrEq("/myrootdir/myrenameddir"))) .Times(1).WillOnce(Return()); Rename("/myrootdir/mydir", "/myrootdir/myrenameddir"); } TEST_F(FuseRenameFilenameTest, RenameDirNestedToNested2) { ReturnIsDirOnLstat("/myrootdir"); ReturnIsDirOnLstat("/myrootdir/myrootdir2"); ReturnIsDirOnLstat("/myrootdir/myrootdir2/mydir"); ReturnDoesntExistOnLstat("/myrootdir/myrootdir2/myrenameddir"); EXPECT_CALL(*fsimpl, rename(StrEq("/myrootdir/myrootdir2/mydir"), StrEq("/myrootdir/myrootdir2/myrenameddir"))) .Times(1).WillOnce(Return()); Rename("/myrootdir/myrootdir2/mydir", "/myrootdir/myrootdir2/myrenameddir"); } TEST_F(FuseRenameFilenameTest, RenameDirNestedToNested_DifferentFolder) { ReturnIsDirOnLstat("/myrootdir"); ReturnIsDirOnLstat("/myrootdir2"); ReturnIsDirOnLstat("/myrootdir/mydir"); ReturnDoesntExistOnLstat("/myrootdir2/myrenameddir"); EXPECT_CALL(*fsimpl, rename(StrEq("/myrootdir/mydir"), StrEq("/myrootdir2/myrenameddir"))) .Times(1).WillOnce(Return()); Rename("/myrootdir/mydir", "/myrootdir2/myrenameddir"); } test/fspp/fuse/rename/testutils/000077500000000000000000000000001347701267100172665ustar00rootroot00000000000000test/fspp/fuse/rename/testutils/FuseRenameTest.cpp000066400000000000000000000007451347701267100226720ustar00rootroot00000000000000#include "FuseRenameTest.h" void FuseRenameTest::Rename(const char *from, const char *to) { int error = RenameReturnError(from, to); EXPECT_EQ(0, error); } int FuseRenameTest::RenameReturnError(const char *from, const char *to) { auto fs = TestFS(); auto realfrom = fs->mountDir() / from; auto realto = fs->mountDir() / to; int retval = ::rename(realfrom.string().c_str(), realto.string().c_str()); if (0 == retval) { return 0; } else { return errno; } } test/fspp/fuse/rename/testutils/FuseRenameTest.h000066400000000000000000000006601347701267100223330ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_RENAME_TESTUTILS_FUSERENAMETEST_H_ #define MESSMER_FSPP_TEST_FUSE_RENAME_TESTUTILS_FUSERENAMETEST_H_ #include "../../../testutils/FuseTest.h" class FuseRenameTest: public FuseTest { public: const char *FILENAME1 = "/myfile1"; const char *FILENAME2 = "/myfile2"; void Rename(const char *from, const char *to); int RenameReturnError(const char *from, const char *to); }; #endif test/fspp/fuse/rmdir/000077500000000000000000000000001347701267100150745ustar00rootroot00000000000000test/fspp/fuse/rmdir/FuseRmdirDirnameTest.cpp000066400000000000000000000024521347701267100216430ustar00rootroot00000000000000#include "testutils/FuseRmdirTest.h" using ::testing::StrEq; class FuseRmdirDirnameTest: public FuseRmdirTest { }; TEST_F(FuseRmdirDirnameTest, Rmdir) { ReturnIsDirOnLstat("/mydir"); EXPECT_CALL(*fsimpl, rmdir(StrEq("/mydir"))) // After rmdir was called, lstat should return that it doesn't exist anymore // This is needed to make the ::rmdir() syscall pass. .Times(1).WillOnce(FromNowOnReturnDoesntExistOnLstat()); Rmdir("/mydir"); } TEST_F(FuseRmdirDirnameTest, RmdirNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mysubdir"); EXPECT_CALL(*fsimpl, rmdir(StrEq("/mydir/mysubdir"))) // After rmdir was called, lstat should return that it doesn't exist anymore // This is needed to make the ::rmdir() syscall pass. .Times(1).WillOnce(FromNowOnReturnDoesntExistOnLstat()); Rmdir("/mydir/mysubdir"); } TEST_F(FuseRmdirDirnameTest, RmdirNested2) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); ReturnIsDirOnLstat("/mydir/mydir2/mydir3"); EXPECT_CALL(*fsimpl, rmdir(StrEq("/mydir/mydir2/mydir3"))) // After rmdir was called, lstat should return that it doesn't exist anymore // This is needed to make the ::rmdir() syscall pass. .Times(1).WillOnce(FromNowOnReturnDoesntExistOnLstat()); Rmdir("/mydir/mydir2/mydir3"); } test/fspp/fuse/rmdir/FuseRmdirErrorTest.cpp000066400000000000000000000013651347701267100213570ustar00rootroot00000000000000#include "testutils/FuseRmdirTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::StrEq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseRmdirErrorTest: public FuseRmdirTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseRmdirErrorTest, FuseRmdirErrorTest, Values(EACCES, EBUSY, EFAULT, EINVAL, ELOOP, ENAMETOOLONG, ENOENT, ENOMEM, ENOTDIR, ENOTEMPTY, EPERM, EROFS)); TEST_P(FuseRmdirErrorTest, ReturnedErrorIsCorrect) { ReturnIsDirOnLstat(DIRNAME); EXPECT_CALL(*fsimpl, rmdir(StrEq(DIRNAME))) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = RmdirReturnError(DIRNAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/rmdir/testutils/000077500000000000000000000000001347701267100171345ustar00rootroot00000000000000test/fspp/fuse/rmdir/testutils/FuseRmdirTest.cpp000066400000000000000000000011471347701267100224030ustar00rootroot00000000000000#include "FuseRmdirTest.h" using ::testing::Action; using ::testing::Invoke; void FuseRmdirTest::Rmdir(const char *dirname) { int error = RmdirReturnError(dirname); EXPECT_EQ(0, error); } int FuseRmdirTest::RmdirReturnError(const char *dirname) { auto fs = TestFS(); auto realpath = fs->mountDir() / dirname; int retval = ::rmdir(realpath.string().c_str()); if (retval == 0) { return 0; } else { return errno; } } Action FuseRmdirTest::FromNowOnReturnDoesntExistOnLstat() { return Invoke([this](const char *dirname) { ReturnDoesntExistOnLstat(dirname); }); } test/fspp/fuse/rmdir/testutils/FuseRmdirTest.h000066400000000000000000000006621347701267100220510ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_RMDIR_TESTUTILS_FUSERMDIRTEST_H_ #define MESSMER_FSPP_TEST_FUSE_RMDIR_TESTUTILS_FUSERMDIRTEST_H_ #include "../../../testutils/FuseTest.h" class FuseRmdirTest: public FuseTest { public: const char *DIRNAME = "/mydir"; void Rmdir(const char *dirname); int RmdirReturnError(const char *dirname); ::testing::Action FromNowOnReturnDoesntExistOnLstat(); }; #endif test/fspp/fuse/statfs/000077500000000000000000000000001347701267100152635ustar00rootroot00000000000000test/fspp/fuse/statfs/FuseStatfsErrorTest.cpp000066400000000000000000000017331347701267100217340ustar00rootroot00000000000000#include "testutils/FuseStatfsTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::Throw; using ::testing::Return; using ::testing::WithParamInterface; using ::testing::Values; using fspp::fuse::FuseErrnoException; class FuseStatfsErrorTest: public FuseStatfsTest, public WithParamInterface { public: }; INSTANTIATE_TEST_CASE_P(FuseStatfsErrorTest, FuseStatfsErrorTest, Values(EACCES, EBADF, EFAULT, EINTR, EIO, ELOOP, ENAMETOOLONG, ENOENT, ENOMEM, ENOSYS, ENOTDIR, EOVERFLOW)); TEST_F(FuseStatfsErrorTest, ReturnNoError) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, statfs(_)).Times(1).WillOnce(Return()); int error = StatfsReturnError(FILENAME); EXPECT_EQ(0, error); } TEST_P(FuseStatfsErrorTest, ReturnError) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, statfs( _)).Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = StatfsReturnError(FILENAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/statfs/FuseStatfsReturnBavailTest.cpp000066400000000000000000000011641347701267100232370ustar00rootroot00000000000000#include "testutils/FuseStatfsReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseStatfsReturnBavailTest: public FuseStatfsReturnTest, public WithParamInterface { private: void set(struct ::statvfs *stat, uint64_t value) override { stat->f_bavail = value; } }; INSTANTIATE_TEST_CASE_P(FuseStatfsReturnBavailTest, FuseStatfsReturnBavailTest, Values( 0, 10, 256, 1024, 4096 )); TEST_P(FuseStatfsReturnBavailTest, ReturnedBavailIsCorrect) { struct ::statvfs result = CallStatfsWithValue(GetParam()); EXPECT_EQ(GetParam(), result.f_bavail); } test/fspp/fuse/statfs/FuseStatfsReturnBfreeTest.cpp000066400000000000000000000011551347701267100230640ustar00rootroot00000000000000#include "testutils/FuseStatfsReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseStatfsReturnBfreeTest: public FuseStatfsReturnTest, public WithParamInterface { private: void set(struct ::statvfs *stat, uint64_t value) override { stat->f_bfree = value; } }; INSTANTIATE_TEST_CASE_P(FuseStatfsReturnBfreeTest, FuseStatfsReturnBfreeTest, Values( 0, 10, 256, 1024, 4096 )); TEST_P(FuseStatfsReturnBfreeTest, ReturnedBfreeIsCorrect) { struct ::statvfs result = CallStatfsWithValue(GetParam()); EXPECT_EQ(GetParam(), result.f_bfree); } test/fspp/fuse/statfs/FuseStatfsReturnBlocksTest.cpp000066400000000000000000000011641347701267100232560ustar00rootroot00000000000000#include "testutils/FuseStatfsReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseStatfsReturnBlocksTest: public FuseStatfsReturnTest, public WithParamInterface { private: void set(struct ::statvfs *stat, uint64_t value) override { stat->f_blocks = value; } }; INSTANTIATE_TEST_CASE_P(FuseStatfsReturnBlocksTest, FuseStatfsReturnBlocksTest, Values( 0, 10, 256, 1024, 4096 )); TEST_P(FuseStatfsReturnBlocksTest, ReturnedBlocksIsCorrect) { struct ::statvfs result = CallStatfsWithValue(GetParam()); EXPECT_EQ(GetParam(), result.f_blocks); } test/fspp/fuse/statfs/FuseStatfsReturnBsizeTest.cpp000066400000000000000000000011741347701267100231160ustar00rootroot00000000000000#include "testutils/FuseStatfsReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseStatfsReturnBsizeTest: public FuseStatfsReturnTest, public WithParamInterface { private: void set(struct ::statvfs *stat, unsigned long value) override { stat->f_bsize = value; } }; INSTANTIATE_TEST_CASE_P(FuseStatfsReturnBsizeTest, FuseStatfsReturnBsizeTest, Values( 0, 10, 256, 1024, 4096 )); TEST_P(FuseStatfsReturnBsizeTest, ReturnedBsizeIsCorrect) { struct ::statvfs result = CallStatfsWithValue(GetParam()); EXPECT_EQ(GetParam(), result.f_bsize); } test/fspp/fuse/statfs/FuseStatfsReturnFfreeTest.cpp000066400000000000000000000011551347701267100230700ustar00rootroot00000000000000#include "testutils/FuseStatfsReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseStatfsReturnFfreeTest: public FuseStatfsReturnTest, public WithParamInterface { private: void set(struct ::statvfs *stat, uint64_t value) override { stat->f_ffree = value; } }; INSTANTIATE_TEST_CASE_P(FuseStatfsReturnFfreeTest, FuseStatfsReturnFfreeTest, Values( 0, 10, 256, 1024, 4096 )); TEST_P(FuseStatfsReturnFfreeTest, ReturnedFfreeIsCorrect) { struct ::statvfs result = CallStatfsWithValue(GetParam()); EXPECT_EQ(GetParam(), result.f_ffree); } test/fspp/fuse/statfs/FuseStatfsReturnFilesTest.cpp000066400000000000000000000011551347701267100231030ustar00rootroot00000000000000#include "testutils/FuseStatfsReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseStatfsReturnFilesTest: public FuseStatfsReturnTest, public WithParamInterface { private: void set(struct ::statvfs *stat, uint64_t value) override { stat->f_files = value; } }; INSTANTIATE_TEST_CASE_P(FuseStatfsReturnFilesTest, FuseStatfsReturnFilesTest, Values( 0, 10, 256, 1024, 4096 )); TEST_P(FuseStatfsReturnFilesTest, ReturnedFilesIsCorrect) { struct ::statvfs result = CallStatfsWithValue(GetParam()); EXPECT_EQ(GetParam(), result.f_files); } test/fspp/fuse/statfs/FuseStatfsReturnNamemaxTest.cpp000066400000000000000000000012121347701267100234210ustar00rootroot00000000000000#include "testutils/FuseStatfsReturnTest.h" using ::testing::WithParamInterface; using ::testing::Values; class FuseStatfsReturnNamemaxTest: public FuseStatfsReturnTest, public WithParamInterface { private: void set(struct ::statvfs *stat, unsigned long value) override { stat->f_namemax = value; } }; INSTANTIATE_TEST_CASE_P(FuseStatfsReturnNamemaxTest, FuseStatfsReturnNamemaxTest, Values( 0, 10, 256, 1024, 4096 )); TEST_P(FuseStatfsReturnNamemaxTest, ReturnedNamemaxIsCorrect) { struct ::statvfs result = CallStatfsWithValue(GetParam()); EXPECT_EQ(GetParam(), result.f_namemax); } test/fspp/fuse/statfs/testutils/000077500000000000000000000000001347701267100173235ustar00rootroot00000000000000test/fspp/fuse/statfs/testutils/FuseStatfsReturnTest.h000066400000000000000000000025111347701267100236220ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_STATFS_TESTUTILS_FUSESTATFSRETURNTEST_H_ #define MESSMER_FSPP_TEST_FUSE_STATFS_TESTUTILS_FUSESTATFSRETURNTEST_H_ #include "FuseStatfsTest.h" // This class offers test helpers for testing (struct statfs) entries. We return them from // our mock filesystem, set up a temporary filesystem, call statfs syscall on it, and // then check the return value. template class FuseStatfsReturnTest: public FuseStatfsTest { public: // Set the specified (struct statfs) entry to the given value, and test whether it is correctly returned from the syscall. struct ::statvfs CallStatfsWithValue(Property value); private: std::function SetPropertyImpl(Property value); // Override this function to specify, how to set the specified (struct statfs) entry on the passed (struct statfs *) object. virtual void set(struct ::statvfs *statfs, Property value) = 0; }; template inline struct ::statvfs FuseStatfsReturnTest::CallStatfsWithValue(Property value) { return CallStatfsWithImpl(SetPropertyImpl(value)); } template inline std::function FuseStatfsReturnTest::SetPropertyImpl(Property value) { return [this, value] (struct ::statvfs *stat) { set(stat, value); }; } #endif test/fspp/fuse/statfs/testutils/FuseStatfsTest.cpp000066400000000000000000000022321347701267100227550ustar00rootroot00000000000000#include "FuseStatfsTest.h" using std::function; using ::testing::_; using ::testing::Invoke; void FuseStatfsTest::Statfs(const std::string &path) { struct ::statvfs dummy{}; Statfs(path, &dummy); } int FuseStatfsTest::StatfsReturnError(const std::string &path) { struct ::statvfs dummy{}; return StatfsReturnError(path, &dummy); } void FuseStatfsTest::Statfs(const std::string &path, struct ::statvfs *result) { int error = StatfsReturnError(path, result); EXPECT_EQ(0, error) << "lstat syscall failed. errno: " << errno; } int FuseStatfsTest::StatfsReturnError(const std::string &path, struct ::statvfs *result) { auto fs = TestFS(); auto realpath = fs->mountDir() / path; int retval = ::statvfs(realpath.string().c_str(), result); if (retval == 0) { return 0; } else { return errno; } } struct ::statvfs FuseStatfsTest::CallStatfsWithImpl(function implementation) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, statfs(_)).WillRepeatedly(Invoke([implementation](struct ::statvfs *stat) { implementation(stat); })); struct ::statvfs result{}; Statfs(FILENAME, &result); return result; } test/fspp/fuse/statfs/testutils/FuseStatfsTest.h000066400000000000000000000027731347701267100224340ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_STATFS_TESTUTILS_FUSESTATFSTEST_H_ #define MESSMER_FSPP_TEST_FUSE_STATFS_TESTUTILS_FUSESTATFSTEST_H_ #include #include #include "../../../testutils/FuseTest.h" // This class offers some utility functions for testing statfs(). class FuseStatfsTest: public FuseTest { protected: const char *FILENAME = "/myfile"; // Set up a temporary filesystem (using the fsimpl mock in FuseTest as filesystem implementation) // and call the statfs syscall on the given (filesystem-relative) path. void Statfs(const std::string &path); // Same as Statfs above, but also return the result of the statfs syscall. void Statfs(const std::string &path, struct ::statvfs *result); // These two functions are the same as Statfs above, but they don't fail the test when the statfs syscall // crashes. Instead, they return the result value of the statfs syscall. int StatfsReturnError(const std::string &path); int StatfsReturnError(const std::string &path, struct ::statvfs *result); // You can specify an implementation, which can modify the (struct statfs *) result, // our fuse mock filesystem implementation will then return this to fuse on an statfs call. // This functions then set up a temporary filesystem with this mock, calls statfs on a filesystem node // and returns the (struct statfs) returned from an statfs syscall to this filesystem. struct ::statvfs CallStatfsWithImpl(std::function implementation); }; #endif test/fspp/fuse/truncate/000077500000000000000000000000001347701267100156045ustar00rootroot00000000000000test/fspp/fuse/truncate/FuseTruncateErrorTest.cpp000066400000000000000000000015141347701267100225730ustar00rootroot00000000000000#include "testutils/FuseTruncateTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::StrEq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseTruncateErrorTest: public FuseTruncateTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseTruncateErrorTest, FuseTruncateErrorTest, Values(EACCES, EFAULT, EFBIG, EINTR, EINVAL, EIO, EISDIR, ELOOP, ENAMETOOLONG, ENOENT, ENOTDIR, EPERM, EROFS, ETXTBSY)); TEST_P(FuseTruncateErrorTest, ReturnedErrorIsCorrect) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, truncate(StrEq(FILENAME), _)) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = TruncateFileReturnError(FILENAME, fspp::num_bytes_t(0)); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/truncate/FuseTruncateFilenameTest.cpp000066400000000000000000000017711347701267100232270ustar00rootroot00000000000000#include "testutils/FuseTruncateTest.h" using ::testing::_; using ::testing::StrEq; using ::testing::Return; class FuseTruncateFilenameTest: public FuseTruncateTest { }; TEST_F(FuseTruncateFilenameTest, TruncateFile) { ReturnIsFileOnLstat("/myfile"); EXPECT_CALL(*fsimpl, truncate(StrEq("/myfile"), _)) .Times(1).WillOnce(Return()); TruncateFile("/myfile", fspp::num_bytes_t(0)); } TEST_F(FuseTruncateFilenameTest, TruncateFileNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsFileOnLstat("/mydir/myfile"); EXPECT_CALL(*fsimpl, truncate(StrEq("/mydir/myfile"), _)) .Times(1).WillOnce(Return()); TruncateFile("/mydir/myfile", fspp::num_bytes_t(0)); } TEST_F(FuseTruncateFilenameTest, TruncateFileNested2) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); ReturnIsFileOnLstat("/mydir/mydir2/myfile"); EXPECT_CALL(*fsimpl, truncate(StrEq("/mydir/mydir2/myfile"), _)) .Times(1).WillOnce(Return()); TruncateFile("/mydir/mydir2/myfile", fspp::num_bytes_t(0)); } test/fspp/fuse/truncate/FuseTruncateSizeTest.cpp000066400000000000000000000013231347701267100224120ustar00rootroot00000000000000#include "testutils/FuseTruncateTest.h" using ::testing::StrEq; using ::testing::Return; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; class FuseTruncateSizeTest: public FuseTruncateTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseTruncateSizeTest, FuseTruncateSizeTest, Values( fspp::num_bytes_t(0), fspp::num_bytes_t(1), fspp::num_bytes_t(10), fspp::num_bytes_t(1024), fspp::num_bytes_t(1024*1024*1024))); TEST_P(FuseTruncateSizeTest, TruncateFile) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, truncate(StrEq(FILENAME), Eq(GetParam()))) .Times(1).WillOnce(Return()); TruncateFile(FILENAME, GetParam()); } test/fspp/fuse/truncate/testutils/000077500000000000000000000000001347701267100176445ustar00rootroot00000000000000test/fspp/fuse/truncate/testutils/FuseTruncateTest.cpp000066400000000000000000000007601347701267100236230ustar00rootroot00000000000000#include "FuseTruncateTest.h" void FuseTruncateTest::TruncateFile(const char *filename, fspp::num_bytes_t size) { int error = TruncateFileReturnError(filename, size); EXPECT_EQ(0, error); } int FuseTruncateTest::TruncateFileReturnError(const char *filename, fspp::num_bytes_t size) { auto fs = TestFS(); auto realpath = fs->mountDir() / filename; int retval = ::truncate(realpath.string().c_str(), size.value()); if (retval == 0) { return 0; } else { return errno; } } test/fspp/fuse/truncate/testutils/FuseTruncateTest.h000066400000000000000000000006661347701267100232750ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_TRUNCATE_TESTUTILS_FUSETRUNCATETEST_H_ #define MESSMER_FSPP_TEST_FUSE_TRUNCATE_TESTUTILS_FUSETRUNCATETEST_H_ #include "../../../testutils/FuseTest.h" class FuseTruncateTest: public FuseTest { public: const char *FILENAME = "/myfile"; void TruncateFile(const char *filename, fspp::num_bytes_t size); int TruncateFileReturnError(const char *filename, fspp::num_bytes_t size); }; #endif test/fspp/fuse/unlink/000077500000000000000000000000001347701267100152575ustar00rootroot00000000000000test/fspp/fuse/unlink/FuseUnlinkErrorTest.cpp000066400000000000000000000014031347701267100217160ustar00rootroot00000000000000#include "testutils/FuseUnlinkTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::StrEq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseUnlinkErrorTest: public FuseUnlinkTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseUnlinkErrorTest, FuseUnlinkErrorTest, Values(EACCES, EBUSY, EFAULT, EIO, EISDIR, ELOOP, ENAMETOOLONG, ENOENT, ENOMEM, ENOTDIR, EPERM, EROFS, EINVAL)); TEST_P(FuseUnlinkErrorTest, ReturnedErrorIsCorrect) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, unlink(StrEq(FILENAME))) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = UnlinkReturnError(FILENAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/unlink/FuseUnlinkFilenameTest.cpp000066400000000000000000000025001347701267100223440ustar00rootroot00000000000000#include "testutils/FuseUnlinkTest.h" using ::testing::StrEq; class FuseUnlinkFilenameTest: public FuseUnlinkTest { }; TEST_F(FuseUnlinkFilenameTest, Unlink) { ReturnIsFileOnLstat("/mydir"); EXPECT_CALL(*fsimpl, unlink(StrEq("/mydir"))) // After rmdir was called, lstat should return that it doesn't exist anymore // This is needed to make the ::rmdir() syscall pass. .Times(1).WillOnce(FromNowOnReturnDoesntExistOnLstat()); Unlink("/mydir"); } TEST_F(FuseUnlinkFilenameTest, UnlinkNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsFileOnLstat("/mydir/mysubdir"); EXPECT_CALL(*fsimpl, unlink(StrEq("/mydir/mysubdir"))) // After rmdir was called, lstat should return that it doesn't exist anymore // This is needed to make the ::rmdir() syscall pass. .Times(1).WillOnce(FromNowOnReturnDoesntExistOnLstat()); Unlink("/mydir/mysubdir"); } TEST_F(FuseUnlinkFilenameTest, UnlinkNested2) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); ReturnIsFileOnLstat("/mydir/mydir2/mydir3"); EXPECT_CALL(*fsimpl, unlink(StrEq("/mydir/mydir2/mydir3"))) // After rmdir was called, lstat should return that it doesn't exist anymore // This is needed to make the ::rmdir() syscall pass. .Times(1).WillOnce(FromNowOnReturnDoesntExistOnLstat()); Unlink("/mydir/mydir2/mydir3"); } test/fspp/fuse/unlink/testutils/000077500000000000000000000000001347701267100173175ustar00rootroot00000000000000test/fspp/fuse/unlink/testutils/FuseUnlinkTest.cpp000066400000000000000000000011651347701267100227510ustar00rootroot00000000000000#include "FuseUnlinkTest.h" using ::testing::Action; using ::testing::Invoke; void FuseUnlinkTest::Unlink(const char *filename) { int error = UnlinkReturnError(filename); EXPECT_EQ(0, error); } int FuseUnlinkTest::UnlinkReturnError(const char *filename) { auto fs = TestFS(); auto realpath = fs->mountDir() / filename; int retval = ::unlink(realpath.string().c_str()); if (0 == retval) { return 0; } else { return errno; } } Action FuseUnlinkTest::FromNowOnReturnDoesntExistOnLstat() { return Invoke([this](const char *filename) { ReturnDoesntExistOnLstat(filename); }); } test/fspp/fuse/unlink/testutils/FuseUnlinkTest.h000066400000000000000000000006751347701267100224230ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_UNLINK_TESTUTILS_FUSEUNLINKTEST_H_ #define MESSMER_FSPP_TEST_FUSE_UNLINK_TESTUTILS_FUSEUNLINKTEST_H_ #include "../../../testutils/FuseTest.h" class FuseUnlinkTest: public FuseTest { public: const char *FILENAME = "/myfile"; void Unlink(const char *filename); int UnlinkReturnError(const char *filename); ::testing::Action FromNowOnReturnDoesntExistOnLstat(); }; #endif test/fspp/fuse/utimens/000077500000000000000000000000001347701267100154435ustar00rootroot00000000000000test/fspp/fuse/utimens/FuseUtimensErrorTest.cpp000066400000000000000000000013611347701267100222710ustar00rootroot00000000000000#include "testutils/FuseUtimensTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::StrEq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseUtimensErrorTest: public FuseUtimensTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseUtimensErrorTest, FuseUtimensErrorTest, Values(EACCES, ENOENT, EPERM, EROFS)); TEST_P(FuseUtimensErrorTest, ReturnedErrorIsCorrect) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, utimens(StrEq(FILENAME), _, _)) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = UtimensReturnError(FILENAME, TIMEVALUE, TIMEVALUE); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/utimens/FuseUtimensFilenameTest.cpp000066400000000000000000000034341347701267100227230ustar00rootroot00000000000000#include "testutils/FuseUtimensTest.h" using ::testing::_; using ::testing::StrEq; using ::testing::Return; class FuseUtimensFilenameTest: public FuseUtimensTest { }; TEST_F(FuseUtimensFilenameTest, UtimensFile) { ReturnIsFileOnLstat("/myfile"); EXPECT_CALL(*fsimpl, utimens(StrEq("/myfile"), _, _)) .Times(1).WillOnce(Return()); Utimens("/myfile", TIMEVALUE, TIMEVALUE); } TEST_F(FuseUtimensFilenameTest, UtimensFileNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsFileOnLstat("/mydir/myfile"); EXPECT_CALL(*fsimpl, utimens(StrEq("/mydir/myfile"), _, _)) .Times(1).WillOnce(Return()); Utimens("/mydir/myfile", TIMEVALUE, TIMEVALUE); } TEST_F(FuseUtimensFilenameTest, UtimensFileNested2) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); ReturnIsFileOnLstat("/mydir/mydir2/myfile"); EXPECT_CALL(*fsimpl, utimens(StrEq("/mydir/mydir2/myfile"), _, _)) .Times(1).WillOnce(Return()); Utimens("/mydir/mydir2/myfile", TIMEVALUE, TIMEVALUE); } TEST_F(FuseUtimensFilenameTest, UtimensDir) { ReturnIsDirOnLstat("/mydir"); EXPECT_CALL(*fsimpl, utimens(StrEq("/mydir"), _, _)) .Times(1).WillOnce(Return()); Utimens("/mydir", TIMEVALUE, TIMEVALUE); } TEST_F(FuseUtimensFilenameTest, UtimensDirNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); EXPECT_CALL(*fsimpl, utimens(StrEq("/mydir/mydir2"), _, _)) .Times(1).WillOnce(Return()); Utimens("/mydir/mydir2", TIMEVALUE, TIMEVALUE); } TEST_F(FuseUtimensFilenameTest, UtimensDirNested2) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); ReturnIsDirOnLstat("/mydir/mydir2/mydir3"); EXPECT_CALL(*fsimpl, utimens(StrEq("/mydir/mydir2/mydir3"), _, _)) .Times(1).WillOnce(Return()); Utimens("/mydir/mydir2/mydir3", TIMEVALUE, TIMEVALUE); } test/fspp/fuse/utimens/FuseUtimensTimeParameterTest.cpp000066400000000000000000000041141347701267100237360ustar00rootroot00000000000000#include "testutils/FuseUtimensTest.h" using ::testing::StrEq; using ::testing::Return; using ::testing::WithParamInterface; using ::testing::Values; class FuseUtimensTimeParameterTest: public FuseUtimensTest, public WithParamInterface> { }; const std::array TIMEVAL1 = {FuseUtimensTest::makeTimespec(0,0), FuseUtimensTest::makeTimespec(0,0)}; const std::array TIMEVAL2 = {FuseUtimensTest::makeTimespec(1000,0), FuseUtimensTest::makeTimespec(0,0)}; const std::array TIMEVAL3 = {FuseUtimensTest::makeTimespec(0,1000), FuseUtimensTest::makeTimespec(0,0)}; const std::array TIMEVAL4 = {FuseUtimensTest::makeTimespec(1000,1000), FuseUtimensTest::makeTimespec(0,0)}; const std::array TIMEVAL5 = {FuseUtimensTest::makeTimespec(0,0), FuseUtimensTest::makeTimespec(0,0)}; const std::array TIMEVAL6 = {FuseUtimensTest::makeTimespec(0,0), FuseUtimensTest::makeTimespec(1000,0)}; const std::array TIMEVAL7 = {FuseUtimensTest::makeTimespec(0,0), FuseUtimensTest::makeTimespec(0,1000)}; const std::array TIMEVAL8 = {FuseUtimensTest::makeTimespec(0,0), FuseUtimensTest::makeTimespec(1000,1000)}; const std::array TIMEVAL9 = {FuseUtimensTest::makeTimespec(1417196126,123000), FuseUtimensTest::makeTimespec(1417109713,321000)}; // current timestamp and the day before as of writing this test case const std::array TIMEVAL10 = {FuseUtimensTest::makeTimespec(UINT64_C(1024)*1024*1024*1024,999000), FuseUtimensTest::makeTimespec(UINT64_C(2*1024)*1024*1024*1024,321000)}; // needs 64bit for timestamp representation INSTANTIATE_TEST_CASE_P(FuseUtimensTimeParameterTest, FuseUtimensTimeParameterTest, Values(TIMEVAL1, TIMEVAL2, TIMEVAL3, TIMEVAL4, TIMEVAL5, TIMEVAL6, TIMEVAL7, TIMEVAL8, TIMEVAL9, TIMEVAL10)); TEST_P(FuseUtimensTimeParameterTest, Utimens) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, utimens(StrEq(FILENAME), TimeSpecEq(GetParam()[0]), TimeSpecEq(GetParam()[1]))) .Times(1).WillOnce(Return()); Utimens(FILENAME, GetParam()[0], GetParam()[1]); } test/fspp/fuse/utimens/testutils/000077500000000000000000000000001347701267100175035ustar00rootroot00000000000000test/fspp/fuse/utimens/testutils/FuseUtimensTest.cpp000066400000000000000000000013631347701267100233210ustar00rootroot00000000000000#include "FuseUtimensTest.h" #include void FuseUtimensTest::Utimens(const char *filename, timespec lastAccessTime, timespec lastModificationTime) { int error = UtimensReturnError(filename, lastAccessTime, lastModificationTime); EXPECT_EQ(0, error); } int FuseUtimensTest::UtimensReturnError(const char *filename, timespec lastAccessTime, timespec lastModificationTime) { auto fs = TestFS(); auto realpath = fs->mountDir() / filename; return cpputils::set_filetime(realpath.string().c_str(), lastAccessTime, lastModificationTime); } struct timespec FuseUtimensTest::makeTimespec(time_t tv_sec, long tv_nsec) { struct timespec result{}; result.tv_sec = tv_sec; result.tv_nsec = tv_nsec; return result; } test/fspp/fuse/utimens/testutils/FuseUtimensTest.h000066400000000000000000000013121347701267100227600ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_UTIMENS_TESTUTILS_FUSEUTIMENSTEST_H_ #define MESSMER_FSPP_TEST_FUSE_UTIMENS_TESTUTILS_FUSEUTIMENSTEST_H_ #include "../../../testutils/FuseTest.h" class FuseUtimensTest: public FuseTest { public: const char *FILENAME = "/myfile"; timespec TIMEVALUE = makeTimespec(0,0); void Utimens(const char *filename, timespec lastAccessTime, timespec lastModificationTime); int UtimensReturnError(const char *filename, timespec lastAccessTime, timespec lastModificationTime); static struct timespec makeTimespec(time_t tv_sec, long tv_nsec); }; MATCHER_P(TimeSpecEq, expected, "") { return expected.tv_sec == arg.tv_sec && expected.tv_nsec == arg.tv_nsec; } #endif test/fspp/fuse/write/000077500000000000000000000000001347701267100151115ustar00rootroot00000000000000test/fspp/fuse/write/FuseWriteDataTest.cpp000066400000000000000000000067531347701267100211770ustar00rootroot00000000000000#include #include "testutils/FuseWriteTest.h" #include "../../testutils/InMemoryFile.h" #include "fspp/fs_interface/FuseErrnoException.h" #include #include using ::testing::_; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Combine; using ::testing::Invoke; using ::testing::Action; using std::tuple; using std::get; using cpputils::Data; using cpputils::DataFixture; using namespace fspp::fuse; // We can't test the count or size parameter directly, because fuse doesn't pass them 1:1. // But we can test that the data passed to the ::write syscall is correctly written. struct TestData { TestData(): count(0), offset(0), additional_bytes_at_end_of_file(0) {} TestData(const tuple &data): count(get<0>(data)), offset(get<1>(data)), additional_bytes_at_end_of_file(get<2>(data)) {} fspp::num_bytes_t count; fspp::num_bytes_t offset; //How many more bytes does the file have after the read block? fspp::num_bytes_t additional_bytes_at_end_of_file; fspp::num_bytes_t fileSize() { return count + offset + additional_bytes_at_end_of_file; } }; // The testcase creates random data in memory, offers a mock write() implementation to write to this // memory region and check methods to check for data equality of a region. class FuseWriteDataTest: public FuseWriteTest, public WithParamInterface> { public: std::unique_ptr testFile; TestData testData; FuseWriteDataTest() : testFile(nullptr), testData(GetParam()) { testFile = std::make_unique(DataFixture::generate(testData.fileSize().value(), 1)); ReturnIsFileOnLstatWithSize(FILENAME, testData.fileSize()); OnOpenReturnFileDescriptor(FILENAME, 0); EXPECT_CALL(*fsimpl, write(0, _, _, _)) .WillRepeatedly(WriteToFile); } // This write() mock implementation writes to the stored virtual file. Action WriteToFile = Invoke([this](int, const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) { testFile->write(buf, count, offset); }); }; INSTANTIATE_TEST_CASE_P(FuseWriteDataTest, FuseWriteDataTest, Combine( Values(fspp::num_bytes_t(0), fspp::num_bytes_t(1), fspp::num_bytes_t(10), fspp::num_bytes_t(1000), fspp::num_bytes_t(1024), fspp::num_bytes_t(10*1024*1024)), Values(fspp::num_bytes_t(0), fspp::num_bytes_t(1), fspp::num_bytes_t(10), fspp::num_bytes_t(1024), fspp::num_bytes_t(10*1024*1024)), Values(fspp::num_bytes_t(0), fspp::num_bytes_t(1), fspp::num_bytes_t(10), fspp::num_bytes_t(1024), fspp::num_bytes_t(10*1024*1024)) )); TEST_P(FuseWriteDataTest, DataWasCorrectlyWritten) { Data randomWriteData = DataFixture::generate(testData.count.value(), 2); WriteFile(FILENAME, randomWriteData.data(), testData.count, testData.offset); EXPECT_TRUE(testFile->fileContentEquals(randomWriteData, testData.offset)); } TEST_P(FuseWriteDataTest, RestOfFileIsUnchanged) { Data randomWriteData = DataFixture::generate(testData.count.value(), 2); WriteFile(FILENAME, randomWriteData.data(), testData.count, testData.offset); EXPECT_TRUE(testFile->sizeUnchanged()); EXPECT_TRUE(testFile->regionUnchanged(fspp::num_bytes_t(0), testData.offset)); EXPECT_TRUE(testFile->regionUnchanged(testData.offset + testData.count, testData.additional_bytes_at_end_of_file)); } test/fspp/fuse/write/FuseWriteErrorTest.cpp000066400000000000000000000045501347701267100214100ustar00rootroot00000000000000#include "testutils/FuseWriteTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using ::testing::Ne; using ::testing::Invoke; using ::testing::Throw; using namespace fspp::fuse; class FuseWriteErrorTest: public FuseWriteTest, public WithParamInterface { public: fspp::num_bytes_t FILESIZE = fspp::num_bytes_t(64*1024*1024); fspp::num_bytes_t WRITECOUNT = fspp::num_bytes_t(32*1024*1024); void SetUp() override { //Make the file size big enough that fuse should issue at least two writes ReturnIsFileOnLstatWithSize(FILENAME, FILESIZE); OnOpenReturnFileDescriptor(FILENAME, 0); } }; INSTANTIATE_TEST_CASE_P(FuseWriteErrorTest, FuseWriteErrorTest, Values(EAGAIN, EBADF, EDESTADDRREQ, EDQUOT, EFAULT, EFBIG, EINTR, EINVAL, EIO, ENOSPC, EPIPE, EOVERFLOW, ESPIPE, ENXIO)); TEST_P(FuseWriteErrorTest, ReturnErrorOnFirstWriteCall) { EXPECT_CALL(*fsimpl, write(0, _, _, _)) .WillRepeatedly(Throw(FuseErrnoException(GetParam()))); char *buf = new char[WRITECOUNT.value()]; auto retval = WriteFileReturnError(FILENAME, buf, WRITECOUNT, fspp::num_bytes_t(0)); EXPECT_EQ(GetParam(), retval.error); delete[] buf; } TEST_P(FuseWriteErrorTest, ReturnErrorOnSecondWriteCall) { // The first write request is from the beginning of the file and works, but the later ones fail. // We store the number of bytes the first call could successfully write and check later that our // write syscall returns exactly this number of bytes fspp::num_bytes_t successfullyWrittenBytes = fspp::num_bytes_t(-1); EXPECT_CALL(*fsimpl, write(0, _, _, Eq(fspp::num_bytes_t(0)))) .Times(1) .WillOnce(Invoke([&successfullyWrittenBytes](int, const void*, fspp::num_bytes_t count, fspp::num_bytes_t) { // Store the number of successfully written bytes successfullyWrittenBytes = count; })); EXPECT_CALL(*fsimpl, write(0, _, _, Ne(fspp::num_bytes_t(0)))) .WillRepeatedly(Throw(FuseErrnoException(GetParam()))); char *buf = new char[WRITECOUNT.value()]; auto retval = WriteFileReturnError(FILENAME, buf, WRITECOUNT, fspp::num_bytes_t(0)); EXPECT_EQ(0, retval.error); EXPECT_EQ(successfullyWrittenBytes, retval.written_bytes); // Check that we're getting the number of successfully written bytes (the first write call) returned delete[] buf; } test/fspp/fuse/write/FuseWriteFileDescriptorTest.cpp000066400000000000000000000014511347701267100232320ustar00rootroot00000000000000#include "testutils/FuseWriteTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using ::testing::Return; using namespace fspp::fuse; class FuseWriteFileDescriptorTest: public FuseWriteTest, public WithParamInterface { }; INSTANTIATE_TEST_CASE_P(FuseWriteFileDescriptorTest, FuseWriteFileDescriptorTest, Values(0,1,10,1000,1024*1024*1024)); TEST_P(FuseWriteFileDescriptorTest, FileDescriptorIsCorrect) { ReturnIsFileOnLstat(FILENAME); OnOpenReturnFileDescriptor(FILENAME, GetParam()); EXPECT_CALL(*fsimpl, write(Eq(GetParam()), _, _, _)) .Times(1).WillOnce(Return()); std::array buf{}; WriteFile(FILENAME, buf.data(), fspp::num_bytes_t(1), fspp::num_bytes_t(0)); } test/fspp/fuse/write/FuseWriteOverflowTest.cpp000066400000000000000000000061701347701267100221220ustar00rootroot00000000000000#include #include "testutils/FuseWriteTest.h" #include "../../testutils/InMemoryFile.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::_; using ::testing::Invoke; using ::testing::Action; using cpputils::DataFixture; using cpputils::Data; using namespace fspp::fuse; class FuseWriteOverflowTest: public FuseWriteTest { public: fspp::num_bytes_t FILESIZE; fspp::num_bytes_t WRITESIZE; fspp::num_bytes_t OFFSET; WriteableInMemoryFile testFile; Data writeData; FuseWriteOverflowTest(fspp::num_bytes_t filesize, fspp::num_bytes_t writesize, fspp::num_bytes_t offset) : FILESIZE(filesize), WRITESIZE(writesize), OFFSET(offset), testFile(DataFixture::generate(FILESIZE.value())), writeData(DataFixture::generate(WRITESIZE.value())) { ReturnIsFileOnLstatWithSize(FILENAME, FILESIZE); OnOpenReturnFileDescriptor(FILENAME, 0); EXPECT_CALL(*fsimpl, write(0, _, _, _)).WillRepeatedly(WriteToFile); } // This write() mock implementation writes to the stored virtual file. Action WriteToFile = Invoke([this](int, const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) { testFile.write(buf, count, offset); }); }; class FuseWriteOverflowTestWithNonemptyFile: public FuseWriteOverflowTest { public: FuseWriteOverflowTestWithNonemptyFile(): FuseWriteOverflowTest(fspp::num_bytes_t(1000), fspp::num_bytes_t(2000), fspp::num_bytes_t(500)) {} }; TEST_F(FuseWriteOverflowTestWithNonemptyFile, WriteMoreThanFileSizeFromBeginning) { WriteFile(FILENAME, writeData.data(), WRITESIZE, fspp::num_bytes_t(0)); EXPECT_EQ(WRITESIZE, testFile.size()); EXPECT_TRUE(testFile.fileContentEquals(writeData, fspp::num_bytes_t(0))); } TEST_F(FuseWriteOverflowTestWithNonemptyFile, WriteMoreThanFileSizeFromMiddle) { WriteFile(FILENAME, writeData.data(), WRITESIZE, OFFSET); EXPECT_EQ(OFFSET + WRITESIZE, testFile.size()); EXPECT_TRUE(testFile.regionUnchanged(fspp::num_bytes_t(0), OFFSET)); EXPECT_TRUE(testFile.fileContentEquals(writeData, OFFSET)); } TEST_F(FuseWriteOverflowTestWithNonemptyFile, WriteAfterFileEnd) { WriteFile(FILENAME, writeData.data(), WRITESIZE, FILESIZE + OFFSET); EXPECT_EQ(FILESIZE + OFFSET + WRITESIZE, testFile.size()); EXPECT_TRUE(testFile.regionUnchanged(fspp::num_bytes_t(0), FILESIZE)); EXPECT_TRUE(testFile.fileContentEquals(writeData, FILESIZE + OFFSET)); } class FuseWriteOverflowTestWithEmptyFile: public FuseWriteOverflowTest { public: FuseWriteOverflowTestWithEmptyFile(): FuseWriteOverflowTest(fspp::num_bytes_t(0), fspp::num_bytes_t(2000), fspp::num_bytes_t(500)) {} }; TEST_F(FuseWriteOverflowTestWithEmptyFile, WriteToBeginOfEmptyFile) { WriteFile(FILENAME, writeData.data(), WRITESIZE, fspp::num_bytes_t(0)); EXPECT_EQ(WRITESIZE, testFile.size()); EXPECT_TRUE(testFile.fileContentEquals(writeData, fspp::num_bytes_t(0))); } TEST_F(FuseWriteOverflowTestWithEmptyFile, WriteAfterFileEnd) { WriteFile(FILENAME, writeData.data(), WRITESIZE, OFFSET); EXPECT_EQ(OFFSET + WRITESIZE, testFile.size()); EXPECT_TRUE(testFile.fileContentEquals(writeData, OFFSET)); } test/fspp/fuse/write/testutils/000077500000000000000000000000001347701267100171515ustar00rootroot00000000000000test/fspp/fuse/write/testutils/FuseWriteTest.cpp000066400000000000000000000020241347701267100224300ustar00rootroot00000000000000#include "FuseWriteTest.h" using cpputils::unique_ref; using cpputils::make_unique_ref; void FuseWriteTest::WriteFile(const char *filename, const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) { auto retval = WriteFileReturnError(filename, buf, count, offset); EXPECT_EQ(0, retval.error); EXPECT_EQ(count, retval.written_bytes); } FuseWriteTest::WriteError FuseWriteTest::WriteFileReturnError(const char *filename, const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) { auto fs = TestFS(); auto fd = OpenFile(fs.get(), filename); WriteError result{}; errno = 0; result.written_bytes = fspp::num_bytes_t(::pwrite(fd->fd(), buf, count.value(), offset.value())); result.error = errno; return result; } unique_ref FuseWriteTest::OpenFile(const TempTestFS *fs, const char *filename) { auto realpath = fs->mountDir() / filename; auto fd = make_unique_ref(realpath.string().c_str(), O_WRONLY); EXPECT_GE(fd->fd(), 0) << "Error opening file"; return fd; } test/fspp/fuse/write/testutils/FuseWriteTest.h000066400000000000000000000013501347701267100220760ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_FUSE_WRITE_TESTUTILS_FUSEWRITETEST_H_ #define MESSMER_FSPP_TEST_FUSE_WRITE_TESTUTILS_FUSEWRITETEST_H_ #include "../../../testutils/FuseTest.h" #include "../../../testutils/OpenFileHandle.h" class FuseWriteTest: public FuseTest { public: const char *FILENAME = "/myfile"; struct WriteError { int error{}; fspp::num_bytes_t written_bytes; }; void WriteFile(const char *filename, const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset); WriteError WriteFileReturnError(const char *filename, const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset); private: cpputils::unique_ref OpenFile(const TempTestFS *fs, const char *filename); }; #endif test/fspp/impl/000077500000000000000000000000001347701267100137565ustar00rootroot00000000000000test/fspp/impl/FuseOpenFileListTest.cpp000066400000000000000000000071041347701267100205040ustar00rootroot00000000000000#include #include #include "fspp/impl/FuseOpenFileList.h" #include using cpputils::make_unique_ref; using namespace fspp; class MockOpenFile: public OpenFile { public: MockOpenFile(int fileid_, int flags_): fileid(fileid_), flags(flags_), destructed(false) {} int fileid, flags; bool destructed; ~MockOpenFile() {destructed = true;} MOCK_CONST_METHOD0(stat, OpenFile::stat_info()); MOCK_CONST_METHOD1(truncate, void(fspp::num_bytes_t)); MOCK_CONST_METHOD3(read, fspp::num_bytes_t(void*, fspp::num_bytes_t, fspp::num_bytes_t)); MOCK_METHOD3(write, void(const void*, fspp::num_bytes_t, fspp::num_bytes_t)); MOCK_METHOD0(flush, void()); MOCK_METHOD0(fsync, void()); MOCK_METHOD0(fdatasync, void()); }; struct FuseOpenFileListTest: public ::testing::Test { static constexpr int FILEID1 = 4; static constexpr int FLAGS1 = 5; static constexpr int FILEID2 = 6; static constexpr int FLAGS2 = 7; static constexpr int FILEID3 = 8; static constexpr int FLAGS3 = 9; FuseOpenFileListTest(): list() {} FuseOpenFileList list; int open(int fileid, int flags) { return list.open(make_unique_ref(fileid, flags)); } int open() { return open(FILEID1, FILEID2); } void check(int id, int fileid, int flags) { list.load(id, [=](OpenFile* _openFile) { MockOpenFile *openFile = dynamic_cast(_openFile); EXPECT_EQ(fileid, openFile->fileid); EXPECT_EQ(flags, openFile->flags); }); } }; TEST_F(FuseOpenFileListTest, EmptyList1) { ASSERT_THROW(list.load(0, [](OpenFile*) {}), fspp::fuse::FuseErrnoException); } TEST_F(FuseOpenFileListTest, EmptyList2) { ASSERT_THROW(list.load(3, [](OpenFile*) {}), fspp::fuse::FuseErrnoException); } TEST_F(FuseOpenFileListTest, InvalidId) { int valid_id = open(); int invalid_id = valid_id + 1; ASSERT_THROW(list.load(invalid_id, [](OpenFile*) {}), fspp::fuse::FuseErrnoException); } TEST_F(FuseOpenFileListTest, Open1AndGet) { int id = open(FILEID1, FLAGS1); check(id, FILEID1, FLAGS1); } TEST_F(FuseOpenFileListTest, Open2AndGet) { int id1 = open(FILEID1, FLAGS1); int id2 = open(FILEID2, FLAGS2); check(id1, FILEID1, FLAGS1); check(id2, FILEID2, FLAGS2); } TEST_F(FuseOpenFileListTest, Open3AndGet) { int id1 = open(FILEID1, FLAGS1); int id2 = open(FILEID2, FLAGS2); int id3 = open(FILEID3, FLAGS3); check(id1, FILEID1, FLAGS1); check(id3, FILEID3, FLAGS3); check(id2, FILEID2, FLAGS2); } //TODO Test case fails. Disabled it. Figure out why and reenable. /*TEST_F(FuseOpenFileListTest, DestructOnClose) { int id = open(); MockOpenFile *openFile = dynamic_cast(list.get(id)); EXPECT_FALSE(openFile->destructed); list.close(id); EXPECT_TRUE(openFile->destructed); }*/ TEST_F(FuseOpenFileListTest, GetClosedItemOnEmptyList) { int id = open(); ASSERT_NO_THROW(list.load(id, [](OpenFile*) {})); list.close(id); ASSERT_THROW(list.load(id, [](OpenFile*) {}), fspp::fuse::FuseErrnoException); } TEST_F(FuseOpenFileListTest, GetClosedItemOnNonEmptyList) { int id = open(); open(); ASSERT_NO_THROW(list.load(id, [](OpenFile*) {})); list.close(id); ASSERT_THROW(list.load(id, [](OpenFile*) {}), fspp::fuse::FuseErrnoException); } TEST_F(FuseOpenFileListTest, CloseOnEmptyList1) { ASSERT_THROW(list.close(0), std::out_of_range); } TEST_F(FuseOpenFileListTest, CloseOnEmptyList2) { ASSERT_THROW(list.close(4), std::out_of_range); } TEST_F(FuseOpenFileListTest, RemoveInvalidId) { int valid_id = open(); int invalid_id = valid_id + 1; ASSERT_THROW(list.close(invalid_id), std::out_of_range); } test/fspp/impl/IdListTest.cpp000066400000000000000000000042541347701267100165170ustar00rootroot00000000000000#include #include "fspp/impl/IdList.h" #include using cpputils::make_unique_ref; using namespace fspp; class MyObj { public: MyObj(int val_): val(val_) {} int val; }; struct IdListTest: public ::testing::Test { static constexpr int OBJ1 = 3; static constexpr int OBJ2 = 10; static constexpr int OBJ3 = 8; IdListTest(): list() {} IdList list; int add(int num) { return list.add(make_unique_ref(num)); } int add() { return add(OBJ1); } void check(int id, int num) { EXPECT_EQ(num, list.get(id)->val); } void checkConst(int id, int num) { const IdList &constList = list; EXPECT_EQ(num, constList.get(id)->val); } }; TEST_F(IdListTest, EmptyList1) { ASSERT_THROW(list.get(0), std::out_of_range); } TEST_F(IdListTest, EmptyList2) { ASSERT_THROW(list.get(3), std::out_of_range); } TEST_F(IdListTest, InvalidId) { int valid_id = add(); int invalid_id = valid_id + 1; ASSERT_THROW(list.get(invalid_id), std::out_of_range); } TEST_F(IdListTest, GetRemovedItemOnEmptyList) { int id = add(); list.remove(id); ASSERT_THROW(list.get(id), std::out_of_range); } TEST_F(IdListTest, GetRemovedItemOnNonEmptyList) { int id = add(); add(); list.remove(id); ASSERT_THROW(list.get(id), std::out_of_range); } TEST_F(IdListTest, RemoveOnEmptyList1) { ASSERT_THROW(list.remove(0), std::out_of_range); } TEST_F(IdListTest, RemoveOnEmptyList2) { ASSERT_THROW(list.remove(4), std::out_of_range); } TEST_F(IdListTest, RemoveInvalidId) { int valid_id = add(); int invalid_id = valid_id + 1; ASSERT_THROW(list.remove(invalid_id), std::out_of_range); } TEST_F(IdListTest, Add1AndGet) { int id = add(OBJ1); check(id, OBJ1); } TEST_F(IdListTest, Add2AndGet) { int id1 = add(OBJ1); int id2 = add(OBJ2); check(id1, OBJ1); check(id2, OBJ2); } TEST_F(IdListTest, Add3AndGet) { int id1 = add(OBJ1); int id2 = add(OBJ2); int id3 = add(OBJ3); check(id1, OBJ1); check(id3, OBJ3); check(id2, OBJ2); } TEST_F(IdListTest, Add3AndConstGet) { int id1 = add(OBJ1); int id2 = add(OBJ2); int id3 = add(OBJ3); checkConst(id1, OBJ1); checkConst(id3, OBJ3); checkConst(id2, OBJ2); } test/fspp/testutils/000077500000000000000000000000001347701267100150555ustar00rootroot00000000000000test/fspp/testutils/FuseTest.cpp000066400000000000000000000125021347701267100173230ustar00rootroot00000000000000#include "FuseTest.h" using ::testing::StrEq; using ::testing::_; using ::testing::Return; using ::testing::Throw; using ::testing::Action; using ::testing::Invoke; using std::make_shared; using std::shared_ptr; using cpputils::unique_ref; using cpputils::make_unique_ref; namespace bf = boost::filesystem; using namespace fspp::fuse; MockFilesystem::MockFilesystem() {} MockFilesystem::~MockFilesystem() {} FuseTest::FuseTest(): fsimpl(make_shared()) { auto defaultAction = Throw(FuseErrnoException(EIO)); ON_CALL(*fsimpl, openFile(_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, closeFile(_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, lstat(_,_)).WillByDefault(Throw(FuseErrnoException(ENOENT))); ON_CALL(*fsimpl, fstat(_,_)).WillByDefault(Throw(FuseErrnoException(ENOENT))); ON_CALL(*fsimpl, truncate(_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, ftruncate(_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, read(_,_,_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, write(_,_,_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, fsync(_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, fdatasync(_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, access(_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, createAndOpenFile(_,_,_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, mkdir(_,_,_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, rmdir(_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, unlink(_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, rename(_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, readDir(_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, utimens(_,_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, statfs(_)).WillByDefault(Invoke([](struct statvfs *result) { ::statvfs("/", result); // As dummy value take the values from the root filesystem })); ON_CALL(*fsimpl, chmod(_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, chown(_,_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, createSymlink(_,_,_,_)).WillByDefault(defaultAction); ON_CALL(*fsimpl, readSymlink(_,_,_)).WillByDefault(defaultAction); EXPECT_CALL(*fsimpl, access(_,_)).WillRepeatedly(Return()); ReturnIsDirOnLstat("/"); } unique_ref FuseTest::TestFS() { return make_unique_ref(fsimpl); } FuseTest::TempTestFS::TempTestFS(shared_ptr fsimpl) :_mountDir(), _fuse([fsimpl] (Fuse*) {return fsimpl;}, []{}, "fusetest", boost::none), _fuse_thread(&_fuse) { _fuse_thread.start(_mountDir.path(), {"-f"}); } FuseTest::TempTestFS::~TempTestFS() { _fuse_thread.stop(); } const bf::path &FuseTest::TempTestFS::mountDir() const { return _mountDir.path(); } Action FuseTest::ReturnIsFileWithSize(fspp::num_bytes_t size) { return Invoke([size](const char*, fspp::fuse::STAT* result) { result->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; result->st_nlink = 1; result->st_size = size.value(); }); } //TODO Combine ReturnIsFile and ReturnIsFileFstat. This should be possible in gmock by either (a) using ::testing::Undefined as parameter type or (b) using action macros Action FuseTest::ReturnIsFile = ReturnIsFileWithSize(fspp::num_bytes_t(0)); Action FuseTest::ReturnIsFileFstat = Invoke([](int, fspp::fuse::STAT* result) { result->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; result->st_nlink = 1; }); Action FuseTest::ReturnIsFileFstatWithSize(fspp::num_bytes_t size) { return Invoke([size](int, struct ::stat *result) { result->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; result->st_nlink = 1; result->st_size = size.value(); }); } Action FuseTest::ReturnIsDir = Invoke([](const char*, fspp::fuse::STAT* result) { result->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH; result->st_nlink = 1; }); Action FuseTest::ReturnDoesntExist = Throw(fspp::fuse::FuseErrnoException(ENOENT)); void FuseTest::OnOpenReturnFileDescriptor(const char *filename, int descriptor) { EXPECT_CALL(*fsimpl, openFile(StrEq(filename), _)).Times(1).WillOnce(Return(descriptor)); } void FuseTest::ReturnIsFileOnLstat(const bf::path &path) { EXPECT_CALL(*fsimpl, lstat(::testing::StrEq(path.string().c_str()), ::testing::_)).WillRepeatedly(ReturnIsFile); } void FuseTest::ReturnIsFileOnLstatWithSize(const bf::path &path, const fspp::num_bytes_t size) { EXPECT_CALL(*fsimpl, lstat(::testing::StrEq(path.string().c_str()), ::testing::_)).WillRepeatedly(ReturnIsFileWithSize(size)); } void FuseTest::ReturnIsDirOnLstat(const bf::path &path) { EXPECT_CALL(*fsimpl, lstat(::testing::StrEq(path.string().c_str()), ::testing::_)).WillRepeatedly(ReturnIsDir); } void FuseTest::ReturnDoesntExistOnLstat(const bf::path &path) { EXPECT_CALL(*fsimpl, lstat(::testing::StrEq(path.string().c_str()), ::testing::_)).WillRepeatedly(ReturnDoesntExist); } void FuseTest::ReturnIsFileOnFstat(int descriptor) { EXPECT_CALL(*fsimpl, fstat(descriptor, _)).WillRepeatedly(ReturnIsFileFstat); } void FuseTest::ReturnIsFileOnFstatWithSize(int descriptor, fspp::num_bytes_t size) { EXPECT_CALL(*fsimpl, fstat(descriptor, _)).WillRepeatedly(ReturnIsFileFstatWithSize(size)); }test/fspp/testutils/FuseTest.h000066400000000000000000000136441347701267100170000ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_TESTUTILS_FUSETEST_H_ #define MESSMER_FSPP_TEST_TESTUTILS_FUSETEST_H_ #include #include #include "fspp/fuse/Filesystem.h" #include "fspp/fs_interface/FuseErrnoException.h" #include "fspp/fuse/Fuse.h" #include "fspp/fs_interface/Dir.h" #include #include #include "FuseThread.h" #define MOCK_PATH_METHOD1(NAME, RETURNTYPE) \ RETURNTYPE NAME(const boost::filesystem::path &path) override { \ return NAME(path.string().c_str()); \ } \ MOCK_METHOD1(NAME, RETURNTYPE(const char*)) \ #define MOCK_PATH_METHOD2(NAME, RETURNTYPE, PARAM1) \ RETURNTYPE NAME(const boost::filesystem::path &path, PARAM1 param1) override { \ return NAME(path.string().c_str(), param1); \ } \ MOCK_METHOD2(NAME, RETURNTYPE(const char*, PARAM1)) \ #define MOCK_PATH_METHOD3(NAME, RETURNTYPE, PARAM1, PARAM2) \ RETURNTYPE NAME(const boost::filesystem::path &path, PARAM1 p1, PARAM2 p2) override { \ return NAME(path.string().c_str(), p1, p2); \ } \ MOCK_METHOD3(NAME, RETURNTYPE(const char*, PARAM1, PARAM2)) \ #define MOCK_PATH_METHOD4(NAME, RETURNTYPE, PARAM1, PARAM2, PARAM3) \ RETURNTYPE NAME(const boost::filesystem::path &path, PARAM1 p1, PARAM2 p2, PARAM3 p3) override { \ return NAME(path.string().c_str(), p1, p2, p3); \ } \ MOCK_METHOD4(NAME, RETURNTYPE(const char*, PARAM1, PARAM2, PARAM3)) \ class MockFilesystem: public fspp::fuse::Filesystem { public: MockFilesystem(); virtual ~MockFilesystem(); MOCK_PATH_METHOD2(openFile, int, int); MOCK_METHOD1(closeFile, void(int)); MOCK_PATH_METHOD2(lstat, void, fspp::fuse::STAT*); MOCK_METHOD2(fstat, void(int, fspp::fuse::STAT*)); MOCK_PATH_METHOD2(truncate, void, fspp::num_bytes_t); MOCK_METHOD2(ftruncate, void(int, fspp::num_bytes_t)); MOCK_METHOD4(read, fspp::num_bytes_t(int, void*, fspp::num_bytes_t, fspp::num_bytes_t)); MOCK_METHOD4(write, void(int, const void*, fspp::num_bytes_t, fspp::num_bytes_t)); MOCK_METHOD1(flush, void(int)); MOCK_METHOD1(fsync, void(int)); MOCK_METHOD1(fdatasync, void(int)); MOCK_PATH_METHOD2(access, void, int); MOCK_PATH_METHOD4(createAndOpenFile, int, mode_t, uid_t, gid_t); MOCK_PATH_METHOD4(mkdir, void, mode_t, uid_t, gid_t); MOCK_PATH_METHOD1(rmdir, void); MOCK_PATH_METHOD1(unlink, void); void rename(const boost::filesystem::path &from, const boost::filesystem::path &to) override { return rename(from.string().c_str(), to.string().c_str()); } MOCK_METHOD2(rename, void(const char*, const char*)); cpputils::unique_ref> readDir(const boost::filesystem::path &path) override { return cpputils::nullcheck(std::unique_ptr>(readDir(path.string().c_str()))).value(); } MOCK_METHOD1(readDir, std::vector*(const char*)); void utimens(const boost::filesystem::path &path, timespec lastAccessTime, timespec lastModificationTime) override { return utimens(path.string().c_str(), lastAccessTime, lastModificationTime); } MOCK_METHOD3(utimens, void(const char*, timespec, timespec)); MOCK_METHOD1(statfs, void(struct statvfs*)); void createSymlink(const boost::filesystem::path &to, const boost::filesystem::path &from, uid_t uid, gid_t gid) override { return createSymlink(to.string().c_str(), from.string().c_str(), uid, gid); } MOCK_PATH_METHOD2(chmod, void, mode_t); MOCK_PATH_METHOD3(chown, void, uid_t, gid_t); MOCK_METHOD4(createSymlink, void(const char*, const char*, uid_t, gid_t)); MOCK_PATH_METHOD3(readSymlink, void, char*, fspp::num_bytes_t); }; class FuseTest: public ::testing::Test { public: static constexpr const char* FILENAME = "/myfile"; FuseTest(); class TempTestFS { public: TempTestFS(std::shared_ptr fsimpl); virtual ~TempTestFS(); public: const boost::filesystem::path &mountDir() const; private: cpputils::TempDir _mountDir; fspp::fuse::Fuse _fuse; FuseThread _fuse_thread; }; cpputils::unique_ref TestFS(); std::shared_ptr fsimpl; //TODO Combine ReturnIsFile and ReturnIsFileFstat. This should be possible in gmock by either (a) using ::testing::Undefined as parameter type or (b) using action macros static ::testing::Action ReturnIsFile; static ::testing::Action ReturnIsFileWithSize(fspp::num_bytes_t size); static ::testing::Action ReturnIsFileFstat; static ::testing::Action ReturnIsFileFstatWithSize(fspp::num_bytes_t size); static ::testing::Action ReturnIsDir; static ::testing::Action ReturnDoesntExist; void ReturnIsFileOnLstat(const boost::filesystem::path &path); void ReturnIsFileOnLstatWithSize(const boost::filesystem::path &path, fspp::num_bytes_t size); void ReturnIsDirOnLstat(const boost::filesystem::path &path); void ReturnDoesntExistOnLstat(const boost::filesystem::path &path); void OnOpenReturnFileDescriptor(const char *filename, int descriptor); void ReturnIsFileOnFstat(int descriptor); void ReturnIsFileOnFstatWithSize(int descriptor, fspp::num_bytes_t size); }; #endif test/fspp/testutils/FuseThread.cpp000066400000000000000000000021751347701267100176200ustar00rootroot00000000000000#include #include "FuseThread.h" #include #include #include "fspp/fuse/Fuse.h" using boost::thread; using boost::chrono::seconds; using std::string; using std::vector; namespace bf = boost::filesystem; using fspp::fuse::Fuse; FuseThread::FuseThread(Fuse *fuse) :_fuse(fuse), _child() { } void FuseThread::start(const bf::path &mountDir, const vector &fuseOptions) { _child = thread([this, mountDir, fuseOptions] () { _fuse->run(mountDir, fuseOptions); }); //Wait until it is running (busy waiting is simple and doesn't hurt much here) while(!_fuse->running()) {} #ifdef __APPLE__ // On Mac OS X, _fuse->running() returns true too early, because osxfuse calls init() when it's not ready yet. Give it a bit time. std::this_thread::sleep_for(std::chrono::milliseconds(200)); #endif } void FuseThread::stop() { _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) while (_fuse->running()) {} } test/fspp/testutils/FuseThread.h000066400000000000000000000011101347701267100172510ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_TESTUTILS_FUSETHREAD_H_ #define MESSMER_FSPP_TEST_TESTUTILS_FUSETHREAD_H_ #include #include #include #include namespace fspp { namespace fuse { class Fuse; } } class FuseThread { public: FuseThread(fspp::fuse::Fuse *fuse); void start(const boost::filesystem::path &mountDir, const std::vector &fuseOptions); void stop(); private: fspp::fuse::Fuse *_fuse; boost::thread _child; DISALLOW_COPY_AND_ASSIGN(FuseThread); }; #endif test/fspp/testutils/InMemoryFile.cpp000066400000000000000000000034341347701267100201240ustar00rootroot00000000000000#include "InMemoryFile.h" using cpputils::Data; InMemoryFile::InMemoryFile(Data data): _data(std::move(data)) { } InMemoryFile::~InMemoryFile() { } fspp::num_bytes_t InMemoryFile::read(void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) const { fspp::num_bytes_t realCount = std::min(count, fspp::num_bytes_t(_data.size()) - offset); std::memcpy(buf, _data.dataOffset(offset.value()), realCount.value()); return realCount; } const void *InMemoryFile::data() const { return _data.data(); } bool InMemoryFile::fileContentEquals(const Data &expected, fspp::num_bytes_t offset) const { return 0 == std::memcmp(expected.data(), _data.dataOffset(offset.value()), expected.size()); } fspp::num_bytes_t InMemoryFile::size() const { return fspp::num_bytes_t(_data.size()); } WriteableInMemoryFile::WriteableInMemoryFile(Data data): InMemoryFile(std::move(data)), _originalData(_data.copy()) { } void WriteableInMemoryFile::write(const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) { _extendFileSizeIfNecessary(count + offset); std::memcpy(_data.dataOffset(offset.value()), buf, count.value()); } void WriteableInMemoryFile::_extendFileSizeIfNecessary(fspp::num_bytes_t size) { if (size > fspp::num_bytes_t(_data.size())) { _extendFileSize(size); } } void WriteableInMemoryFile::_extendFileSize(fspp::num_bytes_t size) { Data newfile(size.value()); std::memcpy(newfile.data(), _data.data(), _data.size()); _data = std::move(newfile); } bool WriteableInMemoryFile::sizeUnchanged() const { return _data.size() == _originalData.size(); } bool WriteableInMemoryFile::regionUnchanged(fspp::num_bytes_t offset, fspp::num_bytes_t count) const { return 0 == std::memcmp(_data.dataOffset(offset.value()), _originalData.dataOffset(offset.value()), count.value()); } test/fspp/testutils/InMemoryFile.h000066400000000000000000000020111347701267100175570ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_TESTUTILS_INMEMORYFILE_H_ #define MESSMER_FSPP_TEST_TESTUTILS_INMEMORYFILE_H_ #include #include class InMemoryFile { public: InMemoryFile(cpputils::Data data); virtual ~InMemoryFile(); fspp::num_bytes_t read(void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset) const; const void *data() const; fspp::num_bytes_t size() const; bool fileContentEquals(const cpputils::Data &expected, fspp::num_bytes_t offset) const; protected: cpputils::Data _data; }; class WriteableInMemoryFile: public InMemoryFile { public: WriteableInMemoryFile(cpputils::Data data); void write(const void *buf, fspp::num_bytes_t count, fspp::num_bytes_t offset); bool sizeUnchanged() const; bool regionUnchanged(fspp::num_bytes_t offset, fspp::num_bytes_t count) const; private: void _extendFileSizeIfNecessary(fspp::num_bytes_t size); void _extendFileSize(fspp::num_bytes_t size); cpputils::Data _originalData; }; #endif test/fspp/testutils/OpenFileHandle.cpp000066400000000000000000000000341347701267100203730ustar00rootroot00000000000000#include "OpenFileHandle.h" test/fspp/testutils/OpenFileHandle.h000066400000000000000000000021041347701267100200400ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_TEST_TESTUTILS_OPENFILEHANDLE_H_ #define MESSMER_FSPP_TEST_TESTUTILS_OPENFILEHANDLE_H_ #include #include #include #include #include #if defined(_MSC_VER) #include #else #include #endif class OpenFileHandle final { public: OpenFileHandle(const char *path, int flags): fd_(::open(path, flags)), errno_(fd_ >= 0 ? 0 : errno) { } OpenFileHandle(const char *path, int flags, int flags2): fd_(::open(path, flags, flags2)), errno_(fd_ >= 0 ? 0 : errno) { } ~OpenFileHandle() { if (fd_ >= 0) { ::close(fd_); #ifdef __APPLE__ // On Mac OS X, we might have to give it some time to free up the file std::this_thread::sleep_for(std::chrono::milliseconds(50)); #endif } } int fd() { return fd_; } int errorcode() { return errno_; } void release() { fd_ = -1; // don't close anymore } private: int fd_; const int errno_; DISALLOW_COPY_AND_ASSIGN(OpenFileHandle); }; #endif test/gitversion/000077500000000000000000000000001347701267100142365ustar00rootroot00000000000000test/gitversion/CMakeLists.txt000066400000000000000000000005161347701267100170000ustar00rootroot00000000000000project (gitversion-test) set(SOURCES ParserTest.cpp VersionCompareTest.cpp ) add_executable(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} my-gtest-main googletest gitversion) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) test/gitversion/ParserTest.cpp000066400000000000000000000225261347701267100170450ustar00rootroot00000000000000#include #include using namespace gitversion; TEST(ParserTest, TestUnknownVersion) { VersionInfo info = Parser::parse("0+unknown"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("0", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("", info.gitCommitId); EXPECT_EQ("", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestReleaseVersion_1) { VersionInfo info = Parser::parse("0.9.2"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("2", info.hotfixVersion); EXPECT_FALSE( info.isDevVersion); EXPECT_TRUE( info.isStableVersion); EXPECT_EQ("", info.gitCommitId); EXPECT_EQ("", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestReleaseVersion_2) { VersionInfo info = Parser::parse("1.02.3"); EXPECT_EQ("1", info.majorVersion); EXPECT_EQ("02", info.minorVersion); EXPECT_EQ("3", info.hotfixVersion); EXPECT_FALSE( info.isDevVersion); EXPECT_TRUE( info.isStableVersion); EXPECT_EQ("", info.gitCommitId); EXPECT_EQ("", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestReleaseVersion_3) { VersionInfo info = Parser::parse("01.020.3"); EXPECT_EQ("01", info.majorVersion); EXPECT_EQ("020", info.minorVersion); EXPECT_EQ("3", info.hotfixVersion); EXPECT_FALSE( info.isDevVersion); EXPECT_TRUE( info.isStableVersion); EXPECT_EQ("", info.gitCommitId); EXPECT_EQ("", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestDirtyReleaseVersion) { VersionInfo info = Parser::parse("0.9.0+0.g5753e4f.dirty"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("5753e4f", info.gitCommitId); EXPECT_EQ("", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestDevVersion) { VersionInfo info = Parser::parse("0.9.0+2.g0123abcdef"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("0123abcdef", info.gitCommitId); EXPECT_EQ("", info.versionTag); EXPECT_EQ(2u, info.commitsSinceTag); } TEST(ParserTest, TestDirtyDevVersion) { VersionInfo info = Parser::parse("0.9.0+20.g0123abcdef.dirty"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("0123abcdef", info.gitCommitId); EXPECT_EQ("", info.versionTag); EXPECT_EQ(20u, info.commitsSinceTag); } TEST(ParserTest, TestReleaseVersion_StableTag) { VersionInfo info = Parser::parse("0.9.2-stable"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("2", info.hotfixVersion); EXPECT_FALSE( info.isDevVersion); EXPECT_TRUE( info.isStableVersion); EXPECT_EQ("", info.gitCommitId); EXPECT_EQ("stable", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestDirtyReleaseVersion_StableTag) { VersionInfo info = Parser::parse("0.9.0-stable+0.g5753e4f.dirty"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("5753e4f", info.gitCommitId); EXPECT_EQ("stable", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestDevVersion_StableTag) { VersionInfo info = Parser::parse("0.9.0-stable+2.g0123abcdef"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("0123abcdef", info.gitCommitId); EXPECT_EQ("stable", info.versionTag); EXPECT_EQ(2u, info.commitsSinceTag); } TEST(ParserTest, TestDirtyDevVersion_StableTag) { VersionInfo info = Parser::parse("0.9.0-stable+20.g0123abcdef.dirty"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("0123abcdef", info.gitCommitId); EXPECT_EQ("stable", info.versionTag); EXPECT_EQ(20u, info.commitsSinceTag); } TEST(ParserTest, TestReleaseVersion_AlphaTag) { VersionInfo info = Parser::parse("0.9.2-alpha"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("2", info.hotfixVersion); EXPECT_FALSE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("", info.gitCommitId); EXPECT_EQ("alpha", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestDirtyReleaseVersion_AlphaTag) { VersionInfo info = Parser::parse("0.9.0-alpha+0.g5753e4f.dirty"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("5753e4f", info.gitCommitId); EXPECT_EQ("alpha", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestDevVersion_AlphaTag) { VersionInfo info = Parser::parse("0.9.0-alpha+2.g0123abcdef"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("0123abcdef", info.gitCommitId); EXPECT_EQ("alpha", info.versionTag); EXPECT_EQ(2u, info.commitsSinceTag); } TEST(ParserTest, TestDirtyDevVersion_AlphaTag) { VersionInfo info = Parser::parse("0.9.0-alpha+20.g0123abcdef.dirty"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("0123abcdef", info.gitCommitId); EXPECT_EQ("alpha", info.versionTag); EXPECT_EQ(20u, info.commitsSinceTag); } TEST(ParserTest, TestReleaseVersion_WithoutHotfixVersion) { VersionInfo info = Parser::parse("1.0-beta"); EXPECT_EQ("1", info.majorVersion); EXPECT_EQ("0", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_FALSE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("", info.gitCommitId); EXPECT_EQ("beta", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestReleaseVersion_RCTag) { VersionInfo info = Parser::parse("0.9.2-rc1"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("2", info.hotfixVersion); EXPECT_FALSE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("", info.gitCommitId); EXPECT_EQ("rc1", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestDirtyReleaseVersion_RCTag) { VersionInfo info = Parser::parse("0.9.0-rc1+0.g5753e4f.dirty"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("5753e4f", info.gitCommitId); EXPECT_EQ("rc1", info.versionTag); EXPECT_EQ(0u, info.commitsSinceTag); } TEST(ParserTest, TestDevVersion_RCTag) { VersionInfo info = Parser::parse("0.9.0-rc1+2.g0123abcdef"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("0123abcdef", info.gitCommitId); EXPECT_EQ("rc1", info.versionTag); EXPECT_EQ(2u, info.commitsSinceTag); } TEST(ParserTest, TestDirtyDevVersion_RCTag) { VersionInfo info = Parser::parse("0.9.0-rc1+20.g0123abcdef.dirty"); EXPECT_EQ("0", info.majorVersion); EXPECT_EQ("9", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("0123abcdef", info.gitCommitId); EXPECT_EQ("rc1", info.versionTag); EXPECT_EQ(20u, info.commitsSinceTag); } TEST(ParserTest, TestDirtyDevVersion_WithoutMinorVersion) { VersionInfo info = Parser::parse("1-rc1+20.g0123abcdef.dirty"); EXPECT_EQ("1", info.majorVersion); EXPECT_EQ("0", info.minorVersion); EXPECT_EQ("0", info.hotfixVersion); EXPECT_TRUE( info.isDevVersion); EXPECT_FALSE( info.isStableVersion); EXPECT_EQ("0123abcdef", info.gitCommitId); EXPECT_EQ("rc1", info.versionTag); EXPECT_EQ(20u, info.commitsSinceTag); }test/gitversion/VersionCompareTest.cpp000066400000000000000000000054551347701267100205470ustar00rootroot00000000000000#include #include using namespace gitversion; using std::string; class VersionCompareTest : public ::testing::Test { public: void EXPECT_IS_OLDER_THAN(const string &v1, const string &v2) { EXPECT_TRUE(VersionCompare::isOlderThan(v1, v2)); EXPECT_FALSE(VersionCompare::isOlderThan(v2, v1)); } void EXPECT_IS_SAME_AGE(const string &v1, const string &v2) { EXPECT_FALSE(VersionCompare::isOlderThan(v1, v2)); EXPECT_FALSE(VersionCompare::isOlderThan(v2, v1)); } }; TEST_F(VersionCompareTest, IsDifferentVersion) { EXPECT_IS_OLDER_THAN("0.8", "0.8.1"); EXPECT_IS_OLDER_THAN("0.8", "1.0"); EXPECT_IS_OLDER_THAN("0.8", "1.0.1"); EXPECT_IS_OLDER_THAN("0.8.1", "1.0"); EXPECT_IS_OLDER_THAN("0.7.9", "0.8.0"); EXPECT_IS_OLDER_THAN("1.0.0", "1.0.1"); EXPECT_IS_OLDER_THAN("1", "1.0.1"); EXPECT_IS_OLDER_THAN("1.0.0", "1.1"); } TEST_F(VersionCompareTest, IsSameVersion) { EXPECT_IS_SAME_AGE("0.8", "0.8"); EXPECT_IS_SAME_AGE("1.0", "1.0"); EXPECT_IS_SAME_AGE("1", "1.0"); EXPECT_IS_SAME_AGE("1.0.0", "1.0.0"); EXPECT_IS_SAME_AGE("0.8", "0.8.0"); EXPECT_IS_SAME_AGE("1", "1.0.0.0"); } TEST_F(VersionCompareTest, ZeroPrefix) { EXPECT_IS_OLDER_THAN("1.00.0", "1.0.01"); EXPECT_IS_SAME_AGE("1.0.01", "1.0.1"); EXPECT_IS_SAME_AGE("01.0.01", "1.0.1"); } TEST_F(VersionCompareTest, VersionTags) { EXPECT_IS_OLDER_THAN("0.9.3-alpha", "0.9.3-beta"); EXPECT_IS_OLDER_THAN("1.0-beta", "1.0-rc1"); EXPECT_IS_OLDER_THAN("1.0-rc1", "1.0-rc2"); EXPECT_IS_OLDER_THAN("1.0-rc2", "1.0"); EXPECT_IS_OLDER_THAN("0.9.5", "0.10-m1"); EXPECT_IS_OLDER_THAN("0.10-m1", "0.10.0"); EXPECT_IS_OLDER_THAN("1.0-alpha", "1.0"); EXPECT_IS_SAME_AGE("0.9.3-alpha", "0.9.3-alpha"); EXPECT_IS_SAME_AGE("1-beta", "1-beta"); EXPECT_IS_SAME_AGE("0.9.3-rc1", "0.9.3-rc1"); } TEST_F(VersionCompareTest, DevVersions) { EXPECT_IS_OLDER_THAN("0.8", "0.8.1+1.g1234"); EXPECT_IS_OLDER_THAN("0.8.1", "0.8.2+1.g1234"); EXPECT_IS_OLDER_THAN("0.8.1+1.g1234", "0.8.2"); EXPECT_IS_OLDER_THAN("0.8+1.g1234", "0.8.1"); EXPECT_IS_OLDER_THAN("0.8+1.g1234", "0.9"); EXPECT_IS_OLDER_THAN("0.9+1.g1234", "0.9+2.g1234"); EXPECT_IS_SAME_AGE("0.9.1+1.g1234", "0.9.1+1.g3456"); EXPECT_IS_SAME_AGE("0.9.1+5.g1234", "0.9.1+5.g2345.dirty"); } TEST_F(VersionCompareTest, DevVersions_VersionTags) { EXPECT_IS_OLDER_THAN("0.9.3-alpha+3.gabcd", "0.9.3-alpha+4.gabcd"); EXPECT_IS_OLDER_THAN("0.9.3-alpha+5.gabcd", "0.9.3-beta"); EXPECT_IS_OLDER_THAN("0.9.3-alpha+5.gabcd", "0.9.3-beta+1.gabcd"); EXPECT_IS_OLDER_THAN("0.9.3-alpha+5.gabcd", "1+0.gabcd.dirty"); EXPECT_IS_OLDER_THAN("0.9.3-alpha+5.gabcd", "1"); EXPECT_IS_SAME_AGE("0.9.3-alpha+3.gabcd", "0.9.3-alpha+3.gabcd"); }test/my-gtest-main/000077500000000000000000000000001347701267100145405ustar00rootroot00000000000000test/my-gtest-main/CMakeLists.txt000066400000000000000000000005571347701267100173070ustar00rootroot00000000000000project (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}) test/my-gtest-main/my-gtest-main.cpp000066400000000000000000000012751347701267100177440ustar00rootroot00000000000000#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(); } test/my-gtest-main/my-gtest-main.h000066400000000000000000000001451347701267100174040ustar00rootroot00000000000000#pragma once #include const boost::filesystem::path& get_executable(); test/parallelaccessstore/000077500000000000000000000000001347701267100161005ustar00rootroot00000000000000test/parallelaccessstore/CMakeLists.txt000066400000000000000000000005501347701267100206400ustar00rootroot00000000000000project (parallelaccessstore-test) set(SOURCES ParallelAccessBaseStoreTest.cpp DummyTest.cpp ) add_executable(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} my-gtest-main googletest parallelaccessstore) add_test(${PROJECT_NAME} ${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) test/parallelaccessstore/DummyTest.cpp000066400000000000000000000000661347701267100205410ustar00rootroot00000000000000#include TEST(Dummy, DummyTest) { } test/parallelaccessstore/ParallelAccessBaseStoreTest.cpp000066400000000000000000000002001347701267100241220ustar00rootroot00000000000000#include "parallelaccessstore/ParallelAccessBaseStore.h" // Test that ParallelAccessBaseStore.h can be included without errors vendor/000077500000000000000000000000001347701267100123635ustar00rootroot00000000000000vendor/CMakeLists.txt000066400000000000000000000001211347701267100151150ustar00rootroot00000000000000add_subdirectory(googletest) add_subdirectory(spdlog) add_subdirectory(cryptopp) vendor/README000066400000000000000000000011651347701267100132460ustar00rootroot00000000000000This directory contains external projects, taken from the following locations: googletest: https://github.com/google/googletest/tree/4e4df226fc197c0dda6e37f5c8c3845ca1e73a49 - changed: added NOLINT comment as workaround for clang-tidy warning https://github.com/google/googletest/issues/853 spdlog: https://github.com/gabime/spdlog/tree/v0.16.3/include/spdlog cryptopp: https://github.com/weidai11/cryptopp/tree/CRYPTOPP_8_2_0 - changed: added CMakeLists.txt and cryptopp-config.cmake from https://github.com/noloader/cryptopp-cmake/tree/CRYPTOPP_8_2_0 - changed: commented out line including winapifamily.h in CMakeLists.txt vendor/cryptopp/000077500000000000000000000000001347701267100142435ustar00rootroot00000000000000vendor/cryptopp/CMakeLists.txt000066400000000000000000000110771347701267100170110ustar00rootroot00000000000000project(mycryptopp) add_library(cryptopp dummy.cpp) # note: include directory is called vendor_cryptopp instead of cryptopp to avoid include clashes with system headers target_include_directories(cryptopp SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) # Forward debug build info (i.e. set CRYPTOPP_DEBUG variable if building in debug mode) target_compile_definitions(cryptopp PUBLIC $<$:CRYPTOPP_DEBUG>) # add to all targets depending on this add_compile_options($<$:-DCRYPTOPP_DEBUG>) # add to stuff built in subdirectories (like the actual library) if(NOT DISABLE_OPENMP) find_package(OpenMP) if (OPENMP_FOUND OR OPENMP_CXX_FOUND) message(STATUS "Found libomp without any special flags") endif() # If OpenMP wasn't found, try if we can find it in the default Macports location if((NOT OPENMP_FOUND) AND (NOT OPENMP_CXX_FOUND) AND EXISTS "/opt/local/lib/libomp/libomp.dylib") # older cmake uses OPENMP_FOUND, newer cmake also sets OPENMP_CXX_FOUND, homebrew installations seem only to get the latter set. set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I/opt/local/include/libomp/") set(OpenMP_CXX_LIB_NAMES omp) set(OpenMP_omp_LIBRARY /opt/local/lib/libomp/libomp.dylib) find_package(OpenMP) if (OPENMP_FOUND OR OPENMP_CXX_FOUND) message(STATUS "Found libomp in macports default location.") else() message(FATAL_ERROR "Didn't find libomp. Tried macports default location but also didn't find it.") endif() endif() # If OpenMP wasn't found, try if we can find it in the default Homebrew location if((NOT OPENMP_FOUND) AND (NOT OPENMP_CXX_FOUND) AND EXISTS "/usr/local/opt/libomp/lib/libomp.dylib") set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I/usr/local/opt/libomp/include") set(OpenMP_CXX_LIB_NAMES omp) set(OpenMP_omp_LIBRARY /usr/local/opt/libomp/lib/libomp.dylib) find_package(OpenMP) if (OPENMP_FOUND OR OPENMP_CXX_FOUND) message(STATUS "Found libomp in homebrew default location.") else() message(FATAL_ERROR "Didn't find libomp. Tried homebrew default location but also didn't find it.") endif() endif() set(Additional_OpenMP_Libraries_Workaround "") # Workaround because older cmake on apple doesn't support FindOpenMP if((NOT OPENMP_FOUND) AND (NOT OPENMP_CXX_FOUND)) if((APPLE AND ((CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang"))) AND ((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "7.0") AND (CMAKE_VERSION VERSION_LESS "3.12.0"))) message(STATUS "Applying workaround for OSX OpenMP with old cmake that doesn't have FindOpenMP") set(OpenMP_CXX_FLAGS "-Xclang -fopenmp") set(Additional_OpenMP_Libraries_Workaround "-lomp") else() message(FATAL_ERROR "Did not find OpenMP. Build with -DDISABLE_OPENMP=ON if you want to allow this and are willing to take the performance hit.") endif() endif() if(NOT TARGET OpenMP::OpenMP_CXX) # We're on cmake < 3.9, handle behavior of the old FindOpenMP implementation message(STATUS "Applying workaround for old CMake that doesn't define FindOpenMP using targets") add_library(OpenMP_TARGET INTERFACE) add_library(OpenMP::OpenMP_CXX ALIAS OpenMP_TARGET) target_compile_options(OpenMP_TARGET INTERFACE ${OpenMP_CXX_FLAGS}) # add to all targets depending on this find_package(Threads REQUIRED) target_link_libraries(OpenMP_TARGET INTERFACE Threads::Threads) target_link_libraries(OpenMP_TARGET INTERFACE ${OpenMP_CXX_FLAGS} ${Additional_OpenMP_Libraries_Workaround}) endif() target_link_libraries(cryptopp PUBLIC ${OpenMP_CXX_FLAGS}) # Workaround for Ubuntu 18.04 that otherwise doesn't set -fopenmp for linking target_link_libraries(cryptopp PUBLIC OpenMP::OpenMP_CXX) # also add these flags to the third party Crypto++ build setup that is built in a subdirectory message(STATUS "OpenMP flags: ${OpenMP_CXX_FLAGS}") string(REPLACE " " ";" REPLACED_FLAGS ${OpenMP_CXX_FLAGS}) add_compile_options(${REPLACED_FLAGS}) else() message(WARNING "OpenMP is disabled. This can cause degraded performance.") endif() set(BUILD_TESTING OFF CACHE BOOL "") set(BUILD_DOCUMENTATION OFF CACHE BOOL "") set(BUILD_SHARED OFF CACHE BOOL "") set(BUILD_STATIC ON CACHE BOOL "") set(cryptocpp_DISPLAY_CMAKE_SUPPORT_WARNING OFF CACHE BOOL "") add_subdirectory(vendor_cryptopp EXCLUDE_FROM_ALL) target_link_libraries(cryptopp PRIVATE cryptopp-static) vendor/cryptopp/dummy.cpp000066400000000000000000000000001347701267100160700ustar00rootroot00000000000000vendor/cryptopp/vendor_cryptopp/000077500000000000000000000000001347701267100175005ustar00rootroot00000000000000vendor/cryptopp/vendor_cryptopp/.gitignore000066400000000000000000000071761347701267100215030ustar00rootroot00000000000000#################################### # C++ generic ignore # Allows you to use test.cxx and # avoid getting in the way of things #################################### *.cxx #################### ## Crypto++ specific #################### adhoc.cpp adhoc.cpp.copied libcryptopp.a libcryptopp.so libcryptopp.dylib cryptest.exe cryptopp-test.cxx cryptopp-test.exe cryptopp-test.s cryptopp.mac.done GNUmakefile.deps ############## ## Patch files ############## *.diff *.patch ################# ## GNU/GCC ################# ## Ignore GNU/GCC artifacts a.out ## Ignore GCC temproary files. It appears Fedora ## changed a behavior somewhere along the lines *.o ################# ## Eclipse ################# *.pydevproject .project .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .classpath .settings/ .loadpath # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # CDT-specific .cproject # PDT-specific .buildpath ################# ## Visual Studio ################# ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## https://msdn.microsoft.com/en-us/library/hx0cxhaw.aspx # User-specific files *.suo *.user *.sdf # Build results [Dd]ebug/ [Rr]elease/ x64/ build/ [Bb]in/ [Oo]bj/ [Ll]ibs/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* *_i.c *_p.c *.ilk *.meta *.obj *.o *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.log *.scc *.exe *.a # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch *.ncrunch* .*crunch*.local.xml # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.Publish.xml *.pubxml *.publishproj # NuGet Packages Directory ## TODO: If you have NuGet Package Restore enabled, uncomment the next line #packages/ # Windows Azure Build Output csx *.build.csdef # Windows Store app package directory AppPackages/ # Others sql/ *.Cache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.[Pp]ublish.xml *.pfx *.publishsettings # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file to a newer # Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files App_Data/*.mdf App_Data/*.ldf ############# ## Windows detritus ############# # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ ############# # Mac crap ############# .DS_Store ############# ## Python ############# *.py[cod] # Packages *.egg *.egg-info dist/ build/ eggs/ parts/ var/ sdist/ develop-eggs/ .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox #Translations *.mo #Mr Developer .mr.developer.cfg ################# ## C++Builder ################# ## Ignore C++Builder temporary files and build results. ## http://docwiki.embarcadero.com/RADStudio/en/File_Extensions_of_Files_Generated_by_RAD_Studio # Static library file *.lib # User-specific project options *.local # Dependency file *.d vendor/cryptopp/vendor_cryptopp/3way.cpp000066400000000000000000000067571347701267100211060ustar00rootroot00000000000000// 3way.cpp - modifed by Wei Dai from Joan Daemen's 3way.c // The original code and all modifications are in the public domain. #include "pch.h" #include "3way.h" #include "misc.h" NAMESPACE_BEGIN(CryptoPP) #if defined(CRYPTOPP_DEBUG) && !defined(CRYPTOPP_DOXYGEN_PROCESSING) void ThreeWay_TestInstantiations() { ThreeWay::Encryption x1; ThreeWay::Decryption x2; } #endif namespace { const word32 START_E = 0x0b0b; // round constant of first encryption round const word32 START_D = 0xb1b1; // round constant of first decryption round } static inline word32 reverseBits(word32 a) { a = ((a & 0xAAAAAAAA) >> 1) | ((a & 0x55555555) << 1); a = ((a & 0xCCCCCCCC) >> 2) | ((a & 0x33333333) << 2); return ((a & 0xF0F0F0F0) >> 4) | ((a & 0x0F0F0F0F) << 4); } #define mu(a0, a1, a2) \ { \ a1 = reverseBits(a1); \ word32 t = reverseBits(a0); \ a0 = reverseBits(a2); \ a2 = t; \ } #define pi_gamma_pi(a0, a1, a2) \ { \ word32 b0, b2; \ b2 = rotlConstant<1>(a2); \ b0 = rotlConstant<22>(a0); \ a0 = rotlConstant<1>(b0 ^ (a1|(~b2))); \ a2 = rotlConstant<22>(b2 ^ (b0|(~a1))); \ a1 ^= (b2|(~b0)); \ } // thanks to Paulo Barreto for this optimized theta() #define theta(a0, a1, a2) \ { \ word32 b0, b1, c; \ c = a0 ^ a1 ^ a2; \ c = rotlConstant<16>(c) ^ rotlConstant<8>(c); \ b0 = (a0 << 24) ^ (a2 >> 8) ^ (a1 << 8) ^ (a0 >> 24); \ b1 = (a1 << 24) ^ (a0 >> 8) ^ (a2 << 8) ^ (a1 >> 24); \ a0 ^= c ^ b0; \ a1 ^= c ^ b1; \ a2 ^= c ^ (b0 >> 16) ^ (b1 << 16); \ } #define rho(a0, a1, a2) \ { \ theta(a0, a1, a2); \ pi_gamma_pi(a0, a1, a2); \ } void ThreeWay::Base::UncheckedSetKey(const byte *uk, unsigned int length, const NameValuePairs ¶ms) { AssertValidKeyLength(length); m_rounds = GetRoundsAndThrowIfInvalid(params, this); for (unsigned int i=0; i<3; i++) m_k[i] = (word32)uk[4*i+3] | ((word32)uk[4*i+2]<<8) | ((word32)uk[4*i+1]<<16) | ((word32)uk[4*i]<<24); if (!IsForwardTransformation()) { theta(m_k[0], m_k[1], m_k[2]); mu(m_k[0], m_k[1], m_k[2]); m_k[0] = ByteReverse(m_k[0]); m_k[1] = ByteReverse(m_k[1]); m_k[2] = ByteReverse(m_k[2]); } } void ThreeWay::Enc::ProcessAndXorBlock(const byte *inBlock, const byte *xorBlock, byte *outBlock) const { typedef BlockGetAndPut Block; word32 a0, a1, a2; Block::Get(inBlock)(a0)(a1)(a2); word32 rc = START_E; for(unsigned i=0; i Block; word32 a0, a1, a2; Block::Get(inBlock)(a0)(a1)(a2); word32 rc = START_D; mu(a0, a1, a2); for(unsigned i=0; i, public FixedKeyLength<12>, public VariableRounds<11> { CRYPTOPP_STATIC_CONSTEXPR const char* StaticAlgorithmName() {return "3-Way";} }; /// \brief ThreeWay block cipher /// \sa 3-Way class ThreeWay : public ThreeWay_Info, public BlockCipherDocumentation { /// \brief Class specific implementation and overrides used to operate the cipher. /// \details Implementations and overrides in \p Base apply to both \p ENCRYPTION and \p DECRYPTION directions class CRYPTOPP_NO_VTABLE Base : public BlockCipherImpl { public: void UncheckedSetKey(const byte *key, unsigned int length, const NameValuePairs ¶ms); protected: unsigned int m_rounds; FixedSizeSecBlock m_k; }; /// \brief Class specific methods used to operate the cipher in the forward direction. /// \details Implementations and overrides in \p Enc apply to \p ENCRYPTION. class CRYPTOPP_NO_VTABLE Enc : public Base { public: void ProcessAndXorBlock(const byte *inBlock, const byte *xorBlock, byte *outBlock) const; }; /// \brief Class specific methods used to operate the cipher in the reverse direction. /// \details Implementations and overrides in \p Dec apply to \p DECRYPTION. class CRYPTOPP_NO_VTABLE Dec : public Base { public: void ProcessAndXorBlock(const byte *inBlock, const byte *xorBlock, byte *outBlock) const; }; public: typedef BlockCipherFinal Encryption; typedef BlockCipherFinal Decryption; }; typedef ThreeWay::Encryption ThreeWayEncryption; typedef ThreeWay::Decryption ThreeWayDecryption; NAMESPACE_END #endif vendor/cryptopp/vendor_cryptopp/CMakeLists.txt000066400000000000000000001467271347701267100222610ustar00rootroot00000000000000# Please ensure your changes or patch meets minimum requirements. # The minimum requirements are 2.8.6. It roughly equates to # Ubuntu 14.05 LTS or Solaris 11.3. Please do not check in something # for 3.5.0 or higher because it will break LTS operating systems # and a number of developer boards used for testing. To test your # changes, please set up a Ubuntu 14.05 LTS system. # Should we be setting things like this? We are not a C project # so nothing should be done with the C compiler. But there is # no reliable way to tell CMake we are C++. # Cannot set this... Breaks Linux PowerPC with Clang: # SET(CMAKE_C_COMPILER ${CMAKE_CXX_COMPILER}) # # error "The CMAKE_C_COMPILER is set to a C++ compiler" if(NOT DEFINED cryptocpp_DISPLAY_CMAKE_SUPPORT_WARNING) set(cryptocpp_DISPLAY_CMAKE_SUPPORT_WARNING 1) endif() if(cryptocpp_DISPLAY_CMAKE_SUPPORT_WARNING) message( STATUS "*************************************************************************\n" "The Crypto++ library does not officially support CMake. CMake support is a\n" "community effort, and the library works with the folks using CMake to help\n" "improve it. If you find an issue then please fix it or report it at\n" "https://github.com/noloader/cryptopp-cmake.\n" "-- *************************************************************************" ) endif() # Print useful information message( STATUS "CMake version ${CMAKE_VERSION}" ) cmake_minimum_required(VERSION 2.8.6) if (${CMAKE_VERSION} VERSION_LESS "3.0.0") project(cryptopp) set(cryptopp_VERSION_MAJOR 8) set(cryptopp_VERSION_MINOR 2) set(cryptopp_VERSION_PATCH 0) else () cmake_policy(SET CMP0048 NEW) project(cryptopp VERSION 8.2.0) if (NOT ${CMAKE_VERSION} VERSION_LESS "3.1.0") cmake_policy(SET CMP0054 NEW) endif () endif () # Need to set SRC_DIR manually after removing the Python library code. set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) # Make RelWithDebInfo the default (it does e.g. add '-O2 -g -DNDEBUG' for GNU) # If not in multi-configuration environments, no explicit build type or CXX # flags are set by the user and if we are the root CMakeLists.txt file. if (NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_NO_BUILD_TYPE AND NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CXX_FLAGS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) set(CMAKE_BUILD_TYPE RelWithDebInfo) endif () include(GNUInstallDirs) include(CheckCXXCompilerFlag) # We now carry around test programs. test_cxx.cxx is the default C++ one. # Also see https://github.com/weidai11/cryptopp/issues/741. set(TEST_PROG_DIR ${SRC_DIR}/TestPrograms) set(TEST_CXX_FILE ${TEST_PROG_DIR}/test_cxx.cxx) #============================================================================ # Settable options #============================================================================ option(BUILD_STATIC "Build static library" ON) option(BUILD_SHARED "Build shared library" ON) option(BUILD_TESTING "Build library tests" ON) option(BUILD_DOCUMENTATION "Use Doxygen to create the HTML based API documentation" OFF) option(USE_INTERMEDIATE_OBJECTS_TARGET "Use a common intermediate objects target for the static and shared library targets" ON) # These are IA-32 options. TODO: Add ARM A-32, Aarch64 and Power8 options. option(DISABLE_ASM "Disable ASM" OFF) option(DISABLE_SSSE3 "Disable SSSE3" OFF) option(DISABLE_SSE4 "Disable SSE4" OFF) option(DISABLE_AESNI "Disable AES-NI" OFF) option(DISABLE_SHA "Disable SHA" OFF) option(DISABLE_AVX "Disable AVX" OFF) option(DISABLE_AVX2 "Disable AVX2" OFF) option(CRYPTOPP_NATIVE_ARCH "Enable native architecture" OFF) set(CRYPTOPP_DATA_DIR "" CACHE PATH "Crypto++ test data directory") #============================================================================ # Compiler options #============================================================================ set(CRYPTOPP_COMPILE_DEFINITIONS) set(CRYPTOPP_COMPILE_OPTIONS) # Stop hiding the damn output... # set(CMAKE_VERBOSE_MAKEFILE on) # Always 1 ahead in Master. Also see http://groups.google.com/forum/#!topic/cryptopp-users/SFhqLDTQPG4 set(LIB_VER ${cryptopp_VERSION_MAJOR}${cryptopp_VERSION_MINOR}${cryptopp_VERSION_PATCH}) # Don't use RPATH's. The resulting binary could fail a security audit. set(CMAKE_MACOSX_RPATH 0) if (CMAKE_CXX_COMPILER_ID STREQUAL "Intel") list(APPEND CRYPTOPP_COMPILE_OPTIONS -wd68 -wd186 -wd279 -wd327 -wd161 -wd3180) endif () # Also see http://github.com/weidai11/cryptopp/issues/395 if (DISABLE_ASM) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_ASM) endif () if (DISABLE_SSSE3) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_SSSE3) endif () if (DISABLE_SSE4) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_SSSE4) endif () if (DISABLE_AESNI) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_AESNI) endif () if (DISABLE_SHA) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_SHA) endif () if (DISABLE_ALTIVEC) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_ALTIVEC) endif () if (DISABLE_POWER7) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_POWER7) endif () if (DISABLE_POWER8) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_POWER8) endif () if (DISABLE_POWER9) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_POWER9) endif () if (NOT CRYPTOPP_DATA_DIR STREQUAL "") list(APPEND CRYPTOPP_COMPILE_DEFINITIONS "CRYPTOPP_DATA_DIR=${CRYPTOPP_DATA_DIR}") endif () ############################################################################### # Try to find a Posix compatible grep and sed. Solaris, Digital Unix, # Tru64, HP-UX and a few others need tweaking if (EXISTS /usr/xpg4/bin/grep) set(GREP_CMD /usr/xpg4/bin/grep) elseif (EXISTS /usr/gnu/bin/grep) set(GREP_CMD /usr/gnu/bin/grep) elseif (EXISTS /usr/linux/bin/grep) set(GREP_CMD /usr/linux/bin/grep) else () set(GREP_CMD grep) endif () if (EXISTS /usr/xpg4/bin/sed) set(SED_CMD /usr/xpg4/bin/sed) elseif (EXISTS /usr/gnu/bin/sed) set(SED_CMD /usr/gnu/bin/sed) elseif (EXISTS /usr/linux/bin/sed) set(SED_CMD /usr/linux/bin/sed) else () set(SED_CMD sed) endif () ############################################################################### function(CheckCompileOption opt var) if (MSVC) # TODO: improve this... CHECK_CXX_COMPILER_FLAG(${opt} ${var}) elseif (CMAKE_CXX_COMPILER_ID MATCHES "SunPro") message(STATUS "Performing Test ${var}") execute_process( COMMAND sh -c "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${opt} -c ${TEST_CXX_FILE} 2>&1" COMMAND ${GREP_CMD} -i -c -E "illegal value ignored" RESULT_VARIABLE COMMAND_RESULT OUTPUT_VARIABLE COMMAND_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE) # No dereference below. Thanks for the warning, CMake (not!). if (COMMAND_RESULT AND NOT COMMAND_OUTPUT) set(${var} 1 PARENT_SCOPE) message(STATUS "Performing Test ${var} - Success") else () set(${var} 0 PARENT_SCOPE) message(STATUS "Performing Test ${var} - Failed") endif () # Must use CMAKE_CXX_COMPILER here due to XLC 13.1 and LLVM front-end. elseif (CMAKE_CXX_COMPILER MATCHES "xlC") message(STATUS "Performing Test ${var}") execute_process( COMMAND sh -c "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${opt} -c ${TEST_CXX_FILE} 2>&1" COMMAND ${GREP_CMD} -i -c -E "Unrecognized value" RESULT_VARIABLE COMMAND_RESULT OUTPUT_VARIABLE COMMAND_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE) # No dereference below. Thanks for the warning, CMake (not!). if (COMMAND_RESULT AND NOT COMMAND_OUTPUT) set(${var} 1 PARENT_SCOPE) message(STATUS "Performing Test ${var} - Success") else () set(${var} 0 PARENT_SCOPE) message(STATUS "Performing Test ${var} - Failed") endif () else () CHECK_CXX_COMPILER_FLAG(${opt} ${var}) endif () endfunction(CheckCompileOption) function(CheckCompileLinkOption opt var prog) if (MSVC) # TODO: improve this... CHECK_CXX_COMPILER_FLAG(${opt} ${var}) else () message(STATUS "Performing Test ${var}") execute_process( COMMAND sh -c "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${opt} ${prog} 2>&1" RESULT_VARIABLE COMMAND_RESULT OUTPUT_VARIABLE COMMAND_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE) # message(STATUS "RESULT_VARIABLE ${RESULT_VARIABLE}") # message(STATUS "COMMAND_RESULT ${COMMAND_RESULT}") # message(STATUS "OUTPUT_VARIABLE ${OUTPUT_VARIABLE}") # message(STATUS "COMMAND_OUTPUT ${COMMAND_OUTPUT}") # This test is strict. We require two things. First, the invocation # of the compile command must return 0. Second, there must be no # messages on the console. We are interested in diagnostics like # warnings to decide when to reject an option. But we will probably # capture chatty compiler that want to say, "Hooray, success". For # chatty compilers we will need to find a quiet option and use it # for the test. Microsoft compilers come to mind. if ("${COMMAND_RESULT}" EQUAL 0 AND "${COMMAND_OUTPUT}" STREQUAL "") set(${var} 1 PARENT_SCOPE) message(STATUS "Performing Test ${var} - Success") else () set(${var} 0 PARENT_SCOPE) message(STATUS "Performing Test ${var} - Failed") endif () endif () endfunction(CheckCompileLinkOption) function(AddCompileOption opt) if ("${COMMAND_OUTPUT}" NOT STREQUAL "") list(APPEND CRYPTOPP_COMPILE_OPTIONS "${opt}") endif () endfunction(AddCompileOption) ############################################################################### function(DumpMachine output pattern) if (MSVC) # CMake does not provide a generic shell/terminal mechanism # and Microsoft environments don't know what 'sh' is. set(${output} 0 PARENT_SCOPE) else () execute_process( COMMAND sh -c "${CMAKE_CXX_COMPILER} -dumpmachine 2>&1" COMMAND ${GREP_CMD} -i -c -E "${pattern}" OUTPUT_VARIABLE ${output} OUTPUT_STRIP_TRAILING_WHITESPACE) set(${output} "${${output}}" PARENT_SCOPE) endif() endfunction(DumpMachine) # Thansk to Anonimal for MinGW; see http://github.com/weidai11/cryptopp/issues/466 DumpMachine(CRYPTOPP_AMD64 "amd64|x86_64") DumpMachine(CRYPTOPP_I386 "i.86") DumpMachine(CRYPTOPP_MINGW32 "\\") DumpMachine(CRYPTOPP_MINGW64 "w64-mingw32|mingw64") DumpMachine(CRYPTOPP_X32 "x32") DumpMachine(CRYPTOPP_AARCH32 "Aarch32") DumpMachine(CRYPTOPP_AARCH64 "Aarch64") DumpMachine(CRYPTOPP_ARMHF "armhf|arm7l|eabihf") DumpMachine(CRYPTOPP_ARM "\\") # Detecting PowerPC is only good with GCC. IBM XLC compiler is # a little different and I don't know how to ask to the triplet # XLC is targeting. Below we punt by setting CRYPTOPP_POWERPC64 # if we detect the compiler is XLC. DumpMachine(CRYPTOPP_POWERPC "ppc|powerpc") DumpMachine(CRYPTOPP_POWERPC64 "ppc64") ############################################################################### # Test SunCC for a string like 'CC: Sun C++ 5.13 SunOS_i386' if (NOT CRYPTOPP_SOLARIS) execute_process(COMMAND sh -c "${CMAKE_CXX_COMPILER} -V 2>&1" COMMAND ${GREP_CMD} -i -c "SunOS" OUTPUT_VARIABLE CRYPTOPP_SOLARIS OUTPUT_STRIP_TRAILING_WHITESPACE) endif () # Test GCC for a string like 'i386-pc-solaris2.11' if (NOT CRYPTOPP_SOLARIS) execute_process(COMMAND sh -c "${CMAKE_CXX_COMPILER} -dumpmachine 2>&1" COMMAND ${GREP_CMD} -i -c "Solaris" OUTPUT_VARIABLE CRYPTOPP_SOLARIS OUTPUT_STRIP_TRAILING_WHITESPACE) endif () # Fixup PowerPC. If both 32-bit and 64-bit use 64-bit. if (CRYPTOPP_POWERPC AND CRYPTOPP_POWERPC64) unset(CRYPTOPP_POWERPC) endif () # Fixup for xlC compiler. -dumpmachine fails so we miss PowerPC # TODO: something better than proxying the platform via compiler # Must use CMAKE_CXX_COMPILER here due to XLC 13.1 and LLVM front-end. if (CMAKE_CXX_COMPILER MATCHES "xlC") message ("-- Fixing platform due to IBM xlC") set(CRYPTOPP_POWERPC64 1) endif () # DumpMachine SunCC style if (CMAKE_CXX_COMPILER_ID STREQUAL "SunPro") # SunCC is 32-bit, but it builds both 32 and 64 bit. Use execute_process(COMMAND sh -c "${CMAKE_CXX_COMPILER} -V 2>&1" COMMAND ${GREP_CMD} -i -c "Sparc" OUTPUT_VARIABLE CRYPTOPP_SPARC OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND sh -c "${CMAKE_CXX_COMPILER} -V 2>&1" COMMAND ${GREP_CMD} -i -c -E "i386|i86" OUTPUT_VARIABLE CRYPTOPP_I386 OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND isainfo -k COMMAND ${GREP_CMD} -i -c "i386" OUTPUT_VARIABLE KERNEL_I386 OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND isainfo -k COMMAND ${GREP_CMD} -i -c "amd64" OUTPUT_VARIABLE KERNEL_AMD64 OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND isainfo -k COMMAND ${GREP_CMD} -i -c "Sparc" OUTPUT_VARIABLE KERNEL_SPARC OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND isainfo -k COMMAND ${GREP_CMD} -i -c -E "UltraSarc|Sparc64|SparcV9" OUTPUT_VARIABLE KERNEL_SPARC64 OUTPUT_STRIP_TRAILING_WHITESPACE) endif () ############################################################################### if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") execute_process(COMMAND sh -c "${CMAKE_CXX_COMPILER} --version 2>&1" COMMAND ${GREP_CMD} -i -c "macports" OUTPUT_VARIABLE MACPORTS OUTPUT_STRIP_TRAILING_WHITESPACE) if (MACPORTS EQUAL 0) # Get GAS version, add defs + set as appropriate set(GAS_CMD sh -c "${CMAKE_CXX_COMPILER} -xc -c /dev/null -Wa,-v -o/dev/null 2>&1") execute_process(COMMAND ${GAS_CMD} OUTPUT_VARIABLE GAS_STRING OUTPUT_STRIP_TRAILING_WHITESPACE) string(FIND "${GAS_STRING}" "GNU assembler" GAS_OUTPUT) if (NOT GAS_OUTPUT EQUAL -1) #.intel_syntax wasn't supported until GNU assembler 2.10 # TODO(unassigned): string() REGEX was not cooperating at time of writing. Re-implement as needed. execute_process(COMMAND echo ${GAS_STRING} COMMAND ${GREP_CMD} -i -c -E "GNU.[Aa]ssembler.*(2\\.[1-9][0-9]|[3-9])" OUTPUT_VARIABLE GAS210_OR_LATER) if (GAS210_OR_LATER EQUAL 0) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_ASM) set(DISABLE_ASM 1) endif () execute_process(COMMAND echo ${GAS_STRING} COMMAND ${GREP_CMD} -i -c -E "GNU.[Aa]ssembler.*(2\\.1[7-9]|2\\.[2-9]|[3-9])" OUTPUT_VARIABLE GAS217_OR_LATER) if (GAS217_OR_LATER EQUAL 0) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_SSSE3) set(DISABLE_SSSE3 1) endif () # OpenBSD and CentOS 5 needed this one due to ARIA and BLAKE2 execute_process(COMMAND echo ${GAS_STRING} COMMAND ${GREP_CMD} -i -c -E "GNU.[Aa]ssembler.*(2\\.1[8-9]|2\\.[2-9]|[3-9])" OUTPUT_VARIABLE GAS218_OR_LATER) if (GAS218_OR_LATER EQUAL 0) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_SSSE4) set(DISABLE_SSE4 1) endif () execute_process(COMMAND echo ${GAS_STRING} COMMAND ${GREP_CMD} -i -c -E "GNU.[Aa]ssembler.*(2\\.19|2\\.[2-9]|[3-9])" OUTPUT_VARIABLE GAS219_OR_LATER) if (GAS219_OR_LATER EQUAL 0) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_AESNI) set(DISABLE_AESNI 1) endif () # Ubuntu 10 and Ubuntu 12 needed this one execute_process(COMMAND echo ${GAS_STRING} COMMAND ${GREP_CMD} -i -c -E "GNU.[Aa]ssembler.*(2\\.2[3-9]|2\\.[3-9]|[3-9])" OUTPUT_VARIABLE GAS223_OR_LATER) if (GAS223_OR_LATER EQUAL 0) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_SHA) set(DISABLE_SHA 1) endif () endif () endif () endif () # TODO: what about ICC and LLVM on Windows? if (MSVC) if (CMAKE_SYSTEM_VERSION MATCHES "10\\.0.*") list(APPEND CRYPTOPP_COMPILE_DEFINITIONS "_WIN32_WINNT=0x0A00") endif () #list(APPEND CRYPTOPP_COMPILE_OPTIONS "/FI\"winapifamily.h\"") endif () # Enable PIC for all target machines except 32-bit i386 due to register pressures. if (NOT CRYPTOPP_I386) SET(CMAKE_POSITION_INDEPENDENT_CODE 1) endif () # IBM XLC compiler options for AIX and Linux. # Must use CMAKE_CXX_COMPILER here due to XLC 13.1 and LLVM front-end. if (CMAKE_CXX_COMPILER MATCHES "xlC") #CheckCompileLinkOption("-qxlcompatmacros" CRYPTOPP_XLC_COMPAT "${TEST_CXX_FILE}") #if (CRYPTOPP_XLC_COMPAT) # list(APPEND CRYPTOPP_COMPILE_OPTIONS "-qxlcompatmacros") #endif () CheckCompileLinkOption("-qrtti" CRYPTOPP_PPC_RTTI "${TEST_CXX_FILE}") if (CRYPTOPP_PPC_RTTI) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-qrtti") endif () CheckCompileLinkOption("-qmaxmem=-1" CRYPTOPP_PPC_MAXMEM "${TEST_CXX_FILE}") if (CRYPTOPP_PPC_MAXMEM) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-qmaxmem=-1") endif () CheckCompileLinkOption("-qthreaded" CRYPTOPP_PPC_THREADED "${TEST_CXX_FILE}") if (CRYPTOPP_PPC_THREADED) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-qthreaded") endif () endif () # Solaris specific if (CRYPTOPP_SOLARIS) # SunCC needs -template=no%extdef if (CMAKE_CXX_COMPILER_ID STREQUAL "SunPro") list(APPEND CRYPTOPP_COMPILE_OPTIONS "-template=no%extdef") endif () # SunCC needs -xregs=no%appl on Sparc (not x86) for libraries (not test program) # TODO: wire this up properly if (CMAKE_CXX_COMPILER_ID STREQUAL "SunPro" AND (CRYPTOPP_SPARC OR CRYPTOPP_SPARC64)) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-xregs=no%appl") endif () # GCC needs to enable use of '/' for division in the assembler if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") list(APPEND CRYPTOPP_COMPILE_OPTIONS "-Wa,--divide") endif () endif () #============================================================================ # Sources & headers #============================================================================ # Library headers file(GLOB cryptopp_HEADERS ${SRC_DIR}/*.h) # Remove headers used to build test suite list(REMOVE_ITEM cryptopp_HEADERS ${SRC_DIR}/bench.h ${SRC_DIR}/validate.h ) # Test sources. You can use the GNUmakefile to generate the list: `make sources`. set(cryptopp_SOURCES_TEST ${SRC_DIR}/test.cpp ${SRC_DIR}/bench1.cpp ${SRC_DIR}/bench2.cpp ${SRC_DIR}/bench3.cpp ${SRC_DIR}/validat0.cpp ${SRC_DIR}/validat1.cpp ${SRC_DIR}/validat2.cpp ${SRC_DIR}/validat3.cpp ${SRC_DIR}/validat4.cpp ${SRC_DIR}/validat5.cpp ${SRC_DIR}/validat6.cpp ${SRC_DIR}/validat7.cpp ${SRC_DIR}/validat8.cpp ${SRC_DIR}/validat9.cpp ${SRC_DIR}/validat10.cpp ${SRC_DIR}/regtest1.cpp ${SRC_DIR}/regtest2.cpp ${SRC_DIR}/regtest3.cpp ${SRC_DIR}/regtest4.cpp ${SRC_DIR}/datatest.cpp ${SRC_DIR}/fipsalgt.cpp ${SRC_DIR}/fipstest.cpp ${SRC_DIR}/dlltest.cpp #${SRC_DIR}/adhoc.cpp ) # Library sources. You can use the GNUmakefile to generate the list: `make sources`. # Makefile sorted them at http://github.com/weidai11/cryptopp/pull/426. file(GLOB cryptopp_SOURCES ${SRC_DIR}/*.cpp) list(SORT cryptopp_SOURCES) list(REMOVE_ITEM cryptopp_SOURCES ${SRC_DIR}/cryptlib.cpp ${SRC_DIR}/cpu.cpp ${SRC_DIR}/integer.cpp ${SRC_DIR}/pch.cpp ${SRC_DIR}/simple.cpp ${SRC_DIR}/adhoc.cpp ${cryptopp_SOURCES_TEST} ) set(cryptopp_SOURCES ${SRC_DIR}/cryptlib.cpp ${SRC_DIR}/cpu.cpp ${SRC_DIR}/integer.cpp ${cryptopp_SOURCES} ) set(cryptopp_SOURCES_ASM) if (MSVC AND NOT DISABLE_ASM) if (${CMAKE_GENERATOR} MATCHES ".*ARM") message(STATUS "Disabling ASM because ARM is specified as target platform.") else () enable_language(ASM_MASM) list(APPEND cryptopp_SOURCES_ASM ${SRC_DIR}/rdrand.asm ) if (CMAKE_SIZEOF_VOID_P EQUAL 8) list(APPEND cryptopp_SOURCES_ASM ${SRC_DIR}/x64dll.asm ${SRC_DIR}/x64masm.asm ) set_source_files_properties(${cryptopp_SOURCES_ASM} PROPERTIES COMPILE_DEFINITIONS "_M_X64") else () set_source_files_properties(${cryptopp_SOURCES_ASM} PROPERTIES COMPILE_DEFINITIONS "_M_X86" COMPILE_FLAGS "/safeseh") endif () set_source_files_properties(${cryptopp_SOURCES_ASM} PROPERTIES LANGUAGE ASM_MASM) endif () endif () #============================================================================ # Architecture flags #============================================================================ # TODO: Android, AIX, IBM xlC, iOS and a few other profiles are missing. # New as of Pull Request 461, http://github.com/weidai11/cryptopp/pull/461. # Must use CMAKE_CXX_COMPILER here due to XLC 13.1 and LLVM front-end. if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Intel" OR CMAKE_CXX_COMPILER MATCHES "xlC") if (CRYPTOPP_AMD64 OR CRYPTOPP_I386 OR CRYPTOPP_X32) CheckCompileLinkOption("-msse2" CRYPTOPP_IA32_SSE2 "${TEST_PROG_DIR}/test_x86_sse2.cxx") CheckCompileLinkOption("-mssse3" CRYPTOPP_IA32_SSSE3 "${TEST_PROG_DIR}/test_x86_ssse3.cxx") CheckCompileLinkOption("-msse4.1" CRYPTOPP_IA32_SSE41 "${TEST_PROG_DIR}/test_x86_sse41.cxx") CheckCompileLinkOption("-msse4.2" CRYPTOPP_IA32_SSE42 "${TEST_PROG_DIR}/test_x86_sse42.cxx") CheckCompileLinkOption("-mssse3 -mpclmul" CRYPTOPP_IA32_CLMUL "${TEST_PROG_DIR}/test_x86_clmul.cxx") CheckCompileLinkOption("-msse4.1 -maes" CRYPTOPP_IA32_AES "${TEST_PROG_DIR}/test_x86_aes.cxx") CheckCompileLinkOption("-mavx" CRYPTOPP_IA32_AVX "${TEST_PROG_DIR}/test_x86_avx.cxx") CheckCompileLinkOption("-mavx2" CRYPTOPP_IA32_AVX2 "${TEST_PROG_DIR}/test_x86_avx2.cxx") CheckCompileLinkOption("-msse4.2 -msha" CRYPTOPP_IA32_SHA "${TEST_PROG_DIR}/test_x86_sha.cxx") CheckCompileLinkOption("" CRYPTOPP_MIXED_ASM "${TEST_PROG_DIR}/test_mixed_asm.cxx") # https://github.com/weidai11/cryptopp/issues/756 if (NOT CRYPTOPP_MIXED_ASM) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_MIXED_ASM") endif () if (NOT CRYPTOPP_IA32_SSE2 AND NOT DISABLE_ASM) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_ASM") elseif (CRYPTOPP_IA32_SSE2 AND NOT DISABLE_ASM) set_source_files_properties(${SRC_DIR}/sse_simd.cpp PROPERTIES COMPILE_FLAGS "-msse2") set_source_files_properties(${SRC_DIR}/chacha_simd.cpp PROPERTIES COMPILE_FLAGS "-msse2") set_source_files_properties(${SRC_DIR}/donna_sse.cpp PROPERTIES COMPILE_FLAGS "-msse2") endif () if (NOT CRYPTOPP_IA32_SSSE3 AND NOT DISABLE_SSSE3) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_SSSE3") elseif (CRYPTOPP_IA32_SSSE3 AND NOT DISABLE_SSSE3) set_source_files_properties(${SRC_DIR}/aria_simd.cpp PROPERTIES COMPILE_FLAGS "-mssse3") set_source_files_properties(${SRC_DIR}/cham_simd.cpp PROPERTIES COMPILE_FLAGS "-mssse3") set_source_files_properties(${SRC_DIR}/keccak_simd.cpp PROPERTIES COMPILE_FLAGS "-mssse3") set_source_files_properties(${SRC_DIR}/lea_simd.cpp PROPERTIES COMPILE_FLAGS "-mssse3") set_source_files_properties(${SRC_DIR}/simeck_simd.cpp PROPERTIES COMPILE_FLAGS "-mssse3") set_source_files_properties(${SRC_DIR}/simon128_simd.cpp PROPERTIES COMPILE_FLAGS "-mssse3") set_source_files_properties(${SRC_DIR}/speck128_simd.cpp PROPERTIES COMPILE_FLAGS "-mssse3") if (NOT CRYPTOPP_IA32_SSE41 AND NOT DISABLE_SSE4) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_SSE4") elseif (CRYPTOPP_IA32_SSE41 AND NOT DISABLE_SSE4) set_source_files_properties(${SRC_DIR}/blake2s_simd.cpp PROPERTIES COMPILE_FLAGS "-msse4.1") set_source_files_properties(${SRC_DIR}/blake2b_simd.cpp PROPERTIES COMPILE_FLAGS "-msse4.1") set_source_files_properties(${SRC_DIR}/simon64_simd.cpp PROPERTIES COMPILE_FLAGS "-msse4.1") set_source_files_properties(${SRC_DIR}/speck64_simd.cpp PROPERTIES COMPILE_FLAGS "-msse4.1") endif () if (NOT CRYPTOPP_IA32_SSE42 AND NOT DISABLE_SSE4) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_SSE4") elseif (CRYPTOPP_IA32_SSE42 AND NOT DISABLE_SSE4) set_source_files_properties(${SRC_DIR}/crc_simd.cpp PROPERTIES COMPILE_FLAGS "-msse4.2") if (NOT CRYPTOPP_IA32_CLMUL AND NOT DISABLE_AES) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_CLMUL") elseif (CRYPTOPP_IA32_CLMUL AND NOT DISABLE_AES) set_source_files_properties(${SRC_DIR}/gcm_simd.cpp PROPERTIES COMPILE_FLAGS "-mssse3 -mpclmul") set_source_files_properties(${SRC_DIR}/gf2n_simd.cpp PROPERTIES COMPILE_FLAGS "-mpclmul") endif () if (NOT CRYPTOPP_IA32_AES AND NOT DISABLE_AES) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_AESNI") elseif (CRYPTOPP_IA32_AES AND NOT DISABLE_AES) set_source_files_properties(${SRC_DIR}/rijndael_simd.cpp PROPERTIES COMPILE_FLAGS "-msse4.1 -maes") set_source_files_properties(${SRC_DIR}/sm4_simd.cpp PROPERTIES COMPILE_FLAGS "-mssse3 -maes") endif () #if (NOT CRYPTOPP_IA32_AVX AND NOT DISABLE_AVX) # list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_AVX") #elseif (CRYPTOPP_IA32_AVX AND NOT DISABLE_AVX) # set_source_files_properties(${SRC_DIR}/XXX_avx.cpp PROPERTIES COMPILE_FLAGS "-mavx") #endif () if (NOT CRYPTOPP_IA32_AVX2 AND NOT DISABLE_AVX2) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_AVX2") elseif (CRYPTOPP_IA32_AVX2 AND NOT DISABLE_AVX2) set_source_files_properties(${SRC_DIR}/chacha_avx.cpp PROPERTIES COMPILE_FLAGS "-mavx2") endif () if (NOT CRYPTOPP_IA32_SHA AND NOT DISABLE_SHA) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_SHANI") elseif (CRYPTOPP_IA32_SHA AND NOT DISABLE_SHA) set_source_files_properties(${SRC_DIR}/sha_simd.cpp PROPERTIES COMPILE_FLAGS "-msse4.2 -msha") set_source_files_properties(${SRC_DIR}/shacal2_simd.cpp PROPERTIES COMPILE_FLAGS "-msse4.2 -msha") endif () endif () endif () elseif (CRYPTOPP_AARCH32 OR CRYPTOPP_AARCH64) CheckCompileOption("-march=armv8-a" CRYPTOPP_ARMV8A_ASIMD) CheckCompileOption("-march=armv8-a+crc" CRYPTOPP_ARMV8A_CRC) CheckCompileOption("-march=armv8-a+crypto" CRYPTOPP_ARMV8A_CRYPTO) CheckCompileOption("-march=armv8-a" CRYPTOPP_ARMV8A_NATIVE) if (CRYPTOPP_ARMV8A_ASIMD) set_source_files_properties(${SRC_DIR}/aria_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/blake2s_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/blake2b_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/chacha_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/cham_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/lea_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/neon_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/simeck_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/simon64_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/simon128_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/speck64_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/speck128_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") endif () if (CRYPTOPP_ARMV8A_CRC) set_source_files_properties(${SRC_DIR}/crc_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a+crc") endif () if (CRYPTOPP_ARMV8A_CRYPTO) set_source_files_properties(${SRC_DIR}/gcm_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a+crypto") set_source_files_properties(${SRC_DIR}/gf2n_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a+crypto") set_source_files_properties(${SRC_DIR}/rijndael_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a+crypto") set_source_files_properties(${SRC_DIR}/sha_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a+crypto") set_source_files_properties(${SRC_DIR}/shacal2_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a+crypto") endif () elseif (CRYPTOPP_ARM OR CRYPTOPP_ARMHF) # Need to set floating point ABI to something, like "hard" of "softfp". # Most Linux use hard floats. CheckCompileLinkOption("-march=armv7-a -mfpu=neon" CRYPTOPP_ARMV7A_NEON "${TEST_PROG_DIR}/test_arm_neon.cxx") CheckCompileLinkOption("-march=armv7-a -mfloat-abi=hard -mfpu=neon" CRYPTOPP_ARMV7A_HARD "${TEST_PROG_DIR}/test_arm_neon.cxx") CheckCompileLinkOption("-march=armv7-a -mfloat-abi=softfp -mfpu=neon" CRYPTOPP_ARMV7A_SOFTFP "${TEST_PROG_DIR}/test_arm_neon.cxx") if (CRYPTOPP_ARMV7A_HARD) set(CRYPTOPP_ARMV7A_FLAGS "-march=armv7-a -mfloat-abi=hard -mfpu=neon") elseif (CRYPTOPP_ARMV7A_SOFTFP) set(CRYPTOPP_ARMV7A_FLAGS "-march=armv7-a -mfloat-abi=softfp -mfpu=neon") else () AddCompileOption("-DCRYPTOPP_DISABLE_NEON") endif() if (CRYPTOPP_ARMV7A_HARD OR CRYPTOPP_ARMV7A_SOFTFP) # Add ASM files for ARM if (NOT MSVC) list(APPEND cryptopp_SOURCES ${SRC_DIR}/aes_armv4.S) set_source_files_properties(${SRC_DIR}/aes_armv4.S PROPERTIES LANGUAGE C) endif () set_source_files_properties(${SRC_DIR}/aes_armv4.S PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/aria_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/blake2s_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/blake2b_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/chacha_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/cham_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/crc_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/lea_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/gcm_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/rijndael_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/neon_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/sha_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/simeck_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/simon64_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/simon128_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/speck64_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/speck128_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") set_source_files_properties(${SRC_DIR}/sm4_simd.cpp PROPERTIES COMPILE_FLAGS "${CRYPTOPP_ARMV7A_FLAGS}") endif () elseif (CRYPTOPP_POWERPC OR CRYPTOPP_POWERPC64) if (CMAKE_CXX_COMPILER MATCHES "xlC") set(CRYPTOPP_ALTIVEC_FLAGS "-qaltivec") set(CRYPTOPP_POWER4_FLAGS "-qarch=pwr4 -qaltivec") set(CRYPTOPP_POWER5_FLAGS "-qarch=pwr5 -qaltivec") set(CRYPTOPP_POWER6_FLAGS "-qarch=pwr6 -qaltivec") set(CRYPTOPP_POWER7_FLAGS "-qarch=pwr7 -qaltivec") set(CRYPTOPP_POWER8_FLAGS "-qarch=pwr8 -qaltivec") set(CRYPTOPP_POWER9_FLAGS "-qarch=pwr9 -qaltivec") else () set(CRYPTOPP_ALTIVEC_FLAGS "-maltivec") set(CRYPTOPP_POWER7_FLAGS "-mcpu=power7 -maltivec") set(CRYPTOPP_POWER8_FLAGS "-mcpu=power8 -maltivec") set(CRYPTOPP_POWER9_FLAGS "-mcpu=power9 -maltivec") endif () CheckCompileLinkOption("${CRYPTOPP_ALTIVEC_FLAGS}" PPC_ALTIVEC_FLAG "${TEST_PROG_DIR}/test_ppc_altivec.cxx") # Hack for XLC if (CMAKE_CXX_COMPILER MATCHES "xlC") if (NOT PPC_ALTIVEC_FLAG) CheckCompileLinkOption("${CRYPTOPP_POWER4_FLAGS}" PPC_POWER4_FLAG "${TEST_PROG_DIR}/test_ppc_altivec.cxx") if (PPC_POWER4_FLAG) set(PPC_ALTIVEC_FLAG 1) set(CRYPTOPP_ALTIVEC_FLAGS "${CRYPTOPP_POWER4_FLAGS}") endif () endif () if (NOT PPC_ALTIVEC_FLAG) CheckCompileLinkOption("${CRYPTOPP_POWER5_FLAGS}" PPC_POWER5_FLAG "${TEST_PROG_DIR}/test_ppc_altivec.cxx") if (PPC_POWER5_FLAG) set(PPC_ALTIVEC_FLAG 1) set(CRYPTOPP_ALTIVEC_FLAGS "${CRYPTOPP_POWER5_FLAGS}") endif () endif () if (NOT PPC_ALTIVEC_FLAG) CheckCompileLinkOption("${CRYPTOPP_POWER6_FLAGS}" PPC_POWER6_FLAG "${TEST_PROG_DIR}/test_ppc_altivec.cxx") if (PPC_POWER6_FLAG) set(PPC_ALTIVEC_FLAG 1) set(CRYPTOPP_ALTIVEC_FLAGS "${CRYPTOPP_POWER6_FLAGS}") endif () endif () endif () CheckCompileLinkOption("${CRYPTOPP_POWER7_FLAGS}" PPC_POWER7_FLAG "${TEST_PROG_DIR}/test_ppc_power7.cxx") CheckCompileLinkOption("${CRYPTOPP_POWER8_FLAGS}" PPC_POWER8_FLAG "${TEST_PROG_DIR}/test_ppc_power8.cxx") CheckCompileLinkOption("${CRYPTOPP_POWER9_FLAGS}" PPC_POWER9_FLAG "${TEST_PROG_DIR}/test_ppc_power9.cxx") if (PPC_POWER9_FLAG AND NOT DISABLE_POWER9) set_source_files_properties(${SRC_DIR}/ppc_power9.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER9_FLAGS}) endif () if (PPC_POWER8_FLAG AND NOT DISABLE_POWER8) set_source_files_properties(${SRC_DIR}/ppc_power8.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER8_FLAGS}) set_source_files_properties(${SRC_DIR}/blake2b_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER8_FLAGS}) #set_source_files_properties(${SRC_DIR}/crc_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER8_FLAGS}) set_source_files_properties(${SRC_DIR}/gcm_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER8_FLAGS}) set_source_files_properties(${SRC_DIR}/gf2n_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER8_FLAGS}) set_source_files_properties(${SRC_DIR}/rijndael_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER8_FLAGS}) set_source_files_properties(${SRC_DIR}/sha_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER8_FLAGS}) set_source_files_properties(${SRC_DIR}/shacal2_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER8_FLAGS}) set_source_files_properties(${SRC_DIR}/simon128_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER8_FLAGS}) set_source_files_properties(${SRC_DIR}/speck128_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER8_FLAGS}) endif () if (PPC_POWER7_FLAG AND NOT DISABLE_POWER7) set_source_files_properties(${SRC_DIR}/ppc_power7.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER7_FLAGS}) set_source_files_properties(${SRC_DIR}/aria_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER7_FLAGS}) set_source_files_properties(${SRC_DIR}/blake2s_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER7_FLAGS}) set_source_files_properties(${SRC_DIR}/chacha_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER7_FLAGS}) set_source_files_properties(${SRC_DIR}/cham_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER7_FLAGS}) set_source_files_properties(${SRC_DIR}/lea_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER7_FLAGS}) set_source_files_properties(${SRC_DIR}/simeck_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER7_FLAGS}) set_source_files_properties(${SRC_DIR}/simon64_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER7_FLAGS}) set_source_files_properties(${SRC_DIR}/speck64_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER7_FLAGS}) endif () if (PPC_ALTIVEC_FLAG AND NOT DISABLE_ALTIVEC) set_source_files_properties(${SRC_DIR}/ppc_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_ALTIVEC_FLAGS}) endif () # Drop to Power7 if Power8 unavailable if (NOT PPC_POWER8_FLAG) if (PPC_POWER7_FLAG) set_source_files_properties(${SRC_DIR}/gcm_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_POWER7_FLAGS}) endif () endif () # Drop to Altivec if Power7 unavailable if (NOT PPC_POWER7_FLAG) if (PPC_ALTIVEC_FLAG) set_source_files_properties(${SRC_DIR}/blake2s_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_ALTIVEC_FLAGS}) set_source_files_properties(${SRC_DIR}/chacha_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_ALTIVEC_FLAGS}) set_source_files_properties(${SRC_DIR}/simon64_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_ALTIVEC_FLAGS}) set_source_files_properties(${SRC_DIR}/speck64_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_ALTIVEC_FLAGS}) endif () endif () if (NOT PPC_ALTIVEC_FLAG) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_ALTIVEC") elseif (NOT PPC_POWER7_FLAG) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_POWER7") elseif (NOT PPC_POWER8_FLAG) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_POWER8") elseif (NOT PPC_POWER9_FLAG) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_POWER9") endif () endif () endif () # New as of Pull Request 461, http://github.com/weidai11/cryptopp/pull/461. if (CMAKE_CXX_COMPILER_ID STREQUAL "SunPro") if (CRYPTOPP_AMD64 OR CRYPTOPP_I386 OR CRYPTOPP_X32) CheckCompileLinkOption("-xarch=sse2" CRYPTOPP_IA32_SSE2 "${TEST_PROG_DIR}/test_x86_sse2.cxx") CheckCompileLinkOption("-xarch=ssse3" CRYPTOPP_IA32_SSSE3 "${TEST_PROG_DIR}/test_x86_ssse3.cxx") CheckCompileLinkOption("-xarch=sse4_1" CRYPTOPP_IA32_SSE41 "${TEST_PROG_DIR}/test_x86_sse41.cxx") CheckCompileLinkOption("-xarch=sse4_2" CRYPTOPP_IA32_SSE42 "${TEST_PROG_DIR}/test_x86_sse42.cxx") CheckCompileLinkOption("-xarch=aes" CRYPTOPP_IA32_CLMUL "${TEST_PROG_DIR}/test_x86_clmul.cxx") CheckCompileLinkOption("-xarch=aes" CRYPTOPP_IA32_AES "${TEST_PROG_DIR}/test_x86_aes.cxx") CheckCompileLinkOption("-xarch=avx" CRYPTOPP_IA32_AVX "${TEST_PROG_DIR}/test_x86_avx.cxx") CheckCompileLinkOption("-xarch=avx2" CRYPTOPP_IA32_AVX2 "${TEST_PROG_DIR}/test_x86_avx2.cxx") CheckCompileLinkOption("-xarch=sha" CRYPTOPP_IA32_SHA "${TEST_PROG_DIR}/test_x86_sha.cxx") # Each -xarch=XXX options must be added to LDFLAGS if the option is used during a compile. set(XARCH_LDFLAGS "") if (CRYPTOPP_IA32_SSE2 AND NOT DISABLE_ASM) set_source_files_properties(${SRC_DIR}/sse_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=sse2") set_source_files_properties(${SRC_DIR}/chacha_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=sse2") set(XARCH_LDFLAGS "-xarch=sse2") endif () if (CRYPTOPP_IA32_SSSE3 AND NOT DISABLE_SSSE3) set_source_files_properties(${SRC_DIR}/aria_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=ssse3") set_source_files_properties(${SRC_DIR}/cham_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=ssse3") set_source_files_properties(${SRC_DIR}/lea_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=ssse3") set_source_files_properties(${SRC_DIR}/simeck_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=ssse3") set_source_files_properties(${SRC_DIR}/simon128_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=ssse3") set_source_files_properties(${SRC_DIR}/speck128_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=ssse3") set(XARCH_LDFLAGS "${XARCH_LDFLAGS} -xarch=ssse3") if (CRYPTOPP_IA32_SSE41 AND NOT DISABLE_SSE4) set_source_files_properties(${SRC_DIR}/blake2s_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=sse4_1") set_source_files_properties(${SRC_DIR}/blake2b_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=sse4_1") set_source_files_properties(${SRC_DIR}/simon64_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=sse4_1") set_source_files_properties(${SRC_DIR}/speck64_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=sse4_1") set(XARCH_LDFLAGS "${XARCH_LDFLAGS} -xarch=sse4_1") endif () if (CRYPTOPP_IA32_SSE42 AND NOT DISABLE_SSE4) set_source_files_properties(${SRC_DIR}/crc_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=sse4_2") set(XARCH_LDFLAGS "${XARCH_LDFLAGS} -xarch=sse4_2") if (CRYPTOPP_IA32_CLMUL AND NOT DISABLE_CLMUL) set_source_files_properties(${SRC_DIR}/gcm_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=aes") set_source_files_properties(${SRC_DIR}/gf2n_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=aes") endif () if (CRYPTOPP_IA32_AES AND NOT DISABLE_AES) set_source_files_properties(${SRC_DIR}/rijndael_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=aes") set_source_files_properties(${SRC_DIR}/sm4_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=aes") set(XARCH_LDFLAGS "${XARCH_LDFLAGS} -xarch=aes") endif () #if (CRYPTOPP_IA32_AVX AND NOT DISABLE_AVX) # set_source_files_properties(${SRC_DIR}/XXX_avx.cpp PROPERTIES COMPILE_FLAGS "-xarch=avx2") # set(XARCH_LDFLAGS "${XARCH_LDFLAGS} -xarch=avx") #endif () if (CRYPTOPP_IA32_AVX2 AND NOT DISABLE_AVX2) set_source_files_properties(${SRC_DIR}/chacha_avx.cpp PROPERTIES COMPILE_FLAGS "-xarch=avx2") set(XARCH_LDFLAGS "${XARCH_LDFLAGS} -xarch=avx2") endif () if (CRYPTOPP_IA32_SHA AND NOT DISABLE_SHA) set_source_files_properties(${SRC_DIR}/sha_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=sha") set_source_files_properties(${SRC_DIR}/shacal2_simd.cpp PROPERTIES COMPILE_FLAGS "-xarch=sha") set(XARCH_LDFLAGS "${XARCH_LDFLAGS} -xarch=sha") endif () endif () endif () # https://stackoverflow.com/a/6088646/608639 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${XARCH_LDFLAGS} -M${SRC_DIR}/cryptopp.mapfile") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${XARCH_LDFLAGS} -M${SRC_DIR}/cryptopp.mapfile") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${XARCH_LDFLAGS} -M${SRC_DIR}/cryptopp.mapfile") # elseif (CRYPTOPP_SPARC OR CRYPTOPP_SPARC64) endif () endif () # Attempt to determine a suitable native option if (CRYPTOPP_NATIVE_ARCH) CheckCompileOption("-march=native" NATIVE_ARCH) if (NATIVE_ARCH) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-march=native") else () CheckCompileOption("-native" NATIVE_ARCH) if (NATIVE_ARCH) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-native") endif () endif () if (NOT NATIVE_ARCH) message(WARNING "CRYPTOPP_NATIVE_ARCH enabled, but failed to detect native architecture option") endif () endif() #============================================================================ # Compile targets #============================================================================ # Work around the archaic versions of cmake that do not support # target_compile_xxxx commands # !!! DO NOT try to use the old way for newer version - it does not work !!! function(cryptopp_target_compile_properties target) if (NOT ${CMAKE_VERSION} VERSION_LESS "2.8.11") target_compile_definitions(${target} PUBLIC ${CRYPTOPP_COMPILE_DEFINITIONS}) else() string (REPLACE ";" " " PROP_STR "${CRYPTOPP_COMPILE_DEFINITIONS}") set_target_properties(${target} PROPERTIES COMPILE_DEFINITIONS "${CRYPTOPP_COMPILE_DEFINITIONS}") endif() if (NOT ${CMAKE_VERSION} VERSION_LESS "2.8.12") target_compile_options(${target} PUBLIC ${CRYPTOPP_COMPILE_OPTIONS}) else() string (REPLACE ";" " " PROP_STR "${CRYPTOPP_COMPILE_OPTIONS}") set_target_properties(${target} PROPERTIES COMPILE_FLAGS "${PROP_STR}") endif() endfunction() set(cryptopp_LIBRARY_SOURCES ${cryptopp_SOURCES_ASM}) if (USE_INTERMEDIATE_OBJECTS_TARGET AND NOT ${CMAKE_VERSION} VERSION_LESS "2.8.8") add_library(cryptopp-object OBJECT ${cryptopp_SOURCES}) cryptopp_target_compile_properties(cryptopp-object) list(APPEND cryptopp_LIBRARY_SOURCES $ ) else () list(APPEND cryptopp_LIBRARY_SOURCES ${cryptopp_SOURCES} ) endif () if (BUILD_STATIC) add_library(cryptopp-static STATIC ${cryptopp_LIBRARY_SOURCES}) cryptopp_target_compile_properties(cryptopp-static) if (NOT ${CMAKE_VERSION} VERSION_LESS "2.8.11") target_include_directories(cryptopp-static PUBLIC $ $) else () set_target_properties(cryptopp-static PROPERTIES INCLUDE_DIRECTORIES "$ $") endif () endif () if (BUILD_SHARED) add_library(cryptopp-shared SHARED ${cryptopp_LIBRARY_SOURCES}) cryptopp_target_compile_properties(cryptopp-shared) if (NOT ${CMAKE_VERSION} VERSION_LESS "2.8.11") target_include_directories(cryptopp-shared PUBLIC $ $) else () set_target_properties(cryptopp-shared PROPERTIES INCLUDE_DIRECTORIES "$ $") endif () endif () # Set filenames for targets to be "cryptopp" if (NOT MSVC) set(COMPAT_VERSION ${cryptopp_VERSION_MAJOR}.${cryptopp_VERSION_MINOR}) if (BUILD_STATIC) set_target_properties(cryptopp-static PROPERTIES OUTPUT_NAME cryptopp) endif () if (BUILD_SHARED) set_target_properties(cryptopp-shared PROPERTIES SOVERSION ${COMPAT_VERSION} OUTPUT_NAME cryptopp) endif () endif () # Add alternate ways to invoke the build for the shared library that are # similar to how the crypto++ 'make' tool works. # see https://github.com/noloader/cryptopp-cmake/issues/32 if (BUILD_STATIC) add_custom_target(static DEPENDS cryptopp-static) endif () if (BUILD_SHARED) add_custom_target(shared DEPENDS cryptopp-shared) add_custom_target(dynamic DEPENDS cryptopp-shared) endif () #============================================================================ # Third-party libraries #============================================================================ if (WIN32) if (BUILD_STATIC) target_link_libraries(cryptopp-static ws2_32) endif () if (BUILD_SHARED) target_link_libraries(cryptopp-shared ws2_32) endif () endif () # This may need to be expanded to "Solaris" if (CRYPTOPP_SOLARIS) if (BUILD_STATIC) target_link_libraries(cryptopp-static nsl socket) endif () if (BUILD_SHARED) target_link_libraries(cryptopp-shared nsl socket) endif () endif () find_package(Threads) if (BUILD_STATIC) target_link_libraries(cryptopp-static ${CMAKE_THREAD_LIBS_INIT}) endif () if (BUILD_SHARED) target_link_libraries(cryptopp-shared ${CMAKE_THREAD_LIBS_INIT}) endif () #============================================================================ # Tests #============================================================================ enable_testing() if (BUILD_TESTING) add_executable(cryptest ${cryptopp_SOURCES_TEST}) target_link_libraries(cryptest cryptopp-static) # Setting "cryptest" binary name to "cryptest.exe" if (NOT (WIN32 OR CYGWIN)) set_target_properties(cryptest PROPERTIES OUTPUT_NAME cryptest.exe) endif () if (NOT TARGET cryptest.exe) add_custom_target(cryptest.exe) add_dependencies(cryptest.exe cryptest) endif () file(COPY ${SRC_DIR}/TestData DESTINATION ${PROJECT_BINARY_DIR}) file(COPY ${SRC_DIR}/TestVectors DESTINATION ${PROJECT_BINARY_DIR}) add_test(NAME build_cryptest COMMAND "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} --target cryptest) add_test(NAME cryptest COMMAND $ v) set_tests_properties(cryptest PROPERTIES DEPENDS build_cryptest) endif () #============================================================================ # Doxygen documentation #============================================================================ if (BUILD_DOCUMENTATION) find_package(Doxygen REQUIRED) set(in_source_DOCS_DIR "${SRC_DIR}/html-docs") set(out_source_DOCS_DIR "${PROJECT_BINARY_DIR}/html-docs") add_custom_target(docs ALL COMMAND ${DOXYGEN_EXECUTABLE} Doxyfile -d CRYPTOPP_DOXYGEN_PROCESSING WORKING_DIRECTORY ${SRC_DIR} SOURCES ${SRC_DIR}/Doxyfile ) if (NOT ${in_source_DOCS_DIR} STREQUAL ${out_source_DOCS_DIR}) add_custom_command( TARGET docs POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${in_source_DOCS_DIR}" "${out_source_DOCS_DIR}" COMMAND ${CMAKE_COMMAND} -E remove_directory "${in_source_DOCS_DIR}" ) endif () endif () #============================================================================ # Install #============================================================================ set(export_name "cryptopp-targets") # Runtime package if (BUILD_SHARED) export(TARGETS cryptopp-shared FILE ${export_name}.cmake ) install( TARGETS cryptopp-shared EXPORT ${export_name} DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) endif () # Development package if (BUILD_STATIC) export(TARGETS cryptopp-static FILE ${export_name}.cmake ) install(TARGETS cryptopp-static EXPORT ${export_name} DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif () install(FILES ${cryptopp_HEADERS} DESTINATION include/cryptopp) # CMake Package if (NOT CMAKE_VERSION VERSION_LESS 2.8.8) include(CMakePackageConfigHelpers) write_basic_package_version_file("${PROJECT_BINARY_DIR}/cryptopp-config-version.cmake" VERSION ${cryptopp_VERSION_MAJOR}.${cryptopp_VERSION_MINOR}.${cryptopp_VERSION_PATCH} COMPATIBILITY SameMajorVersion) install(FILES cryptopp-config.cmake ${PROJECT_BINARY_DIR}/cryptopp-config-version.cmake DESTINATION "lib/cmake/cryptopp") install(EXPORT ${export_name} DESTINATION "lib/cmake/cryptopp") endif () # Tests if (BUILD_TESTING) install(TARGETS cryptest DESTINATION ${CMAKE_INSTALL_BINDIR}) install(DIRECTORY ${SRC_DIR}/TestData DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/cryptopp) install(DIRECTORY ${SRC_DIR}/TestVectors DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/cryptopp) endif () # Documentation if (BUILD_DOCUMENTATION) install(DIRECTORY "${out_source_DOCS_DIR}" DESTINATION ${CMAKE_INSTALL_DOCDIR}) endif () # Print a configuration summary. We want CXX and CXXFLAGS, but they are not includd in ALL. if (CRYPTOPP_I386) message(STATUS "Platform: i386/i686") elseif (CRYPTOPP_AMD64) message(STATUS "Platform: x86_64") elseif (CRYPTOPP_X32) message(STATUS "Platform: x86_64-x32") elseif (CRYPTOPP_ARMHF) message(STATUS "Platform: armhf") elseif (CRYPTOPP_ARM) message(STATUS "Platform: arm") elseif (CRYPTOPP_AARCH32) message(STATUS "Platform: Aarch32") elseif (CRYPTOPP_AARCH64) message(STATUS "Platform: Aarch64") elseif (CRYPTOPP_SPARC) message(STATUS "Platform: Sparc") elseif (CRYPTOPP_SPARC64) message(STATUS "Platform: Sparc64") elseif (CRYPTOPP_POWERPC) message(STATUS "Platform: PowerPC") elseif (CRYPTOPP_POWERPC64) message(STATUS "Platform: PowerPC-64") elseif (CRYPTOPP_MINGW32) message(STATUS "Platform: MinGW-32") elseif (CRYPTOPP_MINGW32) message(STATUS "Platform: MinGW-64") endif () if (CRYPTOPP_ARMV7A_NEON) message(STATUS "NEON: TRUE") endif () if (CRYPTOPP_NATIVE_ARCH) message(STATUS "Native arch: TRUE") else () message(STATUS "Native arch: FALSE") endif () message(STATUS "Compiler: ${CMAKE_CXX_COMPILER}") message(STATUS "Compiler options: ${CMAKE_CXX_FLAGS} ${CRYPTOPP_COMPILE_OPTIONS}") message(STATUS "Compiler definitions: ${CRYPTOPP_COMPILE_DEFINITIONS}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") vendor/cryptopp/vendor_cryptopp/Doxyfile000066400000000000000000003176271347701267100212260ustar00rootroot00000000000000# Doxyfile 1.8.9 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). # # The file can be upgraded to the latest version of Doxygen with `doxygen -u