pax_global_header00006660000000000000000000000064144561426100014515gustar00rootroot0000000000000052 comment=2c86a6ed376244cb2e3e1fe7e174554b813fcec1 .circleci/000077500000000000000000000000001445614261000127145ustar00rootroot00000000000000.circleci/config.yml000066400000000000000000000007331445614261000147070ustar00rootroot00000000000000# This config file is a dummy CircleCI config that does nothing. We migrated away from CircleCI to Github Actions. # But our release/0.10 branch still uses CircleCI, so we can't disable the service entirely and need some way # to disable it only for newer versions. That's what this file is doing. version: 2.1 jobs: build: docker: - image: circleci/node:11.12.0 steps: - run: name: Dummy command: 'echo Not running any Circle CI' .clang-tidy000066400000000000000000000046341445614261000131240ustar00rootroot00000000000000--- # 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? # TODO Check if there's new checks in clang-tidy-9 and later and potentially enable them 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, -cppcoreguidelines-avoid-non-const-global-variables, -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/000077500000000000000000000000001445614261000124215ustar00rootroot00000000000000.github/ISSUE_TEMPLATE.md000066400000000000000000000002531445614261000151260ustar00rootroot00000000000000## Expected Behavior ## Actual Behavior ## Steps to Reproduce the Problem 1. 2. 3. ## Specifications - CryFS Version: - Operating System (incl. Version): .github/workflows/000077500000000000000000000000001445614261000144565ustar00rootroot00000000000000.github/workflows/actions/000077500000000000000000000000001445614261000161165ustar00rootroot00000000000000.github/workflows/actions/install_local_dependencies/000077500000000000000000000000001445614261000234445ustar00rootroot00000000000000.github/workflows/actions/install_local_dependencies/action.yaml000066400000000000000000000052721445614261000256130ustar00rootroot00000000000000name: 'Install local dependencies' description: 'Install local dependencies' runs: using: "composite" steps: - name: Install local dependencies shell: bash run: | set -v # TODO Cache these dependencies for faster runtime export NUMCORES=`nproc` && if [ ! -n "$NUMCORES" ]; then export NUMCORES=`sysctl -n hw.ncpu`; fi echo Using $NUMCORES cores echo Download range-v3 cd ~ wget https://github.com/ericniebler/range-v3/archive/0.11.0.tar.gz -O range-v3-0.11.0.tar.gz if [ $(sha512sum range-v3-0.11.0.tar.gz | awk '{print $1;}') == "9d6cdcbc1e50104206ba731c3bdc9aab3acfcf69cd83f0e0b4de18b88df2a9e73d64e55638421768d4433c542b6619f6e5af6b17cccd3090cf8b4d4efe9863e4" ]; then echo Correct sha512sum else echo Wrong sha512sum sha512sum range-v3-0.11.0.tar.gz exit 1 fi tar -xvf range-v3-0.11.0.tar.gz cd range-v3-0.11.0/ echo Install range-v3 mkdir build cd build cmake .. -DRANGES_HAS_WERROR=off -DRANGE_V3_EXAMPLES=off -DRANGE_V3_TESTS=off make -j$NUMCORES sudo make install cd ~ rm -rf range-v3-0.11.0 rm range-v3-0.11.0.tar.gz echo Download spdlog cd ~ wget https://github.com/gabime/spdlog/archive/v1.8.5.tar.gz -O spdlog.tar.gz if [ $(sha512sum spdlog.tar.gz | awk '{print $1;}') == "77cc9df0c40bbdbfe1f3e5818dccf121918bfceac28f2608f39e5bf944968b7e8e24a6fc29f01bc58a9bae41b8892d49cfb59c196935ec9868884320b50f130c" ]; then echo Correct sha512sum else echo Wrong sha512sum sha512sum spdlog.tar.gz exit 1 fi tar -xvf spdlog.tar.gz rm spdlog.tar.gz cd spdlog-1.8.5 echo Install spdlog mkdir build cd build cmake .. make -j$NUMCORES sudo make install echo Download boost cd ~ wget -O boost.tar.bz2 https://sourceforge.net/projects/boost/files/boost/1.75.0/boost_1_75_0.tar.bz2/download if [ $(sha512sum boost.tar.bz2 | awk '{print $1;}') == "d86f060245e98dca5c7f3f831c98ea9ccbfa8310f20830dd913d9d4c939fbe7cb94accd35f1128e7c4faf6c27adb6f4bb54e5477a6bde983dfc7aa33c4eed03a" ]; then echo Correct sha512sum else echo Wrong sha512sum sha512sum boost.tar.bz2 exit 1 fi echo Extracting boost tar -xf boost.tar.bz2 rm boost.tar.bz2 cd boost_1_75_0 echo Install boost ./bootstrap.sh --with-libraries=filesystem,system,thread,chrono,program_options sudo ./b2 link=shared cxxflags=-fPIC --prefix=/usr -d0 -j$NUMCORES install .github/workflows/actions/run_build/000077500000000000000000000000001445614261000201015ustar00rootroot00000000000000.github/workflows/actions/run_build/action.yaml000066400000000000000000000037111445614261000222440ustar00rootroot00000000000000name: 'Build' description: 'Compile CryFS' inputs: cc: description: "Which C compiler to use for the build" required: true cxx: description: "Which C++ compiler to use for the build" required: true build_type: description: "Which cmake build type to use (e.g. Release, Debug, RelWithDebInfo)" required: true extra_cmake_flags: description: "Extra flags to add to the cmake command" required: true extra_cxxflags: description: "Extra flags to add to the compiler" required: true runs: using: "composite" steps: - name: Show build system information shell: bash run: | set -v echo CMake version: cmake --version echo Ninja version: ninja --version echo CC: ${{inputs.cc}} ${{inputs.cc}} --version echo CXX: ${{inputs.cxx}} ${{inputs.cxx}} --version echo CCache: ccache --version ccache -s - name: Run cmake shell: bash run: | set -v export CXXFLAGS="$CXXFLAGS ${{inputs.extra_cxxflags}}" if [[ "${{inputs.cxx}}" == clang* && "${{inputs.build_type}}" == "Debug" ]]; then # TODO Our linux clang build actually use libstdc++11 instead of libc++, we need to fix this check # TODO Add the corresponding libstdc++11 debug macros when building with gcc echo We are doing a debug build on clang. Adding some more debug flags for libc++ export CXXFLAGS="$CXXFLAGS -D_LIBCPP_DEBUG=1 -D_LIBCPP_ENABLE_NODISCARD=1 -D_LIBCPP_ENABLE_DEPRECATION_WARNINGS=1" fi mkdir build cd build cmake .. -GNinja -DCMAKE_CXX_COMPILER=${{inputs.cxx}} -DCMAKE_C_COMPILER=${{inputs.cc}} -DBUILD_TESTING=on -DCMAKE_BUILD_TYPE=${{inputs.build_type}} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache ${{inputs.extra_cmake_flags}} - name: Run ninja shell: bash run: | set -v cd build ninja .github/workflows/actions/run_tests/000077500000000000000000000000001445614261000201445ustar00rootroot00000000000000.github/workflows/actions/run_tests/action.yaml000066400000000000000000000024501445614261000223060ustar00rootroot00000000000000name: 'Test' description: 'Run CryFS Tests' inputs: gtest_args: description: "Extra arguments for gtest runners, for example tests to exclude" required: true extra_env_vars: description: "Extra environment variables to set before running tests" required: true runs: using: "composite" steps: - name: Run tests shell: bash run: | set -v echo Running on ${{runner.os}} cd build export ${{ inputs.extra_env_vars }} ./test/gitversion/gitversion-test ${{inputs.gtest_args}} ./test/cpp-utils/cpp-utils-test ${{inputs.gtest_args}} ./test/parallelaccessstore/parallelaccessstore-test ${{inputs.gtest_args}} ./test/blockstore/blockstore-test ${{inputs.gtest_args}} ./test/blobstore/blobstore-test ${{inputs.gtest_args}} ./test/cryfs/cryfs-test ${{inputs.gtest_args}} # TODO Also run on macOS once fixed if [[ "${{runner.os}}" == "macOS" ]]; then echo Skipping some tests because they are not fixed for macOS yet else # TODO Also run with TSAN once fixed if [[ "${{matrix.name}}" != "TSAN" ]]; then ./test/fspp/fspp-test ${{inputs.gtest_args}} fi ./test/cryfs-cli/cryfs-cli-test ${{inputs.gtest_args}} fi .github/workflows/actions/setup_linux/000077500000000000000000000000001445614261000204755ustar00rootroot00000000000000.github/workflows/actions/setup_linux/action.yaml000066400000000000000000000066021445614261000226420ustar00rootroot00000000000000name: 'Setup Linux' description: 'Setup Linux' inputs: os: description: "Exact os (i.e. ubuntu version) this runs on" required: true extra_apt_packages: description: "Job-specific apt packages to install (e.g. the compiler)" required: true runs: using: "composite" steps: - name: Install Linux dependencies shell: bash run: | echo 'Acquire::Retries "20";' | sudo tee -a /etc/apt/apt.conf.d/80-retries if [[ "${{inputs.os}}" == "ubuntu-22.04" ]]; then echo Adding apt repositories for newer clang versions on Ubuntu 22.04 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 echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy main" >> /etc/apt/sources.list.d/clang.list echo "deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy main" >> /etc/apt/sources.list.d/clang.list echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" >> /etc/apt/sources.list.d/clang.list echo "deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" >> /etc/apt/sources.list.d/clang.list echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-16 main" >> /etc/apt/sources.list.d/clang.list echo "deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-16 main" >> /etc/apt/sources.list.d/clang.list sudo chmod o-w /etc/apt/sources.list.d/clang.list elif [[ "${{inputs.os}}" == "ubuntu-20.04" ]]; then echo Adding apt repositories for newer clang versions on Ubuntu 20.04 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 echo "deb http://apt.llvm.org/focal/ llvm-toolchain-focal main" >> /etc/apt/sources.list.d/clang.list echo "deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal main" >> /etc/apt/sources.list.d/clang.list echo "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main" >> /etc/apt/sources.list.d/clang.list echo "deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main" >> /etc/apt/sources.list.d/clang.list echo "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main" >> /etc/apt/sources.list.d/clang.list echo "deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main" >> /etc/apt/sources.list.d/clang.list echo "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main" >> /etc/apt/sources.list.d/clang.list echo "deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main" >> /etc/apt/sources.list.d/clang.list echo "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-16 main" >> /etc/apt/sources.list.d/clang.list echo "deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal-16 main" >> /etc/apt/sources.list.d/clang.list sudo chmod o-w /etc/apt/sources.list.d/clang.list fi sudo apt-get update sudo apt-get install ninja-build libcurl4-openssl-dev libfuse-dev ccache ${{inputs.extra_apt_packages}} - name: Speed up random generator run: | set -v # Use /dev/urandom when /dev/random is accessed to use less entropy sudo cp -a /dev/urandom /dev/random shell: bash .github/workflows/actions/setup_macos/000077500000000000000000000000001445614261000204405ustar00rootroot00000000000000.github/workflows/actions/setup_macos/action.yaml000066400000000000000000000006131445614261000226010ustar00rootroot00000000000000name: 'Setup macOS' description: 'Setup macOS' inputs: extra_homebrew_packages: description: "Job-specific homebrew packages to install (e.g. the compiler)" required: true runs: using: "composite" steps: - name: Install macOS dependencies shell: bash run: | brew install ninja macfuse libomp ccache md5sha1sum pkg-config ${{inputs.extra_homebrew_packages}} .github/workflows/actions/setup_windows/000077500000000000000000000000001445614261000210305ustar00rootroot00000000000000.github/workflows/actions/setup_windows/action.yaml000066400000000000000000000004231445614261000231700ustar00rootroot00000000000000name: 'Setup Windows' description: 'Setup Windows' runs: using: "composite" steps: - name: Install Windows dependencies shell: bash run: | choco install -y ninja choco install -y dokany --version 1.2.2.1001 --installargs INSTALLDEVFILES=1 .github/workflows/main.yaml000066400000000000000000001071131445614261000162710ustar00rootroot00000000000000name: CI on: ['push', 'pull_request'] jobs: linux_macos: name: CI (Linux/macOS) strategy: fail-fast: false matrix: name: [""] os: - macos-11 - macos-12 - macos-13 - ubuntu-20.04 - ubuntu-22.04 compiler: - cxx: g++-7 cc: gcc-7 macos_cxx: g++-7 macos_cc: gcc-7 homebrew_package: gcc@7 apt_package: g++-7 - cxx: g++-8 cc: gcc-8 macos_cxx: g++-8 macos_cc: gcc-8 homebrew_package: gcc@8 apt_package: g++-8 - cxx: g++-9 cc: gcc-9 macos_cxx: g++-9 macos_cc: gcc-9 apt_package: g++-9 homebrew_package: gcc@9 - cxx: g++-10 cc: gcc-10 macos_cxx: g++-10 macos_cc: gcc-10 apt_package: g++-10 homebrew_package: gcc@10 - cxx: g++-11 cc: gcc-11 macos_cxx: g++-11 macos_cc: gcc-11 apt_package: g++-11 homebrew_package: gcc@11 - cxx: g++-12 cc: gcc-12 macos_cxx: g++-12 macos_cc: gcc-12 apt_package: g++-12 homebrew_package: gcc@12 - cxx: g++-13 cc: gcc-13 macos_cxx: g++-13 macos_cc: gcc-13 apt_package: g++-13 homebrew_package: gcc@13 - cxx: clang++-7 cc: clang-7 macos_cxx: /usr/local/opt/llvm@7/bin/clang++ macos_cc: /usr/local/opt/llvm@7/bin/clang apt_package: clang-7 homebrew_package: llvm@7 - cxx: clang++-8 cc: clang-8 macos_cxx: /usr/local/opt/llvm@8/bin/clang++ macos_cc: /usr/local/opt/llvm@8/bin/clang apt_package: clang-8 homebrew_package: llvm@8 - cxx: clang++-9 cc: clang-9 macos_cxx: /usr/local/opt/llvm@9/bin/clang++ macos_cc: /usr/local/opt/llvm@9/bin/clang apt_package: clang-9 homebrew_package: llvm@9 - cxx: clang++-10 cc: clang-10 macos_cxx: /usr/local/opt/llvm@10/bin/clang++ macos_cc: /usr/local/opt/llvm@10/bin/clang apt_package: clang-10 homebrew_package: llvm@10 - cxx: clang++-11 cc: clang-11 macos_cxx: /usr/local/opt/llvm@11/bin/clang++ macos_cc: /usr/local/opt/llvm@11/bin/clang apt_package: clang-11 libomp5-11 libomp-11-dev homebrew_package: llvm@11 - cxx: clang++-12 cc: clang-12 macos_cxx: /usr/local/opt/llvm@12/bin/clang++ macos_cc: /usr/local/opt/llvm@12/bin/clang apt_package: clang-12 libomp5-12 libomp-12-dev homebrew_package: llvm@12 - cxx: clang++-13 cc: clang-13 macos_cxx: /usr/local/opt/llvm@13/bin/clang++ macos_cc: /usr/local/opt/llvm@13/bin/clang apt_package: clang-13 libomp5-13 libomp-13-dev homebrew_package: llvm@13 - cxx: clang++-14 cc: clang-14 macos_cxx: /usr/local/opt/llvm@14/bin/clang++ macos_cc: /usr/local/opt/llvm@14/bin/clang apt_package: clang-14 libomp5-14 libomp-14-dev homebrew_package: llvm@14 - cxx: clang++-15 cc: clang-15 macos_cxx: /usr/local/opt/llvm@15/bin/clang++ macos_cc: /usr/local/opt/llvm@15/bin/clang apt_package: clang-15 libomp5-15 libomp-15-dev homebrew_package: llvm@15 # Apple Clang # - cxx: clang++ # cc: clang # homebrew_package: "" build_type: - Debug - Release - RelWithDebInfo extra_cmake_flags: [""] extra_cxxflags: [""] extra_env_vars_for_test: [""] install_dependencies_manually: [false] run_build: [true] run_tests: [true] run_clang_tidy: [false] exclude: # MacOS CI doesn't have Clang 7, 8, 9, 10 or GCC 7, 8 anymore - os: macos-11 compiler: {cxx: clang++-7, cc: clang-7, macos_cxx: /usr/local/opt/llvm@7/bin/clang++, macos_cc: /usr/local/opt/llvm@7/bin/clang, apt_package: clang-7, homebrew_package: llvm@7} - os: macos-11 compiler: {cxx: clang++-8, cc: clang-8, macos_cxx: /usr/local/opt/llvm@8/bin/clang++, macos_cc: /usr/local/opt/llvm@8/bin/clang, apt_package: clang-8, homebrew_package: llvm@8} - os: macos-11 compiler: {cxx: clang++-9, cc: clang-9, macos_cxx: /usr/local/opt/llvm@9/bin/clang++, macos_cc: /usr/local/opt/llvm@9/bin/clang, apt_package: clang-9, homebrew_package: llvm@9} - os: macos-11 compiler: {cxx: clang++-10, cc: clang-10, macos_cxx: /usr/local/opt/llvm@10/bin/clang++, macos_cc: /usr/local/opt/llvm@10/bin/clang, apt_package: clang-10, homebrew_package: llvm@10} - os: macos-11 compiler: {cxx: g++-7, cc: gcc-7, macos_cxx: g++-7, macos_cc: gcc-7, homebrew_package: gcc@7, apt_package: g++-7} - os: macos-11 compiler: {cxx: g++-8, cc: gcc-8, macos_cxx: g++-8, macos_cc: gcc-8, homebrew_package: gcc@8, apt_package: g++-8} - os: macos-12 compiler: {cxx: clang++-7, cc: clang-7, macos_cxx: /usr/local/opt/llvm@7/bin/clang++, macos_cc: /usr/local/opt/llvm@7/bin/clang, apt_package: clang-7, homebrew_package: llvm@7} - os: macos-12 compiler: {cxx: clang++-8, cc: clang-8, macos_cxx: /usr/local/opt/llvm@8/bin/clang++, macos_cc: /usr/local/opt/llvm@8/bin/clang, apt_package: clang-8, homebrew_package: llvm@8} - os: macos-12 compiler: {cxx: clang++-9, cc: clang-9, macos_cxx: /usr/local/opt/llvm@9/bin/clang++, macos_cc: /usr/local/opt/llvm@9/bin/clang, apt_package: clang-9, homebrew_package: llvm@9} - os: macos-12 compiler: {cxx: g++-7, cc: gcc-7, macos_cxx: g++-7, macos_cc: gcc-7, homebrew_package: gcc@7, apt_package: g++-7} - os: macos-12 compiler: {cxx: g++-8, cc: gcc-8, macos_cxx: g++-8, macos_cc: gcc-8, homebrew_package: gcc@8, apt_package: g++-8} - os: macos-12 compiler: {cxx: g++-9, cc: gcc-9, macos_cxx: g++-9, macos_cc: gcc-9, homebrew_package: gcc@9, apt_package: g++-9} - os: macos-12 compiler: {cxx: clang++-10, cc: clang-10, macos_cxx: /usr/local/opt/llvm@10/bin/clang++, macos_cc: /usr/local/opt/llvm@10/bin/clang, apt_package: clang-10, homebrew_package: llvm@10} - os: macos-13 compiler: {cxx: clang++-7, cc: clang-7, macos_cxx: /usr/local/opt/llvm@7/bin/clang++, macos_cc: /usr/local/opt/llvm@7/bin/clang, apt_package: clang-7, homebrew_package: llvm@7} - os: macos-13 compiler: {cxx: clang++-10, cc: clang-10, macos_cxx: /usr/local/opt/llvm@10/bin/clang++, macos_cc: /usr/local/opt/llvm@10/bin/clang, apt_package: clang-10, homebrew_package: llvm@10} # Ubuntu 20.04 doesn't have GCC 12, 13 yet - os: ubuntu-20.04 compiler: {cxx: g++-12, cc: gcc-12, macos_cxx: g++-12, macos_cc: gcc-12, homebrew_package: gcc@12, apt_package: g++-12} - os: ubuntu-20.04 compiler: {cxx: g++-13, cc: gcc-13, macos_cxx: g++-13, macos_cc: gcc-13, homebrew_package: gcc@13, apt_package: g++-13} # Ubuntu 22.04 doesn't have gcc 7, 8 or clang 7, 8, 9, 10 anymore - os: ubuntu-22.04 compiler: {cxx: g++-7, cc: gcc-7, macos_cxx: g++-7, macos_cc: gcc-7, homebrew_package: gcc@7, apt_package: g++-7} - os: ubuntu-22.04 compiler: {cxx: g++-8, cc: gcc-8, macos_cxx: g++-8, macos_cc: gcc-8, homebrew_package: gcc@8, apt_package: g++-8} - os: ubuntu-22.04 compiler: {cxx: clang++-7, cc: clang-7, macos_cxx: /usr/local/opt/llvm@7/bin/clang++, macos_cc: /usr/local/opt/llvm@7/bin/clang, apt_package: clang-7, homebrew_package: llvm@7} - os: ubuntu-22.04 compiler: {cxx: clang++-8, cc: clang-8, macos_cxx: /usr/local/opt/llvm@8/bin/clang++, macos_cc: /usr/local/opt/llvm@8/bin/clang, apt_package: clang-8, homebrew_package: llvm@8} - os: ubuntu-22.04 compiler: {cxx: clang++-9, cc: clang-9, macos_cxx: /usr/local/opt/llvm@9/bin/clang++, macos_cc: /usr/local/opt/llvm@9/bin/clang, apt_package: clang-9, homebrew_package: llvm@9} - os: ubuntu-22.04 compiler: {cxx: clang++-10, cc: clang-10, macos_cxx: /usr/local/opt/llvm@10/bin/clang++, macos_cc: /usr/local/opt/llvm@10/bin/clang, apt_package: clang-10, homebrew_package: llvm@10} # Clang 11 on Ubuntu seems to have a bug that fails CI - os: ubuntu-22.04 compiler: {cxx: clang++-11, cc: clang-11, macos_cxx: /usr/local/opt/llvm@11/bin/clang++, macos_cc: /usr/local/opt/llvm@11/bin/clang, apt_package: "clang-11 libomp5-11 libomp-11-dev", homebrew_package: llvm@11} build_type: Debug include: - name: Local dependencies os: ubuntu-22.04 compiler: cxx: clang++-11 cc: clang-11 apt_package: clang-11 libomp5-11 libomp-11-dev build_type: RelWithDebInfo extra_cmake_flags: -DDEPENDENCY_CONFIG=../cmake-utils/DependenciesFromLocalSystem.cmake extra_cxxflags: "" extra_env_vars_for_test: "" install_dependencies_manually: true run_build: true run_tests: true - name: Local dependencies os: ubuntu-22.04 compiler: cxx: clang++-11 cc: clang-11 apt_package: clang-11 libomp5-11 libomp-11-dev build_type: RelWithDebInfo extra_cmake_flags: -DDEPENDENCY_CONFIG=../cmake-utils/DependenciesFromLocalSystem.cmake extra_cxxflags: "" extra_env_vars_for_test: "" install_dependencies_manually: true run_build: true run_tests: true - name: Werror gcc os: ubuntu-22.04 compiler: cxx: g++-9 cc: gcc-9 apt_package: g++-9 build_type: RelWithDebInfo extra_cmake_flags: -DUSE_WERROR=on extra_cxxflags: "" install_dependencies_manually: false run_build: true run_tests: false - name: Werror clang os: ubuntu-22.04 compiler: cxx: clang++-11 cc: clang-11 apt_package: clang-11 libomp5-11 libomp-11-dev build_type: RelWithDebInfo extra_cmake_flags: -DUSE_WERROR=on extra_cxxflags: "" install_dependencies_manually: false run_build: true run_tests: false - name: No compatibility os: ubuntu-22.04 compiler: cxx: clang++-11 cc: clang-11 apt_package: clang-11 libomp5-11 libomp-11-dev build_type: RelWithDebInfo extra_cmake_flags: "" extra_cxxflags: "-DCRYFS_NO_COMPATIBILITY" extra_env_vars_for_test: "" install_dependencies_manually: false run_build: true run_tests: true - name: ASAN # TODO Update to ubuntu-22.04 os: ubuntu-20.04 compiler: cxx: clang++-11 cc: clang-11 apt_package: clang-11 libomp5-11 libomp-11-dev build_type: Debug # OpenMP crashes under asan. Disable OpenMP. # TODO is it enough to replace this with omp_num_threads: 1 ? extra_cmake_flags: "-DDISABLE_OPENMP=ON" extra_cxxflags: "-O1 -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls -fno-common -fsanitize-address-use-after-scope" extra_env_vars_for_test: ASAN_OPTIONS="detect_leaks=1 check_initialization_order=1 detect_stack_use_after_return=1 detect_invalid_pointer_pairs=1 atexit=1" install_dependencies_manually: false run_build: true run_tests: true - name: UBSAN # TODO Update to ubuntu-22.04 os: ubuntu-20.04 compiler: cxx: clang++-11 cc: clang-11 apt_package: clang-11 libomp5-11 libomp-11-dev build_type: Debug # OpenMP crashes under ubsan. Disable OpenMP. # TODO is it enough to replace this with omp_num_threads: 1 ? extra_cmake_flags: "-DDISABLE_OPENMP=ON" extra_cxxflags: "-O1 -fno-sanitize-recover=undefined,nullability,implicit-conversion,unsigned-integer-overflow,local-bounds,float-divide-by-zero -fno-omit-frame-pointer -fno-optimize-sibling-calls -fno-common" extra_env_vars_for_test: UBSAN_OPTIONS="print_stacktrace=1" install_dependencies_manually: false run_build: true run_tests: true - name: TSAN # TODO Update to ubuntu-22.04 os: ubuntu-20.04 compiler: cxx: clang++-11 cc: clang-11 apt_package: clang-11 libomp5-11 libomp-11-dev build_type: Debug extra_cmake_flags: "" extra_cxxflags: "-O2 -fsanitize=thread -fno-omit-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -fno-common" install_dependencies_manually: false run_build: true run_tests: true gtest_args: "--gtest_filter=-LoggingTest.LoggingAlsoWorksAfterFork:AssertTest_*:BacktraceTest.*:SubprocessTest.*:SignalCatcherTest.*_thenDies:SignalHandlerTest.*_thenDies:SignalHandlerTest.givenMultipleSigIntHandlers_whenRaising_thenCatchesCorrectSignal:CliTest_Setup.*:CliTest_IntegrityCheck.*:*/CliTest_WrongEnvironment.*:CliTest_Unmount.*:CliTest.WorksWithCommasInBasedir" extra_env_vars_for_test: OMP_NUM_THREADS=1 - name: clang-tidy os: ubuntu-22.04 compiler: cxx: clang++-11 cc: clang-11 apt_package: clang-11 clang-tidy-11 libomp5-11 libomp-11-dev build_type: RelWithDebInfo extra_cmake_flags: "" extra_cxxflags: "" install_dependencies_manually: false run_build: false run_tests: false extra_env_vars_for_test: "" run_clang_tidy: true runs-on: ${{matrix.os}} env: # Setting conan cache dir to a location where our Github Cache Action can find it CONAN_USER_HOME: "${{ github.workspace }}/conan-cache/" steps: - name: Checkout uses: actions/checkout@v1 #TODO Ideally, all the setup actions would be in their own subaction, but Github doesn't support using third party actions (e.g. cache) from nested actions yet, see https://github.com/actions/runner/issues/862 - name: Setup MacOS if: ${{ runner.os == 'macOS' }} uses: ./.github/workflows/actions/setup_macos with: extra_homebrew_packages: ${{ matrix.compiler.homebrew_package }} - name: Setup Linux if: ${{ runner.os == 'Linux' }} uses: ./.github/workflows/actions/setup_linux with: os: ${{ matrix.os }} extra_apt_packages: ${{ matrix.compiler.apt_package }} - name: Install local dependencies if: ${{ matrix.install_dependencies_manually }} uses: ./.github/workflows/actions/install_local_dependencies - name: Find pip cache location id: pip_cache_dir run: | # We need at least pip 20.1 to get the "pip cache dir" command. Ubuntu doesn't have pip 20.1 by default yet, let's upgrade it python3 -m pip install -U pip python3 -m pip --version echo "::set-output name=pip_cache_dir::$(python3 -m pip cache dir)" shell: bash - name: Retrieve pip cache # Many jobs access the cache in parallel an we might observe an incomplete state that isn't valid. This would fail with a checksum error. Let's not fail the CI job but continue it, later on this job will upload a new new cache as part of the regular job run. continue-on-error: true # We're using an S3 based cache because the standard GitHub Action cache (actions/cache) only gives us 5GB of storage and we need more uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 with: action: get # note: this access key has read-only access to the cache. It's public so it runs on PRs. aws-access-key-id: AKIAV5S2KH4F5OUZXV5E aws-secret-access-key: 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 aws-region: eu-west-1 bucket: ci-cache.cryfs key: v0-${{ runner.os }}-${{ matrix.os }}-setup-pip - name: Install Conan shell: bash run: | # Using "python3 -m pip" instead of "pip3" to make sure we get the same pip that we queried the cache dir for the Github Cache action python3 -m pip install conan==1.59 - name: Save pip cache # note: this access key has write access to the cache. This can't run on PRs. if: ${{github.event_name == 'push' }} # Cache things sometimes indeterministically fail (roughly 1% of times this is run), let's not fail the job for it continue-on-error: true uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 with: action: put aws-access-key-id: ${{ secrets.CACHE_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.CACHE_AWS_SECRET_ACCESS_KEY }} aws-region: eu-west-1 bucket: ci-cache.cryfs key: v0-${{ runner.os }}-${{ matrix.os }}-setup-pip artifacts: ${{ steps.pip_cache_dir.outputs.pip_cache_dir }} #TODO Ideally, the Setup ccache step would be part of the build action, but Github doesn't support nested actions yet, see https://github.com/actions/runner/issues/862 - name: Configure ccache shell: bash run: | set -v ccache --set-config=compiler_check=content ccache --set-config=max_size=500M ccache --set-config=cache_dir=${{github.workspace}}/.ccache ccache --set-config=compression=true ccache --set-config=sloppiness=include_file_mtime,include_file_ctime echo CCache config: ccache -p echo Clearing ccache statistics ccache -z - name: Hash flags id: hash_flags run: | # Write it into file first so we fail if the command fails. Errors inside $() are ignored by bash unfortunately. echo __${{matrix.extra_cmake_flags}}__${{matrix.extra_cxxflags}}__ | md5sum > /tmp/hash_flags echo "::set-output name=hash_flags::$(cat /tmp/hash_flags)" rm /tmp/hash_flags shell: bash - name: Retrieve ccache cache # Many jobs access the cache in parallel an we might observe an incomplete state that isn't valid. This would fail with a checksum error. Let's not fail the CI job but continue it, later on this job will upload a new new cache as part of the regular job run. continue-on-error: true # We're using an S3 based cache because the standard GitHub Action cache (actions/cache) only gives us 5GB of storage and we need more uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 with: action: get # note: this access key has read-only access to the cache. It's public so it runs on PRs. aws-access-key-id: AKIAV5S2KH4F5OUZXV5E aws-secret-access-key: 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 aws-region: eu-west-1 bucket: ci-cache.cryfs key: v0-${{ runner.os }}-${{ matrix.os }}-ccache__${{matrix.compiler.cxx}}__${{matrix.compiler.cc}}__${{matrix.build_type}}__${{matrix.run_build}}__${{matrix.run_clang_tidy}}__${{steps.hash_flags.outputs.hash_flags}}__ - name: Show ccache statistics shell: bash run: | set -v ccache -s # TODO Ideally, the Setup conan cache step would be part of the build action, but Github doesn't support nested actions yet, see https://github.com/actions/runner/issues/862 - name: Retrieve conan cache # Many jobs access the cache in parallel an we might observe an incomplete state that isn't valid. This would fail with a checksum error. Let's not fail the CI job but continue it, later on this job will upload a new new cache as part of the regular job run. continue-on-error: true # We're using an S3 based cache because the standard GitHub Action cache (actions/cache) only gives us 5GB of storage and we need more uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 with: action: get # note: this access key has read-only access to the cache. It's public so it runs on PRs. aws-access-key-id: AKIAV5S2KH4F5OUZXV5E aws-secret-access-key: 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 aws-region: eu-west-1 bucket: ci-cache.cryfs key: v1-${{ runner.os }}-${{ matrix.os }}-conancache__${{matrix.compiler.cxx}}__${{matrix.compiler.cc}}__${{matrix.build_type}}__ - name: Build (macOS) if: ${{ matrix.run_build && runner.os == 'macOS' }} uses: ./.github/workflows/actions/run_build with: cxx: ${{ matrix.compiler.macos_cxx }} cc: ${{ matrix.compiler.macos_cc }} build_type: ${{ matrix.build_type }} - name: Build (Linux) if: ${{ matrix.run_build && runner.os == 'Linux' }} uses: ./.github/workflows/actions/run_build with: cxx: ${{ matrix.compiler.cxx }} cc: ${{ matrix.compiler.cc }} build_type: ${{ matrix.build_type }} extra_cmake_flags: ${{ matrix.extra_cmake_flags }} extra_cxxflags: ${{ matrix.extra_cxxflags }} - name: Run clang-tidy id: clang_tidy if: ${{ matrix.run_clang_tidy }} shell: bash run: | set -v mkdir cmake cd cmake if ! ../run-clang-tidy.sh -fix ; then git diff > /tmp/clang-tidy-fixes echo Found clang tidy fixes: cat /tmp/clang-tidy-fixes exit 1 else echo Did not find any clang-tidy fixes fi - name: Upload fixes as artifact if: ${{ always() && matrix.run_clang_tidy }} uses: actions/upload-artifact@v2 with: name: clang-tidy-fixes path: /tmp/clang-tidy-fixes - name: Show ccache statistics shell: bash run: | set -v ccache -s - name: Reduce ccache size if: ${{ runner.os == 'macOS' }} shell: bash run: | set -v ccache --evict-older-than 7d ccache -s - name: Save ccache cache # note: this access key has write access to the cache. This can't run on PRs. if: ${{ github.event_name == 'push' }} # Cache things sometimes indeterministically fail (roughly 1% of times this is run), let's not fail the job for it continue-on-error: true uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 with: action: put aws-access-key-id: ${{ secrets.CACHE_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.CACHE_AWS_SECRET_ACCESS_KEY }} aws-region: eu-west-1 bucket: ci-cache.cryfs key: v0-${{ runner.os }}-${{ matrix.os }}-ccache__${{matrix.compiler.cxx}}__${{matrix.compiler.cc}}__${{matrix.build_type}}__${{matrix.run_build}}__${{matrix.run_clang_tidy}}__${{steps.hash_flags.outputs.hash_flags}}__ artifacts: ${{ github.workspace }}/.ccache - name: Save conan cache # note: this access key has write access to the cache. This can't run on PRs. if: ${{ github.event_name == 'push' }} # Cache things sometimes indeterministically fail (roughly 1% of times this is run), let's not fail the job for it continue-on-error: true uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 with: action: put aws-access-key-id: ${{ secrets.CACHE_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.CACHE_AWS_SECRET_ACCESS_KEY }} aws-region: eu-west-1 bucket: ci-cache.cryfs key: v1-${{ runner.os }}-${{ matrix.os }}-conancache__${{matrix.compiler.cxx}}__${{matrix.compiler.cc}}__${{matrix.build_type}}__ artifacts: ${{ env.CONAN_USER_HOME }} - name: Test if: ${{ matrix.run_tests }} uses: ./.github/workflows/actions/run_tests with: gtest_args: ${{matrix.gtest_args}} extra_env_vars: ${{matrix.extra_env_vars_for_test}} windows: name: CI (Windows) strategy: fail-fast: false matrix: name: [""] os: - windows-2019 arch: - Win32 - x64 build_type: - Debug - Release - RelWithDebInfo runs-on: ${{matrix.os}} env: # Setting conan cache dir to a location where our Github Cache Action can find it CONAN_USER_HOME: "D:/.conan/f/" CONAN_USER_HOME_SHORT: "D:/.conan/s/" steps: - name: Checkout uses: actions/checkout@v1 #TODO Ideally, all the setup actions would be in their own subaction, but Github doesn't support using third party actions (e.g. cache) from nested actions yet, see https://github.com/actions/runner/issues/862 - name: Setup Windows uses: ./.github/workflows/actions/setup_windows - name: Find pip cache location id: pip_cache_dir run: | # We need at least pip 20.1 to get the "pip cache dir" command. Ubuntu doesn't have pip 20.1 by default yet, let's upgrade it python3 -m pip install -U pip python3 -m pip --version echo "::set-output name=pip_cache_dir::$(python3 -m pip cache dir)" shell: bash - name: Retrieve pip cache # Many jobs access the cache in parallel an we might observe an incomplete state that isn't valid. This would fail with a checksum error. Let's not fail the CI job but continue it, later on this job will upload a new new cache as part of the regular job run. continue-on-error: true # We're using an S3 based cache because the standard GitHub Action cache (actions/cache) only gives us 5GB of storage and we need more uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 with: action: get # note: this access key has read-only access to the cache. It's public so it runs on PRs. aws-access-key-id: AKIAV5S2KH4F5OUZXV5E aws-secret-access-key: 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 aws-region: eu-west-1 bucket: ci-cache.cryfs key: v0-${{ runner.os }}-${{ matrix.os }}-setup-pip - name: Install Conan shell: bash run: | # Using "python3 -m pip" instead of "pip3" to make sure we get the same pip that we queried the cache dir for the Github Cache action python3 -m pip install conan==1.59 - name: Save pip cache # note: this access key has write access to the cache. This can't run on PRs. if: ${{github.event_name == 'push' }} # Cache things sometimes indeterministically fail (roughly 1% of times this is run), let's not fail the job for it continue-on-error: true uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 with: action: put aws-access-key-id: ${{ secrets.CACHE_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.CACHE_AWS_SECRET_ACCESS_KEY }} aws-region: eu-west-1 bucket: ci-cache.cryfs key: v0-${{ runner.os }}-${{ matrix.os }}-setup-pip artifacts: ${{ steps.pip_cache_dir.outputs.pip_cache_dir }} #TODO Ideally, the Setup ccache step would be part of the build action, but Github doesn't support nested actions yet, see https://github.com/actions/runner/issues/862 # - name: Configure ccache # shell: bash # run: | # set -v # ccache --set-config=compiler_check=content # ccache --set-config=max_size=500M # ccache --set-config=cache_dir=${{github.workspace}}/.ccache # ccache --set-config=compression=true # ccache --set-config=sloppiness=include_file_mtime,include_file_ctime # echo CCache config: # ccache -p # echo Clearing ccache statistics # ccache -z # - name: Hash flags # id: hash_flags # run: | # # Write it into file first so we fail if the command fails. Errors inside $() are ignored by bash unfortunately. # echo __${{matrix.extra_cmake_flags}}__${{matrix.extra_cxxflags}}__ | md5sum > /tmp/hash_flags # echo "::set-output name=hash_flags::$(cat /tmp/hash_flags)" # rm /tmp/hash_flags # shell: bash # - name: Retrieve ccache cache # # Many jobs access the cache in parallel an we might observe an incomplete state that isn't valid. This would fail with a checksum error. Let's not fail the CI job but continue it, later on this job will upload a new new cache as part of the regular job run. # continue-on-error: true # # We're using an S3 based cache because the standard GitHub Action cache (actions/cache) only gives us 5GB of storage and we need more # uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 # with: # action: get # # note: this access key has read-only access to the cache. It's public so it runs on PRs. # aws-access-key-id: AKIAV5S2KH4F5OUZXV5E # aws-secret-access-key: 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 # aws-region: eu-west-1 # bucket: ci-cache.cryfs # key: v0-${{ runner.os }}-${{ matrix.os }}-ccache__${{matrix.compiler.cxx}}__${{matrix.compiler.cc}}__${{matrix.build_type}}__${{matrix.run_build}}__${{matrix.run_clang_tidy}}__${{steps.hash_flags.outputs.hash_flags}}__ # - name: Show ccache statistics # shell: bash # run: | # set -v # ccache -s # TODO Ideally, the Setup conan cache step would be part of the build action, but Github doesn't support nested actions yet, see https://github.com/actions/runner/issues/862 - name: Retrieve conan cache # Many jobs access the cache in parallel an we might observe an incomplete state that isn't valid. This would fail with a checksum error. Let's not fail the CI job but continue it, later on this job will upload a new new cache as part of the regular job run. continue-on-error: true # We're using an S3 based cache because the standard GitHub Action cache (actions/cache) only gives us 5GB of storage and we need more uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 with: action: get # note: this access key has read-only access to the cache. It's public so it runs on PRs. aws-access-key-id: AKIAV5S2KH4F5OUZXV5E aws-secret-access-key: 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 aws-region: eu-west-1 bucket: ci-cache.cryfs key: v1-${{ runner.os }}-${{ matrix.os }}-conancache__${{matrix.compiler.cxx}}__${{matrix.compiler.cc}}__${{matrix.build_type}}__ - name: Build shell: bash run: | set -v # 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) # note: The CMAKE_SYSTEM_VERSION variable is set to 10.0.18362.0 because as of this writing, appveyor uses 10.0.17763.0 and that has a bug, see https://developercommunity.visualstudio.com/content/problem/343296/sdk-and-experimentalpreprocessor.html # TODO CMAKE_SYSTEM_VERSION is probably not needed anymore mkdir build cd build cmake .. -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DBUILD_TESTING=on -DDOKAN_PATH="C:/Program Files/Dokan/DokanLibrary-1.2.2" -A ${{matrix.arch}} -DCMAKE_SYSTEM_VERSION="10.0.18362.0" cmake --build . --config ${{matrix.build_type}} # - name: Show ccache statistics # shell: bash # run: | # set -v # ccache -s # - name: Reduce ccache size # if: ${{ runner.os == 'macOS' }} # shell: bash # run: | # set -v # ccache --evict-older-than 7d # ccache -s # - name: Save ccache cache # # note: this access key has write access to the cache. This can't run on PRs. # if: ${{ github.event_name == 'push' }} # # Cache things sometimes indeterministically fail (roughly 1% of times this is run), let's not fail the job for it # continue-on-error: true # uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 # with: # action: put # aws-access-key-id: ${{ secrets.CACHE_AWS_ACCESS_KEY_ID }} # aws-secret-access-key: ${{ secrets.CACHE_AWS_SECRET_ACCESS_KEY }} # aws-region: eu-west-1 # bucket: ci-cache.cryfs # key: v0-${{ runner.os }}-${{ matrix.os }}-ccache__${{matrix.compiler.cxx}}__${{matrix.compiler.cc}}__${{matrix.build_type}}__${{matrix.run_build}}__${{matrix.run_clang_tidy}}__${{steps.hash_flags.outputs.hash_flags}}__ # artifacts: ${{ github.workspace }}/.ccache - name: Save conan cache # note: this access key has write access to the cache. This can't run on PRs. if: ${{ github.event_name == 'push' }} # Cache things sometimes indeterministically fail (roughly 1% of times this is run), let's not fail the job for it continue-on-error: true uses: leroy-merlin-br/action-s3-cache@8d75079437b388688b9ea9c7d73dff4ef975c5fa # v1.0.5 with: action: put aws-access-key-id: ${{ secrets.CACHE_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.CACHE_AWS_SECRET_ACCESS_KEY }} aws-region: eu-west-1 bucket: ci-cache.cryfs key: v1-${{ runner.os }}-${{ matrix.os }}-conancache__${{matrix.compiler.cxx}}__${{matrix.compiler.cc}}__${{matrix.build_type}}__ artifacts: | ${{ env.CONAN_USER_HOME }} ${{ env.CONAN_USER_HOME_SHORT }} - name: Test shell: bash run: | set -v cd build ./test/gitversion/${{matrix.build_type}}/gitversion-test.exe ./test/cpp-utils/${{matrix.build_type}}/cpp-utils-test.exe # ./test/fspp/${{matrix.build_type}}/fspp-test.exe ./test/parallelaccessstore/${{matrix.build_type}}/parallelaccessstore-test.exe ./test/blockstore/${{matrix.build_type}}/blockstore-test.exe ./test/blobstore/${{matrix.build_type}}/blobstore-test.exe ./test/cryfs/${{matrix.build_type}}/cryfs-test.exe # TODO Enable cryfs-cli-test on Windows # ./test/cryfs-cli/${{matrix.build_type}}/cryfs-cli-test.exe - name: CPack shell: bash run: | set -v cd build cpack -C ${{matrix.build_type}} --verbose -G WIX - name: Upload installers as artifact uses: actions/upload-artifact@v2 with: name: cryfs-${{matrix.arch}}-${{matrix.build_type}}.msi path: build/cryfs-*.msi .gitignore000066400000000000000000000002751445614261000130550ustar00rootroot00000000000000umltest.inner.sh umltest.status /build /cmake /cmake-build-* /.idea *~ /.vs /.vscode src/gitversion/*.pyc src/gitversion/__pycache__ cmake-build-debug cmake-build-release cmake-build-test CMakeLists.txt000066400000000000000000000037361445614261000136320ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10 FATAL_ERROR) # TODO Remove this deprecated policy switch once we're on cmake 3.4 or later cmake_policy(SET CMP0065 OLD) # 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(7.0) require_clang_version(7.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) set(DEPENDENCY_CONFIG "cmake-utils/DependenciesFromConan.cmake" CACHE FILEPATH "cmake configuration file defining how to get dependencies") 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) # We don't use LTO because crypto++ has problems with it, see https://github.com/weidai11/cryptopp/issues/1031 and https://www.cryptopp.com/wiki/Link_Time_Optimization # The MSVC version on AppVeyor CI needs this if(MSVC) add_definitions(/bigobj) endif() include(${DEPENDENCY_CONFIG}) add_subdirectory(vendor EXCLUDE_FROM_ALL) add_subdirectory(src) add_subdirectory(doc) add_subdirectory(test) add_subdirectory(cpack) CMakeSettings.json000066400000000000000000000040171445614261000144570ustar00rootroot00000000000000{ "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 -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 -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 -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 -DDOKAN_PATH=\"C:\\Program Files\\Dokan\\Dokan Library-1.2.2\"", "buildCommandArgs": "-v", "ctestCommandArgs": "" } ] } ChangeLog.txt000066400000000000000000000407101445614261000134530ustar00rootroot00000000000000Version 0.11.4 --------------- * Fixed build issue with GCC 13 (see https://github.com/cryfs/cryfs/pull/448 ) * Fixed build issue with Python 3.12 (see https://github.com/cryfs/cryfs/issues/459 ) Version 0.11.3 --------------- * Fixed build issue on systems with libfmt 9.0 (see https://github.com/cryfs/cryfs/issues/432 ) * Fixed build issue on Apple Silicon Macs (see https://github.com/cryfs/homebrew-tap/issues/10 ) * Fixed build issue on systems that only have `python3` but no `python` executable (see https://github.com/cryfs/homebrew-tap/issues/12 ) Version 0.11.2 --------------- Bugfix: * Time to mount a file system was very long because the build didn't correctly use OpenMP. This is now fixed and file systems should open faster again. Version 0.11.1 --------------- Bugfix: * Fix building of the range-v3 dependency. The conan remote URL for this dependency changed and we have to use the new URL. See https://github.com/cryfs/cryfs/issues/398 * Update to CryptoPP 8.6. This fixes a rare bug where CryptoPP 8.5 encrypts data wrongly, see https://github.com/weidai11/cryptopp/issues/1069 * cryfs-unmount correctly unmounts paths that contain spaces, see https://github.com/cryfs/cryfs/issues/372 * Updated to DokanY 1.2.2.1001 Version 0.11.0 --------------- Backwards Compatibility: * Filesystems created with CryFS 0.10.x can be mounted without requiring a migration. * Filesystems created with CryFS 0.11.x can be mounted by CryFS 0.10.x if you configure it to use a cipher supported by CryFS 0.10.x, e.g. AES-256-GCM. The new default, XChaCha20-Poly1305, is not supported by CryFS 0.10.x. Security: * Added the XChaCha20-Poly1305 encryption cipher. For new filesystems, this will be the default, but you're still able to create a filesystem with the previous default of AES-256-GCM by saying "no" to the "use default settings?" question when creating the file system. Also, old filesystems will not be automatically converted and will keep using AES-256-GCM. XChaCha20-Poly1305 is significantly slower than AES-256-GCM on modern CPUs, but it is more secure for large filesystems (>64GB). For AES-256-GCM, it is recommended to encrypt at most 2^32 blocks, which at the CryFS default block size of 16KB would be 64GB. The more the filesystem grows above that, the more likely it gets that a nonce gets reused and the two corresponding blocks become decryptable by an adversary. Other blocks would not be affected, but an adversary being able to access those two blocks (i.e. 64KB of the stored data) is bad enough. See Section 8.3 in https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf XChaCha20-Poly1305 does not suffer from this constraint and stays secure even if the filesystem gets very large. New platforms: * CryFS now works on devices with Apple M1 silicon Build changes: * Switch to Conan package manager * Allow an easy way to modify how the dependencies are found. This is mostly helpful for package maintainers. See "Using local dependencies" in the README. * Build with macFUSE instead of osxfuse on OSX * Now requires CMake 3.10 or later, and GCC 7 or later, or Clang 7 or later * Fix a build issue on Gentoo systems * Fix a build issue when building with boost 1.77 Improvements: * Display the file system configuration when mounting a file system * Now shows a better error message when failing to load the config file that distinguishes between "wrong password" and "config file not found". New features: * Add support for atime mount options (noatime, strictatime, relatime, atime, nodiratime). * The new default is now *noatime* (in 0.10.x is was relatime). Noatime reduces the amount of writes necessary and with that reduces the probability of synchronization conflicts, and the probability of corrupted file systems if a power outage happens while writing. * Add an --immediate flag to cryfs-unmount that tries to unmount immediately and doesn't wait for processes to release their locks on the file system. * Add a --create-missing-basedir and --create-missing-mountpoint flag to create the base directory and mount directory respectively, if they don't exist, skipping the confirmation prompt. Other: * Updated to spdlog 1.8.5 * Updated to ranges-v3 0.11.0 * Updated to boost 1.75 * Updated to crypto++ 8.5 Version 0.10.4 -------------- Fixed bugs: * Fixed an issue when compiling with GCC 11, see https://github.com/cryfs/cryfs/issues/389 Version 0.10.3 --------------- Fixed bugs: * A comma in the base directory name would make the file system fail to mount, https://github.com/cryfs/cryfs/issues/326 * Fixed determining the user's homedir: If $HOME and the /etc/passwd entry for the current user contradict each other, now $HOME takes preference over /etc/passwd. * Fix Android compilation, https://github.com/cryfs/cryfs/issues/345 * Remove cryfs-stats tool which isn't ready yet and could destroy the file system * Fixed crash on startup when running in an environment that doesn't have $HOME set (e.g. an empty env), https://github.com/cryfs/cryfs/issues/374 Version 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.11 -------------- Fixed bugs: * Fix a race condition when a file descriptor is closed while there's read/write requests for that file being processed. 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.txt000066400000000000000000000167431445614261000127170ustar00rootroot00000000000000 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.md000066400000000000000000000245761445614261000123560ustar00rootroot00000000000000# 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 The following should work on Arch and Arch-based distros: sudo pacman -S cryfs Additionally, the following would work for any Linux distro with the Nix package manager: nix-env -iA nixpkgs.cryfs OSX ---- CryFS is distributed via Homebrew, MacPorts, and Nix. If you use Homebrew: brew install --cask macfuse brew install cryfs/tap/cryfs If you use MacPorts (only available for OSX 10.12 to 10.14 at the moment): port install cryfs For Nix, the macOS build for cryfs is available in the Nixpkgs channel 21.05 and later: brew install --cask macfuse # or download from https://osxfuse.github.io/ nix-env -iA nixpkgs.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 2019](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 Stability / Production readiness ==================== CryFS 0.10 or later is stable for most use cases, but has a couple of known issues that can corrupt your file system. They don't happen in normal day to day use, but can happen if you don't pay attention or aren't aware of them. This is why the version number hasn't reached 1.0 yet. - If you kill the CryFS process while it was in the middle of writing data (either intentionally or unintentionally by losing power to your PC), your file system could get corrupted. CryFS does not do journaling. Note that in 0.10.x, read accesses into a CryFS file system can cause writes because file timestamps get updated. So if you're unlucky, your file system could get corrupted if you lose power while you were reading files as well. Read accesses aren't an issue in CryFS 0.11.x anymore, because it mounts the filesystem with `noatime` by default. - The same corruption mentioned above can happen when CryFS is trying to write data but your disk ran out of space, causing the write to fail. - CryFS does not currently support concurrent access, i.e. accessing a file system from multiple devices at the same time. CryFS works very well for storing data in a cloud and using it from multiple devices, but you need to make sure that only one CryFS process is active at any point in time, and you also need to make sure that the cloud synchronization client (e.g. Dropbox) finishes its synchronization before you switch devices. There are some ideas on how concurrent access could be supported in future versions, but it's a hard problem to solve. If you do happen to access the file system from multiple devices at the same time, it will likely go well most of the time, but it can corrupt your file system. - In addition to the scenarios above that can corrupt your file system, note that there is currently no fsck-like tool for CryFS that could recover your data. Although such a tool is in theory, possible, it hasn't been implemented yet and a corrupted file system will most likely cause a loss of your data. If the scenarios mentioned above don't apply to you, then you can consider CryFS 0.10.x as stable. The 0.9.x versions are not recommended anymore. Building from source ==================== Requirements ------------ - Git (for getting the source code) - GCC version >= 7 or Clang >= 7 - CMake version >= 3.10 - pkg-config (on Unix) - Conan package manager (version 1.x) - libcurl4 (including development headers) - SSL development libraries (including development headers, e.g. libssl-dev) - libFUSE version >= 2.8.6 (including development headers), on Mac OS X instead install macFUSE from https://osxfuse.github.io/ - Python >= 3.5 - OpenMP You can use the following commands to install these requirements # Ubuntu $ sudo apt install git g++ cmake make pkg-config libcurl4-openssl-dev libssl-dev libfuse-dev python python3-pip $ sudo pip3 install conan==1.59 # Fedora $ sudo dnf install git gcc-c++ cmake make pkgconf libcurl-devel openssl-devel fuse-devel python python3-pip $ sudo pip3 install conan==1.59 # Macintosh $ brew install cmake pkg-config openssl libomp macfuse $ sudo pip3 install conan==1.59 Build & Install --------------- 1. Clone repository $ git clone https://github.com/cryfs/cryfs.git cryfs $ cd cryfs 2. Build $ mkdir build && cd build $ 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) ---------------------------------- 1. Install conan. If you want to use "pip install conan", you may have to install Python first. 2. Install DokanY 1.2.2. Other versions may not work. 3. Run CMake to generate Visual Studio 2019 project files (this may not be necessary, but it makes sure everything works as expected and you can see potential errors happening during this step) $ mkdir build && cd build $ cmake .. -G "Visual Studio 16 2019" -DDOKAN_PATH=[dokan library location, e.g. "C:\Program Files\Dokan\DokanLibrary-1.2.2"] 4. Potentially modify CMakeSettings.json file to fit your needs 5. Open the cryfs source folder with Visual Studio 2019, or alternatively build on command line using $ cd build && cmake --build . --config RelWithDebInfo Troubleshooting --------------- On most systems, CMake should find the libraries automatically. However, that doesn't always work. 1. **Fuse library not found** Pass in the library path with PKG_CONFIG_PATH=/path-to-fuse-or-macFUSE/lib/pkgconfig cmake .. 2. **Fuse headers not found** Pass in the include path with PKG_CONFIG_PATH=/path-to-fuse-or-macFUSE/lib/pkgconfig cmake .. 3. **Openssl headers not found** Pass in the include path with cmake .. -DCMAKE_C_FLAGS="-I/path/to/openssl/include" 4. **OpenMP not found (osx)** Either build it without OpenMP cmake .. -DDISABLE_OPENMP=on but this will cause slower file system mount times (performance after mounting will be unaffected). If you installed OpenMP with homebrew or macports, it will 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 Using local dependencies ------------------------------- Starting with CryFS 0.11, Conan is used for dependency management. When you build CryFS, Conan downloads the exact version of each dependency library that was also used for development. All dependencies are linked statically, so there should be no incompatibility with locally installed libraries. This is the recommended way because it has the highest probability of working correctly. However, some distributions prefer software packages to be built against dependencies dynamically and against locally installed versions of libraries. So if you're building a package for such a distribution, you have the option of doing that, at the cost of potential incompatibilities. If you follow this workflow, please make sure to extensively test your build of CryFS. You're using a setup that wasn't tested by the CryFS developers. To use local dependencies, you need to tell the CryFS build how to get these dependencies. You can do this by writing a small CMake configuration file and passing it to the CryFS build using `-DDEPENDENCY_CONFIG=filename`. This configuration file needs to define a cmake target for each of the dependencies. Here's an [example config file](cmake-utils/DependenciesFromConan.cmake) that gets the dependencies from conan. And here's another [example config file](cmake-utils/DependenciesFromLocalSystem.cmake) that works for getting dependencies that are locally installed in Ubuntu. You can create your own configuration file to tell the build how to get its dependencies and, for example, mix and match. Get some dependencies from Conan and others from the local system. Creating .deb and .rpm packages ------------------------------- It is recommended to install CryFS using packages, because that allows for an easy way to uninstall it again once you don't need it anymore. If you want to create a .rpm package, you need to install rpmbuild. 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 ---------------------- In 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. Then, remove all copies of the compromised filesystem and config file(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.yml000066400000000000000000000002361445614261000134520ustar00rootroot00000000000000image: - Visual Studio 2019 platform: - x64 configuration: - Release build_script: - cmd: echo Appveyor CI is disabled since we now have Github Actions archive.sh000077500000000000000000000004201445614261000130350ustar00rootroot00000000000000#!/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/000077500000000000000000000000001445614261000132775ustar00rootroot00000000000000cmake-utils/DependenciesFromConan.cmake000066400000000000000000000012431445614261000204720ustar00rootroot00000000000000include(cmake-utils/conan.cmake) conan_cmake_autodetect(settings) conan_cmake_install( PATH_OR_REFERENCE ${CMAKE_CURRENT_SOURCE_DIR}/conanfile.py BUILD missing SETTINGS ${settings}) include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup(TARGETS SKIP_STD NO_OUTPUT_DIRS) add_library(CryfsDependencies_range-v3 INTERFACE) target_link_libraries(CryfsDependencies_range-v3 INTERFACE CONAN_PKG::range-v3) add_library(CryfsDependencies_spdlog INTERFACE) target_link_libraries(CryfsDependencies_spdlog INTERFACE CONAN_PKG::spdlog) add_library(CryfsDependencies_boost INTERFACE) target_link_libraries(CryfsDependencies_boost INTERFACE CONAN_PKG::boost) cmake-utils/DependenciesFromLocalSystem.cmake000066400000000000000000000054061445614261000217000ustar00rootroot00000000000000# This configuration file can be used to build CryFS against local dependencies instead of using Conan. # # Example: # $ mkdir build && cd build && cmake .. -DDEPENDENCY_CONFIG=../cmake-utils/DependenciesFromLocalSystem.cmake # # Note that this is only provided as an example and not officially supported. Please still open issues # on GitHub if it doesn't work though. # # There's another file in this directory, DependenciesFromConan.cmake, which, well, gets the dependencies from # Conan instead of from the local system. This is the default. You can also create your own file to tell the build # how to get its dependencies, for example you can mix and match, get some dependencies from Conan and others # from the local system. If you mix and match Conan and local dependencies, please call conan_basic_setup() # **after** running all find_package() for your local dependencies, otherwise find_package() might also find # the versions from Conan. # # Note that if you use dependencies from the local system, you're very likely using different versions of the # dependencies than were used in the development of CryFS. The official version of each dependency required is # listed in conanfile.py. Different versions might work but are untested. Please intensively test your CryFS build # if you build it with different versions of the dependencies. function(check_target_is_not_from_conan TARGET) get_target_property(INCLUDE_DIRS ${TARGET} INTERFACE_INCLUDE_DIRECTORIES) if("${INCLUDE_DIRS}" MATCHES "conan") message(WARNING "It seems setting up the local ${TARGET} dependency didn't work correctly and it got the version from Conan instead. Please set up cmake so that it sets up conan after all local dependencies are defined.") endif() endfunction() # Setup range-v3 dependency find_package(range-v3 REQUIRED) check_target_is_not_from_conan(range-v3::range-v3) add_library(CryfsDependencies_range-v3 INTERFACE) target_link_libraries(CryfsDependencies_range-v3 INTERFACE range-v3::range-v3) # Setup boost dependency set(Boost_USE_STATIC_LIBS OFF) find_package(Boost 1.65.1 REQUIRED COMPONENTS filesystem system thread chrono program_options) check_target_is_not_from_conan(Boost::boost) add_library(CryfsDependencies_boost INTERFACE) target_link_libraries(CryfsDependencies_boost INTERFACE Boost::boost Boost::filesystem Boost::thread Boost::chrono Boost::program_options) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") # Also link to rt, because boost thread needs that. target_link_libraries(CryfsDependencies_boost INTERFACE rt) endif() # Setup spdlog dependency find_package(spdlog REQUIRED) check_target_is_not_from_conan(spdlog::spdlog) add_library(CryfsDependencies_spdlog INTERFACE) target_link_libraries(CryfsDependencies_spdlog INTERFACE spdlog::spdlog) cmake-utils/TargetArch.cmake000066400000000000000000000155161445614261000163350ustar00rootroot00000000000000# 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/conan.cmake000066400000000000000000001065701445614261000154100ustar00rootroot00000000000000# Taken from https://github.com/conan-io/cmake-conan/blob/v0.16.1/conan.cmake # The MIT License (MIT) # Copyright (c) 2018 JFrog # 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. # This file comes from: https://github.com/conan-io/cmake-conan. Please refer # to this repository for issues and documentation. # Its purpose is to wrap and launch Conan C/C++ Package Manager when cmake is called. # It will take CMake current settings (os, compiler, compiler version, architecture) # and translate them to conan settings for installing and retrieving dependencies. # It is intended to facilitate developers building projects that have conan dependencies, # but it is only necessary on the end-user side. It is not necessary to create conan # packages, in fact it shouldn't be use for that. Check the project documentation. # version: 0.16.1 include(CMakeParseArguments) function(_get_msvc_ide_version result) set(${result} "" PARENT_SCOPE) if(NOT MSVC_VERSION VERSION_LESS 1400 AND MSVC_VERSION VERSION_LESS 1500) set(${result} 8 PARENT_SCOPE) elseif(NOT MSVC_VERSION VERSION_LESS 1500 AND MSVC_VERSION VERSION_LESS 1600) set(${result} 9 PARENT_SCOPE) elseif(NOT MSVC_VERSION VERSION_LESS 1600 AND MSVC_VERSION VERSION_LESS 1700) set(${result} 10 PARENT_SCOPE) elseif(NOT MSVC_VERSION VERSION_LESS 1700 AND MSVC_VERSION VERSION_LESS 1800) set(${result} 11 PARENT_SCOPE) elseif(NOT MSVC_VERSION VERSION_LESS 1800 AND MSVC_VERSION VERSION_LESS 1900) set(${result} 12 PARENT_SCOPE) elseif(NOT MSVC_VERSION VERSION_LESS 1900 AND MSVC_VERSION VERSION_LESS 1910) set(${result} 14 PARENT_SCOPE) elseif(NOT MSVC_VERSION VERSION_LESS 1910 AND MSVC_VERSION VERSION_LESS 1920) set(${result} 15 PARENT_SCOPE) elseif(NOT MSVC_VERSION VERSION_LESS 1920 AND MSVC_VERSION VERSION_LESS 1930) set(${result} 16 PARENT_SCOPE) else() message(FATAL_ERROR "Conan: Unknown MSVC compiler version [${MSVC_VERSION}]") endif() endfunction() macro(_conan_detect_build_type) conan_parse_arguments(${ARGV}) if(ARGUMENTS_BUILD_TYPE) set(_CONAN_SETTING_BUILD_TYPE ${ARGUMENTS_BUILD_TYPE}) elseif(CMAKE_BUILD_TYPE) set(_CONAN_SETTING_BUILD_TYPE ${CMAKE_BUILD_TYPE}) else() message(FATAL_ERROR "Please specify in command line CMAKE_BUILD_TYPE (-DCMAKE_BUILD_TYPE=Release)") endif() string(TOUPPER ${_CONAN_SETTING_BUILD_TYPE} _CONAN_SETTING_BUILD_TYPE_UPPER) if (_CONAN_SETTING_BUILD_TYPE_UPPER STREQUAL "DEBUG") set(_CONAN_SETTING_BUILD_TYPE "Debug") elseif(_CONAN_SETTING_BUILD_TYPE_UPPER STREQUAL "RELEASE") set(_CONAN_SETTING_BUILD_TYPE "Release") elseif(_CONAN_SETTING_BUILD_TYPE_UPPER STREQUAL "RELWITHDEBINFO") set(_CONAN_SETTING_BUILD_TYPE "RelWithDebInfo") elseif(_CONAN_SETTING_BUILD_TYPE_UPPER STREQUAL "MINSIZEREL") set(_CONAN_SETTING_BUILD_TYPE "MinSizeRel") endif() endmacro() macro(_conan_check_system_name) #handle -s os setting if(CMAKE_SYSTEM_NAME AND NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") #use default conan os setting if CMAKE_SYSTEM_NAME is not defined set(CONAN_SYSTEM_NAME ${CMAKE_SYSTEM_NAME}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") set(CONAN_SYSTEM_NAME Macos) endif() if(${CMAKE_SYSTEM_NAME} STREQUAL "QNX") set(CONAN_SYSTEM_NAME Neutrino) endif() set(CONAN_SUPPORTED_PLATFORMS Windows Linux Macos Android iOS FreeBSD WindowsStore WindowsCE watchOS tvOS FreeBSD SunOS AIX Arduino Emscripten Neutrino) list (FIND CONAN_SUPPORTED_PLATFORMS "${CONAN_SYSTEM_NAME}" _index) if (${_index} GREATER -1) #check if the cmake system is a conan supported one set(_CONAN_SETTING_OS ${CONAN_SYSTEM_NAME}) else() message(FATAL_ERROR "cmake system ${CONAN_SYSTEM_NAME} is not supported by conan. Use one of ${CONAN_SUPPORTED_PLATFORMS}") endif() endif() endmacro() macro(_conan_check_language) get_property(_languages GLOBAL PROPERTY ENABLED_LANGUAGES) if (";${_languages};" MATCHES ";CXX;") set(LANGUAGE CXX) set(USING_CXX 1) elseif (";${_languages};" MATCHES ";C;") set(LANGUAGE C) set(USING_CXX 0) else () message(FATAL_ERROR "Conan: Neither C or C++ was detected as a language for the project. Unabled to detect compiler version.") endif() endmacro() macro(_conan_detect_compiler) conan_parse_arguments(${ARGV}) if(ARGUMENTS_ARCH) set(_CONAN_SETTING_ARCH ${ARGUMENTS_ARCH}) endif() if (${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL GNU) # using GCC # TODO: Handle other params string(REPLACE "." ";" VERSION_LIST ${CMAKE_${LANGUAGE}_COMPILER_VERSION}) list(GET VERSION_LIST 0 MAJOR) list(GET VERSION_LIST 1 MINOR) set(COMPILER_VERSION ${MAJOR}.${MINOR}) if(${MAJOR} GREATER 4) set(COMPILER_VERSION ${MAJOR}) endif() set(_CONAN_SETTING_COMPILER gcc) set(_CONAN_SETTING_COMPILER_VERSION ${COMPILER_VERSION}) if (USING_CXX) conan_cmake_detect_unix_libcxx(_LIBCXX) set(_CONAN_SETTING_COMPILER_LIBCXX ${_LIBCXX}) endif () elseif (${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL Intel) string(REPLACE "." ";" VERSION_LIST ${CMAKE_${LANGUAGE}_COMPILER_VERSION}) list(GET VERSION_LIST 0 MAJOR) list(GET VERSION_LIST 1 MINOR) set(COMPILER_VERSION ${MAJOR}.${MINOR}) set(_CONAN_SETTING_COMPILER intel) set(_CONAN_SETTING_COMPILER_VERSION ${COMPILER_VERSION}) if (USING_CXX) conan_cmake_detect_unix_libcxx(_LIBCXX) set(_CONAN_SETTING_COMPILER_LIBCXX ${_LIBCXX}) endif () elseif (${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL AppleClang) # using AppleClang string(REPLACE "." ";" VERSION_LIST ${CMAKE_${LANGUAGE}_COMPILER_VERSION}) list(GET VERSION_LIST 0 MAJOR) list(GET VERSION_LIST 1 MINOR) set(_CONAN_SETTING_COMPILER apple-clang) set(_CONAN_SETTING_COMPILER_VERSION ${MAJOR}.${MINOR}) if (USING_CXX) conan_cmake_detect_unix_libcxx(_LIBCXX) set(_CONAN_SETTING_COMPILER_LIBCXX ${_LIBCXX}) endif () elseif (${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL Clang) string(REPLACE "." ";" VERSION_LIST ${CMAKE_${LANGUAGE}_COMPILER_VERSION}) list(GET VERSION_LIST 0 MAJOR) list(GET VERSION_LIST 1 MINOR) set(_CONAN_SETTING_COMPILER clang) set(_CONAN_SETTING_COMPILER_VERSION ${MAJOR}.${MINOR}) if(APPLE) cmake_policy(GET CMP0025 APPLE_CLANG_POLICY) if(NOT APPLE_CLANG_POLICY STREQUAL NEW) message(STATUS "Conan: APPLE and Clang detected. Assuming apple-clang compiler. Set CMP0025 to avoid it") set(_CONAN_SETTING_COMPILER apple-clang) endif() endif() if(${_CONAN_SETTING_COMPILER} STREQUAL clang AND ${MAJOR} GREATER 7) set(_CONAN_SETTING_COMPILER_VERSION ${MAJOR}) endif() if (USING_CXX) conan_cmake_detect_unix_libcxx(_LIBCXX) set(_CONAN_SETTING_COMPILER_LIBCXX ${_LIBCXX}) endif () elseif(${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL MSVC) set(_VISUAL "Visual Studio") _get_msvc_ide_version(_VISUAL_VERSION) if("${_VISUAL_VERSION}" STREQUAL "") message(FATAL_ERROR "Conan: Visual Studio not recognized") else() set(_CONAN_SETTING_COMPILER ${_VISUAL}) set(_CONAN_SETTING_COMPILER_VERSION ${_VISUAL_VERSION}) endif() if(NOT _CONAN_SETTING_ARCH) if (MSVC_${LANGUAGE}_ARCHITECTURE_ID MATCHES "64") set(_CONAN_SETTING_ARCH x86_64) elseif (MSVC_${LANGUAGE}_ARCHITECTURE_ID MATCHES "^ARM") message(STATUS "Conan: Using default ARM architecture from MSVC") set(_CONAN_SETTING_ARCH armv6) elseif (MSVC_${LANGUAGE}_ARCHITECTURE_ID MATCHES "86") set(_CONAN_SETTING_ARCH x86) else () message(FATAL_ERROR "Conan: Unknown MSVC architecture [${MSVC_${LANGUAGE}_ARCHITECTURE_ID}]") endif() endif() conan_cmake_detect_vs_runtime(_vs_runtime ${ARGV}) message(STATUS "Conan: Detected VS runtime: ${_vs_runtime}") set(_CONAN_SETTING_COMPILER_RUNTIME ${_vs_runtime}) if (CMAKE_GENERATOR_TOOLSET) set(_CONAN_SETTING_COMPILER_TOOLSET ${CMAKE_VS_PLATFORM_TOOLSET}) elseif(CMAKE_VS_PLATFORM_TOOLSET AND (CMAKE_GENERATOR STREQUAL "Ninja")) set(_CONAN_SETTING_COMPILER_TOOLSET ${CMAKE_VS_PLATFORM_TOOLSET}) endif() else() message(FATAL_ERROR "Conan: compiler setup not recognized") endif() endmacro() function(conan_cmake_settings result) #message(STATUS "COMPILER " ${CMAKE_CXX_COMPILER}) #message(STATUS "COMPILER " ${CMAKE_CXX_COMPILER_ID}) #message(STATUS "VERSION " ${CMAKE_CXX_COMPILER_VERSION}) #message(STATUS "FLAGS " ${CMAKE_LANG_FLAGS}) #message(STATUS "LIB ARCH " ${CMAKE_CXX_LIBRARY_ARCHITECTURE}) #message(STATUS "BUILD TYPE " ${CMAKE_BUILD_TYPE}) #message(STATUS "GENERATOR " ${CMAKE_GENERATOR}) #message(STATUS "GENERATOR WIN64 " ${CMAKE_CL_64}) message(STATUS "Conan: Automatic detection of conan settings from cmake") conan_parse_arguments(${ARGV}) _conan_detect_build_type(${ARGV}) _conan_check_system_name() _conan_check_language() _conan_detect_compiler(${ARGV}) # If profile is defined it is used if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND ARGUMENTS_DEBUG_PROFILE) set(_APPLIED_PROFILES ${ARGUMENTS_DEBUG_PROFILE}) elseif(CMAKE_BUILD_TYPE STREQUAL "Release" AND ARGUMENTS_RELEASE_PROFILE) set(_APPLIED_PROFILES ${ARGUMENTS_RELEASE_PROFILE}) elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" AND ARGUMENTS_RELWITHDEBINFO_PROFILE) set(_APPLIED_PROFILES ${ARGUMENTS_RELWITHDEBINFO_PROFILE}) elseif(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel" AND ARGUMENTS_MINSIZEREL_PROFILE) set(_APPLIED_PROFILES ${ARGUMENTS_MINSIZEREL_PROFILE}) elseif(ARGUMENTS_PROFILE) set(_APPLIED_PROFILES ${ARGUMENTS_PROFILE}) endif() foreach(ARG ${_APPLIED_PROFILES}) set(_SETTINGS ${_SETTINGS} -pr=${ARG}) endforeach() foreach(ARG ${ARGUMENTS_PROFILE_BUILD}) conan_check(VERSION 1.24.0 REQUIRED DETECT_QUIET) set(_SETTINGS ${_SETTINGS} -pr:b=${ARG}) endforeach() if(NOT _SETTINGS OR ARGUMENTS_PROFILE_AUTO STREQUAL "ALL") set(ARGUMENTS_PROFILE_AUTO arch build_type compiler compiler.version compiler.runtime compiler.libcxx compiler.toolset) endif() # remove any manually specified settings from the autodetected settings foreach(ARG ${ARGUMENTS_SETTINGS}) string(REGEX MATCH "[^=]*" MANUAL_SETTING "${ARG}") message(STATUS "Conan: ${MANUAL_SETTING} was added as an argument. Not using the autodetected one.") list(REMOVE_ITEM ARGUMENTS_PROFILE_AUTO "${MANUAL_SETTING}") endforeach() # Automatic from CMake foreach(ARG ${ARGUMENTS_PROFILE_AUTO}) string(TOUPPER ${ARG} _arg_name) string(REPLACE "." "_" _arg_name ${_arg_name}) if(_CONAN_SETTING_${_arg_name}) set(_SETTINGS ${_SETTINGS} -s ${ARG}=${_CONAN_SETTING_${_arg_name}}) endif() endforeach() foreach(ARG ${ARGUMENTS_SETTINGS}) set(_SETTINGS ${_SETTINGS} -s ${ARG}) endforeach() message(STATUS "Conan: Settings= ${_SETTINGS}") set(${result} ${_SETTINGS} PARENT_SCOPE) endfunction() function(conan_cmake_detect_unix_libcxx result) # Take into account any -stdlib in compile options get_directory_property(compile_options DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMPILE_OPTIONS) string(GENEX_STRIP "${compile_options}" compile_options) # Take into account any _GLIBCXX_USE_CXX11_ABI in compile definitions get_directory_property(defines DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMPILE_DEFINITIONS) string(GENEX_STRIP "${defines}" defines) foreach(define ${defines}) if(define MATCHES "_GLIBCXX_USE_CXX11_ABI") if(define MATCHES "^-D") set(compile_options ${compile_options} "${define}") else() set(compile_options ${compile_options} "-D${define}") endif() endif() endforeach() # add additional compiler options ala cmRulePlaceholderExpander::ExpandRuleVariable set(EXPAND_CXX_COMPILER ${CMAKE_CXX_COMPILER}) if(CMAKE_CXX_COMPILER_ARG1) # CMake splits CXX="foo bar baz" into CMAKE_CXX_COMPILER="foo", CMAKE_CXX_COMPILER_ARG1="bar baz" # without this, ccache, winegcc, or other wrappers might lose all their arguments separate_arguments(SPLIT_CXX_COMPILER_ARG1 NATIVE_COMMAND ${CMAKE_CXX_COMPILER_ARG1}) list(APPEND EXPAND_CXX_COMPILER ${SPLIT_CXX_COMPILER_ARG1}) endif() if(CMAKE_CXX_COMPILE_OPTIONS_TARGET AND CMAKE_CXX_COMPILER_TARGET) # without --target= we may be calling the wrong underlying GCC list(APPEND EXPAND_CXX_COMPILER "${CMAKE_CXX_COMPILE_OPTIONS_TARGET}${CMAKE_CXX_COMPILER_TARGET}") endif() if(CMAKE_CXX_COMPILE_OPTIONS_EXTERNAL_TOOLCHAIN AND CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN) list(APPEND EXPAND_CXX_COMPILER "${CMAKE_CXX_COMPILE_OPTIONS_EXTERNAL_TOOLCHAIN}${CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN}") endif() if(CMAKE_CXX_COMPILE_OPTIONS_SYSROOT) # without --sysroot= we may find the wrong #include if(CMAKE_SYSROOT_COMPILE) list(APPEND EXPAND_CXX_COMPILER "${CMAKE_CXX_COMPILE_OPTIONS_SYSROOT}${CMAKE_SYSROOT_COMPILE}") elseif(CMAKE_SYSROOT) list(APPEND EXPAND_CXX_COMPILER "${CMAKE_CXX_COMPILE_OPTIONS_SYSROOT}${CMAKE_SYSROOT}") endif() endif() separate_arguments(SPLIT_CXX_FLAGS NATIVE_COMMAND ${CMAKE_CXX_FLAGS}) if(CMAKE_OSX_SYSROOT) set(xcode_sysroot_option "--sysroot=${CMAKE_OSX_SYSROOT}") endif() execute_process( COMMAND ${CMAKE_COMMAND} -E echo "#include " COMMAND ${EXPAND_CXX_COMPILER} ${SPLIT_CXX_FLAGS} -x c++ ${xcode_sysroot_option} ${compile_options} -E -dM - OUTPUT_VARIABLE string_defines ) if(string_defines MATCHES "#define __GLIBCXX__") # Allow -D_GLIBCXX_USE_CXX11_ABI=ON/OFF as argument to cmake if(DEFINED _GLIBCXX_USE_CXX11_ABI) if(_GLIBCXX_USE_CXX11_ABI) set(${result} libstdc++11 PARENT_SCOPE) return() else() set(${result} libstdc++ PARENT_SCOPE) return() endif() endif() if(string_defines MATCHES "#define _GLIBCXX_USE_CXX11_ABI 1\n") set(${result} libstdc++11 PARENT_SCOPE) else() # Either the compiler is missing the define because it is old, and so # it can't use the new abi, or the compiler was configured to use the # old abi by the user or distro (e.g. devtoolset on RHEL/CentOS) set(${result} libstdc++ PARENT_SCOPE) endif() else() set(${result} libc++ PARENT_SCOPE) endif() endfunction() function(conan_cmake_detect_vs_runtime result) conan_parse_arguments(${ARGV}) if(ARGUMENTS_BUILD_TYPE) set(build_type "${ARGUMENTS_BUILD_TYPE}") elseif(CMAKE_BUILD_TYPE) set(build_type "${CMAKE_BUILD_TYPE}") else() message(FATAL_ERROR "Please specify in command line CMAKE_BUILD_TYPE (-DCMAKE_BUILD_TYPE=Release)") endif() if(build_type) string(TOUPPER "${build_type}" build_type) endif() set(variables CMAKE_CXX_FLAGS_${build_type} CMAKE_C_FLAGS_${build_type} CMAKE_CXX_FLAGS CMAKE_C_FLAGS) foreach(variable ${variables}) if(NOT "${${variable}}" STREQUAL "") string(REPLACE " " ";" flags "${${variable}}") foreach (flag ${flags}) if("${flag}" STREQUAL "/MD" OR "${flag}" STREQUAL "/MDd" OR "${flag}" STREQUAL "/MT" OR "${flag}" STREQUAL "/MTd") string(SUBSTRING "${flag}" 1 -1 runtime) set(${result} "${runtime}" PARENT_SCOPE) return() endif() endforeach() endif() endforeach() if("${build_type}" STREQUAL "DEBUG") set(${result} "MDd" PARENT_SCOPE) else() set(${result} "MD" PARENT_SCOPE) endif() endfunction() function(_collect_settings result) set(ARGUMENTS_PROFILE_AUTO arch build_type compiler compiler.version compiler.runtime compiler.libcxx compiler.toolset) foreach(ARG ${ARGUMENTS_PROFILE_AUTO}) string(TOUPPER ${ARG} _arg_name) string(REPLACE "." "_" _arg_name ${_arg_name}) if(_CONAN_SETTING_${_arg_name}) set(detected_setings ${detected_setings} ${ARG}=${_CONAN_SETTING_${_arg_name}}) endif() endforeach() set(${result} ${detected_setings} PARENT_SCOPE) endfunction() function(conan_cmake_autodetect detected_settings) _conan_detect_build_type() _conan_check_system_name() _conan_check_language() _conan_detect_compiler() _collect_settings(collected_settings) set(${detected_settings} ${collected_settings} PARENT_SCOPE) endfunction() macro(conan_parse_arguments) set(options BASIC_SETUP CMAKE_TARGETS UPDATE KEEP_RPATHS NO_LOAD NO_OUTPUT_DIRS OUTPUT_QUIET NO_IMPORTS SKIP_STD) set(oneValueArgs CONANFILE ARCH BUILD_TYPE INSTALL_FOLDER CONAN_COMMAND) set(multiValueArgs DEBUG_PROFILE RELEASE_PROFILE RELWITHDEBINFO_PROFILE MINSIZEREL_PROFILE PROFILE REQUIRES OPTIONS IMPORTS SETTINGS BUILD ENV GENERATORS PROFILE_AUTO INSTALL_ARGS CONFIGURATION_TYPES PROFILE_BUILD BUILD_REQUIRES) cmake_parse_arguments(ARGUMENTS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) endmacro() function(old_conan_cmake_install) # Calls "conan install" # Argument BUILD is equivalant to --build={missing, PkgName,...} or # --build when argument is 'BUILD all' (which builds all packages from source) # Argument CONAN_COMMAND, to specify the conan path, e.g. in case of running from source # cmake does not identify conan as command, even if it is +x and it is in the path conan_parse_arguments(${ARGV}) if(CONAN_CMAKE_MULTI) set(ARGUMENTS_GENERATORS ${ARGUMENTS_GENERATORS} cmake_multi) else() set(ARGUMENTS_GENERATORS ${ARGUMENTS_GENERATORS} cmake) endif() set(CONAN_BUILD_POLICY "") foreach(ARG ${ARGUMENTS_BUILD}) if(${ARG} STREQUAL "all") set(CONAN_BUILD_POLICY ${CONAN_BUILD_POLICY} --build) break() else() set(CONAN_BUILD_POLICY ${CONAN_BUILD_POLICY} --build=${ARG}) endif() endforeach() if(ARGUMENTS_CONAN_COMMAND) set(CONAN_CMD ${ARGUMENTS_CONAN_COMMAND}) else() conan_check(REQUIRED) endif() set(CONAN_OPTIONS "") if(ARGUMENTS_CONANFILE) if(IS_ABSOLUTE ${ARGUMENTS_CONANFILE}) set(CONANFILE ${ARGUMENTS_CONANFILE}) else() set(CONANFILE ${CMAKE_CURRENT_SOURCE_DIR}/${ARGUMENTS_CONANFILE}) endif() else() set(CONANFILE ".") endif() foreach(ARG ${ARGUMENTS_OPTIONS}) set(CONAN_OPTIONS ${CONAN_OPTIONS} -o=${ARG}) endforeach() if(ARGUMENTS_UPDATE) set(CONAN_INSTALL_UPDATE --update) endif() if(ARGUMENTS_NO_IMPORTS) set(CONAN_INSTALL_NO_IMPORTS --no-imports) endif() set(CONAN_INSTALL_FOLDER "") if(ARGUMENTS_INSTALL_FOLDER) set(CONAN_INSTALL_FOLDER -if=${ARGUMENTS_INSTALL_FOLDER}) endif() foreach(ARG ${ARGUMENTS_GENERATORS}) set(CONAN_GENERATORS ${CONAN_GENERATORS} -g=${ARG}) endforeach() foreach(ARG ${ARGUMENTS_ENV}) set(CONAN_ENV_VARS ${CONAN_ENV_VARS} -e=${ARG}) endforeach() set(conan_args install ${CONANFILE} ${settings} ${CONAN_ENV_VARS} ${CONAN_GENERATORS} ${CONAN_BUILD_POLICY} ${CONAN_INSTALL_UPDATE} ${CONAN_INSTALL_NO_IMPORTS} ${CONAN_OPTIONS} ${CONAN_INSTALL_FOLDER} ${ARGUMENTS_INSTALL_ARGS}) string (REPLACE ";" " " _conan_args "${conan_args}") message(STATUS "Conan executing: ${CONAN_CMD} ${_conan_args}") if(ARGUMENTS_OUTPUT_QUIET) execute_process(COMMAND ${CONAN_CMD} ${conan_args} RESULT_VARIABLE return_code OUTPUT_VARIABLE conan_output ERROR_VARIABLE conan_output WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) else() execute_process(COMMAND ${CONAN_CMD} ${conan_args} RESULT_VARIABLE return_code WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) endif() if(NOT "${return_code}" STREQUAL "0") message(FATAL_ERROR "Conan install failed='${return_code}'") endif() endfunction() function(conan_cmake_install) if(DEFINED CONAN_COMMAND) set(CONAN_CMD ${CONAN_COMMAND}) else() conan_check(REQUIRED) endif() set(installOptions UPDATE NO_IMPORTS OUTPUT_QUIET ERROR_QUIET) set(installOneValueArgs PATH_OR_REFERENCE REFERENCE REMOTE LOCKFILE LOCKFILE_OUT LOCKFILE_NODE_ID INSTALL_FOLDER) set(installMultiValueArgs GENERATOR BUILD ENV ENV_HOST ENV_BUILD OPTIONS_HOST OPTIONS OPTIONS_BUILD PROFILE PROFILE_HOST PROFILE_BUILD SETTINGS SETTINGS_HOST SETTINGS_BUILD) cmake_parse_arguments(ARGS "${installOptions}" "${installOneValueArgs}" "${installMultiValueArgs}" ${ARGN}) foreach(arg ${installOptions}) if(ARGS_${arg}) set(${arg} ${${arg}} ${ARGS_${arg}}) endif() endforeach() foreach(arg ${installOneValueArgs}) if(DEFINED ARGS_${arg}) if("${arg}" STREQUAL "REMOTE") set(flag "--remote") elseif("${arg}" STREQUAL "LOCKFILE") set(flag "--lockfile") elseif("${arg}" STREQUAL "LOCKFILE_OUT") set(flag "--lockfile-out") elseif("${arg}" STREQUAL "LOCKFILE_NODE_ID") set(flag "--lockfile-node-id") elseif("${arg}" STREQUAL "INSTALL_FOLDER") set(flag "--install-folder") endif() set(${arg} ${${arg}} ${flag} ${ARGS_${arg}}) endif() endforeach() foreach(arg ${installMultiValueArgs}) if(DEFINED ARGS_${arg}) if("${arg}" STREQUAL "GENERATOR") set(flag "--generator") elseif("${arg}" STREQUAL "BUILD") set(flag "--build") elseif("${arg}" STREQUAL "ENV") set(flag "--env") elseif("${arg}" STREQUAL "ENV_HOST") set(flag "--env:host") elseif("${arg}" STREQUAL "ENV_BUILD") set(flag "--env:build") elseif("${arg}" STREQUAL "OPTIONS") set(flag "--options") elseif("${arg}" STREQUAL "OPTIONS_HOST") set(flag "--options:host") elseif("${arg}" STREQUAL "OPTIONS_BUILD") set(flag "--options:build") elseif("${arg}" STREQUAL "PROFILE") set(flag "--profile") elseif("${arg}" STREQUAL "PROFILE_HOST") set(flag "--profile:host") elseif("${arg}" STREQUAL "PROFILE_BUILD") set(flag "--profile:build") elseif("${arg}" STREQUAL "SETTINGS") set(flag "--settings") elseif("${arg}" STREQUAL "SETTINGS_HOST") set(flag "--settings:host") elseif("${arg}" STREQUAL "SETTINGS_BUILD") set(flag "--settings:build") endif() list(LENGTH ARGS_${arg} numargs) foreach(item ${ARGS_${arg}}) if(${item} STREQUAL "all" AND ${arg} STREQUAL "BUILD") set(${arg} "--build") break() endif() set(${arg} ${${arg}} ${flag} ${item}) endforeach() endif() endforeach() if(DEFINED UPDATE) set(UPDATE --update) endif() if(DEFINED NO_IMPORTS) set(NO_IMPORTS --no-imports) endif() set(install_args install ${PATH_OR_REFERENCE} ${REFERENCE} ${UPDATE} ${NO_IMPORTS} ${REMOTE} ${LOCKFILE} ${LOCKFILE_OUT} ${LOCKFILE_NODE_ID} ${INSTALL_FOLDER} ${GENERATOR} ${BUILD} ${ENV} ${ENV_HOST} ${ENV_BUILD} ${OPTIONS} ${OPTIONS_HOST} ${OPTIONS_BUILD} ${PROFILE} ${PROFILE_HOST} ${PROFILE_BUILD} ${SETTINGS} ${SETTINGS_HOST} ${SETTINGS_BUILD}) string(REPLACE ";" " " _install_args "${install_args}") message(STATUS "Conan executing: ${CONAN_CMD} ${_install_args}") if(ARGS_OUTPUT_QUIET) set(OUTPUT_OPT OUTPUT_QUIET) endif() if(ARGS_ERROR_QUIET) set(ERROR_OPT ERROR_QUIET) endif() execute_process(COMMAND ${CONAN_CMD} ${install_args} RESULT_VARIABLE return_code ${OUTPUT_OPT} ${ERROR_OPT} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) if(NOT "${return_code}" STREQUAL "0") if (ARGS_ERROR_QUIET) message(WARNING "Conan install failed='${return_code}'") else() message(FATAL_ERROR "Conan install failed='${return_code}'") endif() endif() endfunction() function(conan_cmake_setup_conanfile) conan_parse_arguments(${ARGV}) if(ARGUMENTS_CONANFILE) get_filename_component(_CONANFILE_NAME ${ARGUMENTS_CONANFILE} NAME) # configure_file will make sure cmake re-runs when conanfile is updated configure_file(${ARGUMENTS_CONANFILE} ${CMAKE_CURRENT_BINARY_DIR}/${_CONANFILE_NAME}.junk COPYONLY) file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/${_CONANFILE_NAME}.junk) else() conan_cmake_generate_conanfile(ON ${ARGV}) endif() endfunction() function(conan_cmake_configure) conan_cmake_generate_conanfile(OFF ${ARGV}) endfunction() # Generate, writing in disk a conanfile.txt with the requires, options, and imports # specified as arguments # This will be considered as temporary file, generated in CMAKE_CURRENT_BINARY_DIR) function(conan_cmake_generate_conanfile DEFAULT_GENERATOR) conan_parse_arguments(${ARGV}) set(_FN "${CMAKE_CURRENT_BINARY_DIR}/conanfile.txt") file(WRITE ${_FN} "") if(DEFINED ARGUMENTS_REQUIRES) file(APPEND ${_FN} "[requires]\n") foreach(REQUIRE ${ARGUMENTS_REQUIRES}) file(APPEND ${_FN} ${REQUIRE} "\n") endforeach() endif() if (DEFAULT_GENERATOR OR DEFINED ARGUMENTS_GENERATORS) file(APPEND ${_FN} "[generators]\n") if (DEFAULT_GENERATOR) file(APPEND ${_FN} "cmake\n") endif() if (DEFINED ARGUMENTS_GENERATORS) foreach(GENERATOR ${ARGUMENTS_GENERATORS}) file(APPEND ${_FN} ${GENERATOR} "\n") endforeach() endif() endif() if(DEFINED ARGUMENTS_BUILD_REQUIRES) file(APPEND ${_FN} "[build_requires]\n") foreach(BUILD_REQUIRE ${ARGUMENTS_BUILD_REQUIRES}) file(APPEND ${_FN} ${BUILD_REQUIRE} "\n") endforeach() endif() if(DEFINED ARGUMENTS_IMPORTS) file(APPEND ${_FN} "[imports]\n") foreach(IMPORTS ${ARGUMENTS_IMPORTS}) file(APPEND ${_FN} ${IMPORTS} "\n") endforeach() endif() if(DEFINED ARGUMENTS_OPTIONS) file(APPEND ${_FN} "[options]\n") foreach(OPTION ${ARGUMENTS_OPTIONS}) file(APPEND ${_FN} ${OPTION} "\n") endforeach() endif() endfunction() macro(conan_load_buildinfo) if(CONAN_CMAKE_MULTI) set(_CONANBUILDINFO conanbuildinfo_multi.cmake) else() set(_CONANBUILDINFO conanbuildinfo.cmake) endif() if(ARGUMENTS_INSTALL_FOLDER) set(_CONANBUILDINFOFOLDER ${ARGUMENTS_INSTALL_FOLDER}) else() set(_CONANBUILDINFOFOLDER ${CMAKE_CURRENT_BINARY_DIR}) endif() # Checks for the existence of conanbuildinfo.cmake, and loads it # important that it is macro, so variables defined at parent scope if(EXISTS "${_CONANBUILDINFOFOLDER}/${_CONANBUILDINFO}") message(STATUS "Conan: Loading ${_CONANBUILDINFO}") include(${_CONANBUILDINFOFOLDER}/${_CONANBUILDINFO}) else() message(FATAL_ERROR "${_CONANBUILDINFO} doesn't exist in ${CMAKE_CURRENT_BINARY_DIR}") endif() endmacro() macro(conan_cmake_run) conan_parse_arguments(${ARGV}) if(ARGUMENTS_CONFIGURATION_TYPES AND NOT CMAKE_CONFIGURATION_TYPES) message(WARNING "CONFIGURATION_TYPES should only be specified for multi-configuration generators") elseif(ARGUMENTS_CONFIGURATION_TYPES AND ARGUMENTS_BUILD_TYPE) message(WARNING "CONFIGURATION_TYPES and BUILD_TYPE arguments should not be defined at the same time.") endif() if(CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE AND NOT CONAN_EXPORTED AND NOT ARGUMENTS_BUILD_TYPE) set(CONAN_CMAKE_MULTI ON) if (NOT ARGUMENTS_CONFIGURATION_TYPES) set(ARGUMENTS_CONFIGURATION_TYPES "Release;Debug") endif() message(STATUS "Conan: Using cmake-multi generator") else() set(CONAN_CMAKE_MULTI OFF) endif() if(NOT CONAN_EXPORTED) conan_cmake_setup_conanfile(${ARGV}) if(CONAN_CMAKE_MULTI) foreach(CMAKE_BUILD_TYPE ${ARGUMENTS_CONFIGURATION_TYPES}) set(ENV{CONAN_IMPORT_PATH} ${CMAKE_BUILD_TYPE}) conan_cmake_settings(settings ${ARGV}) old_conan_cmake_install(SETTINGS ${settings} ${ARGV}) endforeach() set(CMAKE_BUILD_TYPE) else() conan_cmake_settings(settings ${ARGV}) old_conan_cmake_install(SETTINGS ${settings} ${ARGV}) endif() endif() if (NOT ARGUMENTS_NO_LOAD) conan_load_buildinfo() endif() if(ARGUMENTS_BASIC_SETUP) foreach(_option CMAKE_TARGETS KEEP_RPATHS NO_OUTPUT_DIRS SKIP_STD) if(ARGUMENTS_${_option}) if(${_option} STREQUAL "CMAKE_TARGETS") list(APPEND _setup_options "TARGETS") else() list(APPEND _setup_options ${_option}) endif() endif() endforeach() conan_basic_setup(${_setup_options}) endif() endmacro() macro(conan_check) # Checks conan availability in PATH # Arguments REQUIRED, DETECT_QUIET and VERSION are optional # Example usage: # conan_check(VERSION 1.0.0 REQUIRED) set(options REQUIRED DETECT_QUIET) set(oneValueArgs VERSION) cmake_parse_arguments(CONAN "${options}" "${oneValueArgs}" "" ${ARGN}) if(NOT CONAN_DETECT_QUIET) message(STATUS "Conan: checking conan executable") endif() find_program(CONAN_CMD conan) if(NOT CONAN_CMD AND CONAN_REQUIRED) message(FATAL_ERROR "Conan executable not found! Please install conan.") endif() if(NOT CONAN_DETECT_QUIET) message(STATUS "Conan: Found program ${CONAN_CMD}") endif() execute_process(COMMAND ${CONAN_CMD} --version RESULT_VARIABLE return_code OUTPUT_VARIABLE CONAN_VERSION_OUTPUT ERROR_VARIABLE CONAN_VERSION_OUTPUT) if(NOT "${return_code}" STREQUAL "0") message(FATAL_ERROR "Conan --version failed='${return_code}'") endif() if(NOT CONAN_DETECT_QUIET) message(STATUS "Conan: Version found ${CONAN_VERSION_OUTPUT}") endif() if(DEFINED CONAN_VERSION) string(REGEX MATCH ".*Conan version ([0-9]+\\.[0-9]+\\.[0-9]+)" FOO "${CONAN_VERSION_OUTPUT}") if(${CMAKE_MATCH_1} VERSION_LESS ${CONAN_VERSION}) message(FATAL_ERROR "Conan outdated. Installed: ${CMAKE_MATCH_1}, \ required: ${CONAN_VERSION}. Consider updating via 'pip \ install conan==${CONAN_VERSION}'.") endif() endif() endmacro() function(conan_add_remote) # Adds a remote # Arguments URL and NAME are required, INDEX, COMMAND and VERIFY_SSL are optional # Example usage: # conan_add_remote(NAME bincrafters INDEX 1 # URL https://api.bintray.com/conan/bincrafters/public-conan # VERIFY_SSL True) set(oneValueArgs URL NAME INDEX COMMAND VERIFY_SSL) cmake_parse_arguments(CONAN "" "${oneValueArgs}" "" ${ARGN}) if(DEFINED CONAN_INDEX) set(CONAN_INDEX_ARG "-i ${CONAN_INDEX}") endif() if(DEFINED CONAN_COMMAND) set(CONAN_CMD ${CONAN_COMMAND}) else() conan_check(REQUIRED) endif() set(CONAN_VERIFY_SSL_ARG "True") if(DEFINED CONAN_VERIFY_SSL) set(CONAN_VERIFY_SSL_ARG ${CONAN_VERIFY_SSL}) endif() message(STATUS "Conan: Adding ${CONAN_NAME} remote repository (${CONAN_URL}) verify ssl (${CONAN_VERIFY_SSL_ARG})") execute_process(COMMAND ${CONAN_CMD} remote add ${CONAN_NAME} ${CONAN_INDEX_ARG} -f ${CONAN_URL} ${CONAN_VERIFY_SSL_ARG} RESULT_VARIABLE return_code) if(NOT "${return_code}" STREQUAL "0") message(FATAL_ERROR "Conan remote failed='${return_code}'") endif() endfunction() macro(conan_config_install) # install a full configuration from a local or remote zip file # Argument ITEM is required, arguments TYPE, SOURCE, TARGET and VERIFY_SSL are optional # Example usage: # conan_config_install(ITEM https://github.com/conan-io/cmake-conan.git # TYPE git SOURCE source-folder TARGET target-folder VERIFY_SSL false) set(oneValueArgs ITEM TYPE SOURCE TARGET VERIFY_SSL) set(multiValueArgs ARGS) cmake_parse_arguments(CONAN "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) find_program(CONAN_CMD conan) if(NOT CONAN_CMD AND CONAN_REQUIRED) message(FATAL_ERROR "Conan executable not found!") endif() if(DEFINED CONAN_VERIFY_SSL) set(CONAN_VERIFY_SSL_ARG "--verify-ssl=${CONAN_VERIFY_SSL}") endif() if(DEFINED CONAN_TYPE) set(CONAN_TYPE_ARG "--type=${CONAN_TYPE}") endif() if(DEFINED CONAN_ARGS) set(CONAN_ARGS_ARGS "--args=\"${CONAN_ARGS}\"") endif() if(DEFINED CONAN_SOURCE) set(CONAN_SOURCE_ARGS "--source-folder=${CONAN_SOURCE}") endif() if(DEFINED CONAN_TARGET) set(CONAN_TARGET_ARGS "--target-folder=${CONAN_TARGET}") endif() set (CONAN_CONFIG_INSTALL_ARGS ${CONAN_VERIFY_SSL_ARG} ${CONAN_TYPE_ARG} ${CONAN_ARGS_ARGS} ${CONAN_SOURCE_ARGS} ${CONAN_TARGET_ARGS}) message(STATUS "Conan: Installing config from ${CONAN_ITEM}") execute_process(COMMAND ${CONAN_CMD} config install ${CONAN_ITEM} ${CONAN_CONFIG_INSTALL_ARGS} RESULT_VARIABLE return_code) if(NOT "${return_code}" STREQUAL "0") message(FATAL_ERROR "Conan config failed='${return_code}'") endif() endmacro()cmake-utils/utils.cmake000066400000000000000000000135761445614261000154550ustar00rootroot00000000000000include(CheckCXXCompilerFlag) ################################################### # Activate C++14 # # Uses: target_activate_cpp14(buildtarget) ################################################### function(target_activate_cpp14 TARGET) if(MSVC) # Required by range-v3, see its README.md set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD 17) else() set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD 14) endif() set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD_REQUIRED ON) # 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) message(STATUS Building ${TARGET} with -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) ################################################## function(target_add_boost TARGET) target_link_libraries(${TARGET} PUBLIC CryfsDependencies_boost) target_compile_definitions(${TARGET} PUBLIC BOOST_THREAD_VERSION=4) 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) 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() conanfile.py000066400000000000000000000031251445614261000133720ustar00rootroot00000000000000from conans import ConanFile, CMake class CryFSConan(ConanFile): settings = "os", "compiler", "build_type", "arch" requires = [ "range-v3/0.11.0", "spdlog/1.8.5", "boost/1.75.0", ] generators = "cmake" default_options = { "boost:system_no_deprecated": True, "boost:asio_no_deprecated": True, "boost:filesystem_no_deprecated": True, "boost:without_atomic": False, # needed by boost thread "boost:without_chrono": False, # needed by CryFS "boost:without_container": False, # needed by boost thread "boost:without_context": True, "boost:without_contract": True, "boost:without_coroutine": True, "boost:without_date_time": False, # needed by boost thread "boost:without_exception": False, # needed by boost thread "boost:without_fiber": True, "boost:without_filesystem": False, # needed by CryFS "boost:without_graph": True, "boost:without_graph_parallel": True, "boost:without_iostreams": True, "boost:without_json": True, "boost:without_locale": True, "boost:without_log": True, "boost:without_math": True, "boost:without_mpi": True, "boost:without_nowide": True, "boost:without_program_options": False, # needed by CryFS "boost:without_python": True, "boost:without_random": True, "boost:without_regex": True, "boost:without_serialization": False, # needed by boost date_time "boost:without_stacktrace": True, "boost:without_system": False, # needed by CryFS "boost:without_test": True, "boost:without_thread": False, # needed by CryFS "boost:without_timer": True, "boost:without_type_erasure": True, "boost:without_wave": True, } cpack/000077500000000000000000000000001445614261000121425ustar00rootroot00000000000000cpack/CMakeLists.txt000066400000000000000000000075731445614261000147160ustar00rootroot00000000000000# 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/000077500000000000000000000000001445614261000137175ustar00rootroot00000000000000cpack/debfiles/postinst000077500000000000000000000113161445614261000155320ustar00rootroot00000000000000#!/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/postrm000077500000000000000000000015461445614261000151770ustar00rootroot00000000000000#!/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/000077500000000000000000000000001445614261000127515ustar00rootroot00000000000000cpack/wix/change_path_env.xml000066400000000000000000000003271445614261000166060ustar00rootroot00000000000000 doc/000077500000000000000000000000001445614261000116265ustar00rootroot00000000000000doc/CMakeLists.txt000066400000000000000000000010411445614261000143620ustar00rootroot00000000000000project (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/000077500000000000000000000000001445614261000124015ustar00rootroot00000000000000doc/man/cryfs.1000066400000000000000000000172041445614261000136150ustar00rootroot00000000000000.\" 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 .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 cryfs-unmount .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\-\-create-missing-basedir\fI . Creates the base directory even if there is no directory currently there, skipping the normal confirmation message to create it later. . . .TP \fB\-\-create-missing-mountpoint\fI . Creates the mountpoint even if there is no directory currently there, skipping the normal confirmation message to create it later. . . .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 FUSE Options . .TP \fB\-o\fR \fIoption\fR, \fB\-\-fuse\-option\fR \fIoption\fR . Pass through options to the FUSE filesystem driver. .TP For example: .TP \fB\-o\fR \fIallow_other\fR This option overrides the security measure restricting file access to the filesystem owner, so that all users (including root) can access the files. .TP \fB\-o\fR \fIallow_root\fR This option is similar to allow_other but file access is limited to the filesystem owner and root. This option and allow_other are mutually exclusive. . . . .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.sh000077500000000000000000000020371445614261000142570ustar00rootroot00000000000000#!/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 CXX=clang++-11 CC=clang-11 SCRIPT=run-clang-tidy-11.py 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_CXX_COMPILER=${CXX} -DCMAKE_C_COMPILER=${CC} -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 ${SCRIPT} -j${NUMCORES} -quiet -header-filter "$(realpath ${0%/*})/(src|test)/.*" $@ run-iwyu.sh000077500000000000000000000020451445614261000132200ustar00rootroot00000000000000#!/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/000077500000000000000000000000001445614261000116505ustar00rootroot00000000000000src/CMakeLists.txt000066400000000000000000000005151445614261000144110ustar00rootroot00000000000000include_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/000077500000000000000000000000001445614261000136435ustar00rootroot00000000000000src/blobstore/CMakeLists.txt000066400000000000000000000023741445614261000164110ustar00rootroot00000000000000project (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}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) src/blobstore/implementations/000077500000000000000000000000001445614261000170535ustar00rootroot00000000000000src/blobstore/implementations/onblocks/000077500000000000000000000000001445614261000206655ustar00rootroot00000000000000src/blobstore/implementations/onblocks/BlobOnBlocks.cpp000066400000000000000000000031151445614261000237020ustar00rootroot00000000000000#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.h000066400000000000000000000033101445614261000233440ustar00rootroot00000000000000#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.cpp000066400000000000000000000045471445614261000247310ustar00rootroot00000000000000#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.h000066400000000000000000000027501445614261000243700ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000235215ustar00rootroot00000000000000src/blobstore/implementations/onblocks/datanodestore/DataInnerNode.cpp000066400000000000000000000064411445614261000267050ustar00rootroot00000000000000#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.h000066400000000000000000000025471445614261000263550ustar00rootroot00000000000000#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.h000066400000000000000000000015331445614261000304740ustar00rootroot00000000000000#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.cpp000066400000000000000000000054601445614261000265010ustar00rootroot00000000000000#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.h000066400000000000000000000022701445614261000261420ustar00rootroot00000000000000#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.cpp000066400000000000000000000022011445614261000256770ustar00rootroot00000000000000#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.h000066400000000000000000000017711445614261000253570ustar00rootroot00000000000000#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.cpp000066400000000000000000000116711445614261000267270ustar00rootroot00000000000000#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)); } // NOLINTNEXTLINE(misc-no-recursion) 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.h000066400000000000000000000041721445614261000263720ustar00rootroot00000000000000#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.h000066400000000000000000000137151445614261000262130ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000235335ustar00rootroot00000000000000src/blobstore/implementations/onblocks/datatreestore/DataTree.cpp000066400000000000000000000353201445614261000257330ustar00rootroot00000000000000#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(); } // NOLINTNEXTLINE(misc-no-recursion) 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.h000066400000000000000000000071641445614261000254050ustar00rootroot00000000000000#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.cpp000066400000000000000000000023101445614261000267410ustar00rootroot00000000000000#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.h000066400000000000000000000027431445614261000264200ustar00rootroot00000000000000#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.cpp000066400000000000000000000026061445614261000262260ustar00rootroot00000000000000#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.h000066400000000000000000000025341445614261000256730ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000244745ustar00rootroot00000000000000src/blobstore/implementations/onblocks/datatreestore/impl/CachedValue.cpp000066400000000000000000000000321445614261000273370ustar00rootroot00000000000000#include "CachedValue.h" src/blobstore/implementations/onblocks/datatreestore/impl/CachedValue.h000066400000000000000000000020021445614261000270030ustar00rootroot00000000000000#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.cpp000066400000000000000000000440721445614261000277540ustar00rootroot00000000000000#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); } // NOLINTNEXTLINE(misc-no-recursion) 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); } // NOLINTNEXTLINE(misc-no-recursion) 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); } } // NOLINTNEXTLINE(misc-no-recursion) 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); } } // NOLINTNEXTLINE(misc-no-recursion) 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)); } } // NOLINTNEXTLINE(misc-no-recursion) 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.h000066400000000000000000000104651445614261000274200ustar00rootroot00000000000000#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.cpp000066400000000000000000000055201445614261000273530ustar00rootroot00000000000000#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.h000066400000000000000000000023011445614261000270120ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000264325ustar00rootroot00000000000000src/blobstore/implementations/onblocks/parallelaccessdatatreestore/DataTreeRef.cpp000066400000000000000000000000311445614261000312560ustar00rootroot00000000000000#include "DataTreeRef.h" src/blobstore/implementations/onblocks/parallelaccessdatatreestore/DataTreeRef.h000066400000000000000000000034331445614261000307340ustar00rootroot00000000000000#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.cpp000066400000000000000000000031601445614261000344430ustar00rootroot00000000000000#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.h000066400000000000000000000034661445614261000341210ustar00rootroot00000000000000#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.cpp000066400000000000000000000000601445614261000356610ustar00rootroot00000000000000src/blobstore/implementations/onblocks/parallelaccessdatatreestore#include "ParallelAccessDataTreeStoreAdapter.h" ParallelAccessDataTreeStoreAdapter.h000066400000000000000000000025701445614261000353360ustar00rootroot00000000000000src/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/000077500000000000000000000000001445614261000220255ustar00rootroot00000000000000src/blobstore/implementations/onblocks/utils/Math.cpp000066400000000000000000000000221445614261000234140ustar00rootroot00000000000000#include "Math.h" src/blobstore/implementations/onblocks/utils/Math.h000066400000000000000000000017471445614261000231000ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000156035ustar00rootroot00000000000000src/blobstore/interface/Blob.h000066400000000000000000000015271445614261000166370ustar00rootroot00000000000000#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.h000066400000000000000000000020151445614261000176450ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000140175ustar00rootroot00000000000000src/blockstore/CMakeLists.txt000066400000000000000000000031461445614261000165630ustar00rootroot00000000000000project (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/readonly/ReadOnlyBlockStore2.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}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) src/blockstore/implementations/000077500000000000000000000000001445614261000172275ustar00rootroot00000000000000src/blockstore/implementations/caching/000077500000000000000000000000001445614261000206235ustar00rootroot00000000000000src/blockstore/implementations/caching/CachingBlockStore2.cpp000066400000000000000000000124021445614261000247340ustar00rootroot00000000000000#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.h000066400000000000000000000044021445614261000244020ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000216665ustar00rootroot00000000000000src/blockstore/implementations/caching/cache/Cache.cpp000066400000000000000000000000231445614261000233700ustar00rootroot00000000000000#include "Cache.h" src/blockstore/implementations/caching/cache/Cache.h000066400000000000000000000174451445614261000230550ustar00rootroot00000000000000#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.cpp000066400000000000000000000000301445614261000244100ustar00rootroot00000000000000#include "CacheEntry.h" src/blockstore/implementations/caching/cache/CacheEntry.h000066400000000000000000000020321445614261000240610ustar00rootroot00000000000000#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.cpp000066400000000000000000000014731445614261000247600ustar00rootroot00000000000000#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.h000066400000000000000000000014031445614261000244160ustar00rootroot00000000000000#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.cpp000066400000000000000000000000261445614261000241120ustar00rootroot00000000000000#include "QueueMap.h" src/blockstore/implementations/caching/cache/QueueMap.h000066400000000000000000000065431445614261000235710ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_QUEUEMAP_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_CACHING_CACHE_QUEUEMAP_H_ #include #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/000077500000000000000000000000001445614261000215605ustar00rootroot00000000000000src/blockstore/implementations/compressing/CompressedBlock.cpp000066400000000000000000000000351445614261000253410ustar00rootroot00000000000000#include "CompressedBlock.h" src/blockstore/implementations/compressing/CompressedBlock.h000066400000000000000000000112621445614261000250120ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_COMPRESSING_COMPRESSEDBLOCK_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_COMPRESSING_COMPRESSEDBLOCK_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.cpp000066400000000000000000000000431445614261000265420ustar00rootroot00000000000000#include "CompressingBlockStore.h" src/blockstore/implementations/compressing/CompressingBlockStore.h000066400000000000000000000072201445614261000262130ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000241375ustar00rootroot00000000000000src/blockstore/implementations/compressing/compressors/Gzip.cpp000066400000000000000000000021421445614261000255530ustar00rootroot00000000000000#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.h000066400000000000000000000007151445614261000252240ustar00rootroot00000000000000#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.cpp000066400000000000000000000144261445614261000302270ustar00rootroot00000000000000#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 = 0; 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 = 0; stream->read(reinterpret_cast(&size), sizeof(uint16_t)); ASSERT(stream->good(), "Premature end of stream"); uint8_t value = 0; 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.h000066400000000000000000000025631445614261000276730ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000212245ustar00rootroot00000000000000src/blockstore/implementations/encrypted/EncryptedBlockStore2.cpp000066400000000000000000000000421445614261000257330ustar00rootroot00000000000000#include "EncryptedBlockStore2.h" src/blockstore/implementations/encrypted/EncryptedBlockStore2.h000066400000000000000000000165701445614261000254150ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_ENCRYPTEDBLOCKSTORE2_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_ENCRYPTEDBLOCKSTORE2_H_ #include "../../interface/BlockStore2.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/000077500000000000000000000000001445614261000210665ustar00rootroot00000000000000src/blockstore/implementations/inmemory/InMemoryBlockStore2.cpp000066400000000000000000000050661445614261000254120ustar00rootroot00000000000000#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.h000066400000000000000000000022631445614261000250530ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000212455ustar00rootroot00000000000000src/blockstore/implementations/integrity/ClientIdAndBlockId.cpp000066400000000000000000000000401445614261000253110ustar00rootroot00000000000000#include "ClientIdAndBlockId.h" src/blockstore/implementations/integrity/ClientIdAndBlockId.h000066400000000000000000000022471445614261000247710ustar00rootroot00000000000000#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.cpp000066400000000000000000000233611445614261000260060ustar00rootroot00000000000000#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 { // The obvious reason for this is to prevent adversaries from renaming blocks, but storing the block id in this way also // makes the authenticated cipher more robust, see https://libsodium.gitbook.io/doc/secret-key_cryptography/aead#robustness 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.h000066400000000000000000000104751445614261000254550ustar00rootroot00000000000000#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.cpp000066400000000000000000000232011445614261000255470ustar00rootroot00000000000000#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.h000066400000000000000000000064001445614261000252160ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000220025ustar00rootroot00000000000000src/blockstore/implementations/low2highlevel/LowToHighLevelBlock.cpp000066400000000000000000000047651445614261000263310ustar00rootroot00000000000000#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.h000066400000000000000000000032521445614261000257640ustar00rootroot00000000000000#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.cpp000066400000000000000000000043051445614261000273340ustar00rootroot00000000000000#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.h000066400000000000000000000030641445614261000270020ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000201605ustar00rootroot00000000000000src/blockstore/implementations/mock/MockBlock.cpp000066400000000000000000000007561445614261000225400ustar00rootroot00000000000000#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.h000066400000000000000000000025221445614261000221760ustar00rootroot00000000000000#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.cpp000066400000000000000000000000341445614261000235420ustar00rootroot00000000000000#include "MockBlockStore.h" src/blockstore/implementations/mock/MockBlockStore.h000066400000000000000000000136771445614261000232300ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000205165ustar00rootroot00000000000000src/blockstore/implementations/ondisk/OnDiskBlockStore2.cpp000066400000000000000000000123711445614261000244670ustar00rootroot00000000000000#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.h000066400000000000000000000030671445614261000241360ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000222055ustar00rootroot00000000000000src/blockstore/implementations/parallelaccess/BlockRef.cpp000066400000000000000000000000261445614261000243760ustar00rootroot00000000000000#include "BlockRef.h" src/blockstore/implementations/parallelaccess/BlockRef.h000066400000000000000000000022471445614261000240520ustar00rootroot00000000000000#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.cpp000066400000000000000000000056221445614261000275640ustar00rootroot00000000000000#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.h000066400000000000000000000030521445614261000272240ustar00rootroot00000000000000#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.cpp000066400000000000000000000000551445614261000310600ustar00rootroot00000000000000#include "ParallelAccessBlockStoreAdapter.h" src/blockstore/implementations/parallelaccess/ParallelAccessBlockStoreAdapter.h000066400000000000000000000021351445614261000305260ustar00rootroot00000000000000#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/readonly/000077500000000000000000000000001445614261000210445ustar00rootroot00000000000000src/blockstore/implementations/readonly/ReadOnlyBlockStore2.cpp000066400000000000000000000000411445614261000253320ustar00rootroot00000000000000#include "ReadOnlyBlockStore2.h" src/blockstore/implementations/readonly/ReadOnlyBlockStore2.h000066400000000000000000000055341445614261000250130ustar00rootroot00000000000000#pragma once #ifndef MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_READONLYBLOCKSTORE2_H_ #define MESSMER_BLOCKSTORE_IMPLEMENTATIONS_ENCRYPTED_READONLYBLOCKSTORE2_H_ #include "../../interface/BlockStore2.h" #include namespace blockstore { namespace readonly { // TODO Test /** * Wraps another block store and makes it read-only. * All read operations are passed through to the underlying * blockstore, while all write operations just throw * an exception. This can be used to protect a blockstore * if we're in a mode that's supposed to be read-only, * e.g. recovery after data corruption. */ class ReadOnlyBlockStore2 final: public BlockStore2 { public: ReadOnlyBlockStore2(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; private: cpputils::unique_ref _baseBlockStore; DISALLOW_COPY_AND_ASSIGN(ReadOnlyBlockStore2); }; inline ReadOnlyBlockStore2::ReadOnlyBlockStore2(cpputils::unique_ref baseBlockStore) : _baseBlockStore(std::move(baseBlockStore)) { } inline bool ReadOnlyBlockStore2::tryCreate(const BlockId &/*blockId*/, const cpputils::Data &/*data*/) { throw std::logic_error("Tried to call tryCreate on a ReadOnlyBlockStore. Writes to the block store aren't allowed."); } inline bool ReadOnlyBlockStore2::remove(const BlockId &/*blockId*/) { throw std::logic_error("Tried to call remove on a ReadOnlyBlockStore. Writes to the block store aren't allowed."); } inline boost::optional ReadOnlyBlockStore2::load(const BlockId &blockId) const { return _baseBlockStore->load(blockId); } inline void ReadOnlyBlockStore2::store(const BlockId &/*blockId*/, const cpputils::Data &/*data*/) { throw std::logic_error("Tried to call store on a ReadOnlyBlockStore. Writes to the block store aren't allowed."); } inline uint64_t ReadOnlyBlockStore2::numBlocks() const { return _baseBlockStore->numBlocks(); } inline uint64_t ReadOnlyBlockStore2::estimateNumFreeBytes() const { return _baseBlockStore->estimateNumFreeBytes(); } inline uint64_t ReadOnlyBlockStore2::blockSizeFromPhysicalBlockSize(uint64_t blockSize) const { return _baseBlockStore->blockSizeFromPhysicalBlockSize(blockSize); } inline void ReadOnlyBlockStore2::forEachBlock(std::function callback) const { return _baseBlockStore->forEachBlock(std::move(callback)); } } } #endif src/blockstore/implementations/testfake/000077500000000000000000000000001445614261000210355ustar00rootroot00000000000000src/blockstore/implementations/testfake/FakeBlock.cpp000066400000000000000000000024041445614261000233620ustar00rootroot00000000000000#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.h000066400000000000000000000015221445614261000230270ustar00rootroot00000000000000#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.cpp000066400000000000000000000061341445614261000244030ustar00rootroot00000000000000#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.h000066400000000000000000000057761445614261000240630ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000157575ustar00rootroot00000000000000src/blockstore/interface/Block.h000066400000000000000000000017221445614261000171640ustar00rootroot00000000000000#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.h000066400000000000000000000036431445614261000202050ustar00rootroot00000000000000#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.h000066400000000000000000000026371445614261000202710ustar00rootroot00000000000000#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) { while (true) { BlockId blockId = createBlockId(); bool success = tryCreate(blockId, data); if (success) { return blockId; } } } 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/000077500000000000000000000000001445614261000151575ustar00rootroot00000000000000src/blockstore/utils/BlockId.cpp000066400000000000000000000000251445614261000171670ustar00rootroot00000000000000#include "BlockId.h" src/blockstore/utils/BlockId.h000066400000000000000000000005041445614261000166360ustar00rootroot00000000000000#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; } DEFINE_IDWRAPPER(blockstore::BlockId); #endif src/blockstore/utils/BlockStoreUtils.cpp000066400000000000000000000014461445614261000207600ustar00rootroot00000000000000#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.h000066400000000000000000000006441445614261000204240ustar00rootroot00000000000000#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.cpp000066400000000000000000000005471445614261000226210ustar00rootroot00000000000000#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.h000066400000000000000000000006411445614261000222610ustar00rootroot00000000000000#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.cpp000066400000000000000000000000271445614261000175570ustar00rootroot00000000000000#include "IdWrapper.h" src/blockstore/utils/IdWrapper.h000066400000000000000000000106361445614261000172330ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000135705ustar00rootroot00000000000000src/cpp-utils/CMakeLists.txt000066400000000000000000000064661445614261000163440ustar00rootroot00000000000000project (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.cpp 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 CryfsDependencies_spdlog cryptopp CryfsDependencies_range-v3) target_add_boost(${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) if(MSVC) # Required by range-v3, see its README.md target_compile_options(${PROJECT_NAME} PUBLIC /experimental:preprocessor /permissive- /Zc:twoPhase-) endif() src/cpp-utils/assert/000077500000000000000000000000001445614261000150715ustar00rootroot00000000000000src/cpp-utils/assert/AssertFailed.cpp000066400000000000000000000000321445614261000201360ustar00rootroot00000000000000#include "AssertFailed.h" src/cpp-utils/assert/AssertFailed.h000066400000000000000000000007731445614261000176170ustar00rootroot00000000000000#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: explicit 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.cpp000066400000000000000000000001601445614261000170730ustar00rootroot00000000000000#include "assert.h" thread_local int cpputils::_assert::DisableAbortOnFailedAssertionRAII::num_instances_ = 0; src/cpp-utils/assert/assert.h000066400000000000000000000052241445614261000165460ustar00rootroot00000000000000#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 #include "backtrace.h" #include "../logging/logging.h" namespace cpputils { namespace _assert { struct DisableAbortOnFailedAssertionRAII final { explicit DisableAbortOnFailedAssertionRAII() : thread_id_(std::this_thread::get_id()) { ++num_instances_; } ~DisableAbortOnFailedAssertionRAII() { if (thread_id_ != std::this_thread::get_id()) { using namespace logging; LOG(ERR, "DisableAbortOnFailedAssertionRAII instance must be destructed in the same thread that created it"); } --num_instances_; } static int num_instances() { return num_instances_; } private: static thread_local int num_instances_; // initialized to zero in assert.cpp std::thread::id thread_id_; }; 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) { using namespace logging; auto msg = format(expr, message, file, line); 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; auto msg = format(expr, message, file, line); LOG(ERR, msg); if (DisableAbortOnFailedAssertionRAII::num_instances() > 0) { throw AssertFailed(msg); } else { 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.h000066400000000000000000000005631445614261000171650ustar00rootroot00000000000000#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.cpp000066400000000000000000000020741445614261000220040ustar00rootroot00000000000000#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.cpp000066400000000000000000000140521445614261000212700ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000151105ustar00rootroot00000000000000src/cpp-utils/crypto/RandomPadding.cpp000066400000000000000000000025331445614261000203260ustar00rootroot00000000000000#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.h000066400000000000000000000006061445614261000177720ustar00rootroot00000000000000#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/hash/000077500000000000000000000000001445614261000160335ustar00rootroot00000000000000src/cpp-utils/crypto/hash/Hash.cpp000066400000000000000000000012521445614261000174220ustar00rootroot00000000000000#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.h000066400000000000000000000006501445614261000170700ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000156545ustar00rootroot00000000000000src/cpp-utils/crypto/kdf/PasswordBasedKDF.cpp000066400000000000000000000000361445614261000214450ustar00rootroot00000000000000#include "PasswordBasedKDF.h" src/cpp-utils/crypto/kdf/PasswordBasedKDF.h000066400000000000000000000012401445614261000211100ustar00rootroot00000000000000#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.cpp000066400000000000000000000024571445614261000216400ustar00rootroot00000000000000#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.h000066400000000000000000000043461445614261000213040ustar00rootroot00000000000000#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) { if (this == &rhs) { return *this; } _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.cpp000066400000000000000000000034521445614261000176500ustar00rootroot00000000000000#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.h000066400000000000000000000022131445614261000173070ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000171245ustar00rootroot00000000000000src/cpp-utils/crypto/symmetric/AEAD_Cipher.h000066400000000000000000000076061445614261000212720ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_AEADCIPHER_H_ #define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_AEADCIPHER_H_ #include "../../data/FixedSizeData.h" #include "../../data/Data.h" #include "../../random/Random.h" #include "Cipher.h" #include "EncryptionKey.h" namespace cpputils { template class AEADCipher { 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 = IV_SIZE_; static constexpr unsigned int TAG_SIZE = TAG_SIZE_; }; template constexpr unsigned int AEADCipher::KEYSIZE; template constexpr unsigned int AEADCipher::STRING_KEYSIZE; template Data AEADCipher::encrypt(const CryptoPP::byte *plaintext, unsigned int plaintextSize, const EncryptionKey &encKey) { ASSERT(encKey.binaryLength() == AEADCipher::KEYSIZE, "Wrong key size"); FixedSizeData iv = Random::PseudoRandom().getFixedSize(); typename CryptoPPCipher::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 AEADCipher::decrypt(const CryptoPP::byte *ciphertext, unsigned int ciphertextSize, const EncryptionKey &encKey) { ASSERT(encKey.binaryLength() == AEADCipher::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 CryptoPPCipher::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/CFB_Cipher.h000066400000000000000000000060731445614261000211670ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CFBCIPHER_H_ #define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CFBCIPHER_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.h000066400000000000000000000020311445614261000205030ustar00rootroot00000000000000#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.cpp000066400000000000000000000000331445614261000224270ustar00rootroot00000000000000#include "EncryptionKey.h" src/cpp-utils/crypto/symmetric/EncryptionKey.h000066400000000000000000000071661445614261000221120ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_ENCRYPTIONKEY_H_ #define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_ENCRYPTIONKEY_H_ #include #include #include #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.h000066400000000000000000000006131445614261000211750ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_GCMCIPHER_H_ #define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_GCMCIPHER_H_ #include "AEAD_Cipher.h" #include namespace cpputils { template using GCM_Cipher = AEADCipher, KeySize, BlockCipher::BLOCKSIZE, 16>; } #endif src/cpp-utils/crypto/symmetric/ciphers.cpp000066400000000000000000000015771445614261000212770ustar00rootroot00000000000000#include "ciphers.h" #define DEFINE_CIPHER(InstanceName) \ constexpr const char *InstanceName::NAME; \ namespace cpputils { DEFINE_CIPHER(XChaCha20Poly1305); 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); DEFINE_CIPHER(Mars448_GCM); DEFINE_CIPHER(Mars448_CFB); DEFINE_CIPHER(Mars256_GCM); DEFINE_CIPHER(Mars256_CFB); DEFINE_CIPHER(Mars128_GCM); DEFINE_CIPHER(Mars128_CFB); } src/cpp-utils/crypto/symmetric/ciphers.h000066400000000000000000000072521445614261000207400ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CIPHERS_H_ #define MESSMER_CPPUTILS_CRYPTO_SYMMETRIC_CIPHERS_H_ #include #include #include #include #include #include #include "GCM_Cipher.h" #include "CFB_Cipher.h" namespace cpputils { // REMOVE_PARENTHESES_FROM_TYPENAME is needed because the DECLARE_CIPHER macro will get a typename enclosed in parentheses. #define SINGLE_ARG(...) __VA_ARGS__ #define DECLARE_CIPHER(InstanceName, StringName, Impl) \ class InstanceName final: public Impl { \ public: \ BOOST_CONCEPT_ASSERT((CipherConcept)); \ static constexpr const char *NAME = StringName; \ } \ DECLARE_CIPHER(XChaCha20Poly1305, "xchacha20-poly1305", SINGLE_ARG(AEADCipher)); 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", SINGLE_ARG(GCM_Cipher)); DECLARE_CIPHER(AES256_CFB, "aes-256-cfb", SINGLE_ARG(CFB_Cipher)); DECLARE_CIPHER(AES128_GCM, "aes-128-gcm", SINGLE_ARG(GCM_Cipher)); DECLARE_CIPHER(AES128_CFB, "aes-128-cfb", SINGLE_ARG(CFB_Cipher)); 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", SINGLE_ARG(GCM_Cipher)); DECLARE_CIPHER(Twofish256_CFB, "twofish-256-cfb", SINGLE_ARG(CFB_Cipher)); DECLARE_CIPHER(Twofish128_GCM, "twofish-128-gcm", SINGLE_ARG(GCM_Cipher)); DECLARE_CIPHER(Twofish128_CFB, "twofish-128-cfb", SINGLE_ARG(CFB_Cipher)); 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", SINGLE_ARG(GCM_Cipher)); DECLARE_CIPHER(Serpent256_CFB, "serpent-256-cfb", SINGLE_ARG(CFB_Cipher)); DECLARE_CIPHER(Serpent128_GCM, "serpent-128-gcm", SINGLE_ARG(GCM_Cipher)); DECLARE_CIPHER(Serpent128_CFB, "serpent-128-cfb", SINGLE_ARG(CFB_Cipher)); 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", SINGLE_ARG(GCM_Cipher)); DECLARE_CIPHER(Cast256_CFB, "cast-256-cfb", SINGLE_ARG(CFB_Cipher)); 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", SINGLE_ARG(GCM_Cipher)); DECLARE_CIPHER(Mars448_CFB, "mars-448-cfb", SINGLE_ARG(CFB_Cipher)); DECLARE_CIPHER(Mars256_GCM, "mars-256-gcm", SINGLE_ARG(GCM_Cipher)); DECLARE_CIPHER(Mars256_CFB, "mars-256-cfb", SINGLE_ARG(CFB_Cipher)); DECLARE_CIPHER(Mars128_GCM, "mars-128-gcm", SINGLE_ARG(GCM_Cipher)); DECLARE_CIPHER(Mars128_CFB, "mars-128-cfb", SINGLE_ARG(CFB_Cipher)); } #undef DECLARE_CIPHER #undef SINGLE_ARG #endif src/cpp-utils/crypto/symmetric/testutils/000077500000000000000000000000001445614261000211645ustar00rootroot00000000000000src/cpp-utils/crypto/symmetric/testutils/FakeAuthenticatedCipher.cpp000066400000000000000000000003671445614261000264020ustar00rootroot00000000000000#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.h000066400000000000000000000107031445614261000260420ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_TEST_CRYPTO_SYMMETRIC_TESTUTILS_FAKEAUTHENTICATEDCIPHER_H_ #define MESSMER_CPPUTILS_TEST_CRYPTO_SYMMETRIC_TESTUTILS_FAKEAUTHENTICATEDCIPHER_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/000077500000000000000000000000001445614261000145015ustar00rootroot00000000000000src/cpp-utils/data/Data.cpp000066400000000000000000000033401445614261000160560ustar00rootroot00000000000000#include "Data.h" #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.h000066400000000000000000000121451445614261000155260ustar00rootroot00000000000000#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.cpp000066400000000000000000000016511445614261000174300ustar00rootroot00000000000000#include "DataFixture.h" #include "SerializationHelper.h" namespace cpputils { Data DataFixture::generate(size_t size, unsigned long long int seed) { Data result(size); unsigned long long int val = seed; for(size_t i=0; i(result.dataOffset(i*sizeof(unsigned long long int)), val); } uint64_t alreadyWritten = (size/sizeof(unsigned long long int))*sizeof(unsigned long long int); val *= 6364136223846793005L; val += 1442695040888963407; unsigned 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.h000066400000000000000000000012041445614261000170670ustar00rootroot00000000000000#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, unsigned 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.cpp000066400000000000000000000006161445614261000171020ustar00rootroot00000000000000#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.h000066400000000000000000000007171445614261000165510ustar00rootroot00000000000000#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.cpp000066400000000000000000000000321445614261000176220ustar00rootroot00000000000000#include "Deserializer.h" src/cpp-utils/data/Deserializer.h000066400000000000000000000105241445614261000172760ustar00rootroot00000000000000#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.h000066400000000000000000000074601445614261000173450ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_DATA_FIXEDSIZEDATA_H_ #define MESSMER_CPPUTILS_DATA_FIXEDSIZEDATA_H_ #include #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.cpp000066400000000000000000000000411445614261000211550ustar00rootroot00000000000000#include "SerializationHelper.h" src/cpp-utils/data/SerializationHelper.h000066400000000000000000000051021445614261000206250ustar00rootroot00000000000000#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.cpp000066400000000000000000000000301445614261000173070ustar00rootroot00000000000000#include "Serializer.h" src/cpp-utils/data/Serializer.h000066400000000000000000000107001445614261000167610ustar00rootroot00000000000000#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/either.h000066400000000000000000000251101445614261000152200ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_EITHER_H #define MESSMER_CPPUTILS_EITHER_H #include #include #include "assert/assert.h" namespace cpputils { template class either final { public: template::value && !std::is_constructible::value>* = nullptr> either(Head&& construct_left_head_arg, Tail&&... construct_left_tail_args) noexcept(noexcept(std::declval>()._construct_left(std::forward(construct_left_head_arg), std::forward(construct_left_tail_args)...))) : _side(Side::left) { _construct_left(std::forward(construct_left_head_arg), std::forward(construct_left_tail_args)...); } template::value && std::is_constructible::value>* = nullptr> either(Head&& construct_right_head_arg, Tail&&... construct_right_tail_args) noexcept(noexcept(std::declval>()._construct_right(std::forward(construct_right_head_arg), std::forward(construct_right_tail_args)...))) : _side(Side::right) { _construct_right(std::forward(construct_right_head_arg), std::forward(construct_right_tail_args)...); } //TODO Try allowing copy-construction when Left/Right types are std::is_convertible either(const either &rhs) noexcept(noexcept(std::declval>()._construct_left(rhs._left)) && noexcept(std::declval>()._construct_right(rhs._right))) : _side(rhs._side) { if(_side == Side::left) { _construct_left(rhs._left); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { _construct_right(rhs._right); // NOLINT(cppcoreguidelines-pro-type-union-access) } } either(either &&rhs) noexcept(noexcept(std::declval>()._construct_left(std::move(rhs._left))) && noexcept(std::declval>()._construct_right(std::move(rhs._right)))) : _side(rhs._side) { if(_side == Side::left) { _construct_left(std::move(rhs._left)); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { _construct_right(std::move(rhs._right)); // NOLINT(cppcoreguidelines-pro-type-union-access) } } ~either() { _destruct(); } //TODO Try allowing copy-assignment when Left/Right types are std::is_convertible // NOLINTNEXTLINE(cert-oop54-cpp) either &operator=(const either &rhs) noexcept(noexcept(std::declval>()._construct_left(rhs._left)) && noexcept(std::declval>()._construct_right(rhs._right))) { if (this == &rhs) { return *this; } _destruct(); _side = rhs._side; if (_side == Side::left) { _construct_left(rhs._left); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { _construct_right(rhs._right); // NOLINT(cppcoreguidelines-pro-type-union-access) } return *this; } either &operator=(either &&rhs) noexcept(noexcept(std::declval>()._construct_left(std::move(rhs._left))) && noexcept(std::declval>()._construct_right(std::move(rhs._right)))) { if (this == &rhs) { return *this; } _destruct(); _side = rhs._side; if (_side == Side::left) { _construct_left(std::move(rhs._left)); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { _construct_right(std::move(rhs._right)); // NOLINT(cppcoreguidelines-pro-type-union-access) } return *this; } //TODO fold, map_left, map_right, left_or_else(val), right_or_else(val), left_or_else(func), right_or_else(func) bool is_left() const noexcept { return _side == Side::left; } bool is_right() const noexcept { return _side == Side::right; } const Left &left() const& { if (!is_left()) { throw std::logic_error("Tried to get left side of an either which is right."); } return _left; // NOLINT(cppcoreguidelines-pro-type-union-access) } Left &left() & { return const_cast(const_cast*>(this)->left()); } Left &&left() && { return std::move(left()); } const Right &right() const& { if (!is_right()) { throw std::logic_error("Tried to get right side of an either which is left."); } return _right; // NOLINT(cppcoreguidelines-pro-type-union-access) } Right &right() & { return const_cast(const_cast*>(this)->right()); } Right &&right() && { return std::move(right()); } boost::optional left_opt() const& noexcept { if (_side == Side::left) { return _left; // NOLINT(cppcoreguidelines-pro-type-union-access) } else { return boost::none; } } boost::optional left_opt() & noexcept { if (_side == Side::left) { return _left; // NOLINT(cppcoreguidelines-pro-type-union-access) } else { return boost::none; } } // warning: opposed to the other left_opt variants, this one already moves the content and returns by value. boost::optional left_opt() && noexcept(noexcept(boost::optional(std::move(std::declval>()._left)))) { if (_side == Side::left) { return std::move(_left); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { return boost::none; } } boost::optional right_opt() const& noexcept { if (_side == Side::right) { return _right; // NOLINT(cppcoreguidelines-pro-type-union-access) } else { return boost::none; } } boost::optional right_opt() & noexcept { if (_side == Side::right) { return _right; // NOLINT(cppcoreguidelines-pro-type-union-access) } else { return boost::none; } } // warning: opposed to the other left_opt variants, this one already moves the content and returns by value. boost::optional right_opt() && noexcept(noexcept(boost::optional(std::move(std::declval>()._right)))) { if (_side == Side::right) { return std::move(_right); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { return boost::none; } } private: union { Left _left; Right _right; }; enum class Side : uint8_t {left, right} _side; explicit either(Side side) noexcept : _side(side) {} template void _construct_left(Args&&... args) noexcept(noexcept(new Left(std::forward(args)...))) { new(&_left)Left(std::forward(args)...); // NOLINT(cppcoreguidelines-pro-type-union-access) } template void _construct_right(Args&&... args) noexcept(noexcept(new Right(std::forward(args)...))) { new(&_right)Right(std::forward(args)...); // NOLINT(cppcoreguidelines-pro-type-union-access) } void _destruct() noexcept { if (_side == Side::left) { _left.~Left(); // NOLINT(cppcoreguidelines-pro-type-union-access) } else { _right.~Right(); // NOLINT(cppcoreguidelines-pro-type-union-access) } } template friend either make_left(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))) */; template friend either make_right(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))) */; }; template inline bool operator==(const either &lhs, const either &rhs) noexcept(noexcept(std::declval() == std::declval()) && noexcept(std::declval() == std::declval())) { if (lhs.is_left() != rhs.is_left()) { return false; } if (lhs.is_left()) { return lhs.left() == rhs.left(); } else { return lhs.right() == rhs.right(); } } template inline bool operator!=(const either &lhs, const either &rhs) noexcept(noexcept(operator==(lhs, rhs))) { return !operator==(lhs, rhs); } template inline std::ostream &operator<<(std::ostream &stream, const either &value) { if (value.is_left()) { stream << "Left(" << value.left() << ")"; } else { stream << "Right(" << value.right() << ")"; } return stream; } template inline either make_left(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_left(std::forward(args)...))) */ { either result(either::Side::left); result._construct_left(std::forward(args)...); return result; } template inline either make_right(Args&&... args) /* TODO noexcept(noexcept(std::declval>()._construct_right(std::forward(args)...))) */ { either result(either::Side::right); result._construct_right(std::forward(args)...); return result; } } #endif src/cpp-utils/io/000077500000000000000000000000001445614261000141775ustar00rootroot00000000000000src/cpp-utils/io/Console.cpp000066400000000000000000000000261445614261000163030ustar00rootroot00000000000000#include "Console.h" src/cpp-utils/io/Console.h000066400000000000000000000013231445614261000157510ustar00rootroot00000000000000#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.cpp000066400000000000000000000034201445614261000215620ustar00rootroot00000000000000#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.h000066400000000000000000000014541445614261000212340ustar00rootroot00000000000000#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.cpp000066400000000000000000000067531445614261000177240ustar00rootroot00000000000000#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.h000066400000000000000000000023171445614261000173610ustar00rootroot00000000000000#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.cpp000066400000000000000000000014111445614261000212130ustar00rootroot00000000000000#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.h000066400000000000000000000014401445614261000206620ustar00rootroot00000000000000#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.cpp000066400000000000000000000020031445614261000171270ustar00rootroot00000000000000#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.h000066400000000000000000000011721445614261000166020ustar00rootroot00000000000000#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.cpp000066400000000000000000000000301445614261000170450ustar00rootroot00000000000000#include "pipestream.h" src/cpp-utils/io/pipestream.h000066400000000000000000000135001445614261000165200ustar00rootroot00000000000000// 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/000077500000000000000000000000001445614261000145205ustar00rootroot00000000000000src/cpp-utils/lock/CombinedLock.h000066400000000000000000000015421445614261000172240ustar00rootroot00000000000000#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); }; } #endif src/cpp-utils/lock/ConditionBarrier.h000066400000000000000000000021071445614261000201260ustar00rootroot00000000000000#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.cpp000066400000000000000000000000261445614261000167440ustar00rootroot00000000000000#include "LockPool.h" src/cpp-utils/lock/LockPool.h000066400000000000000000000065731445614261000164260ustar00rootroot00000000000000#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.h000066400000000000000000000023161445614261000174400ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000152165ustar00rootroot00000000000000src/cpp-utils/logging/Logger.h000066400000000000000000000024171445614261000166120ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_LOGGING_LOGGER_H #define MESSMER_CPPUTILS_LOGGING_LOGGER_H #include #include "../macros.h" #include 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.h000066400000000000000000000045251445614261000170230ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_LOGGING_LOGGING_H #define MESSMER_CPPUTILS_LOGGING_LOGGING_H #include "Logger.h" #include #include #include #if defined(_MSC_VER) #include #else #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 flush() { logger()->flush(); } 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_mt(name, name, LOG_PID); #endif } } } #endif src/cpp-utils/macros.h000066400000000000000000000013771445614261000152350ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000152615ustar00rootroot00000000000000src/cpp-utils/network/CurlHttpClient.cpp000066400000000000000000000043451445614261000206770ustar00rootroot00000000000000// 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.h000066400000000000000000000022101445614261000203310ustar00rootroot00000000000000#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.cpp000066400000000000000000000011201445614261000206240ustar00rootroot00000000000000#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.h000066400000000000000000000011251445614261000202760ustar00rootroot00000000000000#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.cpp000066400000000000000000000000301445614261000200340ustar00rootroot00000000000000#include "HttpClient.h" src/cpp-utils/network/HttpClient.h000066400000000000000000000005501445614261000175100ustar00rootroot00000000000000#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.cpp000066400000000000000000000214511445614261000205240ustar00rootroot00000000000000#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.h000066400000000000000000000011071445614261000201650ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000152505ustar00rootroot00000000000000src/cpp-utils/pointer/cast.h000066400000000000000000000012071445614261000163530ustar00rootroot00000000000000#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.h000066400000000000000000000024411445614261000215610ustar00rootroot00000000000000#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.h000066400000000000000000000023711445614261000222340ustar00rootroot00000000000000#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.h000066400000000000000000000145521445614261000175720ustar00rootroot00000000000000#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.h000066400000000000000000000013431445614261000263600ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000152465ustar00rootroot00000000000000src/cpp-utils/process/SignalCatcher.cpp000066400000000000000000000103601445614261000204610ustar00rootroot00000000000000#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.h000066400000000000000000000017221445614261000201300ustar00rootroot00000000000000#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.cpp000066400000000000000000000000331445614261000204610ustar00rootroot00000000000000#include "SignalHandler.h" src/cpp-utils/process/SignalHandler.h000066400000000000000000000123471445614261000201410ustar00rootroot00000000000000#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.cpp000066400000000000000000000026361445614261000177340ustar00rootroot00000000000000#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.h000066400000000000000000000002351445614261000173720ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_PROCESS_DAEMONIZE_H #define MESSMER_CPPUTILS_PROCESS_DAEMONIZE_H namespace cpputils { void daemonize(); } #endif src/cpp-utils/process/subprocess.cpp000066400000000000000000000103211445614261000201370ustar00rootroot00000000000000#include "subprocess.h" #include #include #include #include #include #include using std::string; using std::vector; namespace bp = boost::process; namespace bf = boost::filesystem; namespace ba = boost::asio; namespace bs = boost::system; #if defined(_MSC_VER) constexpr auto PIPE_CLOSED = ba::error::broken_pipe; #else constexpr auto PIPE_CLOSED = ba::error::eof; #endif namespace cpputils { namespace { bf::path _find_executable(const char *command) { bf::path executable = bp::search_path(command); if (executable == "") { throw std::runtime_error("Tried to run command " + std::string(command) + " but didn't find it in the PATH"); } return executable; } class OutputPipeHandler final { public: explicit OutputPipeHandler(ba::io_context* ctx) : vOut_(128 * 1024) , buffer_(ba::buffer(vOut_)) , pipe_(*ctx) , output_() { } void async_read() { std::function onOutput; onOutput = [&](const bs::error_code & ec, size_t n) { output_.reserve(output_.size() + n); output_.insert(output_.end(), vOut_.begin(), vOut_.begin() + n); if (ec) { if (ec != PIPE_CLOSED) { throw SubprocessError(std::string() + "Error getting output from subprocess. Error code: " + std::to_string(ec.value()) + " : " + ec.message()); } } else { ba::async_read(pipe_, buffer_, onOutput); } }; ba::async_read(pipe_, buffer_, onOutput); } bp::async_pipe& pipe() { return pipe_; } std::string output() && { return std::move(output_); } private: std::vector vOut_; ba::mutable_buffer buffer_; bp::async_pipe pipe_; std::string output_; }; class InputPipeHandler final { public: explicit InputPipeHandler(ba::io_context* ctx, const std::string& input) : input_(input) , buffer_(ba::buffer(input_)) , pipe_(*ctx) { } bp::async_pipe& pipe() { return pipe_; } void async_write() { ba::async_write(pipe_, buffer_, [&](const bs::error_code & ec, std::size_t /*n*/) { if (ec) { throw SubprocessError(std::string() + "Error sending input to subprocess. Error code: " + std::to_string(ec.value()) + " : " + ec.message()); } pipe_.async_close(); } ); } private: const std::string& input_; ba::const_buffer buffer_; bp::async_pipe pipe_; }; } SubprocessResult Subprocess::call(const char *command, const vector &args, const string &input) { return call(_find_executable(command), args, input); } SubprocessResult Subprocess::check_call(const char *command, const vector &args, const string& input) { return check_call(_find_executable(command), args, input); } SubprocessResult Subprocess::call(const bf::path& executable, const vector& args, const string& input) { if (!bf::exists(executable)) { throw std::runtime_error("Tried to run executable " + executable.string() + " but didn't find it"); } // Process I/O needs to use the async API to avoid deadlocks, see // - https://www.boost.org/doc/libs/1_78_0/doc/html/boost_process/faq.html // - Code taken from https://www.py4u.net/discuss/97014 and modified ba::io_context ctx; OutputPipeHandler stdout_handler(&ctx); OutputPipeHandler stderr_handler(&ctx); InputPipeHandler stdin_handler(&ctx, input); bp::child child( bp::exe = executable.string(), bp::args(args), bp::std_out > stdout_handler.pipe(), bp::std_err > stderr_handler.pipe(), bp::std_in < stdin_handler.pipe() ); stdin_handler.async_write(); stdout_handler.async_read(); stderr_handler.async_read(); ctx.run(); child.wait(); return SubprocessResult{ std::move(stdout_handler).output(), std::move(stderr_handler).output(), child.exit_code(), }; } SubprocessResult Subprocess::check_call(const bf::path &executable, const vector &args, const string& input) { auto result = call(executable, args, input); if (result.exitcode != 0) { throw SubprocessError("Subprocess \"" + executable.string() + "\" exited with code " + std::to_string(result.exitcode)); } return result; } } src/cpp-utils/process/subprocess.h000066400000000000000000000022731445614261000176130ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_PROCESS_SUBPROCESS_H #define MESSMER_CPPUTILS_PROCESS_SUBPROCESS_H #include #include #include #include #include "../macros.h" namespace cpputils { struct SubprocessResult final { std::string output_stdout; std::string output_stderr; int exitcode; }; struct SubprocessError final : public std::runtime_error { SubprocessError(std::string msg) : std::runtime_error(std::move(msg)) {} }; class Subprocess final { public: static SubprocessResult call(const char *command, const std::vector &args, const std::string& input); static SubprocessResult call(const boost::filesystem::path &executable, const std::vector &args, const std::string& input); static SubprocessResult check_call(const char *command, const std::vector &args, const std::string& input); static SubprocessResult check_call(const boost::filesystem::path &executable, const std::vector &args, const std::string& input); private: DISALLOW_COPY_AND_ASSIGN(Subprocess); }; } #endif src/cpp-utils/random/000077500000000000000000000000001445614261000150505ustar00rootroot00000000000000src/cpp-utils/random/OSRandomGenerator.cpp000066400000000000000000000000371445614261000211050ustar00rootroot00000000000000#include "OSRandomGenerator.h" src/cpp-utils/random/OSRandomGenerator.h000066400000000000000000000012561445614261000205560ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_RANDOM_OSRANDOMGENERATOR_H #define MESSMER_CPPUTILS_RANDOM_OSRANDOMGENERATOR_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.cpp000066400000000000000000000002461445614261000210100ustar00rootroot00000000000000#include "PseudoRandomPool.h" namespace cpputils { constexpr size_t PseudoRandomPool::MIN_BUFFER_SIZE; constexpr size_t PseudoRandomPool::MAX_BUFFER_SIZE; } src/cpp-utils/random/PseudoRandomPool.h000066400000000000000000000020431445614261000204520ustar00rootroot00000000000000#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.cpp000066400000000000000000000001131445614261000167670ustar00rootroot00000000000000#include "Random.h" namespace cpputils { std::mutex Random::_mutex; } src/cpp-utils/random/Random.h000066400000000000000000000014071445614261000164430ustar00rootroot00000000000000#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.cpp000066400000000000000000000000361445614261000207170ustar00rootroot00000000000000#include "RandomDataBuffer.h" src/cpp-utils/random/RandomDataBuffer.h000066400000000000000000000025151445614261000203700ustar00rootroot00000000000000#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.cpp000066400000000000000000000000351445614261000206410ustar00rootroot00000000000000#include "RandomGenerator.h" src/cpp-utils/random/RandomGenerator.h000066400000000000000000000021241445614261000203070ustar00rootroot00000000000000#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.cpp000066400000000000000000000024201445614261000217710ustar00rootroot00000000000000#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.h000066400000000000000000000017271445614261000214470ustar00rootroot00000000000000#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.h000066400000000000000000000052541445614261000223620ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000151145ustar00rootroot00000000000000src/cpp-utils/system/diskspace.cpp000066400000000000000000000014771445614261000175770ustar00rootroot00000000000000#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.h000066400000000000000000000004321445614261000172320ustar00rootroot00000000000000#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.cpp000066400000000000000000000017051445614261000164130ustar00rootroot00000000000000#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.h000066400000000000000000000003121445614261000160510ustar00rootroot00000000000000#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.h000066400000000000000000000004561445614261000170700ustar00rootroot00000000000000#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.cpp000066400000000000000000000015721445614261000217100ustar00rootroot00000000000000#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.cpp000066400000000000000000000052511445614261000211730ustar00rootroot00000000000000#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.cpp000066400000000000000000000022021445614261000211660ustar00rootroot00000000000000#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.h000066400000000000000000000003451445614261000206410ustar00rootroot00000000000000#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.cpp000066400000000000000000000055501445614261000172540ustar00rootroot00000000000000#include "homedir.h" #include namespace bf = boost::filesystem; using std::string; #if !defined(_MSC_VER) #include namespace { bf::path _get_home_directory() { const char* homedir_ = getenv("HOME"); string homedir = (homedir_ == nullptr) ? "" : homedir_; if (homedir == "") { // try the /etc/passwd entry struct passwd* pwd = getpwuid(getuid()); if (pwd) { homedir = pwd->pw_dir; } } 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.h000066400000000000000000000027131445614261000167170ustar00rootroot00000000000000#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.h000066400000000000000000000011421445614261000165730ustar00rootroot00000000000000#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.cpp000066400000000000000000000015011445614261000214120ustar00rootroot00000000000000#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.cpp000066400000000000000000000026211445614261000207030ustar00rootroot00000000000000#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.h000066400000000000000000000010001445614261000162100ustar00rootroot00000000000000#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.h000066400000000000000000000004631445614261000162430ustar00rootroot00000000000000#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.cpp000066400000000000000000000007001445614261000165530ustar00rootroot00000000000000#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.h000066400000000000000000000016721445614261000162310ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000153755ustar00rootroot00000000000000src/cpp-utils/tempfile/TempDir.cpp000066400000000000000000000011101445614261000174360ustar00rootroot00000000000000#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.h000066400000000000000000000006241445614261000171140ustar00rootroot00000000000000#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.cpp000066400000000000000000000016051445614261000176100ustar00rootroot00000000000000#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()) { remove(); } } catch (const boost::filesystem::filesystem_error &e) { LOG(ERR, "Could not delete tempfile."); } } void TempFile::remove() { bf::remove(_path); } bool TempFile::exists() const { return bf::exists(_path); } const bf::path &TempFile::path() const { return _path; } } src/cpp-utils/tempfile/TempFile.h000066400000000000000000000011101445614261000172440ustar00rootroot00000000000000#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; //TODO Test remove() void remove(); private: const boost::filesystem::path _path; DISALLOW_COPY_AND_ASSIGN(TempFile); }; } #endif src/cpp-utils/testutils/000077500000000000000000000000001445614261000156305ustar00rootroot00000000000000src/cpp-utils/testutils/CaptureStderrRAII.h000066400000000000000000000016571445614261000212460ustar00rootroot00000000000000#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/testutils/ExpectThrows.h000066400000000000000000000016521445614261000204440ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CPPUTILS_EXPECTTHROWS_H #define MESSMER_CPPUTILS_EXPECTTHROWS_H #include #include namespace cpputils { template inline void expectThrows(Functor&& functor, const char* expectMessageContains) { try { std::forward(functor)(); } catch (const Exception& e) { EXPECT_THAT(e.what(), testing::HasSubstr(expectMessageContains)); return; } ADD_FAILURE() << "Expected to throw exception containing \"" << expectMessageContains << "\" but didn't throw"; } template inline void expectFailsAssertion(Functor&& functor, const char* expectMessageContains) { cpputils::_assert::DisableAbortOnFailedAssertionRAII _disableAbortOnFailedAssertionRAII; expectThrows(std::forward(functor), expectMessageContains); } } #endif src/cpp-utils/thread/000077500000000000000000000000001445614261000150375ustar00rootroot00000000000000src/cpp-utils/thread/LeftRight.cpp000066400000000000000000000000271445614261000174320ustar00rootroot00000000000000#include "LeftRight.h" src/cpp-utils/thread/LeftRight.h000066400000000000000000000130661445614261000171060ustar00rootroot00000000000000#include #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.cpp000066400000000000000000000014261445614261000176070ustar00rootroot00000000000000#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.h000066400000000000000000000020111445614261000172430ustar00rootroot00000000000000#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.cpp000066400000000000000000000110701445614261000201560ustar00rootroot00000000000000#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.h000066400000000000000000000030101445614261000176160ustar00rootroot00000000000000#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.h000066400000000000000000000006621445614261000171470ustar00rootroot00000000000000#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.cpp000066400000000000000000000055461445614261000217750ustar00rootroot00000000000000#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.cpp000066400000000000000000000060441445614261000212540ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000157455ustar00rootroot00000000000000src/cpp-utils/value_type/ValueType.cpp000066400000000000000000000000271445614261000203660ustar00rootroot00000000000000#include "ValueType.h" src/cpp-utils/value_type/ValueType.h000066400000000000000000000251711445614261000200420ustar00rootroot00000000000000#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_)) { // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) return operator=(IdValueType(rhs)); } 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/000077500000000000000000000000001445614261000135435ustar00rootroot00000000000000src/cryfs-cli/CMakeLists.txt000066400000000000000000000017211445614261000163040ustar00rootroot00000000000000project (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 DESTINATION ${CMAKE_INSTALL_BINDIR} ) src/cryfs-cli/CallAfterTimeout.cpp000066400000000000000000000000361445614261000174520ustar00rootroot00000000000000#include "CallAfterTimeout.h" src/cryfs-cli/CallAfterTimeout.h000066400000000000000000000041011445614261000171140ustar00rootroot00000000000000#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.cpp000066400000000000000000000537311445614261000147670ustar00rootroot00000000000000#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::either; 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. UNUSED(httpClient); #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.is_left()) { switch(config.left()) { case CryConfigFile::LoadError::DecryptionFailed: throw CryfsException("Failed to decrypt the config file. Did you enter the correct password?", ErrorCode::WrongPassword); case CryConfigFile::LoadError::ConfigFileNotFound: throw CryfsException("Could not find the cryfs.config file. Are you sure this is a valid CryFS file system?", ErrorCode::InvalidFilesystem); } } _checkConfigIntegrity(options.baseDir(), localStateDir, *config.right().configFile, options.allowReplacedFilesystem()); return std::move(config.right()); } either 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); } namespace { void printConfig(const CryConfig& oldConfig, const CryConfig& updatedConfig) { auto printValue = [&] (const char* prefix, const char* suffix, auto member) { std::cout << prefix; auto oldConfigValue = member(oldConfig); auto updatedConfigValue = member(updatedConfig); if (oldConfigValue == updatedConfigValue) { std::cout << oldConfigValue; } else { std::cout << oldConfigValue << " -> " << updatedConfigValue; } std::cout << suffix; }; std::cout << "\n----------------------------------------------------" << "\nFilesystem configuration:" << "\n----------------------------------------------------"; printValue("\n- Filesystem format version: ", "", [] (const CryConfig& config) {return config.Version(); }); printValue("\n- Created with: CryFS ", "", [] (const CryConfig& config) { return config.CreatedWithVersion(); }); printValue("\n- Last opened with: CryFS ", "", [] (const CryConfig& config) { return config.LastOpenedWithVersion(); }); printValue("\n- Cipher: ", "", [] (const CryConfig& config) { return config.Cipher(); }); printValue("\n- Blocksize: ", " bytes", [] (const CryConfig& config) { return config.BlocksizeBytes(); }); printValue("\n- Filesystem Id: ", "", [] (const CryConfig& config) { return config.FilesystemId().ToString(); }); std::cout << "\n----------------------------------------------------\n"; } } 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); printConfig(config.oldConfig, *config.configFile->config()); 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; if (options.foreground()) { fuse->runInForeground(options.mountDir(), options.fuseOptions()); } else { fuse->runInBackground(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", options.createMissingBasedir(), ErrorCode::InaccessibleBaseDir); if (!options.mountDirIsDriveLetter()) { _checkDirAccessible(options.mountDir(), "mount directory", options.createMissingMountpoint(), 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, bool createMissingDir, ErrorCode errorCode) { if (!bf::exists(dir)) { bool create = createMissingDir; if (create) { LOG(INFO, "Automatically creating {}", name); } else { 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.h000066400000000000000000000075701445614261000144340ustar00rootroot00000000000000#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); cpputils::either _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, bool createMissingDir, 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.cpp000066400000000000000000000023151445614261000165540ustar00rootroot00000000000000#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.h000066400000000000000000000012551445614261000162230ustar00rootroot00000000000000#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.cpp000066400000000000000000000041471445614261000171670ustar00rootroot00000000000000#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; } // NOLINTNEXTLINE(bugprone-branch-clone) 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.h000066400000000000000000000017221445614261000166300ustar00rootroot00000000000000#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.cpp000066400000000000000000000026261445614261000152010ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000167655ustar00rootroot00000000000000src/cryfs-cli/program_options/Parser.cpp000066400000000000000000000256611445614261000207370ustar00rootroot00000000000000#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"); bool allowFilesystemUpgrade = vm.count("allow-filesystem-upgrade"); bool allowReplacedFilesystem = vm.count("allow-replaced-filesystem"); bool createMissingBasedir = vm.count("create-missing-basedir"); bool createMissingMountpoint = vm.count("create-missing-mountpoint"); 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) { fuseOptions.push_back("-o"); fuseOptions.push_back(option); } } return ProgramOptions(std::move(baseDir), std::move(mountDir), std::move(configfile), foreground, allowFilesystemUpgrade, allowReplacedFilesystem, createMissingBasedir, createMissingMountpoint, 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.") ("create-missing-basedir", "Creates the base directory even if there is no directory currently there, skipping the normal confirmation message to create it later.") ("create-missing-mountpoint", "Creates the mountpoint even if there is no directory currently there, skipping the normal confirmation message to create it later.") ("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.h000066400000000000000000000035351445614261000204000ustar00rootroot00000000000000#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.cpp000066400000000000000000000063761445614261000224700ustar00rootroot00000000000000#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, bool createMissingBasedir, bool createMissingMountpoint, optional unmountAfterIdleMinutes, optional logFile, optional cipher, optional blocksizeBytes, bool allowIntegrityViolations, boost::optional missingBlockIsIntegrityViolation, vector fuseOptions) : _baseDir(bf::absolute(std::move(baseDir))), _mountDir(std::move(mountDir)), _configFile(std::move(configFile)), _foreground(foreground), _allowFilesystemUpgrade(allowFilesystemUpgrade), _allowReplacedFilesystem(allowReplacedFilesystem), _createMissingBasedir(createMissingBasedir), _createMissingMountpoint(createMissingMountpoint), _unmountAfterIdleMinutes(std::move(unmountAfterIdleMinutes)), _logFile(std::move(logFile)), _cipher(std::move(cipher)), _blocksizeBytes(std::move(blocksizeBytes)), _allowIntegrityViolations(allowIntegrityViolations), _missingBlockIsIntegrityViolation(std::move(missingBlockIsIntegrityViolation)), _fuseOptions(std::move(fuseOptions)), _mountDirIsDriveLetter(cpputils::path_is_just_drive_letter(_mountDir)) { 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; } bool ProgramOptions::createMissingBasedir() const { return _createMissingBasedir; } bool ProgramOptions::createMissingMountpoint() const { return _createMissingMountpoint; } 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.h000066400000000000000000000061621445614261000221260ustar00rootroot00000000000000#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, bool createMissingBasedir, bool createMissingMountpoint, 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; const boost::optional &configFile() const; bool foreground() const; bool allowFilesystemUpgrade() const; bool allowReplacedFilesystem() const; bool createMissingBasedir() const; bool createMissingMountpoint() const; const boost::optional &unmountAfterIdleMinutes() const; const boost::optional &logFile() const; const boost::optional &cipher() const; const boost::optional &blocksizeBytes() const; bool allowIntegrityViolations() const; const boost::optional &missingBlockIsIntegrityViolation() const; const std::vector &fuseOptions() const; bool mountDirIsDriveLetter() const; private: boost::filesystem::path _baseDir; // this is always absolute boost::filesystem::path _mountDir; // this is absolute iff !_mountDirIsDriveLetter boost::optional _configFile; bool _foreground; bool _allowFilesystemUpgrade; bool _allowReplacedFilesystem; bool _createMissingBasedir; bool _createMissingMountpoint; boost::optional _unmountAfterIdleMinutes; boost::optional _logFile; boost::optional _cipher; boost::optional _blocksizeBytes; bool _allowIntegrityViolations; boost::optional _missingBlockIsIntegrityViolation; std::vector _fuseOptions; bool _mountDirIsDriveLetter; DISALLOW_COPY_AND_ASSIGN(ProgramOptions); }; } } #endif src/cryfs-cli/program_options/utils.cpp000066400000000000000000000017101445614261000206300ustar00rootroot00000000000000#include "utils.h" #include #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.h000066400000000000000000000007741445614261000203060ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000145015ustar00rootroot00000000000000src/cryfs-unmount/CMakeLists.txt000066400000000000000000000013501445614261000172400ustar00rootroot00000000000000project (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}) target_add_boost(${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 DESTINATION ${CMAKE_INSTALL_BINDIR} ) src/cryfs-unmount/Cli.cpp000066400000000000000000000034261445614261000157210ustar00rootroot00000000000000#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); } bool immediate = options.immediate(); #if defined(__APPLE__) if (options.immediate()) { std::cerr << "Warning: OSX doesn't support the --immediate flag. Ignoring it."; immediate = false; } #elif defined(_MSC_VER) if (options.immediate()) { std::cerr << "Warning: Windows doesn't support the --immediate flag. Ignoring it."; immediate = false; } #endif // TODO This doesn't seem to work with relative paths std::cout << "Unmounting CryFS filesystem at " << options.mountDir() << "." << std::endl; if (immediate) { Fuse::unmount(options.mountDir(), true); // TODO Wait until it is actually unmounted and then show a better success message? std::cout << "Filesystem is unmounting." << std::endl; } else { Fuse::unmount(options.mountDir(), false); // TODO Wait until it is actually unmounted and then show a better success message? std::cout << "Filesystem will unmount as soon as nothing is accessing it anymore." << std::endl; } } } src/cryfs-unmount/Cli.h000066400000000000000000000003031445614261000153550ustar00rootroot00000000000000#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.cpp000066400000000000000000000016431445614261000177220ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000177235ustar00rootroot00000000000000src/cryfs-unmount/program_options/Parser.cpp000066400000000000000000000102051445614261000216610ustar00rootroot00000000000000#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(); bool immediate = vm.count("immediate"); return ProgramOptions(std::move(mountDir), immediate); } 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() ("immediate", "unmount immediately without waiting for processes that currently access the file system to finish their file system operations. With this flag, unmounting can fail if there's processes having a lock on the file system.") ("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.h000066400000000000000000000026161445614261000213350ustar00rootroot00000000000000#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.cpp000066400000000000000000000013421445614261000234120ustar00rootroot00000000000000#include "ProgramOptions.h" #include #include #include using namespace cryfs_unmount::program_options; using std::string; namespace bf = boost::filesystem; ProgramOptions::ProgramOptions(bf::path mountDir, bool immediate) : _mountDir(std::move(mountDir)), _mountDirIsDriveLetter(cpputils::path_is_just_drive_letter(_mountDir)), _immediate(immediate) { if (!_mountDirIsDriveLetter) { _mountDir = bf::absolute(std::move(_mountDir)); } } const bf::path &ProgramOptions::mountDir() const { return _mountDir; } bool ProgramOptions::mountDirIsDriveLetter() const { return _mountDirIsDriveLetter; } bool ProgramOptions::immediate() const { return _immediate; } src/cryfs-unmount/program_options/ProgramOptions.h000066400000000000000000000014711445614261000230620ustar00rootroot00000000000000#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, bool immediate); ProgramOptions(ProgramOptions &&rhs) = default; const boost::filesystem::path &mountDir() const; bool mountDirIsDriveLetter() const; bool immediate() const; private: boost::filesystem::path _mountDir; bool _mountDirIsDriveLetter; bool _immediate; DISALLOW_COPY_AND_ASSIGN(ProgramOptions); }; } // namespace program_options } // namespace cryfs_unmount #endif src/cryfs/000077500000000000000000000000001445614261000127765ustar00rootroot00000000000000src/cryfs/CMakeLists.txt000066400000000000000000000053311445614261000155400ustar00rootroot00000000000000project (cryfs) set(LIB_SOURCES # cryfs.cpp impl/CryfsException.cpp impl/config/crypto/outer/OuterConfig.cpp impl/config/crypto/outer/OuterEncryptor.cpp impl/config/crypto/CryConfigEncryptorFactory.cpp impl/config/crypto/inner/ConcreteInnerEncryptor.cpp impl/config/crypto/inner/InnerConfig.cpp impl/config/crypto/inner/InnerEncryptor.cpp impl/config/crypto/CryConfigEncryptor.cpp impl/config/CryConfigConsole.cpp impl/config/CryConfigLoader.cpp impl/config/CryConfig.cpp impl/config/CryConfigFile.cpp impl/config/CryCipher.cpp impl/config/CryConfigCreator.cpp impl/config/CryKeyProvider.cpp impl/config/CryPasswordBasedKeyProvider.cpp impl/config/CryPresetPasswordBasedKeyProvider.cpp impl/filesystem/CryOpenFile.cpp impl/filesystem/fsblobstore/utils/DirEntry.cpp impl/filesystem/fsblobstore/utils/DirEntryList.cpp impl/filesystem/fsblobstore/FsBlobStore.cpp impl/filesystem/fsblobstore/FsBlobView.cpp impl/filesystem/fsblobstore/FileBlob.cpp impl/filesystem/fsblobstore/FsBlob.cpp impl/filesystem/fsblobstore/SymlinkBlob.cpp impl/filesystem/fsblobstore/DirBlob.cpp impl/filesystem/CryNode.cpp impl/filesystem/parallelaccessfsblobstore/DirBlobRef.cpp impl/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStore.cpp impl/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStoreAdapter.cpp impl/filesystem/parallelaccessfsblobstore/FsBlobRef.cpp impl/filesystem/parallelaccessfsblobstore/FileBlobRef.cpp impl/filesystem/parallelaccessfsblobstore/SymlinkBlobRef.cpp impl/filesystem/CrySymlink.cpp impl/filesystem/CryDir.cpp impl/filesystem/cachingfsblobstore/DirBlobRef.cpp impl/filesystem/cachingfsblobstore/CachingFsBlobStore.cpp impl/filesystem/cachingfsblobstore/FsBlobRef.cpp impl/filesystem/cachingfsblobstore/FileBlobRef.cpp impl/filesystem/cachingfsblobstore/SymlinkBlobRef.cpp impl/filesystem/CryFile.cpp impl/filesystem/CryDevice.cpp impl/localstate/LocalStateDir.cpp impl/localstate/LocalStateMetadata.cpp impl/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}) # 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/cryfs.cpp000066400000000000000000000006121445614261000146270ustar00rootroot00000000000000#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.h000066400000000000000000000004741445614261000143020ustar00rootroot00000000000000#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/impl/000077500000000000000000000000001445614261000137375ustar00rootroot00000000000000src/cryfs/impl/CryfsException.cpp000066400000000000000000000000341445614261000174050ustar00rootroot00000000000000#include "CryfsException.h" src/cryfs/impl/CryfsException.h000066400000000000000000000007401445614261000170560ustar00rootroot00000000000000#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/impl/ErrorCodes.h000066400000000000000000000050741445614261000161650ustar00rootroot00000000000000#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/impl/config/000077500000000000000000000000001445614261000152045ustar00rootroot00000000000000src/cryfs/impl/config/CryCipher.cpp000066400000000000000000000104051445614261000176000ustar00rootroot00000000000000#include "CryCipher.h" #include #include #include "cryfs/impl/config/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>(), 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), make_shared>(), make_shared>(INTEGRITY_WARNING), 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/impl/config/CryCipher.h000066400000000000000000000031451445614261000172500ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYCIPHER_H #define MESSMER_CRYFS_SRC_CONFIG_CRYCIPHER_H #include #include #include #include #include #include "cryfs/impl/config/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/impl/config/CryConfig.cpp000066400000000000000000000122261445614261000175760ustar00rootroot00000000000000#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/impl/config/CryConfig.h000066400000000000000000000054041445614261000172430ustar00rootroot00000000000000#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/impl/config/CryConfigConsole.cpp000066400000000000000000000070001445614261000211130ustar00rootroot00000000000000#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/impl/config/CryConfigConsole.h000066400000000000000000000023361445614261000205670ustar00rootroot00000000000000#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 = "xchacha20-poly1305"; 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/impl/config/CryConfigCreator.cpp000066400000000000000000000103361445614261000211160ustar00rootroot00000000000000#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/impl/config/CryConfigCreator.h000066400000000000000000000036151445614261000205650ustar00rootroot00000000000000#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/impl/config/CryConfigFile.cpp000066400000000000000000000062151445614261000203770ustar00rootroot00000000000000#include "CryConfigFile.h" #include #include #include #include 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; using cpputils::make_unique_ref; using cpputils::either; 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 } either> CryConfigFile::load(bf::path path, CryKeyProvider* keyProvider, Access access) { auto encryptedConfigData = Data::LoadFromFile(path); if (encryptedConfigData == none) { return LoadError::ConfigFileNotFound; } auto encryptor = CryConfigEncryptorFactory::loadExistingKey(*encryptedConfigData, keyProvider); if (encryptor == none) { return LoadError::DecryptionFailed; } auto decrypted = (*encryptor)->decrypt(*encryptedConfigData); if (decrypted == none) { return LoadError::DecryptionFailed; } 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 LoadError::DecryptionFailed; } auto configFile = make_unique_ref(CryConfigFile(std::move(path), std::move(config), std::move(*encryptor), access)); if (decrypted->wasInDeprecatedConfigFormat) { if (access == Access::ReadWrite) { // Migrate it to new format configFile->save(); } } #if !defined(__clang__) && !defined(_MSC_VER) && defined(__GNUC__) && __GNUC__ < 8 return std::move(configFile); #else return configFile; #endif } unique_ref CryConfigFile::create(bf::path path, CryConfig config, CryKeyProvider* keyProvider) { if (bf::exists(path)) { throw std::runtime_error("Config file exists already."); } auto result = make_unique_ref(std::move(path), std::move(config), CryConfigEncryptorFactory::deriveNewKey(keyProvider), CryConfigFile::Access::ReadWrite); result->save(); return result; } CryConfigFile::CryConfigFile(bf::path path, CryConfig config, unique_ref encryptor, Access access) : _path(std::move(path)), _config(std::move(config)), _encryptor(std::move(encryptor)), _access(access) { } void CryConfigFile::save() const { if (_access == Access::ReadOnly) { throw std::logic_error("Tried to save the cryfs.config file while being in read only mode"); } 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/impl/config/CryConfigFile.h000066400000000000000000000036301445614261000200420ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGFILE_H #define MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGFILE_H #include #include #include "CryConfig.h" #include #include #include "cryfs/impl/config/crypto/CryConfigEncryptorFactory.h" namespace cryfs { class CryConfigFile final { public: enum class Access : uint8_t { // Never write to the config file, just read it. // Note that this is only sound if the file system itself // is also loaded read-only, or at least with migrations disabled. // Otherwise, the file system might get migrated but the config // file will still say it's the old version. ReadOnly, // Load the config file and update it if necessary, // e.g. write the "last opened with" entry into it // and potentially upgrade the version number. ReadWrite, }; CryConfigFile(boost::filesystem::path path, CryConfig config, cpputils::unique_ref encryptor, Access access); CryConfigFile(CryConfigFile &&rhs) = default; ~CryConfigFile(); static cpputils::unique_ref create(boost::filesystem::path path, CryConfig config, CryKeyProvider* keyProvider); enum class LoadError {ConfigFileNotFound, DecryptionFailed}; static cpputils::either> load(boost::filesystem::path path, CryKeyProvider* keyProvider, Access access); void save() const; CryConfig *config(); const CryConfig *config() const; private: boost::filesystem::path _path; CryConfig _config; cpputils::unique_ref _encryptor; Access _access; DISALLOW_COPY_AND_ASSIGN(CryConfigFile); }; } #endif src/cryfs/impl/config/CryConfigLoader.cpp000066400000000000000000000211101445614261000207150ustar00rootroot00000000000000#include "CryConfigLoader.h" #include "CryConfigFile.h" #include #include #include #include #include #include #include "cryfs/impl/localstate/LocalStateDir.h" #include "cryfs/impl/localstate/LocalStateMetadata.h" #include "cryfs/impl/CryfsException.h" namespace bf = boost::filesystem; using cpputils::Console; using cpputils::RandomGenerator; using cpputils::unique_ref; using cpputils::either; 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)) { } either CryConfigLoader::_loadConfig(bf::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem, CryConfigFile::Access access) { auto config = CryConfigFile::load(std::move(filename), _keyProvider.get(), access); if (config.is_left()) { return config.left(); } auto oldConfig = *config.right()->config(); #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.right()->config()->Version() == "0.9.7" || config.right()->config()->Version() == "0.9.8") { config.right()->config()->SetVersion("0.9.6"); } #endif _checkVersion(*config.right()->config(), allowFilesystemUpgrade); if (config.right()->config()->Version() != CryConfig::FilesystemFormatVersion) { config.right()->config()->SetVersion(CryConfig::FilesystemFormatVersion); if (access == CryConfigFile::Access::ReadWrite) { config.right()->save(); } } if (config.right()->config()->LastOpenedWithVersion() != gitversion::VersionString()) { config.right()->config()->SetLastOpenedWithVersion(gitversion::VersionString()); if (access == CryConfigFile::Access::ReadWrite) { config.right()->save(); } } _checkCipher(*config.right()->config()); auto localState = LocalStateMetadata::loadOrGenerate(_localStateDir.forFilesystemId(config.right()->config()->FilesystemId()), cpputils::Data::FromString(config.right()->config()->EncryptionKey()), allowReplacedFilesystem); uint32_t myClientId = localState.myClientId(); _checkMissingBlocksAreIntegrityViolations(config.right().get(), myClientId); return ConfigLoadResult {std::move(oldConfig), std::move(config.right()), 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(); } } either CryConfigLoader::load(bf::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem, CryConfigFile::Access access) { return _loadConfig(std::move(filename), allowFilesystemUpgrade, allowReplacedFilesystem, access); } either CryConfigLoader::loadOrCreate(bf::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem) { if (bf::exists(filename)) { return _loadConfig(std::move(filename), allowFilesystemUpgrade, allowReplacedFilesystem, CryConfigFile::Access::ReadWrite); } 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), config.config, _keyProvider.get()); return ConfigLoadResult {std::move(config.config), std::move(result), config.myClientId}; } } src/cryfs/impl/config/CryConfigLoader.h000066400000000000000000000050401445614261000203660ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGLOADER_H_ #define MESSMER_CRYFS_SRC_CONFIG_CRYCONFIGLOADER_H_ #include #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 { CryConfig oldConfig; // loading a config file updates the config file, but this member keeps the original config cpputils::unique_ref configFile; uint32_t myClientId; }; cpputils::either loadOrCreate(boost::filesystem::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem); cpputils::either load(boost::filesystem::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem, CryConfigFile::Access access); private: cpputils::either _loadConfig(boost::filesystem::path filename, bool allowFilesystemUpgrade, bool allowReplacedFilesystem, CryConfigFile::Access access); 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/impl/config/CryKeyProvider.cpp000066400000000000000000000000341445614261000206260ustar00rootroot00000000000000#include "CryKeyProvider.h" src/cryfs/impl/config/CryKeyProvider.h000066400000000000000000000010171445614261000202750ustar00rootroot00000000000000#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/impl/config/CryPasswordBasedKeyProvider.cpp000066400000000000000000000026531445614261000233210ustar00rootroot00000000000000#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/impl/config/CryPasswordBasedKeyProvider.h000066400000000000000000000022201445614261000227540ustar00rootroot00000000000000#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/impl/config/CryPresetPasswordBasedKeyProvider.cpp000066400000000000000000000015351445614261000245020ustar00rootroot00000000000000#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/impl/config/CryPresetPasswordBasedKeyProvider.h000066400000000000000000000015321445614261000241440ustar00rootroot00000000000000#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/impl/config/crypto/000077500000000000000000000000001445614261000165245ustar00rootroot00000000000000src/cryfs/impl/config/crypto/CryConfigEncryptor.cpp000066400000000000000000000044611445614261000230260ustar00rootroot00000000000000#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/impl/config/crypto/CryConfigEncryptor.h000066400000000000000000000030531445614261000224670ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYPTO_CRYCONFIGENCRYPTOR_H #define MESSMER_CRYFS_SRC_CONFIG_CRYPTO_CRYCONFIGENCRYPTOR_H #include #include #include #include #include "cryfs/impl/config/crypto/inner/InnerEncryptor.h" #include "cryfs/impl/config/crypto/outer/OuterEncryptor.h" #include "cryfs/impl/config/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/impl/config/crypto/CryConfigEncryptorFactory.cpp000066400000000000000000000034031445614261000243510ustar00rootroot00000000000000#include "CryConfigEncryptorFactory.h" #include #include "cryfs/impl/config/crypto/outer/OuterConfig.h" #include "cryfs/impl/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/impl/config/crypto/CryConfigEncryptorFactory.h000066400000000000000000000015021445614261000240140ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_SRC_CONFIG_CRYPTO_CRYCONFIGENCRYPTORFACTORY_H #define MESSMER_CRYFS_SRC_CONFIG_CRYPTO_CRYCONFIGENCRYPTORFACTORY_H #include "cryfs/impl/config/crypto/inner/ConcreteInnerEncryptor.h" #include "CryConfigEncryptor.h" #include #include #include "cryfs/impl/config/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/impl/config/crypto/inner/000077500000000000000000000000001445614261000176375ustar00rootroot00000000000000src/cryfs/impl/config/crypto/inner/ConcreteInnerEncryptor.cpp000066400000000000000000000000441445614261000250050ustar00rootroot00000000000000#include "ConcreteInnerEncryptor.h" src/cryfs/impl/config/crypto/inner/ConcreteInnerEncryptor.h000066400000000000000000000044241445614261000244600ustar00rootroot00000000000000#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/impl/config/crypto/inner/InnerConfig.cpp000066400000000000000000000040131445614261000225420ustar00rootroot00000000000000#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/impl/config/crypto/inner/InnerConfig.h000066400000000000000000000013061445614261000222110ustar00rootroot00000000000000#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/impl/config/crypto/inner/InnerEncryptor.cpp000066400000000000000000000000341445614261000233210ustar00rootroot00000000000000#include "InnerEncryptor.h" src/cryfs/impl/config/crypto/inner/InnerEncryptor.h000066400000000000000000000012011445614261000227630ustar00rootroot00000000000000#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/impl/config/crypto/outer/000077500000000000000000000000001445614261000176625ustar00rootroot00000000000000src/cryfs/impl/config/crypto/outer/OuterConfig.cpp000066400000000000000000000062061445614261000226160ustar00rootroot00000000000000#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/impl/config/crypto/outer/OuterConfig.h000066400000000000000000000017761445614261000222720ustar00rootroot00000000000000#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/impl/config/crypto/outer/OuterEncryptor.cpp000066400000000000000000000023411445614261000233720ustar00rootroot00000000000000#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/impl/config/crypto/outer/OuterEncryptor.h000066400000000000000000000017271445614261000230460ustar00rootroot00000000000000#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/impl/filesystem/000077500000000000000000000000001445614261000161235ustar00rootroot00000000000000src/cryfs/impl/filesystem/CryDevice.cpp000066400000000000000000000364431445614261000205160ustar00rootroot00000000000000#include #include #include "cryfs/impl/filesystem/parallelaccessfsblobstore/DirBlobRef.h" #include "CryDevice.h" #include "CryDir.h" #include "CryFile.h" #include "CrySymlink.h" #include #include #include #include #include #include #include "cryfs/impl/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStore.h" #include "cryfs/impl/filesystem/cachingfsblobstore/CachingFsBlobStore.h" #include "cryfs/impl/config/CryCipher.h" #include #include #include #include "cryfs/impl/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(std::shared_ptr configFile, unique_ref blockStore, const LocalStateDir& localStateDir, uint32_t myClientId, bool allowIntegrityViolations, bool missingBlockIsIntegrityViolation, std::function onIntegrityViolation) : _fsBlobStore(CreateFsBlobStore(std::move(blockStore), configFile.get(), localStateDir, myClientId, allowIntegrityViolations, missingBlockIsIntegrityViolation, std::move(onIntegrityViolation))), _rootBlobId(GetOrCreateRootBlobId(configFile.get())), _configFile(std::move(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(); } const CryConfig &CryDevice::config() const { return *_configFile->config(); } 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/impl/filesystem/CryDevice.h000066400000000000000000000107071445614261000201560ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYDEVICE_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYDEVICE_H_ #include #include #include "cryfs/impl/config/CryConfigFile.h" #include #include #include #include "cryfs/impl/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStore.h" #include "cryfs/impl/filesystem/parallelaccessfsblobstore/DirBlobRef.h" #include "cryfs/impl/filesystem/parallelaccessfsblobstore/FileBlobRef.h" #include "cryfs/impl/filesystem/parallelaccessfsblobstore/SymlinkBlobRef.h" namespace cryfs { class CryDevice final: public fspp::Device { public: CryDevice(std::shared_ptr 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; const CryConfig &config() const; void callFsActionCallbacks() const; uint64_t numBlocks() const; private: cpputils::unique_ref _fsBlobStore; blockstore::BlockId _rootBlobId; std::shared_ptr _configFile; 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/impl/filesystem/CryDir.cpp000066400000000000000000000103161445614261000200240ustar00rootroot00000000000000#include "CryDir.h" #include #include #include #include #include "CryDevice.h" #include "CryFile.h" #include "CryOpenFile.h" #include //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); } vector 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(), timestampUpdateBehavior()); } vector children; 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); 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/impl/filesystem/CryDir.h000066400000000000000000000025211445614261000174700ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYDIR_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYDIR_H_ #include #include "CryNode.h" #include "cryfs/impl/filesystem/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) std::vector 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/impl/filesystem/CryFile.cpp000066400000000000000000000036361445614261000201740ustar00rootroot00000000000000#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/impl/filesystem/CryFile.h000066400000000000000000000017171445614261000176370ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYFILE_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYFILE_H_ #include "cryfs/impl/filesystem/parallelaccessfsblobstore/FileBlobRef.h" #include "cryfs/impl/filesystem/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/impl/filesystem/CryNode.cpp000066400000000000000000000156441445614261000202040ustar00rootroot00000000000000#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(); } fspp::TimestampUpdateBehavior CryNode::timestampUpdateBehavior() const { return _device->getContext().timestampUpdateBehavior(); } 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/impl/filesystem/CryNode.h000066400000000000000000000044411445614261000176420ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYNODE_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYNODE_H_ #include #include #include #include "cryfs/impl/filesystem/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(); fspp::TimestampUpdateBehavior timestampUpdateBehavior() const; 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/impl/filesystem/CryOpenFile.cpp000066400000000000000000000037511445614261000210140ustar00rootroot00000000000000#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(), timestampUpdateBehavior()); 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(); } fspp::TimestampUpdateBehavior CryOpenFile::timestampUpdateBehavior() const { return _device->getContext().timestampUpdateBehavior(); } } src/cryfs/impl/filesystem/CryOpenFile.h000066400000000000000000000023461445614261000204600ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYOPENFILE_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYOPENFILE_H_ #include #include "cryfs/impl/filesystem/parallelaccessfsblobstore/FileBlobRef.h" #include "cryfs/impl/filesystem/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; fspp::TimestampUpdateBehavior timestampUpdateBehavior() const; private: const CryDevice *_device; std::shared_ptr _parent; cpputils::unique_ref _fileBlob; DISALLOW_COPY_AND_ASSIGN(CryOpenFile); }; } #endif src/cryfs/impl/filesystem/CrySymlink.cpp000066400000000000000000000034211445614261000207330ustar00rootroot00000000000000#include "CrySymlink.h" #include #include "CryDevice.h" #include "CrySymlink.h" #include "cryfs/impl/filesystem/parallelaccessfsblobstore/SymlinkBlobRef.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(), timestampUpdateBehavior()); 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/impl/filesystem/CrySymlink.h000066400000000000000000000016331445614261000204030ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CRYSYMLINK_H_ #define MESSMER_CRYFS_FILESYSTEM_CRYSYMLINK_H_ #include #include "CryNode.h" #include "cryfs/impl/filesystem/parallelaccessfsblobstore/SymlinkBlobRef.h" #include "cryfs/impl/filesystem/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/impl/filesystem/cachingfsblobstore/000077500000000000000000000000001445614261000217645ustar00rootroot00000000000000src/cryfs/impl/filesystem/cachingfsblobstore/CachingFsBlobStore.cpp000066400000000000000000000031471445614261000261360ustar00rootroot00000000000000#include "CachingFsBlobStore.h" #include "cryfs/impl/filesystem/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/impl/filesystem/cachingfsblobstore/CachingFsBlobStore.h000066400000000000000000000122121445614261000255740ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_CACHINGFSBLOBSTORE_H #define MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_CACHINGFSBLOBSTORE_H #include #include "cryfs/impl/filesystem/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/impl/filesystem/cachingfsblobstore/DirBlobRef.cpp000066400000000000000000000000301445614261000244330ustar00rootroot00000000000000#include "DirBlobRef.h" src/cryfs/impl/filesystem/cachingfsblobstore/DirBlobRef.h000066400000000000000000000112311445614261000241050ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_DIRBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_DIRBLOBREF_H #include "FsBlobRef.h" #include "cryfs/impl/filesystem/fsblobstore/DirBlob.h" #include #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, fspp::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(); } void setLstatSizeGetter(std::function getLstatSize) { return _base->setLstatSizeGetter(getLstatSize); } private: fsblobstore::DirBlob *_base; DISALLOW_COPY_AND_ASSIGN(DirBlobRef); }; } } #endif src/cryfs/impl/filesystem/cachingfsblobstore/FileBlobRef.cpp000066400000000000000000000000311445614261000245750ustar00rootroot00000000000000#include "FileBlobRef.h" src/cryfs/impl/filesystem/cachingfsblobstore/FileBlobRef.h000066400000000000000000000027271445614261000242600ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_FILEBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_FILEBLOBREF_H #include "FsBlobRef.h" #include "cryfs/impl/filesystem/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 override { return _base->blockId(); } fspp::num_bytes_t lstat_size() const override { return _base->lstat_size(); } private: fsblobstore::FileBlob *_base; DISALLOW_COPY_AND_ASSIGN(FileBlobRef); }; } } #endif src/cryfs/impl/filesystem/cachingfsblobstore/FsBlobRef.cpp000066400000000000000000000003561445614261000243000ustar00rootroot00000000000000#include "FsBlobRef.h" #include "CachingFsBlobStore.h" namespace cryfs { namespace cachingfsblobstore { FsBlobRef::~FsBlobRef() { if (_baseBlob.is_valid()) { _fsBlobStore->releaseForCache(std::move(_baseBlob)); } } } } src/cryfs/impl/filesystem/cachingfsblobstore/FsBlobRef.h000066400000000000000000000024641445614261000237470ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_FSBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_FSBLOBREF_H #include "cryfs/impl/filesystem/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/impl/filesystem/cachingfsblobstore/SymlinkBlobRef.cpp000066400000000000000000000000341445614261000253470ustar00rootroot00000000000000#include "SymlinkBlobRef.h" src/cryfs/impl/filesystem/cachingfsblobstore/SymlinkBlobRef.h000066400000000000000000000020771445614261000250250ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_SYMLINKBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_CACHINGFSBLOBSTORE_SYMLINKBLOBREF_H #include "FsBlobRef.h" #include "cryfs/impl/filesystem/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 override { return _base->blockId(); } fspp::num_bytes_t lstat_size() const override { return _base->lstat_size(); } private: fsblobstore::SymlinkBlob *_base; DISALLOW_COPY_AND_ASSIGN(SymlinkBlobRef); }; } } #endif src/cryfs/impl/filesystem/fsblobstore/000077500000000000000000000000001445614261000204475ustar00rootroot00000000000000src/cryfs/impl/filesystem/fsblobstore/DirBlob.cpp000066400000000000000000000213251445614261000224730ustar00rootroot00000000000000#include "DirBlob.h" #include //TODO Remove and replace with exception hierarchy #include #include #include #include "cryfs/impl/filesystem/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, fspp::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/impl/filesystem/fsblobstore/DirBlob.h000066400000000000000000000105751445614261000221450ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_DIRBLOB_H_ #define MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_DIRBLOB_H_ #include #include #include #include #include "FsBlob.h" #include "cryfs/impl/filesystem/fsblobstore/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, fspp::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/impl/filesystem/fsblobstore/FileBlob.cpp000066400000000000000000000024111445614261000226270ustar00rootroot00000000000000#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/impl/filesystem/fsblobstore/FileBlob.h000066400000000000000000000016711445614261000223030ustar00rootroot00000000000000#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/impl/filesystem/fsblobstore/FsBlob.cpp000066400000000000000000000000241445614261000223160ustar00rootroot00000000000000#include "FsBlob.h" src/cryfs/impl/filesystem/fsblobstore/FsBlob.h000066400000000000000000000046021445614261000217710ustar00rootroot00000000000000#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/impl/filesystem/fsblobstore/FsBlobStore.cpp000066400000000000000000000065061445614261000233460ustar00rootroot00000000000000#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); if (rootBlob == none) { throw std::runtime_error("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; } // NOLINTNEXTLINE(misc-no-recursion) 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/impl/filesystem/fsblobstore/FsBlobStore.h000066400000000000000000000077211445614261000230130ustar00rootroot00000000000000#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/impl/filesystem/fsblobstore/FsBlobView.cpp000066400000000000000000000020701445614261000231540ustar00rootroot00000000000000#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/impl/filesystem/fsblobstore/FsBlobView.h000066400000000000000000000127211445614261000226250ustar00rootroot00000000000000#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 = 0; 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 = 0; 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/impl/filesystem/fsblobstore/SymlinkBlob.cpp000066400000000000000000000026721445614261000234070ustar00rootroot00000000000000#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/impl/filesystem/fsblobstore/SymlinkBlob.h000066400000000000000000000017321445614261000230500ustar00rootroot00000000000000#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/impl/filesystem/fsblobstore/utils/000077500000000000000000000000001445614261000216075ustar00rootroot00000000000000src/cryfs/impl/filesystem/fsblobstore/utils/DirEntry.cpp000066400000000000000000000117321445614261000240570ustar00rootroot00000000000000#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/impl/filesystem/fsblobstore/utils/DirEntry.h000066400000000000000000000123611445614261000235230ustar00rootroot00000000000000#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/impl/filesystem/fsblobstore/utils/DirEntryList.cpp000066400000000000000000000243151445614261000247140ustar00rootroot00000000000000#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, fspp::TimestampUpdateBehavior timestampUpdateBehavior) { auto found = _findById(blockId); const timespec lastAccessTime = found->lastAccessTime(); const timespec lastModificationTime = found->lastModificationTime(); const timespec now = cpputils::time::now(); switch (found->type()) { case fspp::Dir::EntryType::FILE: // fallthrough case fspp::Dir::EntryType::SYMLINK: if (timestampUpdateBehavior->shouldUpdateATimeOnFileRead(lastAccessTime, lastModificationTime, now)) { found->setLastAccessTime(now); return true; } return false; case fspp::Dir::EntryType::DIR: if (timestampUpdateBehavior->shouldUpdateATimeOnDirectoryRead(lastAccessTime, lastModificationTime, now)) { found->setLastAccessTime(now); return true; } return false; } throw std::logic_error("Unhandled case"); } void DirEntryList::updateModificationTimestampForChild(const blockstore::BlockId &blockId) { auto found = _findById(blockId); found->setLastModificationTime(cpputils::time::now()); } } } src/cryfs/impl/filesystem/fsblobstore/utils/DirEntryList.h000066400000000000000000000076441445614261000243670ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_UTILS_DIRENTRYLIST_H #define MESSMER_CRYFS_FILESYSTEM_FSBLOBSTORE_UTILS_DIRENTRYLIST_H #include #include #include "DirEntry.h" #include #include //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, fspp::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/impl/filesystem/parallelaccessfsblobstore/000077500000000000000000000000001445614261000233465ustar00rootroot00000000000000src/cryfs/impl/filesystem/parallelaccessfsblobstore/DirBlobRef.cpp000066400000000000000000000000301445614261000260150ustar00rootroot00000000000000#include "DirBlobRef.h" src/cryfs/impl/filesystem/parallelaccessfsblobstore/DirBlobRef.h000066400000000000000000000107201445614261000254710ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_DIRBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_DIRBLOBREF_H #include "FsBlobRef.h" #include "cryfs/impl/filesystem/cachingfsblobstore/DirBlobRef.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, fspp::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/impl/filesystem/parallelaccessfsblobstore/FileBlobRef.cpp000066400000000000000000000000311445614261000261570ustar00rootroot00000000000000#include "FileBlobRef.h" src/cryfs/impl/filesystem/parallelaccessfsblobstore/FileBlobRef.h000066400000000000000000000027341445614261000256400ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_FILEBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_FILEBLOBREF_H #include "FsBlobRef.h" #include "cryfs/impl/filesystem/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/impl/filesystem/parallelaccessfsblobstore/FsBlobRef.cpp000066400000000000000000000000271445614261000256550ustar00rootroot00000000000000#include "FsBlobRef.h" src/cryfs/impl/filesystem/parallelaccessfsblobstore/FsBlobRef.h000066400000000000000000000015441445614261000253270ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_FSBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_FSBLOBREF_H #include #include "cryfs/impl/filesystem/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/impl/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStore.cpp000066400000000000000000000060651445614261000310440ustar00rootroot00000000000000#include "ParallelAccessFsBlobStore.h" #include "ParallelAccessFsBlobStoreAdapter.h" #include "cryfs/impl/filesystem/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/impl/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStore.h000066400000000000000000000067021445614261000305070ustar00rootroot00000000000000#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 "cryfs/impl/filesystem/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/impl/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStoreAdapter.cpp000066400000000000000000000000561445614261000323370ustar00rootroot00000000000000#include "ParallelAccessFsBlobStoreAdapter.h" src/cryfs/impl/filesystem/parallelaccessfsblobstore/ParallelAccessFsBlobStoreAdapter.h000066400000000000000000000024451445614261000320100ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_PARALLELACCESSFSBLOBSTOREADAPTER_H_ #define MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_PARALLELACCESSFSBLOBSTOREADAPTER_H_ #include #include #include "cryfs/impl/filesystem/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/impl/filesystem/parallelaccessfsblobstore/SymlinkBlobRef.cpp000066400000000000000000000000341445614261000267310ustar00rootroot00000000000000#include "SymlinkBlobRef.h" src/cryfs/impl/filesystem/parallelaccessfsblobstore/SymlinkBlobRef.h000066400000000000000000000021061445614261000264000ustar00rootroot00000000000000#pragma once #ifndef MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_SYMLINKBLOBREF_H #define MESSMER_CRYFS_FILESYSTEM_PARALLELACCESSFSBLOBSTORE_SYMLINKBLOBREF_H #include "FsBlobRef.h" #include "cryfs/impl/filesystem/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/impl/localstate/000077500000000000000000000000001445614261000160725ustar00rootroot00000000000000src/cryfs/impl/localstate/BasedirMetadata.cpp000066400000000000000000000042411445614261000216110ustar00rootroot00000000000000#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/impl/localstate/BasedirMetadata.h000066400000000000000000000020571445614261000212610ustar00rootroot00000000000000#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/impl/localstate/LocalStateDir.cpp000066400000000000000000000016111445614261000212670ustar00rootroot00000000000000#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/impl/localstate/LocalStateDir.h000066400000000000000000000012101445614261000207270ustar00rootroot00000000000000#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/impl/localstate/LocalStateMetadata.cpp000066400000000000000000000102701445614261000222720ustar00rootroot00000000000000#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 = 0; 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 = 0; 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/impl/localstate/LocalStateMetadata.h000066400000000000000000000022221445614261000217350ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000126205ustar00rootroot00000000000000src/fspp/CMakeLists.txt000066400000000000000000000000661445614261000153620ustar00rootroot00000000000000add_subdirectory(fs_interface) add_subdirectory(fuse) src/fspp/fs_interface/000077500000000000000000000000001445614261000152505ustar00rootroot00000000000000src/fspp/fs_interface/CMakeLists.txt000066400000000000000000000005551445614261000200150ustar00rootroot00000000000000project (fspp-interface) set(SOURCES Device.cpp Dir.cpp File.cpp Node.cpp OpenFile.cpp Symlink.cpp Types.cpp Context.cpp ) add_library(${PROJECT_NAME} STATIC ${SOURCES}) target_link_libraries(${PROJECT_NAME} PUBLIC cpp-utils) target_add_boost(${PROJECT_NAME}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) src/fspp/fs_interface/Context.cpp000066400000000000000000000000251445614261000173750ustar00rootroot00000000000000#include "Context.h" src/fspp/fs_interface/Context.h000066400000000000000000000122541445614261000170510ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSINTERFACE_CONTEXT_H_ #define MESSMER_FSPP_FSINTERFACE_CONTEXT_H_ #include #include #include namespace fspp { namespace detail { class TimestampUpdateBehaviorBase { public: virtual bool shouldUpdateATimeOnFileRead(timespec oldATime, timespec oldMTime, timespec newATime) const = 0; virtual bool shouldUpdateATimeOnDirectoryRead(timespec oldATime, timespec oldMTime, timespec newATime) const = 0; }; } // Defines how atime timestamps of files and directories are accessed on read accesses // (e.g. atime, strictatime, relatime, nodiratime) using TimestampUpdateBehavior = std::shared_ptr; // atime attribute (of both files and directories) is updated only during write access. inline TimestampUpdateBehavior noatime() { class BehaviorImpl final : public detail::TimestampUpdateBehaviorBase { public: bool shouldUpdateATimeOnFileRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { return false; } bool shouldUpdateATimeOnDirectoryRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { return false; } }; static std::shared_ptr singleton = std::make_shared(); return singleton; } // This causes the atime attribute to update with every file access. (accessing file data, not just the metadata/attributes) inline TimestampUpdateBehavior strictatime() { class BehaviorImpl final : public detail::TimestampUpdateBehaviorBase { public: bool shouldUpdateATimeOnFileRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { return true; } bool shouldUpdateATimeOnDirectoryRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { return true; } }; static std::shared_ptr singleton = std::make_shared(); return singleton; } // This option causes the atime attribute to update only if the previous atime is older than mtime or ctime, or the previous atime is over 24 hours old. inline TimestampUpdateBehavior relatime() { // This option causes the atime attribute to update only if the previous atime is older than mtime or ctime, or the previous atime is over 24 hours old. class BehaviorImpl final : public detail::TimestampUpdateBehaviorBase { public: bool shouldUpdateATimeOnFileRead(timespec oldATime, timespec oldMTime, timespec newATime) const override { const timespec yesterday { /*.tv_sec = */ newATime.tv_sec - 60*60*24, /*.tv_nsec = */ newATime.tv_nsec }; return oldATime < oldMTime || oldATime < yesterday; } bool shouldUpdateATimeOnDirectoryRead(timespec oldATime, timespec oldMTime, timespec newATime) const override { return shouldUpdateATimeOnFileRead(oldATime, oldMTime, newATime); } }; static std::shared_ptr singleton = std::make_shared(); return singleton; } // atime of directories is updated only during write access, can be combined with relatime. atime of files follows the relatime rules. inline TimestampUpdateBehavior nodiratime_relatime() { class BehaviorImpl final : public detail::TimestampUpdateBehaviorBase { public: bool shouldUpdateATimeOnFileRead(timespec oldATime, timespec oldMTime, timespec newATime) const override { return relatime()->shouldUpdateATimeOnFileRead(oldATime, oldMTime, newATime); } bool shouldUpdateATimeOnDirectoryRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { return false; } }; static std::shared_ptr singleton = std::make_shared(); return singleton; } // atime of directories is updated only during write access, can be combined with relatime. atime of files follows the strictatime rules. inline TimestampUpdateBehavior nodiratime_strictatime() { class BehaviorImpl final : public detail::TimestampUpdateBehaviorBase { public: bool shouldUpdateATimeOnFileRead(timespec oldATime, timespec oldMTime, timespec newATime) const override { return strictatime()->shouldUpdateATimeOnFileRead(oldATime, oldMTime, newATime); } bool shouldUpdateATimeOnDirectoryRead(timespec /*oldATime*/, timespec /*oldMTime*/, timespec /*newATime*/) const override { return false; } }; static std::shared_ptr singleton = std::make_shared(); return singleton; } class Context final { public: explicit Context(TimestampUpdateBehavior timestampUpdateBehavior) : _timestampUpdateBehavior(std::move(timestampUpdateBehavior)) {} const TimestampUpdateBehavior& timestampUpdateBehavior() const { return _timestampUpdateBehavior; } void setTimestampUpdateBehavior(TimestampUpdateBehavior value) { _timestampUpdateBehavior = std::move(value); } private: TimestampUpdateBehavior _timestampUpdateBehavior; }; } #endif src/fspp/fs_interface/Device.cpp000066400000000000000000000000241445614261000171470ustar00rootroot00000000000000#include "Device.h" src/fspp/fs_interface/Device.h000066400000000000000000000030121445614261000166140ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FSINTERFACE_DEVICE_H_ #define MESSMER_FSPP_FSINTERFACE_DEVICE_H_ #include #include #include "Types.h" #include "Context.h" #include 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; const Context& getContext() const { ASSERT(_context != boost::none, "Tried to call getContext() but file system isn't running yet."); return *_context; } // called by fspp system on file system init. Don't call this manually. // TODO Is there a better way to do this? void setContext(Context&& context) { _context = std::move(context); } private: boost::optional _context; }; } #endif src/fspp/fs_interface/Dir.cpp000066400000000000000000000000211445614261000164630ustar00rootroot00000000000000#include "Dir.h" src/fspp/fs_interface/Dir.h000066400000000000000000000021611445614261000161370ustar00rootroot00000000000000#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::vector children() const = 0; virtual std::vector children() = 0; }; } #endif src/fspp/fs_interface/File.cpp000066400000000000000000000000221445614261000166250ustar00rootroot00000000000000#include "File.h" src/fspp/fs_interface/File.h000066400000000000000000000006101445614261000162750ustar00rootroot00000000000000#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.cpp000066400000000000000000000000401445614261000215350ustar00rootroot00000000000000#include "FuseErrnoException.h" src/fspp/fs_interface/FuseErrnoException.h000066400000000000000000000014141445614261000212100ustar00rootroot00000000000000#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.cpp000066400000000000000000000000221445614261000166330ustar00rootroot00000000000000#include "Node.h" src/fspp/fs_interface/Node.h000066400000000000000000000013501445614261000163050ustar00rootroot00000000000000#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.cpp000066400000000000000000000000261445614261000174530ustar00rootroot00000000000000#include "OpenFile.h" src/fspp/fs_interface/OpenFile.h000066400000000000000000000012441445614261000171230ustar00rootroot00000000000000#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.cpp000066400000000000000000000000251445614261000173770ustar00rootroot00000000000000#include "Symlink.h" src/fspp/fs_interface/Symlink.h000066400000000000000000000005271445614261000170530ustar00rootroot00000000000000#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.cpp000066400000000000000000000000231445614261000170530ustar00rootroot00000000000000#include "Types.h" src/fspp/fs_interface/Types.h000066400000000000000000000120471445614261000165310ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000141305ustar00rootroot00000000000000src/fspp/fstest/FsTest.h000066400000000000000000000042151445614261000155130ustar00rootroot00000000000000#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_SUITE_P(FS_NAME, FsppDeviceTest_One, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppDeviceTest_Two, FIXTURE); \ INSTANTIATE_NODE_TEST_SUITE( FS_NAME, FsppDeviceTest_Timestamps, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppDirTest, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppDirTest_Timestamps, FIXTURE); \ INSTANTIATE_NODE_TEST_SUITE( FS_NAME, FsppDirTest_Timestamps_Entries, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppFileTest, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppFileTest_Timestamps, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppSymlinkTest, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppSymlinkTest_Timestamps, FIXTURE); \ INSTANTIATE_NODE_TEST_SUITE( FS_NAME, FsppNodeTest_Rename, FIXTURE); \ INSTANTIATE_NODE_TEST_SUITE( FS_NAME, FsppNodeTest_Stat, FIXTURE); \ INSTANTIATE_NODE_TEST_SUITE( FS_NAME, FsppNodeTest_Timestamps, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppNodeTest_Stat_FileOnly, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppNodeTest_Stat_DirOnly, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppNodeTest_Stat_SymlinkOnly, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppOpenFileTest, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, FsppOpenFileTest_Timestamps, FIXTURE); \ #endif src/fspp/fstest/FsppDeviceTest.h000066400000000000000000000356651445614261000172100ustar00rootroot00000000000000#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_SUITE_P, so we have to split it. template class FsppDeviceTest_One: public FsppDeviceTest {}; template class FsppDeviceTest_Two: public FsppDeviceTest {}; TYPED_TEST_SUITE_P(FsppDeviceTest_One); TYPED_TEST_SUITE_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_SUITE_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_SUITE_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.h000066400000000000000000000027651445614261000214110ustar00rootroot00000000000000#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 operation = [this] { auto node = this->CreateNode("/mynode"); return [this] { this->device->Load("/mynode"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Load_While_Not_Loaded() { this->testBuilder().withAnyAtimeConfig([&] { 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_SUITE(FsppDeviceTest_Timestamps, Load_While_Loaded, Load_While_Not_Loaded ); #endif src/fspp/fstest/FsppDirTest.h000066400000000000000000000257021445614261000165160ustar00rootroot00000000000000#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_SUITE_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_SUITE_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.h000066400000000000000000000653551445614261000207340ustar00rootroot00000000000000#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_SUITE_P(FsppDirTest_Timestamps); TYPED_TEST_P(FsppDirTest_Timestamps, createAndOpenFile) { auto operation = [this] { auto dir = this->CreateDir("/mydir"); return [dir = std::move(dir)] { dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); }; }; this->testBuilder().withAnyAtimeConfig([&] { 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 operation = [this] { auto dir = this->LoadDir("/"); return [dir = std::move(dir)] { dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); }*/ TYPED_TEST_P(FsppDirTest_Timestamps, createAndOpenFile_TimestampsOfCreatedFile) { this->testBuilder().withAnyAtimeConfig([&] { auto dir = this->CreateDir("/mydir"); timespec lowerBound = cpputils::time::now(); dir->createAndOpenFile("childname", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); timespec upperBound = cpputils::time::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 operation = [this] { auto dir = this->CreateDir("/mydir"); return [dir = std::move(dir)] { dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); }; }; this->testBuilder().withAnyAtimeConfig([&] { 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 operation = [this] { auto dir = this->LoadDir("/"); return [dir = std::move(dir)] { dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } */ TYPED_TEST_P(FsppDirTest_Timestamps, createDir_TimestampsOfCreatedDir) { this->testBuilder().withAnyAtimeConfig([&] { auto dir = this->CreateDir("/mydir"); timespec lowerBound = cpputils::time::now(); dir->createDir("childname", fspp::mode_t().addDirFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); timespec upperBound = cpputils::time::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 operation = [this] { auto dir = this->CreateDir("/mydir"); return [dir = std::move(dir)] { dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); }; }; this->testBuilder().withAnyAtimeConfig([&] { 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 operation = [this] { auto dir = this->LoadDir("/"); return [dir = std::move(dir)] { dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } */ TYPED_TEST_P(FsppDirTest_Timestamps, createSymlink_TimestampsOfCreatedSymlink) { this->testBuilder().withAnyAtimeConfig([&] { auto dir = this->CreateDir("/mydir"); timespec lowerBound = cpputils::time::now(); dir->createSymlink("childname", "/target", fspp::uid_t(1000), fspp::gid_t(1000)); timespec upperBound = cpputils::time::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, givenAtimeOlderThanMtime_children_empty) { auto operation = [this] { auto dir = this->CreateDir("/mydir"); this->setAtimeOlderThanMtime("/mydir"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtime_children_empty) { auto operation = [this] { auto dir = this->CreateDir("/mydir"); this->setAtimeNewerThanMtime("/mydir"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_children_empty) { auto operation = [this] { auto dir = this->CreateDir("/mydir"); this->setAtimeNewerThanMtimeButBeforeYesterday("/mydir"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } /* TODO Re-enable this test once the root dir handles timestamps correctly TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeOlderThanMtime_children_empty_inRootDir) { auto operation = [this] { auto dir = this->LoadDir("/"); this->setAtimeOlderThanMtime("/"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtime_children_empty_inRootDir) { auto operation = [this] { auto dir = this->LoadDir("/"); this->setAtimeNewerThanMtime("/"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_children_empty_inRootDir) { auto operation = [this] { auto dir = this->LoadDir("/"); this->setAtimeNewerThanMtimeButBeforeYesterday("/"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } */ TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeOlderThanMtime_children_nonempty) { auto operation = [this] { auto dir = this->CreateDir("/mydir"); dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); this->setAtimeOlderThanMtime("/mydir"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtime_children_nonempty) { auto operation = [this] { auto dir = this->CreateDir("/mydir"); dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); this->setAtimeNewerThanMtime("/mydir"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_children_nonempty) { auto operation = [this] { auto dir = this->CreateDir("/mydir"); dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); this->setAtimeNewerThanMtimeButBeforeYesterday("/mydir"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } /* TODO Re-enable this test once the root dir handles timestamps correctly TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeOlderThanMtime_children_nonempty_inRootDir) { auto operation = [this] { auto dir = this->LoadDir("/"); dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); this->setAtimeOlderThanMtime("/"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtime_children_nonempty_inRootDir) { auto operation = [this] { auto dir = this->LoadDir("/"); dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); this->setAtimeNewerThanMtime("/"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppDirTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_children_nonempty_inRootDir) { auto operation = [this] { auto dir = this->LoadDir("/"); dir->createAndOpenFile("filename", fspp::mode_t().addFileFlag(), fspp::uid_t(1000), fspp::gid_t(1000)); this->setAtimeNewerThanMtimeButBeforeYesterday("/"); return [dir = std::move(dir)] { dir->children(); }; }; this->testBuilder().withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } */ template class FsppDirTest_Timestamps_Entries: public FsppNodeTest, public TimestampTestUtils { public: void Test_deleteChild() { auto operation = [this] { auto dir = this->CreateDir("/mydir"); auto child = this->CreateNode("/mydir/childname"); return [child = std::move(child)] { child->remove(); }; }; this->testBuilder().withAnyAtimeConfig([&] { 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 operation = [this] { auto dir = this->LoadDir("/"); auto child = this->CreateNode("/mydir/childname"); return [child = std::move(child)] { child->remove(); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } */ void Test_renameChild() { auto operation = [this] { auto dir = this->CreateDir("/mydir"); auto child = this->CreateNode("/mydir/childname"); return [child = std::move(child)] { child->rename("/mydir/mychild"); }; }; this->testBuilder().withAnyAtimeConfig([&]{ 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 operation = [this] { auto dir = this->LoadDir("/"); auto child = this->CreateNode("/childname"); return [child = std::move(child)] { child->rename("/mychild"); }; }; this->testBuilder().withAnyAtimeConfig([&]{ this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } */ void Test_moveChildIn() { auto operation = [this] { auto sourceDir = this->CreateDir("/sourcedir"); auto child = this->CreateNode("/sourcedir/childname"); auto dir = this->CreateDir("/mydir"); return [child = std::move(child)] { child->rename("/mydir/mychild"); }; }; this->testBuilder().withAnyAtimeConfig([&] { 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 operation = [this] { auto sourceDir = this->CreateDir("/sourcedir"); auto child = this->CreateNode("/sourcedir/childname"); auto dir = this->LoadDir("/"); return [child = std::move(child)] { child->rename("/mychild"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } */ void Test_moveChildOut() { auto operation = [this] { auto dir = this->CreateDir("/mydir"); auto child = this->CreateNode("/mydir/childname"); this->CreateDir("/targetdir"); return [child = std::move(child)] { child->rename("/targetdir/mychild"); }; }; this->testBuilder().withAnyAtimeConfig([&] { 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 operation = [this] { auto dir = this->LoadDir("/"); auto child = this->CreateNode("/childname"); this->CreateDir("/targetdir"); return [child = std::move(child)] { child->rename("/targetdir/mychild"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } */ }; REGISTER_TYPED_TEST_SUITE_P(FsppDirTest_Timestamps, createAndOpenFile, createAndOpenFile_TimestampsOfCreatedFile, createDir, createDir_TimestampsOfCreatedDir, createSymlink, createSymlink_TimestampsOfCreatedSymlink, givenAtimeNewerThanMtime_children_empty, givenAtimeOlderThanMtime_children_empty, givenAtimeNewerThanMtimeButBeforeYesterday_children_empty, givenAtimeNewerThanMtime_children_nonempty, givenAtimeOlderThanMtime_children_nonempty, givenAtimeNewerThanMtimeButBeforeYesterday_children_nonempty ); REGISTER_NODE_TEST_SUITE(FsppDirTest_Timestamps_Entries, deleteChild, renameChild, moveChildIn, moveChildOut ); #endif src/fspp/fstest/FsppFileTest.h000066400000000000000000000177621445614261000166660ustar00rootroot00000000000000#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_SUITE_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_SUITE_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.h000066400000000000000000000124161445614261000210630ustar00rootroot00000000000000#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_SUITE_P(FsppFileTest_Timestamps); TYPED_TEST_P(FsppFileTest_Timestamps, open_nomode) { auto operation = [this] { auto file = this->CreateFile("/myfile"); return [file = std::move(file)] { file->open(fspp::openflags_t(0)); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppFileTest_Timestamps, open_rdonly) { auto operation = [this] { auto file = this->CreateFile("/myfile"); return [file = std::move(file)] { file->open(fspp::openflags_t::RDONLY()); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppFileTest_Timestamps, open_wronly) { auto operation = [this] { auto file = this->CreateFile("/myfile"); return [file = std::move(file)] { file->open(fspp::openflags_t::WRONLY()); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppFileTest_Timestamps, open_rdwr) { auto operation = [this] { auto file = this->CreateFile("/myfile"); return [file = std::move(file)] { file->open(fspp::openflags_t::RDWR()); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_empty_to_empty) { auto operation = [this] { auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(0)); return [file = std::move(file)] { file->truncate(fspp::num_bytes_t(0)); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_empty_to_nonempty) { auto operation = [this] { auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(0)); return [file = std::move(file)] { file->truncate(fspp::num_bytes_t(10)); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } TYPED_TEST_P(FsppFileTest_Timestamps, truncate_nonempty_to_empty) { auto operation = [this] { auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); return [file = std::move(file)] { file->truncate(fspp::num_bytes_t(0)); }; }; this->testBuilder().withAnyAtimeConfig([&] { 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 operation = [this] { auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); return [file = std::move(file)] { file->truncate(fspp::num_bytes_t(5)); }; }; this->testBuilder().withAnyAtimeConfig([&] { 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 operation = [this] { auto file = this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); return [file = std::move(file)] { file->truncate(fspp::num_bytes_t(20)); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/myfile", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } REGISTER_TYPED_TEST_SUITE_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.h000066400000000000000000000217571445614261000201620ustar00rootroot00000000000000#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_SUITE(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.h000066400000000000000000000051111445614261000176500ustar00rootroot00000000000000#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_SUITE_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_SUITE_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_SUITE_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_SUITE(FsppNodeTest_Stat, Nlink ); REGISTER_TYPED_TEST_SUITE_P(FsppNodeTest_Stat_FileOnly, CreatedFileIsEmpty, FileIsFile ); REGISTER_TYPED_TEST_SUITE_P(FsppNodeTest_Stat_DirOnly, DirIsDir ); REGISTER_TYPED_TEST_SUITE_P(FsppNodeTest_Stat_SymlinkOnly, SymlinkIsSymlink ); #endif //TODO More test cases src/fspp/fstest/FsppNodeTest_Timestamps.h000066400000000000000000000406241445614261000210730ustar00rootroot00000000000000#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 template class FsppNodeTest_Timestamps: public FsppNodeTest, public TimestampTestUtils { public: void Test_Create() { this->testBuilder().withAnyAtimeConfig([&] { timespec lowerBound = cpputils::time::now(); auto node = this->CreateNode("/mynode"); timespec upperBound = cpputils::time::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 operation = [this] { auto node = this->CreateNode("/mynode"); return [node = std::move(node)] { node->stat(); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Chmod() { auto operation = [this] { auto node = this->CreateNode("/mynode"); fspp::mode_t mode = this->stat(*node).mode; return [mode, node = std::move(node)] { node->chmod(mode); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Chown() { auto operation = [this] { auto node = this->CreateNode("/mynode"); fspp::uid_t uid = this->stat(*node).uid; fspp::gid_t gid = this->stat(*node).gid; return [uid, gid, node = std::move(node)] { node->chown(uid, gid); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Access() { auto operation = [this] { auto node = this->CreateNode("/mynode"); return [node = std::move(node)] { node->access(0); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Rename_Error_TargetParentDirDoesntExist() { auto operation = [this] { auto node = this->CreateNode("/oldname"); return [node = std::move(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->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Rename_Error_TargetParentDirIsFile() { auto operation = [this] { auto node = this->CreateNode("/oldname"); this->CreateFile("/somefile"); return [node = std::move(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->testBuilder().withAnyAtimeConfig([&] { 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 operation = [this] { auto root = this->Load("/"); return [root = std::move(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->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mynode", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); */ } void Test_Rename_InRoot() { auto operation = [this] { auto node = this->CreateNode("/oldname"); return [node = std::move(node)] { node->rename("/newname"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_InNested() { auto operation = [this] { this->CreateDir("/mydir"); auto node = this->CreateNode("/mydir/oldname"); return [node = std::move(node)] { node->rename("/mydir/newname"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/mydir/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_RootToNested_SameName() { auto operation = [this] { this->CreateDir("/mydir"); auto node = this->CreateNode("/oldname"); return [node = std::move(node)] { node->rename("/mydir/oldname"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/mydir/oldname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_RootToNested_NewName() { auto operation = [this] { this->CreateDir("/mydir"); auto node = this->CreateNode("/oldname"); return [node = std::move(node)] { node->rename("/mydir/newname"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/mydir/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_NestedToRoot_SameName() { auto operation = [this] { this->CreateDir("/mydir"); auto node = this->CreateNode("/mydir/oldname"); return [node = std::move(node)] { node->rename("/oldname"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/oldname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_NestedToRoot_NewName() { auto operation = [this] { this->CreateDir("/mydir"); auto node = this->CreateNode("/mydir/oldname"); return [node = std::move(node)] { node->rename("/newname"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir/oldname", "/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_NestedToNested_SameName() { auto operation = [this] { this->CreateDir("/mydir1"); this->CreateDir("/mydir2"); auto node = this->CreateNode("/mydir1/oldname"); return [node = std::move(node)] { node->rename("/mydir2/oldname"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/oldname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_NestedToNested_NewName() { auto operation = [this] { this->CreateDir("/mydir1"); this->CreateDir("/mydir2"); auto node = this->CreateNode("/mydir1/oldname"); return [node = std::move(node)] { node->rename("/mydir2/newname"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_ToItself() { auto operation = [this] { auto node = this->CreateNode("/oldname"); return [node = std::move(node)] { node->rename("/oldname"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/oldname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_Overwrite_InSameDir() { auto operation = [this] { auto node = this->CreateNode("/oldname"); this->CreateNode("/newname"); return [node = std::move(node)] { node->rename("/newname"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", "/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_Overwrite_InDifferentDir() { auto operation = [this] { this->CreateDir("/mydir1"); this->CreateDir("/mydir2"); this->CreateNode("/mydir2/newname"); auto node = this->CreateNode("/mydir1/oldname"); return [node = std::move(node)] { node->rename("/mydir2/newname"); }; }; this->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", "/mydir2/newname", operation(), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } void Test_Rename_Overwrite_Error_DirWithFile_InSameDir() { auto operation = [this] { this->CreateFile("/oldname"); this->CreateDir("/newname"); auto node = this->Load("/oldname"); return [node = std::move(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->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Rename_Overwrite_Error_DirWithFile_InDifferentDir() { auto operation = [this] { this->CreateDir("/mydir1"); this->CreateDir("/mydir2"); this->CreateFile("/mydir1/oldname"); this->CreateDir("/mydir2/newname"); auto node = this->Load("/mydir1/oldname"); return [node = std::move(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->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Rename_Overwrite_Error_FileWithDir_InSameDir() { auto operation = [this] { this->CreateDir("/oldname"); this->CreateFile("/newname"); auto node = this->Load("/oldname"); return [node = std::move(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->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/oldname", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Rename_Overwrite_Error_FileWithDir_InDifferentDir() { auto operation = [this] { this->CreateDir("/mydir1"); this->CreateDir("/mydir2"); this->CreateDir("/mydir1/oldname"); this->CreateFile("/mydir2/newname"); auto node = this->Load("/mydir1/oldname"); return [node = std::move(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->testBuilder().withAnyAtimeConfig([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mydir1/oldname", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }); } void Test_Utimens() { this->testBuilder().withAnyAtimeConfig([&] { auto node = this->CreateNode("/mynode"); timespec atime = this->xSecondsAgo(100); timespec mtime = this->xSecondsAgo(200); auto operation = [atime, mtime, &node] { 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_SUITE(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.h000066400000000000000000000042161445614261000174760ustar00rootroot00000000000000#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_SUITE_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_SUITE_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.h000066400000000000000000000462111445614261000217050ustar00rootroot00000000000000#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; } void CreateFileWithSize(const boost::filesystem::path &path, fspp::num_bytes_t size) { auto file = this->CreateFile(path); file->truncate(size); } cpputils::unique_ref OpenFile(const boost::filesystem::path &path, fspp::num_bytes_t size) { auto file = this->LoadFile(path); 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_SUITE_P(FsppOpenFileTest_Timestamps); TYPED_TEST_P(FsppOpenFileTest_Timestamps, stat) { auto operation = [] (fspp::OpenFile* openFile){ return [openFile] { openFile->stat(); }; }; this->testBuilder().withAnyAtimeConfig([&] { auto openFile = this->CreateAndOpenFile("/mynode"); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_empty_to_empty) { auto operation = [] (fspp::OpenFile* openFile){ return [openFile] { openFile->truncate(fspp::num_bytes_t(0)); }; }; this->testBuilder().withAnyAtimeConfig([&] { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_empty_to_nonempty) { auto operation = [] (fspp::OpenFile* openFile){ return [openFile] { openFile->truncate(fspp::num_bytes_t(10)); }; }; this->testBuilder().withAnyAtimeConfig([&] { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_nonempty_to_empty) { auto operation = [] (fspp::OpenFile* openFile){ return [openFile] { openFile->truncate(fspp::num_bytes_t(0)); }; }; this->testBuilder().withAnyAtimeConfig([&] { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_nonempty_to_nonempty_shrink) { auto operation = [] (fspp::OpenFile* openFile){ return [openFile] { openFile->truncate(fspp::num_bytes_t(5)); }; }; this->testBuilder().withAnyAtimeConfig([&] { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, truncate_nonempty_to_nonempty_grow) { auto operation = [] (fspp::OpenFile* openFile){ return [openFile] { openFile->truncate(fspp::num_bytes_t(20)); }; }; this->testBuilder().withAnyAtimeConfig([&] { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_read_inbounds) { auto operation = [this] () { this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); this->setAtimeNewerThanMtimeButBeforeYesterday("/myfile"); auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); auto* openFilePtr = openFile.get(); return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { std::array buffer{}; openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(0)); }); }; this->testBuilder() .withNoatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeNewerThanMtime_read_inbounds) { auto operation = [this] () { this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); this->setAtimeNewerThanMtime("/myfile"); auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); auto* openFilePtr = openFile.get(); return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { std::array buffer{}; openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(0)); }); }; this->testBuilder() .withNoatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeOlderThanMtime_read_inbounds) { auto operation = [this] () { this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); this->setAtimeOlderThanMtime("/myfile"); auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); auto* openFilePtr = openFile.get(); return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { std::array buffer{}; openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(0)); }); }; this->testBuilder() .withNoatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_read_outofbounds) { auto operation = [this] () { this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); this->setAtimeNewerThanMtimeButBeforeYesterday("/myfile"); auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); auto* openFilePtr = openFile.get(); return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { std::array buffer{}; openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(2)); }); }; this->testBuilder() .withNoatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeNewerThanMtime_read_outofbounds) { auto operation = [this] () { this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); this->setAtimeNewerThanMtime("/myfile"); auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); auto* openFilePtr = openFile.get(); return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { std::array buffer{}; openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(2)); }); }; this->testBuilder() .withNoatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, givenAtimeOlderThanMtime_read_outofbounds) { auto operation = [this] () { this->CreateFileWithSize("/myfile", fspp::num_bytes_t(10)); this->setAtimeOlderThanMtime("/myfile"); auto openFile = this->OpenFile("/myfile", fspp::num_bytes_t(10)); auto* openFilePtr = openFile.get(); return std::make_pair(openFilePtr, [openFile = std::move(openFile)] { std::array buffer{}; openFile->read(buffer.data(), fspp::num_bytes_t(5), fspp::num_bytes_t(2)); }); }; this->testBuilder() .withNoatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeStrictatime([&] { auto op = operation(); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*op.first, std::move(op.second), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, write_inbounds) { auto operation = [] (fspp::OpenFile* openFile){ return [openFile] { openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); }; }; this->testBuilder().withAnyAtimeConfig([&] { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, write_outofbounds) { auto operation = [] (fspp::OpenFile* openFile){ return [openFile] { openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(2)); }; }; this->testBuilder().withAnyAtimeConfig([&] { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(0)); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAccessTimestamp, this->ExpectUpdatesModificationTimestamp, this->ExpectUpdatesMetadataTimestamp}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, flush) { auto operation = [] (fspp::OpenFile* openFile){ openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); return [openFile] { openFile->flush(); }; }; this->testBuilder().withAnyAtimeConfig([&] { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, fsync) { auto operation = [] (fspp::OpenFile* openFile){ openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); return [openFile] { openFile->fsync(); }; }; this->testBuilder().withAnyAtimeConfig([&] { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAnyTimestamps}); }); } TYPED_TEST_P(FsppOpenFileTest_Timestamps, fdatasync) { auto operation = [] (fspp::OpenFile* openFile){ openFile->write("content", fspp::num_bytes_t(7), fspp::num_bytes_t(0)); return [openFile] { openFile->fdatasync(); }; }; this->testBuilder().withAnyAtimeConfig([&] { auto openFile = this->CreateAndOpenFileWithSize("/myfile", fspp::num_bytes_t(10)); this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(*openFile, operation(openFile.get()), {this->ExpectDoesntUpdateAnyTimestamps}); }); } REGISTER_TYPED_TEST_SUITE_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, givenAtimeNewerThanMtimeButBeforeYesterday_read_inbounds, givenAtimeNewerThanMtime_read_inbounds, givenAtimeOlderThanMtime_read_inbounds, givenAtimeNewerThanMtimeButBeforeYesterday_read_outofbounds, givenAtimeNewerThanMtime_read_outofbounds, givenAtimeOlderThanMtime_read_outofbounds, write_inbounds, write_outofbounds, flush, fsync, fdatasync ); #endif src/fspp/fstest/FsppSymlinkTest.h000066400000000000000000000040351445614261000174220ustar00rootroot00000000000000#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_SUITE_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_SUITE_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.h000066400000000000000000000113111445614261000216230ustar00rootroot00000000000000#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_SUITE_P(FsppSymlinkTest_Timestamps); TYPED_TEST_P(FsppSymlinkTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_target) { auto operation = [this] { auto symlink = this->CreateSymlink("/mysymlink"); this->setAtimeNewerThanMtimeButBeforeYesterday("/mysymlink"); return [symlink = std::move(symlink)] { symlink->target(); }; }; this->testBuilder() .withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }); } TYPED_TEST_P(FsppSymlinkTest_Timestamps, givenAtimeOlderThanMtime_target) { auto operation = [this] { auto symlink = this->CreateSymlink("/mysymlink"); this->setAtimeOlderThanMtime("/mysymlink"); return [symlink = std::move(symlink)] { symlink->target(); }; }; this->testBuilder() .withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }); } TYPED_TEST_P(FsppSymlinkTest_Timestamps, givenAtimeNewerThanMtime_target) { auto operation = [this] { auto symlink = this->CreateSymlink("/mysymlink"); this->setAtimeNewerThanMtime("/mysymlink"); return [symlink = std::move(symlink)] { symlink->target(); }; }; this->testBuilder() .withNoatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }).withRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeRelatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectDoesntUpdateAnyTimestamps}); }).withNodiratimeStrictatime([&] { this->EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS("/mysymlink", operation(), {this->ExpectUpdatesAccessTimestamp, this->ExpectDoesntUpdateModificationTimestamp, this->ExpectDoesntUpdateMetadataTimestamp}); }); } REGISTER_TYPED_TEST_SUITE_P(FsppSymlinkTest_Timestamps, givenAtimeNewerThanMtimeButBeforeYesterday_target, givenAtimeNewerThanMtime_target, givenAtimeOlderThanMtime_target ); #endif src/fspp/fstest/testutils/000077500000000000000000000000001445614261000161705ustar00rootroot00000000000000src/fspp/fstest/testutils/FileSystemTest.h000066400000000000000000000116441445614261000212730ustar00rootroot00000000000000#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(nullptr), device(nullptr) { resetFilesystem(fspp::Context{fspp::relatime()}); } void resetFilesystem(fspp::Context&& context) { device = nullptr; fixture = nullptr; fixture = std::make_unique(); device = fixture->createDevice(); device->setContext(std::move(context)); } std::unique_ptr fixture; std::unique_ptr 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 setAtimeOlderThanMtime(const boost::filesystem::path& path) { auto node = device->Load(path).value(); auto st = node->stat(); st.atime.tv_nsec = st.mtime.tv_nsec - 1; node->utimens( st.atime, st.mtime ); } void setAtimeNewerThanMtime(const boost::filesystem::path& path) { auto node = device->Load(path).value(); auto st = node->stat(); st.atime.tv_nsec = st.mtime.tv_nsec + 1; node->utimens( st.atime, st.mtime ); } void setAtimeNewerThanMtimeButBeforeYesterday(const boost::filesystem::path& path) { auto node = device->Load(path).value(); auto st = node->stat(); const timespec now = cpputils::time::now(); const timespec before_yesterday { /*.tv_sec = */ now.tv_sec - 60*60*24 - 1, /*.tv_nsec = */ now.tv_nsec }; st.atime = before_yesterday; st.mtime.tv_nsec = st.atime.tv_nsec - 1; node->utimens( st.atime, st.mtime ); } }; template constexpr fspp::mode_t FileSystemTest::MODE_PUBLIC; #endif src/fspp/fstest/testutils/FileTest.h000066400000000000000000000050711445614261000200630ustar00rootroot00000000000000#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.h000066400000000000000000000163001445614261000207170ustar00rootroot00000000000000#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_SUITE and INSTANTIATE_NODE_TEST_SUITE. * 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_SUITE(r, Class, Name) \ TYPED_TEST_P(Class, Name) { \ this->BOOST_PP_CAT(Test_,Name)(); \ } \ #define _REGISTER_NODE_TEST_SUITES_FOR_CLASS(Class, ...) \ BOOST_PP_SEQ_FOR_EACH(_REGISTER_SINGLE_NODE_TEST_SUITE, Class, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)); \ REGISTER_TYPED_TEST_SUITE_P(Class, __VA_ARGS__); \ #define _REGISTER_FILE_TEST_SUITE(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_SUITE_P(Class##_FileNode); \ _REGISTER_NODE_TEST_SUITES_FOR_CLASS(Class##_FileNode, __VA_ARGS__); \ #define _REGISTER_DIR_TEST_SUITE(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_SUITE_P(Class##_DirNode); \ _REGISTER_NODE_TEST_SUITES_FOR_CLASS(Class##_DirNode, __VA_ARGS__); \ #define _REGISTER_SYMLINK_TEST_SUITE(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_SUITE_P(Class##_SymlinkNode); \ _REGISTER_NODE_TEST_SUITES_FOR_CLASS(Class##_SymlinkNode, __VA_ARGS__); \ #define REGISTER_NODE_TEST_SUITE(Class, ...) \ _REGISTER_FILE_TEST_SUITE(Class, __VA_ARGS__); \ _REGISTER_DIR_TEST_SUITE(Class, __VA_ARGS__); \ _REGISTER_SYMLINK_TEST_SUITE(Class, __VA_ARGS__); \ #define INSTANTIATE_NODE_TEST_SUITE(FS_NAME, Class, FIXTURE) \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, Class##_FileNode, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, Class##_DirNode, FIXTURE); \ INSTANTIATE_TYPED_TEST_SUITE_P(FS_NAME, Class##_SymlinkNode, FIXTURE); \ #endif src/fspp/fstest/testutils/TimestampTestUtils.h000066400000000000000000000247631445614261000222010ustar00rootroot00000000000000#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 TimestampUpdateExpectation = std::function; static TimestampUpdateExpectation ExpectUpdatesAccessTimestamp; static TimestampUpdateExpectation ExpectDoesntUpdateAccessTimestamp; static TimestampUpdateExpectation ExpectUpdatesModificationTimestamp; static TimestampUpdateExpectation ExpectDoesntUpdateModificationTimestamp; static TimestampUpdateExpectation ExpectUpdatesMetadataTimestamp; static TimestampUpdateExpectation ExpectDoesntUpdateMetadataTimestamp; static TimestampUpdateExpectation ExpectDoesntUpdateAnyTimestamps; void setTimestampUpdateBehavior(fspp::TimestampUpdateBehavior timestampUpdateBehavior) { FileSystemTest::device->setContext(fspp::Context { timestampUpdateBehavior }); } template void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(std::function statOld, std::function statNew, Operation&& 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); } } template void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const fspp::OpenFile &node, Operation&& operation, std::initializer_list behaviorChecks) { EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS( [this, &node](){return this->stat(node);}, [this, &node](){return this->stat(node);}, std::forward(operation), behaviorChecks ); } template void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const boost::filesystem::path &oldPath, const boost::filesystem::path &newPath, Operation&& 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));}, std::forward(operation), behaviorChecks ); } template void EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(const boost::filesystem::path &path, Operation&& operation, std::initializer_list behaviorChecks) { EXPECT_OPERATION_UPDATES_TIMESTAMPS_AS(path, path, std::forward(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()); } class TestBuilder final { public: explicit TestBuilder(TimestampTestUtils* fixture): _fixture(fixture) {} const TestBuilder& withNoatime(std::function expectations) const { _fixture->resetFilesystem(fspp::Context {fspp::noatime()}); expectations(); return *this; } const TestBuilder& withStrictatime(std::function expectations) const { _fixture->resetFilesystem(fspp::Context {fspp::strictatime()}); expectations(); return *this; } const TestBuilder& withRelatime(std::function expectations) const { _fixture->resetFilesystem(fspp::Context {fspp::relatime()}); expectations(); return *this; } const TestBuilder& withNodiratimeRelatime(std::function expectations) const { _fixture->resetFilesystem(fspp::Context {fspp::nodiratime_relatime()}); expectations(); return *this; } const TestBuilder& withNodiratimeStrictatime(std::function expectations) const { _fixture->resetFilesystem(fspp::Context {fspp::nodiratime_strictatime()}); expectations(); return *this; } const TestBuilder& withAnyAtimeConfig(std::function expectations) const { return withNoatime(expectations) .withStrictatime(expectations) .withRelatime(expectations) .withNodiratimeRelatime(expectations) .withNodiratimeStrictatime(expectations); } private: TimestampTestUtils* _fixture; }; TestBuilder testBuilder() { return TestBuilder(this); } 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::TimestampUpdateExpectation 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::TimestampUpdateExpectation 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::TimestampUpdateExpectation 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::TimestampUpdateExpectation 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::TimestampUpdateExpectation 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::TimestampUpdateExpectation 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::TimestampUpdateExpectation 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/000077500000000000000000000000001445614261000135625ustar00rootroot00000000000000src/fspp/fuse/CMakeLists.txt000066400000000000000000000030061445614261000163210ustar00rootroot00000000000000project (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}) 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}" ) else() # Linux and macOS find_package(PkgConfig REQUIRED) pkg_check_modules(Fuse REQUIRED IMPORTED_TARGET fuse) target_link_libraries(${PROJECT_NAME} PUBLIC PkgConfig::Fuse) endif() if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set(CMAKE_FIND_FRAMEWORK LAST) endif() src/fspp/fuse/Filesystem.h000066400000000000000000000053121445614261000160600ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FUSE_FILESYSTEM_H_ #define MESSMER_FSPP_FUSE_FILESYSTEM_H_ #include #include #include #include "../fs_interface/Dir.h" #include "../fs_interface/Context.h" #if defined(_MSC_VER) #include #else #include #endif #include "stat_compatibility.h" namespace fspp { namespace fuse { class Filesystem { public: virtual ~Filesystem() {} virtual void setContext(Context&& context) = 0; //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 std::vector 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.cpp000066400000000000000000001243731445614261000152020ustar00rootroot00000000000000// NOMINMAX works around an MSVC issue, see https://github.com/microsoft/cppwinrt/issues/479 #if defined(_MSC_VER) #define NOMINMAX #endif #include "Fuse.h" #include #include #include "../fs_interface/FuseErrnoException.h" #include "Filesystem.h" #include #include #include #include #include #include #include "InvalidFilesystem.h" #include #include #include #include #include #include #if defined(_MSC_VER) #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::runInForeground(const bf::path &mountdir, vector fuseOptions) { vector realFuseOptions = std::move(fuseOptions); if (std::find(realFuseOptions.begin(), realFuseOptions.end(), "-f") == realFuseOptions.end()) { realFuseOptions.push_back("-f"); } _run(mountdir, std::move(realFuseOptions)); } void Fuse::runInBackground(const bf::path &mountdir, vector fuseOptions) { vector realFuseOptions = std::move(fuseOptions); _removeAndWarnIfExists(&realFuseOptions, "-f"); _removeAndWarnIfExists(&realFuseOptions, "-d"); _run(mountdir, std::move(realFuseOptions)); } void Fuse::_removeAndWarnIfExists(vector *fuseOptions, const std::string &option) { auto found = std::find(fuseOptions->begin(), fuseOptions->end(), option); if (found != fuseOptions->end()) { LOG(WARN, "The fuse option {} only works when running in foreground. Removing fuse option.", option); do { fuseOptions->erase(found); found = std::find(fuseOptions->begin(), fuseOptions->end(), option); } while (found != fuseOptions->end()); } } namespace { void extractAllAtimeOptionsAndRemoveOnesUnknownToLibfuse_(string* csv_options, vector* result) { const auto is_fuse_supported_atime_flag = [] (const std::string& flag) { constexpr std::array flags = {"noatime", "atime"}; return flags.end() != std::find(flags.begin(), flags.end(), flag); }; const auto is_fuse_unsupported_atime_flag = [] (const std::string& flag) { constexpr std::array flags = {"strictatime", "relatime", "nodiratime"}; return flags.end() != std::find(flags.begin(), flags.end(), flag); }; *csv_options = ranges::make_subrange(csv_options->begin(), csv_options->end()) | ranges::views::split(',') | ranges::views::filter( [&] (auto&& elem_) { // TODO string_view would be better std::string elem(&*elem_.begin(), ranges::distance(elem_)); if (is_fuse_unsupported_atime_flag(elem)) { result->push_back(elem); return false; } if (is_fuse_supported_atime_flag(elem)) { result->push_back(elem); } return true; }) | ranges::views::join(',') | ranges::to(); } // Return a list of all atime options (e.g. atime, noatime, relatime, strictatime, nodiratime) that occur in the // fuseOptions input. They must be preceded by a '-o', i.e. {..., '-o', 'noatime', ...} and multiple ones can be // csv-concatenated, i.e. {..., '-o', 'atime,nodiratime', ...}. // Also, this function removes all of these atime options that are unknown to libfuse (i.e. all except atime and noatime) // from the input fuseOptions so we can pass it on to libfuse without crashing. vector extractAllAtimeOptionsAndRemoveOnesUnknownToLibfuse_(vector* fuseOptions) { vector result; bool lastOptionWasDashO = false; for (string& option : *fuseOptions) { if (lastOptionWasDashO) { extractAllAtimeOptionsAndRemoveOnesUnknownToLibfuse_(&option, &result); } lastOptionWasDashO = (option == "-o"); } return result; } } void Fuse::_run(const bf::path &mountdir, 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"); vector atimeOptions = extractAllAtimeOptionsAndRemoveOnesUnknownToLibfuse_(&fuseOptions); _createContext(atimeOptions); _argv = _build_argv(mountdir, fuseOptions); fuse_main(_argv.size(), _argv.data(), operations(), this); } void Fuse::_createContext(const vector &fuseOptions) { const bool has_atime_flag = fuseOptions.end() != std::find(fuseOptions.begin(), fuseOptions.end(), "atime"); const bool has_noatime_flag = fuseOptions.end() != std::find(fuseOptions.begin(), fuseOptions.end(), "noatime"); const bool has_relatime_flag = fuseOptions.end() != std::find(fuseOptions.begin(), fuseOptions.end(), "relatime"); const bool has_strictatime_flag = fuseOptions.end() != std::find(fuseOptions.begin(), fuseOptions.end(), "strictatime"); const bool has_nodiratime_flag = fuseOptions.end() != std::find(fuseOptions.begin(), fuseOptions.end(), "nodiratime"); // Default is NOATIME, this reduces the probability for synchronization conflicts _context = Context(noatime()); if (has_noatime_flag) { ASSERT(!has_atime_flag, "Cannot have both, noatime and atime flags set."); ASSERT(!has_relatime_flag, "Cannot have both, noatime and relatime flags set."); ASSERT(!has_strictatime_flag, "Cannot have both, noatime and strictatime flags set."); // note: can have nodiratime flag set but that is ignored because it is already included in the noatime policy. _context->setTimestampUpdateBehavior(noatime()); } else if (has_relatime_flag) { // note: can have atime and relatime both set, they're identical ASSERT(!has_noatime_flag, "This shouldn't happen, or we would have hit a case above."); ASSERT(!has_strictatime_flag, "Cannot have both, relatime and strictatime flags set."); if (has_nodiratime_flag) { _context->setTimestampUpdateBehavior(nodiratime_relatime()); } else { _context->setTimestampUpdateBehavior(relatime()); } } else if (has_atime_flag) { // note: can have atime and relatime both set, they're identical ASSERT(!has_noatime_flag, "This shouldn't happen, or we would have hit a case above"); ASSERT(!has_strictatime_flag, "Cannot have both, atime and strictatime flags set."); if (has_nodiratime_flag) { _context->setTimestampUpdateBehavior(nodiratime_relatime()); } else { _context->setTimestampUpdateBehavior(relatime()); } } else if (has_strictatime_flag) { ASSERT(!has_noatime_flag, "This shouldn't happen, or we would have hit a case above"); ASSERT(!has_atime_flag, "This shouldn't happen, or we would have hit a case above"); ASSERT(!has_relatime_flag, "This shouldn't happen, or we would have hit a case above"); if (has_nodiratime_flag) { _context->setTimestampUpdateBehavior(nodiratime_strictatime()); } else { _context->setTimestampUpdateBehavior(strictatime()); } } else if (has_nodiratime_flag) { ASSERT(!has_noatime_flag, "This shouldn't happen, or we would have hit a case above"); ASSERT(!has_atime_flag, "This shouldn't happen, or we would have hit a case above"); ASSERT(!has_relatime_flag, "This shouldn't happen, or we would have hit a case above"); ASSERT(!has_strictatime_flag, "This shouldn't happen, or we would have hit a case above"); _context->setTimestampUpdateBehavior(noatime()); // use noatime by default } } 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); auto fsname = _fsname.get_value_or(_fstype); boost::replace_all(fsname, ",", "\\,"); // Avoid fuse options parser bug where a comma in the fsname is misinterpreted as an options delimiter, see https://github.com/cryfs/cryfs/issues/326 _add_fuse_option_if_not_exists(&argv, "fsname", fsname); #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 macFUSE. 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("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::vector args = force ? std::vector({"-u", mountdir.string()}) : std::vector({"-u", "-z", mountdir.string()}); // "-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("fusermount", args, "").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.string(), mode); return ENOSYS; } int Fuse::mkdir(const bf::path &path, ::mode_t mode) { ThreadNameForDebugging _threadName("mkdir"); #ifdef FSPP_LOG LOG(DEBUG, "mkdir({}, {})", path.string(), 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.string(), 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.string(), to.string()); //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); ASSERT(_context != boost::none, "Context should have been initialized in Fuse::run() but somehow didn't"); _fs->setContext(fspp::Context { *_context }); 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.h000066400000000000000000000107351445614261000146430ustar00rootroot00000000000000#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 #include "stat_compatibility.h" #include 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 runInBackground(const boost::filesystem::path &mountdir, std::vector fuseOptions); void runInForeground(const boost::filesystem::path &mountdir, 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 void _removeAndWarnIfExists(std::vector *fuseOptions, const std::string &option); void _run(const boost::filesystem::path &mountdir, std::vector fuseOptions); 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); void _createContext(const std::vector &fuseOptions); 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; boost::optional _context; DISALLOW_COPY_AND_ASSIGN(Fuse); }; } } #endif src/fspp/fuse/InvalidFilesystem.h000066400000000000000000000105001445614261000173620ustar00rootroot00000000000000#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 { void setContext(Context&&) override { throw std::logic_error("Filesystem not initialized yet"); } 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"); } std::vector 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.h000066400000000000000000000002141445614261000152130ustar00rootroot00000000000000#pragma once #ifndef MESSMER_FSPP_FUSE_PARAMS_H_ #define MESSMER_FSPP_FUSE_PARAMS_H_ #define FUSE_USE_VERSION 26 #include #endif src/fspp/fuse/stat_compatibility.h000066400000000000000000000007751445614261000176500ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000135615ustar00rootroot00000000000000src/fspp/impl/FilesystemImpl.cpp000066400000000000000000000321241445614261000172350ustar00rootroot00000000000000#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 } void FilesystemImpl::setContext(Context&& context) { _device->setContext(std::move(context)); } 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); } } vector 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, macFUSE 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.h000066400000000000000000000107071445614261000167050ustar00rootroot00000000000000#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(); void setContext(Context&& context) override; 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; std::vector 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.h000066400000000000000000000056061445614261000171210ustar00rootroot00000000000000#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.h000066400000000000000000000030251445614261000151220ustar00rootroot00000000000000#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.cpp000066400000000000000000000000261445614261000160450ustar00rootroot00000000000000#include "Profiler.h" src/fspp/impl/Profiler.h000066400000000000000000000016611445614261000155200ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000140415ustar00rootroot00000000000000src/gitversion/.gitattributes000066400000000000000000000000711445614261000167320ustar00rootroot00000000000000_version_source.py export-subst _version.py export-subst src/gitversion/CMakeLists.txt000066400000000000000000000007211445614261000166010ustar00rootroot00000000000000project (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.in000066400000000000000000000001051445614261000155730ustar00rootroot00000000000000include versioneer.py include _version_source.py include _version.py src/gitversion/VersionCompare.cpp000066400000000000000000000030201445614261000174740ustar00rootroot00000000000000#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.h000066400000000000000000000005561445614261000171540ustar00rootroot00000000000000#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.py000066400000000000000000000410621445614261000162420ustar00rootroot00000000000000 # 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.11.4, origin/release/0.11, release/0.11)" git_full = "2c86a6ed376244cb2e3e1fe7e174554b813fcec1" 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.py000077500000000000000000000001131445614261000165760ustar00rootroot00000000000000#!/usr/bin/env python3 import versioneer print(versioneer.get_version()) src/gitversion/gitversion.cmake000066400000000000000000000016541445614261000172420ustar00rootroot00000000000000set(DIR_OF_GITVERSION_TOOL "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "DIR_OF_GITVERSION_TOOL") function (get_git_version OUTPUT_VARIABLE) EXECUTE_PROCESS(COMMAND python3 ${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.cpp000066400000000000000000000011171445614261000167360ustar00rootroot00000000000000#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.h000066400000000000000000000004371445614261000164070ustar00rootroot00000000000000#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.cpp000066400000000000000000000064521445614261000160500ustar00rootroot00000000000000#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.h000066400000000000000000000021541445614261000155100ustar00rootroot00000000000000#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.cfg000066400000000000000000000004611445614261000156630ustar00rootroot00000000000000 # 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.py000066400000000000000000000006201445614261000155510ustar00rootroot00000000000000#!/usr/bin/env python3 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.py000066400000000000000000002012471445614261000166020ustar00rootroot00000000000000 # 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.ConfigParser() with open(setup_cfg, "r") as f: parser.read_file(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.cpp000066400000000000000000000004161445614261000174620ustar00rootroot00000000000000#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.h000066400000000000000000000002601445614261000171240ustar00rootroot00000000000000#pragma once #ifndef GITVERSION_VERSIONSTRING_H #define GITVERSION_VERSIONSTRING_H #include namespace gitversion { const std::string &VersionString(); } #endif src/parallelaccessstore/000077500000000000000000000000001445614261000157035ustar00rootroot00000000000000src/parallelaccessstore/CMakeLists.txt000066400000000000000000000005061445614261000204440ustar00rootroot00000000000000project (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}) target_enable_style_warnings(${PROJECT_NAME}) target_activate_cpp14(${PROJECT_NAME}) src/parallelaccessstore/ParallelAccessBaseStore.cpp000066400000000000000000000000451445614261000230740ustar00rootroot00000000000000#include "ParallelAccessBaseStore.h" src/parallelaccessstore/ParallelAccessBaseStore.h000066400000000000000000000012311445614261000225370ustar00rootroot00000000000000#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.cpp000066400000000000000000000000411445614261000222750ustar00rootroot00000000000000#include "ParallelAccessStore.h" src/parallelaccessstore/ParallelAccessStore.h000066400000000000000000000261021445614261000217500ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000130065ustar00rootroot00000000000000src/stats/CMakeLists.txt000066400000000000000000000005321445614261000155460ustar00rootroot00000000000000project (stats) set(SOURCES main.cpp traversal.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.cpp000066400000000000000000000260751445614261000144500ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "traversal.h" #include using std::endl; using std::cout; using std::set; using std::flush; using std::vector; using boost::none; using boost::filesystem::path; using namespace cryfs; using namespace cpputils; using namespace blockstore; using namespace blockstore::ondisk; using namespace blockstore::readonly; using namespace blockstore::integrity; using namespace blockstore::lowtohighlevel; using namespace blobstore::onblocks; using namespace blobstore::onblocks::datanodestore; using namespace cryfs::fsblobstore; using namespace cryfs_stats; 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; } } unique_ref makeBlockStore(const path& basedir, const CryConfigLoader::ConfigLoadResult& config, LocalStateDir& localStateDir) { auto onDiskBlockStore = make_unique_ref(basedir); auto readOnlyBlockStore = make_unique_ref(std::move(onDiskBlockStore)); auto encryptedBlockStore = CryCiphers::find(config.configFile->config()->Cipher()).createEncryptedBlockstore(std::move(readOnlyBlockStore), 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)); } struct AccumulateBlockIds final { public: auto callback() { return [this] (const BlockId& id) { _blockIds.push_back(id); }; } const std::vector& blockIds() const { return _blockIds; } void reserve(size_t size) { _blockIds.reserve(size); } private: std::vector _blockIds; }; class ProgressBar final { public: ProgressBar(size_t numBlocks): _currentBlock(0), _numBlocks(numBlocks) {} auto callback() { return [this] (const BlockId&) { cout << "\r" << (++_currentBlock) << "/" << _numBlocks << flush; }; } private: size_t _currentBlock; size_t _numBlocks; }; 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; AccumulateBlockIds knownBlobIds; cout << "Listing all file system entities (i.e. blobs)..." << flush; auto rootId = BlockId::FromString(config.configFile->config()->RootBlob()); forEachReachableBlob(fsBlobStore.get(), rootId, {knownBlobIds.callback()}); cout << "done" << endl; return knownBlobIds.blockIds(); } 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()); AccumulateBlockIds knownBlockIds; const uint32_t numNodes = nodeStore->numNodes(); knownBlockIds.reserve(numNodes); cout << "Listing all blocks used by these file system entities..." << endl; for (const auto& blobId : knownBlobIds) { forEachReachableBlockInBlob(nodeStore.get(), blobId, { ProgressBar(numNodes).callback(), knownBlockIds.callback() }); } std::cout << "...done" << endl; return knownBlockIds.blockIds(); } set getAllBlockIds(const path& basedir, const CryConfigLoader::ConfigLoadResult& config, LocalStateDir& localStateDir) { auto blockStore = makeBlockStore(basedir, config, localStateDir); AccumulateBlockIds allBlockIds; allBlockIds.reserve(blockStore->numBlocks()); forEachBlock(blockStore.get(), {allBlockIds.callback()}); return set(allBlockIds.blockIds().begin(), allBlockIds.blockIds().end()); } void printConfig(const CryConfig& config) { std::cout << "----------------------------------------------------" << "\nFilesystem configuration:" << "\n----------------------------------------------------" << "\n- Filesystem format version: " << config.Version() << "\n- Created with: CryFS " << config.CreatedWithVersion() << "\n- Last opened with: CryFS " << config.LastOpenedWithVersion() << "\n- Cipher: " << config.Cipher() << "\n- Blocksize: " << config.BlocksizeBytes() << " bytes" << "\n- Filesystem Id: " << config.FilesystemId().ToString() << "\n- Root Blob Id: " << config.RootBlob(); if (config.missingBlockIsIntegrityViolation()) { ASSERT(config.ExclusiveClientId() != boost::none, "ExclusiveClientId must be set if missingBlockIsIntegrityViolation"); std::cout << "\n- Extended integrity measures: enabled." "\n - Exclusive client id: " << *config.ExclusiveClientId(); } else { ASSERT(config.ExclusiveClientId() == boost::none, "ExclusiveClientId must be unset if !missingBlockIsIntegrityViolation"); std::cout << "\n- Extended integrity measures: disabled."; } #ifndef CRYFS_NO_COMPATIBILITY std::cout << "\n- Has parent pointers: " << (config.HasParentPointers() ? "yes" : "no"); std::cout << "\n- Has version numbers: " << (config.HasVersionNumbers() ? "yes" : "no"); #endif std::cout << "\n----------------------------------------------------\n"; } 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, CryConfigFile::Access::ReadOnly); if (config.is_left()) { switch (config.left()) { case CryConfigFile::LoadError::ConfigFileNotFound: throw std::runtime_error("Error loading config file: Config file not found. Are you sure this is a valid CryFS file system?"); case CryConfigFile::LoadError::DecryptionFailed: throw std::runtime_error("Error loading config file: Decryption failed. Did you maybe enter a wrong password?"); } } const auto& config_ = config.right().configFile->config(); std::cout << "Loading filesystem" << std::endl; printConfig(*config_); #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) { 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.right(), localStateDir); cout << "done" << endl; vector accountedBlocks = getKnownBlockIds(basedir, config.right(), 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.right(), localStateDir); auto nodeStore = make_unique_ref(std::move(blockStore), config.right().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"); } src/stats/traversal.cpp000066400000000000000000000045011445614261000155150ustar00rootroot00000000000000#include "traversal.h" #include using blockstore::BlockId; using blockstore::BlockStore; using cryfs::fsblobstore::FsBlobStore; using cryfs::fsblobstore::DirBlob; using blobstore::onblocks::datanodestore::DataNodeStore; using blobstore::onblocks::datanodestore::DataInnerNode; using cpputils::dynamic_pointer_move; using std::vector; using std::function; using boost::none; namespace cryfs_stats { void forEachBlock(BlockStore* blockStore, const vector>& callbacks) { blockStore->forEachBlock([&callbacks] (const BlockId& blockId) { for(const auto& callback : callbacks) { callback(blockId); } }); } // NOLINTNEXTLINE(misc-no-recursion) void forEachReachableBlob(FsBlobStore* blobStore, const BlockId& rootId, const vector>& callbacks) { for (const auto& callback : callbacks) { callback(rootId); } auto rootBlob = blobStore->load(rootId); ASSERT(rootBlob != none, "Blob not found but referenced from directory entry"); auto rootDir = dynamic_pointer_move(*rootBlob); if (rootDir != none) { vector children; children.reserve((*rootDir)->NumChildren()); (*rootDir)->AppendChildrenTo(&children); for (const auto& child : children) { auto childEntry = (*rootDir)->GetChild(child.name); ASSERT(childEntry != none, "We just got this from the entry list, it must exist."); auto childId = childEntry->blockId(); forEachReachableBlob(blobStore, childId, callbacks); } } } // NOLINTNEXTLINE(misc-no-recursion) void forEachReachableBlockInBlob(DataNodeStore* nodeStore, const BlockId& rootId, const vector>& callbacks) { for (const auto& callback : callbacks) { callback(rootId); } auto node = nodeStore->load(rootId); auto innerNode = dynamic_pointer_move(*node); if (innerNode != none) { for (uint32_t childIndex = 0; childIndex < (*innerNode)->numChildren(); ++childIndex) { auto childId = (*innerNode)->readChild(childIndex).blockId(); forEachReachableBlockInBlob(nodeStore, childId, callbacks); } } } } src/stats/traversal.h000066400000000000000000000022571445614261000151700ustar00rootroot00000000000000#pragma once #ifndef CRYFS_STATS_TRAVERSAL_H #define CRYFS_STATS_TRAVERSAL_H #include #include #include #include #include namespace cryfs_stats { // Call the callbacks on each existing block, whether it is connected or orphaned void forEachBlock(blockstore::BlockStore* blockStore, const std::vector>& callbacks); // Call the callbacks on each existing blob that is reachable from the root blob, i.e. not orphaned void forEachReachableBlob(cryfs::fsblobstore::FsBlobStore* blobStore, const blockstore::BlockId& rootId, const std::vector>& callbacks); // Call the callbacks on each block that is reachable from the given blob root, i.e. belongs to this blob. void forEachReachableBlockInBlob(blobstore::onblocks::datanodestore::DataNodeStore* nodeStore, const blockstore::BlockId& rootId, const std::vector>& callbacks); } #endif test/000077500000000000000000000000001445614261000120405ustar00rootroot00000000000000test/CMakeLists.txt000066400000000000000000000006301445614261000145770ustar00rootroot00000000000000if (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/000077500000000000000000000000001445614261000140335ustar00rootroot00000000000000test/blobstore/CMakeLists.txt000066400000000000000000000033041445614261000165730ustar00rootroot00000000000000project (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/000077500000000000000000000000001445614261000172435ustar00rootroot00000000000000test/blobstore/implementations/onblocks/000077500000000000000000000000001445614261000210555ustar00rootroot00000000000000test/blobstore/implementations/onblocks/BigBlobsTest.cpp000066400000000000000000000124551445614261000241130ustar00rootroot00000000000000#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.cpp000066400000000000000000000241661445614261000251170ustar00rootroot00000000000000#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; namespace { 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_SUITE_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.cpp000066400000000000000000000110051445614261000241270ustar00rootroot00000000000000#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 = 0; blob->write(&value, 0, 4); EXPECT_EQ(4u, blob->size()); } TEST_F(BlobSizeTest, WritingAfterEndOfBlobGrowsBlob_Empty) { int value = 0; blob->write(&value, 2, 4); EXPECT_EQ(6u, blob->size()); } TEST_F(BlobSizeTest, WritingOverEndOfBlobGrowsBlob_NonEmpty) { blob->resize(1); int value = 0; blob->write(&value, 0, 4); EXPECT_EQ(4u, blob->size()); } TEST_F(BlobSizeTest, WritingAtEndOfBlobGrowsBlob_NonEmpty) { blob->resize(1); int value = 0; blob->write(&value, 1, 4); EXPECT_EQ(5u, blob->size()); } TEST_F(BlobSizeTest, WritingAfterEndOfBlobGrowsBlob_NonEmpty) { blob->resize(1); int value = 0; 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.cpp000066400000000000000000000027711445614261000243230ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000237115ustar00rootroot00000000000000test/blobstore/implementations/onblocks/datanodestore/DataInnerNodeTest.cpp000066400000000000000000000201771445614261000277370ustar00rootroot00000000000000#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.cpp000066400000000000000000000316541445614261000275350ustar00rootroot00000000000000#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; namespace { #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_SUITE_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_SUITE_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.cpp000066400000000000000000000123271445614261000277560ustar00rootroot00000000000000#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.cpp000066400000000000000000000065611445614261000275770ustar00rootroot00000000000000#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_SUITE_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_SUITE_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/000077500000000000000000000000001445614261000237235ustar00rootroot00000000000000test/blobstore/implementations/onblocks/datatreestore/DataTreeStoreTest.cpp000066400000000000000000000043461445614261000300040ustar00rootroot00000000000000#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.cpp000066400000000000000000000116321445614261000317720ustar00rootroot00000000000000#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_SUITE_P(EmptyLastLeaf, DataTreeTest_NumStoredBytes_P, Values(0u)); INSTANTIATE_TEST_SUITE_P(HalfFullLastLeaf, DataTreeTest_NumStoredBytes_P, Values(5u, 10u)); INSTANTIATE_TEST_SUITE_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.cpp000066400000000000000000000626371445614261000313170ustar00rootroot00000000000000#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.cpp000066400000000000000000000233011445614261000326400ustar00rootroot00000000000000#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)); } // NOLINTNEXTLINE(misc-no-recursion) 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()); } } // NOLINTNEXTLINE(misc-no-recursion) 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(); } // NOLINTNEXTLINE(misc-no-recursion) 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_SUITE_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.cpp000066400000000000000000000242561445614261000320010ustar00rootroot00000000000000#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)); } // NOLINTNEXTLINE(misc-no-recursion) 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()); } } // NOLINTNEXTLINE(misc-no-recursion) 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); } // NOLINTNEXTLINE(misc-no-recursion) 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_SUITE_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.cpp000066400000000000000000000476541445614261000300540ustar00rootroot00000000000000#include "testutils/DataTreeTest.h" #include #include 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_METHOD(void, calledExistingLeaf, (DataLeafNode*, bool, uint32_t)); MOCK_METHOD(shared_ptr, calledCreateLeaf, (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(testing::_, testing::_, testing::_)).Times(0); EXPECT_CALL(traversor, calledCreateLeaf(testing::_)).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/000077500000000000000000000000001445614261000246645ustar00rootroot00000000000000GetLowestInnerRightBorderNodeWithLessThanKChildrenOrNullTest.cpp000066400000000000000000000061211445614261000412420ustar00rootroot00000000000000test/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.cpp000066400000000000000000000101171445614261000400640ustar00rootroot00000000000000test/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/000077500000000000000000000000001445614261000257635ustar00rootroot00000000000000test/blobstore/implementations/onblocks/datatreestore/testutils/DataTreeTest.cpp000066400000000000000000000204311445614261000310200ustar00rootroot00000000000000#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()); } } } // NOLINTNEXTLINE(misc-no-recursion) 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.h000066400000000000000000000103771445614261000304750ustar00rootroot00000000000000#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.h000066400000000000000000000027041445614261000311470ustar00rootroot00000000000000#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.h000066400000000000000000000067541445614261000320520ustar00rootroot00000000000000#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: // NOLINTNEXTLINE(misc-no-recursion) 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/000077500000000000000000000000001445614261000231155ustar00rootroot00000000000000test/blobstore/implementations/onblocks/testutils/BlobStoreTest.cpp000066400000000000000000000010151445614261000263510ustar00rootroot00000000000000#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.h000066400000000000000000000014201445614261000260160ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000222155ustar00rootroot00000000000000test/blobstore/implementations/onblocks/utils/CeilDivisionTest.cpp000066400000000000000000000034641445614261000261510ustar00rootroot00000000000000#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.cpp000066400000000000000000000012551445614261000251020ustar00rootroot00000000000000#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.cpp000066400000000000000000000030331445614261000250000ustar00rootroot00000000000000#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.cpp000066400000000000000000000054141445614261000273700ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000142075ustar00rootroot00000000000000test/blockstore/CMakeLists.txt000066400000000000000000000050061445614261000167500ustar00rootroot00000000000000project (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/000077500000000000000000000000001445614261000174175ustar00rootroot00000000000000test/blockstore/implementations/caching/000077500000000000000000000000001445614261000210135ustar00rootroot00000000000000test/blockstore/implementations/caching/CachingBlockStore2Test_Generic.cpp000066400000000000000000000024261445614261000274250ustar00rootroot00000000000000#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_SUITE_P(Caching2, BlockStoreTest, CachingBlockStoreTestFixture); class CachingBlockStore2TestFixture: public BlockStore2TestFixture { public: unique_ref createBlockStore() override { return make_unique_ref(make_unique_ref()); } }; INSTANTIATE_TYPED_TEST_SUITE_P(Caching, BlockStore2Test, CachingBlockStore2TestFixture); test/blockstore/implementations/caching/CachingBlockStore2Test_Specific.cpp000066400000000000000000000042431445614261000275750ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000220565ustar00rootroot00000000000000test/blockstore/implementations/caching/cache/CacheTest_MoveConstructor.cpp000066400000000000000000000026341445614261000276660ustar00rootroot00000000000000#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.cpp000066400000000000000000000072541445614261000265360ustar00rootroot00000000000000#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.cpp000066400000000000000000000071671445614261000272410ustar00rootroot00000000000000#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.cpp000066400000000000000000000025701445614261000260070ustar00rootroot00000000000000#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.cpp000066400000000000000000000034571445614261000273020ustar00rootroot00000000000000#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.cpp000066400000000000000000000042011445614261000303750ustar00rootroot00000000000000#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.cpp000066400000000000000000000014261445614261000261130ustar00rootroot00000000000000#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.cpp000066400000000000000000000026611445614261000261430ustar00rootroot00000000000000#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.cpp000066400000000000000000000070361445614261000264710ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000241165ustar00rootroot00000000000000test/blockstore/implementations/caching/cache/testutils/CacheTest.cpp000066400000000000000000000005451445614261000264710ustar00rootroot00000000000000#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.h000066400000000000000000000020101445614261000261230ustar00rootroot00000000000000#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.cpp000066400000000000000000000001431445614261000315230ustar00rootroot00000000000000#include "CopyableMovableValueType.h" int CopyableMovableValueType::numCopyConstructorCalled = 0; test/blockstore/implementations/caching/cache/testutils/CopyableMovableValueType.h000066400000000000000000000021061445614261000311710ustar00rootroot00000000000000#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; } // NOLINTNEXTLINE(cert-oop54-cpp) 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.cpp000066400000000000000000000001141445614261000275170ustar00rootroot00000000000000#include "MinimalKeyType.h" std::atomic MinimalKeyType::instances(0); test/blockstore/implementations/caching/cache/testutils/MinimalKeyType.h000066400000000000000000000017171445614261000271760ustar00rootroot00000000000000#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.cpp000066400000000000000000000001201445614261000300400ustar00rootroot00000000000000#include "MinimalValueType.h" std::atomic MinimalValueType::instances(0); test/blockstore/implementations/caching/cache/testutils/MinimalValueType.h000066400000000000000000000024111445614261000275120ustar00rootroot00000000000000#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.cpp000066400000000000000000000020251445614261000272030ustar00rootroot00000000000000#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.h000066400000000000000000000021061445614261000266500ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000217505ustar00rootroot00000000000000test/blockstore/implementations/compressing/CompressingBlockStoreTest.cpp000066400000000000000000000021671445614261000276030ustar00rootroot00000000000000#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_SUITE_P(Compressing_Gzip, BlockStoreTest, CompressingBlockStoreTestFixture); INSTANTIATE_TYPED_TEST_SUITE_P(Compressing_RunLengthEncoding, BlockStoreTest, CompressingBlockStoreTestFixture); test/blockstore/implementations/compressing/compressors/000077500000000000000000000000001445614261000243275ustar00rootroot00000000000000test/blockstore/implementations/compressing/compressors/testutils/000077500000000000000000000000001445614261000263675ustar00rootroot00000000000000test/blockstore/implementations/compressing/compressors/testutils/CompressorTest.cpp000066400000000000000000000064321445614261000320740ustar00rootroot00000000000000#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_SUITE_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_SUITE_P(CompressorTest, Empty, ArbitraryData, Zeroes, Runs, RunsAndArbitrary, LargeData, LargeRuns, LargeRunsAndArbitrary ); INSTANTIATE_TYPED_TEST_SUITE_P(Gzip, CompressorTest, Gzip); INSTANTIATE_TYPED_TEST_SUITE_P(RunLengthEncoding, CompressorTest, RunLengthEncoding); test/blockstore/implementations/encrypted/000077500000000000000000000000001445614261000214145ustar00rootroot00000000000000test/blockstore/implementations/encrypted/EncryptedBlockStoreTest_Generic.cpp000066400000000000000000000054511445614261000303460ustar00rootroot00000000000000#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_SUITE_P(Encrypted_FakeCipher, BlockStoreTest, EncryptedBlockStoreTestFixture); INSTANTIATE_TYPED_TEST_SUITE_P(Encrypted_AES256_GCM, BlockStoreTest, EncryptedBlockStoreTestFixture); INSTANTIATE_TYPED_TEST_SUITE_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_SUITE_P(Encrypted_FakeCipher, BlockStore2Test, EncryptedBlockStore2TestFixture); INSTANTIATE_TYPED_TEST_SUITE_P(Encrypted_AES256_GCM, BlockStore2Test, EncryptedBlockStore2TestFixture); INSTANTIATE_TYPED_TEST_SUITE_P(Encrypted_AES256_CFB, BlockStore2Test, EncryptedBlockStore2TestFixture); test/blockstore/implementations/encrypted/EncryptedBlockStoreTest_Specific.cpp000066400000000000000000000121531445614261000305140ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000212565ustar00rootroot00000000000000test/blockstore/implementations/inmemory/InMemoryBlockStoreTest.cpp000066400000000000000000000023661445614261000263600ustar00rootroot00000000000000#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_SUITE_P(InMemory, BlockStoreTest, InMemoryBlockStoreTestFixture); class InMemoryBlockStore2TestFixture: public BlockStore2TestFixture { public: unique_ref createBlockStore() override { return make_unique_ref(); } }; INSTANTIATE_TYPED_TEST_SUITE_P(InMemory, BlockStore2Test, InMemoryBlockStore2TestFixture); test/blockstore/implementations/integrity/000077500000000000000000000000001445614261000214355ustar00rootroot00000000000000test/blockstore/implementations/integrity/IntegrityBlockStoreTest_Generic.cpp000066400000000000000000000072311445614261000304060ustar00rootroot00000000000000#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_SUITE_P(Integrity_multiclient, BlockStoreTest, IntegrityBlockStoreTestFixture_multiclient); INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_singleclient, BlockStoreTest, IntegrityBlockStoreTestFixture_singleclient); INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_multiclient_allowIntegrityViolations, BlockStoreTest, IntegrityBlockStoreTestFixture_multiclient_allowIntegrityViolations); INSTANTIATE_TYPED_TEST_SUITE_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_SUITE_P(Integrity_multiclient, BlockStore2Test, IntegrityBlockStore2TestFixture_multiclient); INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_singleclient, BlockStore2Test, IntegrityBlockStore2TestFixture_singleclient); INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_multiclient_allowIntegrityViolations, BlockStore2Test, IntegrityBlockStore2TestFixture_multiclient_allowIntegrityViolations); INSTANTIATE_TYPED_TEST_SUITE_P(Integrity_singleclient_allowIntegrityViolations, BlockStore2Test, IntegrityBlockStore2TestFixture_singleclient_allowIntegrityViolations); test/blockstore/implementations/integrity/IntegrityBlockStoreTest_Specific.cpp000066400000000000000000000407761445614261000305720ustar00rootroot00000000000000#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.cpp000066400000000000000000000364151445614261000266120ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000221725ustar00rootroot00000000000000test/blockstore/implementations/low2highlevel/LowToHighLevelBlockStoreTest.cpp000066400000000000000000000016161445614261000303660ustar00rootroot00000000000000#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_SUITE_P(LowToHighLevel, BlockStoreTest, LowToHighLevelBlockStoreTestFixture); test/blockstore/implementations/mock/000077500000000000000000000000001445614261000203505ustar00rootroot00000000000000test/blockstore/implementations/mock/MockBlockStoreTest.cpp000066400000000000000000000013321445614261000245740ustar00rootroot00000000000000#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_SUITE_P(Mock, BlockStoreTest, MockBlockStoreTestFixture); test/blockstore/implementations/ondisk/000077500000000000000000000000001445614261000207065ustar00rootroot00000000000000test/blockstore/implementations/ondisk/OnDiskBlockStoreTest_Generic.cpp000066400000000000000000000024711445614261000270710ustar00rootroot00000000000000#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_SUITE_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_SUITE_P(OnDisk, BlockStore2Test, OnDiskBlockStore2TestFixture); test/blockstore/implementations/ondisk/OnDiskBlockStoreTest_Specific.cpp000066400000000000000000000050051445614261000272360ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000237105ustar00rootroot00000000000000test/blockstore/implementations/ondisk/OnDiskBlockTest/OnDiskBlockCreateTest.cpp000066400000000000000000000057541445614261000305550ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000076601445614261000304310ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000043311445614261000302170ustar00rootroot00000000000000#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_SUITE_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/000077500000000000000000000000001445614261000223755ustar00rootroot00000000000000test/blockstore/implementations/parallelaccess/ParallelAccessBlockStoreTest_Generic.cpp000066400000000000000000000015461445614261000322510ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000042311445614261000324140ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000212255ustar00rootroot00000000000000test/blockstore/implementations/testfake/TestFakeBlockStoreTest.cpp000066400000000000000000000012351445614261000262700ustar00rootroot00000000000000#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_SUITE_P(TestFake, BlockStoreTest, FakeBlockStoreTestFixture); test/blockstore/interface/000077500000000000000000000000001445614261000161475ustar00rootroot00000000000000test/blockstore/interface/BlockStore2Test.cpp000066400000000000000000000127651445614261000216570ustar00rootroot00000000000000#include "blockstore/interface/BlockStore2.h" #include #include #include using ::testing::Test; 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_METHOD(BlockId, createBlockId, (), (const, override)); MOCK_METHOD(bool, tryCreate, (const BlockId &blockId, const cpputils::Data &data), (override)); MOCK_METHOD(void, store, (const BlockId &, const Data &data), (override)); MOCK_METHOD(optional, load, (const BlockId &), (const, override)); MOCK_METHOD(bool, remove, (const BlockId &), (override)); MOCK_METHOD(uint64_t, numBlocks, (), (const, override)); MOCK_METHOD(uint64_t, estimateNumFreeBytes, (), (const, override)); MOCK_METHOD(uint64_t, blockSizeFromPhysicalBlockSize, (uint64_t), (const, override)); MOCK_METHOD(void, forEachBlock, (std::function), (const, override)); }; 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(testing::_, 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(testing::_, 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(testing::_, 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, testing::_)).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(testing::_, testing::_)) .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(testing::_, 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(testing::_, 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.cpp000066400000000000000000000144411445614261000215660ustar00rootroot00000000000000#include "blockstore/interface/BlockStore.h" #include #include #include #include using ::testing::Test; using ::testing::Return; using ::testing::Invoke; using ::testing::Eq; using ::testing::ByRef; using ::testing::Action; using std::string; using cpputils::Data; using cpputils::DataFixture; using cpputils::unique_ref; using cpputils::make_unique_ref; using boost::optional; using namespace blockstore; class BlockStoreMock: public BlockStore { public: MOCK_METHOD(BlockId, createBlockId, (), (override)); MOCK_METHOD(optional>, tryCreate, (const BlockId &, Data data), (override)); MOCK_METHOD(unique_ref, overwrite, (const BlockId &, Data data), (override)); MOCK_METHOD(optional>, load, (const BlockId &), (override)); MOCK_METHOD(void, remove, (unique_ref), (override)); MOCK_METHOD(void, remove, (const BlockId &), (override)); MOCK_METHOD(uint64_t, numBlocks, (), (const, override)); MOCK_METHOD(uint64_t, estimateNumFreeBytes, (), (const, override)); MOCK_METHOD(uint64_t, blockSizeFromPhysicalBlockSize, (uint64_t), (const, override)); MOCK_METHOD(void, forEachBlock, (std::function), (const, override)); }; class BlockMock: public Block { public: BlockMock(): Block(BlockId::Random()) {} MOCK_METHOD(const void*, data, (), (const, override)); MOCK_METHOD(void, write, (const void*, uint64_t, uint64_t), (override)); MOCK_METHOD(void, flush, (), (override)); MOCK_METHOD(size_t, size, (), (const, override)); MOCK_METHOD(void, resize, (size_t), (override)); }; 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; } }; const Action>(const BlockId &, cpputils::Data)> ReturnNewBlockMock = Invoke( [] (const BlockId&, cpputils::Data) { return optional>(unique_ref(make_unique_ref())); }); TEST_F(BlockStoreTest, DataIsPassedThrough0) { Data data = createDataWithSize(0); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data)))).WillOnce(ReturnNewBlockMock); blockStore.create(data); } TEST_F(BlockStoreTest, DataIsPassedThrough1) { Data data = createDataWithSize(1); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data)))).WillOnce(ReturnNewBlockMock); blockStore.create(data); } TEST_F(BlockStoreTest, DataIsPassedThrough1024) { Data data = createDataWithSize(1024); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, tryCreate(testing::_, Eq(ByRef(data)))).WillOnce(ReturnNewBlockMock); blockStore.create(data); } TEST_F(BlockStoreTest, BlockIdIsCorrect) { Data data = createDataWithSize(1024); EXPECT_CALL(blockStoreMock, createBlockId()).WillOnce(Return(blockId1)); EXPECT_CALL(blockStoreMock, tryCreate(blockId1, testing::_)).WillOnce(ReturnNewBlockMock); blockStore.create(data); } TEST_F(BlockStoreTest, TwoBlocksGetDifferentIds) { EXPECT_CALL(blockStoreMock, createBlockId()) .WillOnce(Return(blockId1)) .WillOnce(Return(blockId2)); EXPECT_CALL(blockStoreMock, tryCreate(testing::_, testing::_)) .WillOnce(Invoke([this](const BlockId &blockId, Data) { EXPECT_EQ(blockId1, blockId); return optional>(unique_ref(make_unique_ref())); })) .WillOnce(Invoke([this](const BlockId &blockId, Data) { EXPECT_EQ(blockId2, blockId); return optional>(unique_ref(make_unique_ref())); })); 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, tryCreate(testing::_, Eq(ByRef(data)))) .WillOnce(Invoke([this](const BlockId &blockId, Data ) { EXPECT_EQ(blockId1, blockId); return boost::none; })) .WillOnce(Invoke([this](const BlockId &blockId, Data ) { EXPECT_EQ(blockId2, blockId); return optional>(unique_ref(make_unique_ref())); })); 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, tryCreate(testing::_, Eq(ByRef(data)))) .WillOnce(Invoke([this](const BlockId &blockId, Data) { EXPECT_EQ(blockId1, blockId); return boost::none; })) .WillOnce(Invoke([this](const BlockId &blockId, Data) { EXPECT_EQ(blockId2, blockId); return boost::none; })) .WillOnce(Invoke([this](const BlockId &blockId, Data) { EXPECT_EQ(blockId3, blockId); return optional>(unique_ref(make_unique_ref())); })); blockStore.create(data); } test/blockstore/interface/BlockTest.cpp000066400000000000000000000002241445614261000205430ustar00rootroot00000000000000/* * Tests that the header can be included without needing additional header includes as dependencies. */ #include "blockstore/interface/Block.h" test/blockstore/testutils/000077500000000000000000000000001445614261000162475ustar00rootroot00000000000000test/blockstore/testutils/BlockStore2Test.h000066400000000000000000000553531445614261000214240ustar00rootroot00000000000000#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_SUITE_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_SUITE_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.h000066400000000000000000000400371445614261000213330ustar00rootroot00000000000000#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_SUITE_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_SUITE_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.h000066400000000000000000000177541445614261000222760ustar00rootroot00000000000000#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.h000066400000000000000000000145231445614261000223260ustar00rootroot00000000000000#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.h000066400000000000000000000006711445614261000215000ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000153475ustar00rootroot00000000000000test/blockstore/utils/BlockStoreUtilsTest.cpp000066400000000000000000000066151445614261000220130ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000137605ustar00rootroot00000000000000test/cpp-utils/CMakeLists.txt000066400000000000000000000051041445614261000165200ustar00rootroot00000000000000project (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 either_test.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.cpp000066400000000000000000000001551445614261000200550ustar00rootroot00000000000000#include "cpp-utils/macros.h" // Test that macros.h can be included without needing additional dependencies test/cpp-utils/assert/000077500000000000000000000000001445614261000152615ustar00rootroot00000000000000test/cpp-utils/assert/assert_debug_test.cpp000066400000000000000000000024631445614261000215000ustar00rootroot00000000000000#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, whenDisablingAbort_thenThrowsIfFalse) { cpputils::_assert::DisableAbortOnFailedAssertionRAII _disableAbort; EXPECT_THROW( ASSERT(false, "bla"), cpputils::AssertFailed ); } 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.cpp000066400000000000000000000030061445614261000220240ustar00rootroot00000000000000#include #include #include #ifdef NDEBUG #define REAL_NDEBUG_ #endif //Include the ASSERT macro for a release build #ifndef NDEBUG #define NDEBUG 1 #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.cpp000066400000000000000000000142531445614261000207500ustar00rootroot00000000000000#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 = bf::canonical(get_executable().parent_path()) / "cpp-utils-test_exit_signal.exe"; #else auto executable = bf::canonical(get_executable().parent_path()) / "cpp-utils-test_exit_signal"; #endif if (!bf::exists(executable)) { throw std::runtime_error(executable.string() + " not found."); } auto result = cpputils::Subprocess::call(executable, {kind, signal}, ""); return result.output_stderr; } } #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 { #if !(defined(__clang__) && defined(NDEBUG)) std::string call_process_exiting_with_nullptr_violation() { return call_process_exiting_with("nullptr"); } #endif 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 } #if !(defined(__clang__) && defined(NDEBUG)) // TODO Can we also make this work on clang in Release mode? 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 } #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("#0")); } 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.cpp000066400000000000000000000014671445614261000203030ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000153005ustar00rootroot00000000000000test/cpp-utils/crypto/hash/000077500000000000000000000000001445614261000162235ustar00rootroot00000000000000test/cpp-utils/crypto/hash/HashTest.cpp000066400000000000000000000026351445614261000204600ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000160445ustar00rootroot00000000000000test/cpp-utils/crypto/kdf/SCryptParametersTest.cpp000066400000000000000000000044431445614261000226650ustar00rootroot00000000000000#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.cpp000066400000000000000000000056101445614261000206360ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000173145ustar00rootroot00000000000000test/cpp-utils/crypto/symmetric/CipherTest.cpp000066400000000000000000000271241445614261000221000ustar00rootroot00000000000000#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_SUITE_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_SUITE_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_SUITE_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_SUITE_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_SUITE_P(Fake, CipherTest, FakeAuthenticatedCipher); INSTANTIATE_TYPED_TEST_SUITE_P(Fake, AuthenticatedCipherTest, FakeAuthenticatedCipher); INSTANTIATE_TYPED_TEST_SUITE_P(XChaCha20Poly1305, CipherTest, XChaCha20Poly1305); INSTANTIATE_TYPED_TEST_SUITE_P(XChaCha20Poly1305, AuthenticatedCipherTest, XChaCha20Poly1305); INSTANTIATE_TYPED_TEST_SUITE_P(AES256_CFB, CipherTest, AES256_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_SUITE_P(AES256_GCM, CipherTest, AES256_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(AES256_GCM, AuthenticatedCipherTest, AES256_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(AES128_CFB, CipherTest, AES128_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_SUITE_P(AES128_GCM, CipherTest, AES128_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(AES128_GCM, AuthenticatedCipherTest, AES128_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Twofish256_CFB, CipherTest, Twofish256_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_SUITE_P(Twofish256_GCM, CipherTest, Twofish256_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Twofish256_GCM, AuthenticatedCipherTest, Twofish256_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Twofish128_CFB, CipherTest, Twofish128_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_SUITE_P(Twofish128_GCM, CipherTest, Twofish128_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Twofish128_GCM, AuthenticatedCipherTest, Twofish128_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Serpent256_CFB, CipherTest, Serpent256_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_SUITE_P(Serpent256_GCM, CipherTest, Serpent256_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Serpent256_GCM, AuthenticatedCipherTest, Serpent256_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Serpent128_CFB, CipherTest, Serpent128_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_SUITE_P(Serpent128_GCM, CipherTest, Serpent128_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Serpent128_GCM, AuthenticatedCipherTest, Serpent128_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Cast256_CFB, CipherTest, Cast256_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_SUITE_P(Cast256_GCM, CipherTest, Cast256_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Cast256_GCM, AuthenticatedCipherTest, Cast256_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Mars448_CFB, CipherTest, Mars448_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_SUITE_P(Mars448_GCM, CipherTest, Mars448_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Mars448_GCM, AuthenticatedCipherTest, Mars448_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Mars256_CFB, CipherTest, Mars256_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_SUITE_P(Mars256_GCM, CipherTest, Mars256_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Mars256_GCM, AuthenticatedCipherTest, Mars256_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Mars128_CFB, CipherTest, Mars128_CFB); //CFB mode is not authenticated INSTANTIATE_TYPED_TEST_SUITE_P(Mars128_GCM, CipherTest, Mars128_GCM); INSTANTIATE_TYPED_TEST_SUITE_P(Mars128_GCM, AuthenticatedCipherTest, Mars128_GCM); // Test cipher names TEST(CipherNameTest, TestCipherNames) { EXPECT_EQ("xchacha20-poly1305", string(XChaCha20Poly1305::NAME)); 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)); EXPECT_EQ("mars-448-gcm", string(Mars448_GCM::NAME)); EXPECT_EQ("mars-448-cfb", string(Mars448_CFB::NAME)); 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/000077500000000000000000000000001445614261000146715ustar00rootroot00000000000000test/cpp-utils/data/DataFixtureIncludeTest.cpp000066400000000000000000000001641445614261000217620ustar00rootroot00000000000000#include "cpp-utils/data/DataFixture.h" // Test the header can be included without needing additional dependencies test/cpp-utils/data/DataFixtureTest.cpp000066400000000000000000000044151445614261000204610ustar00rootroot00000000000000#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.cpp000066400000000000000000000001551445614261000204130ustar00rootroot00000000000000#include "cpp-utils/data/Data.h" // Test the header can be included without needing additional dependencies test/cpp-utils/data/DataTest.cpp000066400000000000000000000217501445614261000171130ustar00rootroot00000000000000#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 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_SUITE_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_SUITE_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_METHOD(void* , allocate, (size_t), (override)); MOCK_METHOD(void, free, (void*, size_t), (override)); }; 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(testing::_, testing::_)).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(testing::_, testing::_)).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.cpp000066400000000000000000000001661445614261000222300ustar00rootroot00000000000000#include "cpp-utils/data/FixedSizeData.h" // Test the header can be included without needing additional dependencies test/cpp-utils/data/FixedSizeDataTest.cpp000066400000000000000000000161721445614261000207300ustar00rootroot00000000000000#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_SUITE_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_SUITE_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_SUITE_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.cpp000066400000000000000000000141441445614261000222160ustar00rootroot00000000000000#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/either_test.cpp000066400000000000000000001076231445614261000170140ustar00rootroot00000000000000#include #include #include #include #include #include using std::string; using std::vector; using std::pair; using std::tuple; using std::ostringstream; using cpputils::either; using cpputils::make_left; using cpputils::make_right; // TODO Test noexcept tags are correct namespace { class MovableOnly final { public: explicit MovableOnly(int value): _value(value) {} MovableOnly(const MovableOnly&) = delete; MovableOnly& operator=(const MovableOnly&) = delete; MovableOnly(MovableOnly&& rhs): _value(rhs._value) { rhs._value = 0; } MovableOnly& operator=(MovableOnly&& rhs) { _value = rhs._value; rhs._value = 0; return *this; } int value() const { return _value; } private: int _value; }; bool operator==(const MovableOnly& lhs, const MovableOnly& rhs) { return lhs.value() == rhs.value(); } std::ostream& operator<<(std::ostream& str, const MovableOnly& v) { return str << "MovableOnly(" << v.value() << ")"; } template void test_with_matrix(std::vector)>> setups, std::vector> expectations) { for (const auto& setup: setups) { for (const auto& expectation: expectations) { setup(expectation); } } } template std::vector&)>> EXPECT_IS_LEFT(const Left& expected) { return { [&] (auto& obj) { EXPECT_TRUE(obj.is_left()); }, [&] (auto& obj) { EXPECT_FALSE(obj.is_right()); }, [&] (auto& obj) { EXPECT_EQ(expected, obj.left()); }, [&] (auto& obj) { EXPECT_EQ(expected, std::move(obj).left()); }, [&] (auto& obj) { EXPECT_ANY_THROW(obj.right()); }, [&] (auto& obj) { EXPECT_ANY_THROW(std::move(obj).right()); }, [&] (auto& obj) { EXPECT_EQ(expected, obj.left_opt().value()); }, [&] (auto& obj) { EXPECT_EQ(expected, std::move(obj).left_opt().value()); }, [&] (auto& obj) { EXPECT_TRUE(boost::none == obj.right_opt()); }, [&] (auto& obj) { EXPECT_TRUE(boost::none == std::move(obj).right_opt()); } }; } template std::vector&)>> EXPECT_IS_RIGHT(const Right& expected) { return { [&] (auto& obj) { EXPECT_FALSE(obj.is_left()); }, [&] (auto& obj) { EXPECT_TRUE(obj.is_right()); }, [&] (auto& obj) { EXPECT_EQ(expected, obj.right()); }, [&] (auto& obj) { EXPECT_EQ(expected, std::move(obj).right()); }, [&] (auto& obj) { EXPECT_ANY_THROW(obj.left()); }, [&] (auto& obj) { EXPECT_ANY_THROW(std::move(obj).left()); }, [&] (auto& obj) { EXPECT_EQ(expected, obj.right_opt().value()); }, [&] (auto& obj) { EXPECT_EQ(expected, std::move(obj).right_opt().value()); }, [&] (auto& obj) { EXPECT_TRUE(boost::none == obj.left_opt()); }, [&] (auto& obj) { EXPECT_TRUE(boost::none == std::move(obj).left_opt()); } }; } template std::vector> EXPECT_IS(const Value& v) { return { [&] (auto& obj) { return obj == v; } }; } template struct StoreWith1ByteFlag { T val; char flag; }; template void TestSpaceUsage() { EXPECT_EQ(std::max(sizeof(StoreWith1ByteFlag), sizeof(StoreWith1ByteFlag)), sizeof(either)); } } TEST(EitherTest, SpaceUsage) { TestSpaceUsage(); TestSpaceUsage(); TestSpaceUsage(); TestSpaceUsage(); TestSpaceUsage>(); } TEST(EitherTest, givenLeft) { test_with_matrix({ [] (const auto& test) { either a(4); test(a); }, [] (const auto& test) { either a = 4; test(a); }, }, EXPECT_IS_LEFT(4) ); } TEST(EitherTest, givenRight) { test_with_matrix({ [] (const auto& test) { either a("4"); test(a); }, [] (const auto& test) { either a = string("4"); test(a); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenMakeLeft) { test_with_matrix({ [] (const auto& test) { either a = make_left(4); test(a); }, [] (const auto& test) { auto a = make_left(4); test(a); }, }, EXPECT_IS_LEFT(4) ); } TEST(EitherTest, givenMakeLeftWithSameType) { test_with_matrix({ [] (const auto& test) { either a = make_left(4); test(a); }, [] (const auto& test) { auto a = make_left(4); test(a); }, }, EXPECT_IS_LEFT(4) ); } TEST(EitherTest, givenMakeRight) { test_with_matrix({ [] (const auto& test) { either a = make_right("4"); test(a); }, [] (const auto& test) { auto a = make_right("4"); test(a); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenMakeRightWithSameType) { test_with_matrix({ [] (const auto& test) { either a = make_right("4"); test(a); }, [] (const auto& test) { auto a = make_right("4"); test(a); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenMovableOnlyMakeLeft) { test_with_matrix({ [] (const auto& test) { either a = make_left(3); test(a); }, [] (const auto& test) { auto a = make_left(3); test(a); }, }, EXPECT_IS_LEFT(MovableOnly(3)) ); } TEST(EitherTest, givenMovableOnlyMakeRight) { test_with_matrix({ [] (const auto& test) { either a = make_right(3); test(a); }, [] (const auto& test) { auto a = make_right(3); test(a); } }, EXPECT_IS_RIGHT(MovableOnly(3)) ); } TEST(EitherTest, givenMultiParamMakeLeft) { test_with_matrix({ [] (const auto& test) { either, string> a = make_left, string>(5, 6); test(a); }, [] (const auto& test) { auto a = make_left, string>(5, 6); test(a); }, }, EXPECT_IS_LEFT, string>(pair(5, 6)) ); } TEST(EitherTest, givenMultiParamMakeRight) { test_with_matrix({ [] (const auto& test) { either> a = make_right>(5, 6); test(a); }, [] (const auto& test) { auto a = make_right>(5, 6); test(a); } }, EXPECT_IS_RIGHT>(pair(5, 6)) ); } TEST(EitherTest, givenLeftCopyConstructedFromValue_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { string a = "4"; either b(a); test(b); } }, EXPECT_IS_LEFT("4") ); } TEST(EitherTest, givenLeftCopyConstructedFromValue_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { string a = "4"; either b(a); test(a); } }, EXPECT_IS("4") ); } TEST(EitherTest, givenRightCopyConstructedFromValue_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { string a = "4"; either b(a); test(b); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenRightCopyConstructedFromValue_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { string a = "4"; either b(a); test(a); } }, EXPECT_IS("4") ); } TEST(EitherTest, givenLeftMoveConstructedFromValue_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { MovableOnly a(3); either b(std::move(a)); test(b); } }, EXPECT_IS_LEFT(MovableOnly(3)) ); } TEST(EitherTest, givenLeftMoveConstructedFromValue_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { MovableOnly a(3); either b(std::move(a)); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS(MovableOnly(0)) // 0 is moved-from value ); } TEST(EitherTest, givenRightMoveConstructedFromValue_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { MovableOnly a(3); either b(std::move(a)); test(b); } }, EXPECT_IS_RIGHT(MovableOnly(3)) ); } TEST(EitherTest, givenRightMoveConstructedFromValue_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { MovableOnly a(3); either b(std::move(a)); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS(MovableOnly(0)) // 0 is moved-from value ); } TEST(EitherTest, givenLeftCopyAssignedFromValue_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { string a = "4"; either b(2); b = a; test(b); }, [] (const auto& test) { string a = "4"; either b("2"); b = a; test(b); } }, EXPECT_IS_LEFT("4") ); } TEST(EitherTest, givenLeftCopyAssignedFromValue_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { string a = "4"; either b(2); b = a; test(a); }, [] (const auto& test) { string a = "4"; either b("2"); b = a; test(a); } }, EXPECT_IS("4") ); } TEST(EitherTest, givenRightCopyAssignedFromValue_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { string a = "4"; either b(2); b = a; test(b); }, [] (const auto& test) { string a = "4"; either b("2"); b = a; test(b); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenRightCopyAssignedFromValue_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { string a = "4"; either b(2); b = a; test(a); }, [] (const auto& test) { string a = "4"; either b("2"); b = a; test(a); } }, EXPECT_IS("4") ); } TEST(EitherTest, givenLeftMoveAssignedFromValue_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { MovableOnly a(3); either b(2); b = std::move(a); test(b); }, [] (const auto& test) { MovableOnly a(3); either b(MovableOnly(2)); b = std::move(a); test(b); } }, EXPECT_IS_LEFT(MovableOnly(3)) ); } TEST(EitherTest, givenLeftMoveAssignedFromValue_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { MovableOnly a(3); either b("2"); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) }, [] (const auto& test) { MovableOnly a(3); either b(MovableOnly(0)); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS(MovableOnly(0)) ); } TEST(EitherTest, givenRightMoveAssignedFromValue_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { MovableOnly a(3); either b("2"); b = std::move(a); test(b); }, [] (const auto& test) { MovableOnly a(3); either b(MovableOnly(2)); b = std::move(a); test(b); } }, EXPECT_IS_RIGHT(MovableOnly(3)) ); } TEST(EitherTest, givenRightMoveAssignedFromValue_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { MovableOnly a(3); either b("2"); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) }, [] (const auto& test) { MovableOnly a(3); either b(MovableOnly(2)); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS(MovableOnly(0)) // 0 is moved-from value ); } TEST(EitherTest, givenLeftCopyConstructed_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a("4"); either b(a); test(b); } }, EXPECT_IS_LEFT("4") ); } TEST(EitherTest, givenLeftCopyConstructed_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a("4"); either b(a); test(a); } }, EXPECT_IS_LEFT("4") ); } TEST(EitherTest, givenLeftCopyConstructed_withSameType_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_left("4"); either b(a); test(b); } }, EXPECT_IS_LEFT("4") ); } TEST(EitherTest, givenLeftCopyConstructed_withSameType_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_left("4"); either b(a); test(a); } }, EXPECT_IS_LEFT("4") ); } TEST(EitherTest, givenRightCopyConstructed_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a("4"); either b(a); test(b); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenRightCopyConstructed_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a("4"); either b(a); test(a); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenRightCopyConstructed_withSameType_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_right("4"); either b(a); test(b); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenRightCopyConstructed_withSameType_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_right("4"); either b(a); test(a); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenLeftMoveConstructed_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a(MovableOnly(3)); either b(std::move(a)); test(b); } }, EXPECT_IS_LEFT(MovableOnly(3)) ); } TEST(EitherTest, givenLeftMoveConstructed_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a(MovableOnly(3)); either b(std::move(a)); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS_LEFT(MovableOnly(0)) // 0 is moved-from value ); } TEST(EitherTest, givenLeftMoveConstructed_withSameType_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_left(MovableOnly(3)); either b(std::move(a)); test(b); } }, EXPECT_IS_LEFT(MovableOnly(3)) ); } TEST(EitherTest, givenLeftMoveConstructed_withSameType_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_left(MovableOnly(3)); either b(std::move(a)); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS_LEFT(MovableOnly(0)) // 0 is moved-from value ); } TEST(EitherTest, givenRightMoveConstructed_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a(MovableOnly(3)); either b(std::move(a)); test(b); } }, EXPECT_IS_RIGHT(MovableOnly(3)) ); } TEST(EitherTest, givenRightMoveConstructed_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a(MovableOnly(3)); either b(std::move(a)); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS_RIGHT(MovableOnly(0)) // 0 is moved-from value ); } TEST(EitherTest, givenRightMoveConstructed_withSameType_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_right(MovableOnly(3)); either b(std::move(a)); test(b); } }, EXPECT_IS_RIGHT(MovableOnly(3)) ); } TEST(EitherTest, givenRightMoveConstructed_withSameType_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_right(MovableOnly(3)); either b(std::move(a)); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS_RIGHT(MovableOnly(0)) // 0 is moved-from value ); } TEST(EitherTest, givenLeftCopyAssigned_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a("4"); either b(2); b = a; test(b); }, [] (const auto& test) { either a("4"); either b("2"); b = a; test(b); } }, EXPECT_IS_LEFT("4") ); } TEST(EitherTest, givenLeftCopyAssigned_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a("4"); either b(2); b = a; test(a); }, [] (const auto& test) { either a("4"); either b("2"); b = a; test(a); } }, EXPECT_IS_LEFT("4") ); } TEST(EitherTest, givenLeftCopyAssigned_withSameType_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_left("4"); either b = make_right("2"); b = a; test(b); }, [] (const auto& test) { either a = make_left("4"); either b = make_left("2"); b = a; test(b); } }, EXPECT_IS_LEFT("4") ); } TEST(EitherTest, givenLeftCopyAssigned_withSameType_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_left("4"); either b = make_right("2"); b = a; test(a); }, [] (const auto& test) { either a = make_left("4"); either b = make_left("2"); b = a; test(a); } }, EXPECT_IS_LEFT("4") ); } TEST(EitherTest, givenRightCopyAssigned_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a("4"); either b(2); b = a; test(b); }, [] (const auto& test) { either a("4"); either b("2"); b = a; test(b); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenRightCopyAssigned_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a("4"); either b(2); b = a; test(a); }, [] (const auto& test) { either a("4"); either b("2"); b = a; test(a); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenRightCopyAssigned_withSameType_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_right("4"); either b = make_left("2"); b = a; test(b); }, [] (const auto& test) { either a = make_right("4"); either b = make_right("2"); b = a; test(b); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenRightCopyAssigned_withSameType_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_right("4"); either b = make_left("2"); b = a; test(a); }, [] (const auto& test) { either a = make_right("4"); either b = make_right("2"); b = a; test(a); } }, EXPECT_IS_RIGHT("4") ); } TEST(EitherTest, givenLeftMoveAssigned_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a(MovableOnly(3)); either b(2); b = std::move(a); test(b); }, [] (const auto& test) { either a(MovableOnly(3)); either b(MovableOnly(2)); b = std::move(a); test(b); } }, EXPECT_IS_LEFT(MovableOnly(3)) ); } TEST(EitherTest, givenLeftMoveAssigned_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a(MovableOnly(3)); either b(2); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) }, [] (const auto& test) { either a(MovableOnly(3)); either b(MovableOnly(2)); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS_LEFT(MovableOnly(0)) // 0 is moved-from value ); } TEST(EitherTest, givenLeftMoveAssigned_withSameType_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_left(3); either b = make_right(2); b = std::move(a); test(b); }, [] (const auto& test) { either a = make_left(3); either b = make_left(2); b = std::move(a); test(b); } }, EXPECT_IS_LEFT(MovableOnly(3)) ); } TEST(EitherTest, givenLeftMoveAssigned_withSameType_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_left(3); either b = make_right(2); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) }, [] (const auto& test) { either a = make_left(3); either b = make_left(2); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS_LEFT(MovableOnly(0)) // 0 is moved-from value ); } TEST(EitherTest, givenRightMoveAssigned_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a(MovableOnly(3)); either b("2"); b = std::move(a); test(b); }, [] (const auto& test) { either a(MovableOnly(3)); either b(MovableOnly(2)); b = std::move(a); test(b); } }, EXPECT_IS_RIGHT(MovableOnly(3)) ); } TEST(EitherTest, givenRightMoveAssigned_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a(MovableOnly(3)); either b("2"); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) }, [] (const auto& test) { either a(MovableOnly(3)); either b(MovableOnly(2)); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS_RIGHT(MovableOnly(0)) // 0 is moved-from value ); } TEST(EitherTest, givenRightMoveAssigned_withSameType_thenNewIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_right(3); either b = make_left(2); b = std::move(a); test(b); }, [] (const auto& test) { either a = make_right(3); either b = make_right(2); b = std::move(a); test(b); } }, EXPECT_IS_RIGHT(MovableOnly(3)) ); } TEST(EitherTest, givenRightMoveAssigned_withSameType_thenOldIsCorrect) { test_with_matrix({ [] (const auto& test) { either a = make_right(3); either b = make_left(2); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) }, [] (const auto& test) { either a = make_right(3); either b = make_right(2); b = std::move(a); test(a); // NOLINT(bugprone-use-after-move) } }, EXPECT_IS_RIGHT(MovableOnly(0)) // 0 is moved-from value ); } TEST(EitherTest, givenLeft_whenModified_thenValueIsChanged) { test_with_matrix({ [] (const auto& test) { either a(4); a.left() = 5; test(a); }, [] (const auto& test) { either a(4); *a.left_opt() = 5; // NOLINT(clang-analyzer-core.uninitialized.UndefReturn) test(a); } }, EXPECT_IS_LEFT(5) ); } TEST(EitherTest, givenRight_whenModified_thenValueIsChanged) { test_with_matrix({ [] (const auto& test) { either a("4"); a.right() = "5"; test(a); }, [] (const auto& test) { either a("4"); *a.right_opt() = "5"; // NOLINT(clang-analyzer-core.uninitialized.UndefReturn) test(a); } }, EXPECT_IS_RIGHT("5") ); } TEST(EitherTest, canEmplaceConstructLeft) { test_with_matrix({ [] (const auto& test) { either, tuple> a(2, 3); test(a); } }, EXPECT_IS_LEFT, tuple>(tuple(2, 3)) ); } TEST(EitherTest, canEmplaceConstructRight) { test_with_matrix({ [] (const auto& test) { either, tuple> a(2, "3", 4); test(a); } }, EXPECT_IS_RIGHT, tuple>(tuple(2, "3", 4)) ); } TEST(EitherTest, givenEqualLefts_thenAreEqual) { either a("3"); either b("3"); EXPECT_TRUE(a == b); } TEST(EitherTest, givenEqualLefts_thenAreNotUnequal) { either a("3"); either b("3"); EXPECT_FALSE(a != b); } TEST(EitherTest, givenEqualRights_thenAreEqual) { either a(3); either b(3); EXPECT_TRUE(a == b); } TEST(EitherTest, givenEqualRights_thenAreNotUnequal) { either a(3); either b(3); EXPECT_FALSE(a != b); } TEST(EitherTest, givenLeftAndRight_thenAreNotEqual) { either a("3"); either b(3); EXPECT_FALSE(a == b); EXPECT_FALSE(b == a); } TEST(EitherTest, givenLeftAndRight_thenAreUnequal) { either a("3"); either b(3); EXPECT_TRUE(a != b); EXPECT_TRUE(b != a); } TEST(EitherTest, OutputLeft) { ostringstream str; str << either("mystring"); EXPECT_EQ("Left(mystring)", str.str()); } TEST(EitherTest, OutputRight) { ostringstream str; str << either("mystring"); EXPECT_EQ("Right(mystring)", str.str()); } TEST(EitherTest, givenLeftAndRightWithSameType_thenAreNotEqual) { either a = make_left("3"); either b = make_right("3"); EXPECT_FALSE(a == b); EXPECT_FALSE(b == a); } TEST(EitherTest, givenLeftAndRightWithSameType_thenAreUnequal) { either a = make_left("3"); either b = make_right("3"); EXPECT_TRUE(a != b); EXPECT_TRUE(b != a); } namespace { class DestructorCallback { public: MOCK_METHOD(void, call, (), (const)); void EXPECT_CALLED(int times = 1) { EXPECT_CALL(*this, call()).Times(times); } }; class ClassWithDestructorCallback { public: ClassWithDestructorCallback(const DestructorCallback *destructorCallback) : _destructorCallback(destructorCallback) {} ClassWithDestructorCallback(const ClassWithDestructorCallback &rhs): _destructorCallback(rhs._destructorCallback) {} ~ClassWithDestructorCallback() { _destructorCallback->call(); } private: const DestructorCallback *_destructorCallback; ClassWithDestructorCallback &operator=(const ClassWithDestructorCallback &rhs) = delete; }; class OnlyMoveableClassWithDestructorCallback { public: OnlyMoveableClassWithDestructorCallback(const DestructorCallback *destructorCallback) : _destructorCallback(destructorCallback) { } OnlyMoveableClassWithDestructorCallback(OnlyMoveableClassWithDestructorCallback &&source): _destructorCallback(source._destructorCallback) {} ~OnlyMoveableClassWithDestructorCallback() { _destructorCallback->call(); } private: DISALLOW_COPY_AND_ASSIGN(OnlyMoveableClassWithDestructorCallback); const DestructorCallback *_destructorCallback; }; } TEST(EitherTest_Destructor, LeftDestructorIsCalled) { DestructorCallback destructorCallback; destructorCallback.EXPECT_CALLED(2); //Once for the temp object, once when the either class destructs ClassWithDestructorCallback temp(&destructorCallback); either var = temp; } TEST(EitherTest_Destructor, RightDestructorIsCalled) { DestructorCallback destructorCallback; destructorCallback.EXPECT_CALLED(2); //Once for the temp object, once when the either class destructs ClassWithDestructorCallback temp(&destructorCallback); either var = temp; } TEST(EitherTest_Destructor, LeftDestructorIsCalledAfterCopying) { DestructorCallback destructorCallback; destructorCallback.EXPECT_CALLED(3); //Once for the temp object, once for var1 and once for var2 ClassWithDestructorCallback temp(&destructorCallback); either var1 = temp; either var2 = var1; } TEST(EitherTest_Destructor, RightDestructorIsCalledAfterCopying) { DestructorCallback destructorCallback; destructorCallback.EXPECT_CALLED(3); //Once for the temp object, once for var1 and once for var2 ClassWithDestructorCallback temp(&destructorCallback); either var1 = temp; either var2 = var1; } TEST(EitherTest_Destructor, LeftDestructorIsCalledAfterMoving) { DestructorCallback destructorCallback; destructorCallback.EXPECT_CALLED(3); //Once for the temp object, once for var1 and once for var2 OnlyMoveableClassWithDestructorCallback temp(&destructorCallback); either var1 = std::move(temp); either var2 = std::move(var1); } TEST(EitherTest_Destructor, RightDestructorIsCalledAfterMoving) { DestructorCallback destructorCallback; destructorCallback.EXPECT_CALLED(3); //Once for the temp object, once for var1 and once for var2 OnlyMoveableClassWithDestructorCallback temp(&destructorCallback); either var1 = std::move(temp); either var2 = std::move(var1); } TEST(EitherTest_Destructor, LeftDestructorIsCalledAfterAssignment) { DestructorCallback destructorCallback1; DestructorCallback destructorCallback2; destructorCallback1.EXPECT_CALLED(2); //Once for the temp1 object, once at the assignment destructorCallback2.EXPECT_CALLED(3); //Once for the temp2 object, once in destructor of var2, once in destructor of var1 ClassWithDestructorCallback temp1(&destructorCallback1); either var1 = temp1; ClassWithDestructorCallback temp2(&destructorCallback2); either var2 = temp2; var1 = var2; } TEST(EitherTest_Destructor, RightDestructorIsCalledAfterAssignment) { DestructorCallback destructorCallback1; DestructorCallback destructorCallback2; destructorCallback1.EXPECT_CALLED(2); //Once for the temp1 object, once at the assignment destructorCallback2.EXPECT_CALLED(3); //Once for the temp2 object, once in destructor of var2, once in destructor of var1 ClassWithDestructorCallback temp1(&destructorCallback1); either var1 = temp1; ClassWithDestructorCallback temp2(&destructorCallback2); either var2 = temp2; var1 = var2; } TEST(EitherTest_Destructor, LeftDestructorIsCalledAfterMoveAssignment) { DestructorCallback destructorCallback1; DestructorCallback destructorCallback2; destructorCallback1.EXPECT_CALLED(2); //Once for the temp1 object, once at the assignment destructorCallback2.EXPECT_CALLED(3); //Once for the temp2 object, once in destructor of var2, once in destructor of var1 OnlyMoveableClassWithDestructorCallback temp1(&destructorCallback1); either var1 = std::move(temp1); OnlyMoveableClassWithDestructorCallback temp2(&destructorCallback2); either var2 = std::move(temp2); var1 = std::move(var2); } TEST(EitherTest_Destructor, RightDestructorIsCalledAfterMoveAssignment) { DestructorCallback destructorCallback1; DestructorCallback destructorCallback2; destructorCallback1.EXPECT_CALLED(2); //Once for the temp1 object, once at the assignment destructorCallback2.EXPECT_CALLED(3); //Once for the temp2 object, once in destructor of var2, once in destructor of var1 OnlyMoveableClassWithDestructorCallback temp1(&destructorCallback1); either var1 = std::move(temp1); OnlyMoveableClassWithDestructorCallback temp2(&destructorCallback2); either var2 = std::move(temp2); var1 = std::move(var2); } test/cpp-utils/io/000077500000000000000000000000001445614261000143675ustar00rootroot00000000000000test/cpp-utils/io/ConsoleIncludeTest.cpp000066400000000000000000000001561445614261000206430ustar00rootroot00000000000000#include "cpp-utils/io/Console.h" // Test the header can be included without needing additional dependencies test/cpp-utils/io/ConsoleTest.h000066400000000000000000000052331445614261000170050ustar00rootroot00000000000000#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 = 0; _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.cpp000066400000000000000000000123751445614261000201430ustar00rootroot00000000000000#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.cpp000066400000000000000000000012101445614261000216500ustar00rootroot00000000000000#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.cpp000066400000000000000000000062431445614261000211160ustar00rootroot00000000000000#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.cpp000066400000000000000000000002341445614261000205100ustar00rootroot00000000000000#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.cpp000066400000000000000000000003201445614261000226060ustar00rootroot00000000000000#include #include using cpputils::DontEchoStdinToStdoutRAII; TEST(DontEchoStdinToStdoutRAIITest, DoesntCrash) { DontEchoStdinToStdoutRAII a; } test/cpp-utils/io/ProgressBarTest.cpp000066400000000000000000000030221445614261000201610ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000147105ustar00rootroot00000000000000test/cpp-utils/lock/ConditionBarrierIncludeTest.cpp000066400000000000000000000001711445614261000230140ustar00rootroot00000000000000#include "cpp-utils/lock/ConditionBarrier.h" // Test the header can be included without needing additional dependencies test/cpp-utils/lock/LockPoolIncludeTest.cpp000066400000000000000000000001611445614261000213000ustar00rootroot00000000000000#include "cpp-utils/lock/LockPool.h" // Test the header can be included without needing additional dependencies test/cpp-utils/lock/MutexPoolLockIncludeTest.cpp000066400000000000000000000001661445614261000223300ustar00rootroot00000000000000#include "cpp-utils/lock/MutexPoolLock.h" // Test the header can be included without needing additional dependencies test/cpp-utils/logging/000077500000000000000000000000001445614261000154065ustar00rootroot00000000000000test/cpp-utils/logging/LoggerIncludeTest.cpp000066400000000000000000000001621445614261000214740ustar00rootroot00000000000000#include "cpp-utils/logging/Logger.h" // Test the header can be included without needing additional dependencies test/cpp-utils/logging/LoggerTest.cpp000066400000000000000000000006241445614261000201730ustar00rootroot00000000000000#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.cpp000066400000000000000000000001631445614261000216440ustar00rootroot00000000000000#include "cpp-utils/logging/logging.h" // Test the header can be included without needing additional dependencies test/cpp-utils/logging/LoggingLevelTest.cpp000066400000000000000000000107421445614261000213340ustar00rootroot00000000000000#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.cpp000066400000000000000000000166751445614261000203570ustar00rootroot00000000000000#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; // Disable the next tests for MSVC debug builds since writing to stderr doesn't seem to work well there #if !defined(_MSC_VER) || NDEBUG TEST_F(LoggingTest, DefaultLoggerIsStderr) { string output = captureStderr([]{ LOG(INFO, "My log message"); cpputils::logging::flush(); }); // 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"); cpputils::logging::flush(); }); // 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.*"))); } #endif TEST_F(LoggingTest, SetNonStderrLogger_LogsToNewLogger) { setLogger(mockLogger.get()); logger()->info("My log message"); cpputils::logging::flush(); // 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"); cpputils::logging::flush(); }); EXPECT_EQ("", output); } TEST_F(LoggingTest, InfoLog) { setLogger(mockLogger.get()); LOG(INFO, "My log message"); cpputils::logging::flush(); // 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"); cpputils::logging::flush(); // 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"); cpputils::logging::flush(); // 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"); cpputils::logging::flush(); // 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); cpputils::logging::flush(); 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"); cpputils::logging::flush(); // 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); cpputils::logging::flush(); // 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); cpputils::logging::flush(); // 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"); cpputils::logging::flush(); // 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); cpputils::logging::flush(); // 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); cpputils::logging::flush(); // 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/000077500000000000000000000000001445614261000174465ustar00rootroot00000000000000test/cpp-utils/logging/testutils/LoggingTest.h000066400000000000000000000022061445614261000220450ustar00rootroot00000000000000#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(), _logger(spdlog::create("MockLogger", _capturedLogData, true)) { } ~MockLogger() { spdlog::drop("MockLogger"); }; std::shared_ptr get() { return _logger; } std::string capturedLog() const { return _capturedLogData.str(); } private: std::ostringstream _capturedLogData; 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/000077500000000000000000000000001445614261000154515ustar00rootroot00000000000000test/cpp-utils/network/CurlHttpClientTest.cpp000066400000000000000000000022671445614261000217300ustar00rootroot00000000000000#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.cpp000066400000000000000000000025641445614261000216710ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000154405ustar00rootroot00000000000000test/cpp-utils/pointer/cast_include_test.cpp000066400000000000000000000001601445614261000216350ustar00rootroot00000000000000#include "cpp-utils/pointer/cast.h" // Test the header can be included without needing additional dependencies test/cpp-utils/pointer/cast_test.cpp000066400000000000000000000144611445614261000201430ustar00rootroot00000000000000#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_METHOD(void, call, (), (const)); }; 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.cpp000066400000000000000000000002021445614261000255100ustar00rootroot00000000000000#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.cpp000066400000000000000000000065001445614261000240140ustar00rootroot00000000000000#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.cpp000066400000000000000000000002261445614261000316440ustar00rootroot00000000000000#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.cpp000066400000000000000000000001661445614261000230530ustar00rootroot00000000000000#include "cpp-utils/pointer/unique_ref.h" // Test the header can be included without needing additional dependencies test/cpp-utils/pointer/unique_ref_test.cpp000066400000000000000000000717561445614261000213650ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000154365ustar00rootroot00000000000000test/cpp-utils/process/SignalCatcherTest.cpp000066400000000000000000000120601445614261000215100ustar00rootroot00000000000000#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.cpp000066400000000000000000000054611445614261000215230ustar00rootroot00000000000000#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.cpp000066400000000000000000000124531445614261000211370ustar00rootroot00000000000000#include #include #include #include #include "my-gtest-main.h" using cpputils::Subprocess; using cpputils::SubprocessError; using std::string; namespace bf = boost::filesystem; // TODO Test passing input to stdin of processes // TODO Test stderr #if defined(_MSC_VER) constexpr const char* NEWLINE = "\r\n"; #else constexpr const char* NEWLINE = "\n"; #endif namespace { bf::path exit_with_message_and_status() { #if defined(_MSC_VER) auto executable = bf::canonical(get_executable().parent_path()) / "cpp-utils-test_exit_status.exe"; #else auto executable = bf::canonical(get_executable().parent_path()) / "cpp-utils-test_exit_status"; #endif if (!bf::exists(executable)) { throw std::runtime_error(executable.string() + " not found."); } return executable; } } TEST(SubprocessTest, CheckCall_success_output) { EXPECT_EQ(std::string("hello") + NEWLINE, Subprocess::check_call(exit_with_message_and_status(), {"0", "hello"}, "").output_stdout); } TEST(SubprocessTest, CheckCall_successwithemptyoutput_output) { EXPECT_EQ("", Subprocess::check_call(exit_with_message_and_status(), {"0"}, "").output_stdout); } TEST(SubprocessTest, CheckCall_success_exitcode) { EXPECT_EQ(0, Subprocess::check_call(exit_with_message_and_status(), {"0", "hello"}, "").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(), {"1", "hello"}, ""), SubprocessError); } TEST(SubprocessTest, CheckCall_error5withoutput) { EXPECT_THROW( Subprocess::check_call(exit_with_message_and_status(), {"5", "hello"}, ""), SubprocessError); } TEST(SubprocessTest, Call_success_exitcode) { EXPECT_EQ(0, Subprocess::call(exit_with_message_and_status(), {"0", "hello"}, "").exitcode); } TEST(SubprocessTest, Call_success_output) { EXPECT_EQ(std::string("hello") + NEWLINE, Subprocess::call(exit_with_message_and_status(), {"0", "hello"}, "").output_stdout); } 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_stdout); } 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_stdout); } TEST(SubprocessTest, Call_errorwithoutput_output) { EXPECT_EQ(std::string("hello") + NEWLINE, Subprocess::call(exit_with_message_and_status(), {"1", "hello"}, "").output_stdout); } TEST(SubprocessTest, Call_errorwithoutput_exitcode) { EXPECT_EQ(1, Subprocess::call(exit_with_message_and_status(), {"1", "hello"}, "").exitcode); } TEST(SubprocessTest, Call_error5withoutput_output) { EXPECT_EQ(std::string("hello") + NEWLINE, Subprocess::call(exit_with_message_and_status(), {"5", "hello"}, "").output_stdout); } TEST(SubprocessTest, Call_error5withoutput_exitcode) { EXPECT_EQ(5, Subprocess::call(exit_with_message_and_status(), {"5", "hello"}, "").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(), {"0", "hello"}, ""); EXPECT_EQ(0, result.exitcode); EXPECT_EQ(std::string("hello") + NEWLINE, result.output_stdout); 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(SubprocessTest, Call_argumentwithspaces) { // Test that arguments can have spaces and are still treated as one argument EXPECT_EQ(std::string("hello world") + NEWLINE, Subprocess::check_call(exit_with_message_and_status(), {"0", "hello world"}, "").output_stdout); EXPECT_EQ(std::string("hello") + NEWLINE + "world" + NEWLINE, Subprocess::check_call(exit_with_message_and_status(), {"0", "hello", "world"}, "").output_stdout); } #if !defined(_MSC_VER) TEST(SubprocessTest, Call_withcommandfrompath) { // Test that we can call a system command without specifying the full path EXPECT_EQ("hello\n", Subprocess::check_call("echo", {"hello"}, "").output_stdout); } #endif test/cpp-utils/process/daemonize_include_test.cpp000066400000000000000000000001661445614261000226620ustar00rootroot00000000000000#include "cpp-utils/process/daemonize.h" // Test the header can be included without needing additional dependencies test/cpp-utils/process/exit_status.cpp000066400000000000000000000007551445614261000205250ustar00rootroot00000000000000// This is a small executable that exits with the exit status in its first argument and before exiting prints all other arguments, each on a separate line. #include #include int main(int argc, char *argv[]) { if (argc < 2) { std::cerr << "Wrong number of arguments" << std::endl; std::abort(); } for (int i = 2; i < argc; ++i) { std::cout << argv[i] << "\n"; } int exit_status = static_cast(std::strtol(argv[1], nullptr, 10)); return exit_status; } test/cpp-utils/process/subprocess_include_test.cpp000066400000000000000000000001671445614261000231000ustar00rootroot00000000000000#include "cpp-utils/process/subprocess.h" // Test the header can be included without needing additional dependencies test/cpp-utils/random/000077500000000000000000000000001445614261000152405ustar00rootroot00000000000000test/cpp-utils/random/RandomIncludeTest.cpp000066400000000000000000000001611445614261000213260ustar00rootroot00000000000000#include "cpp-utils/random/Random.h" // Test the header can be included without needing additional dependencies test/cpp-utils/system/000077500000000000000000000000001445614261000153045ustar00rootroot00000000000000test/cpp-utils/system/EnvTest.cpp000066400000000000000000000011341445614261000173770ustar00rootroot00000000000000#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.cpp000066400000000000000000000020031445614261000204010ustar00rootroot00000000000000#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.cpp000066400000000000000000000004471445614261000215710ustar00rootroot00000000000000#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.cpp000066400000000000000000000046301445614261000202420ustar00rootroot00000000000000#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.cpp000066400000000000000000000007521445614261000201240ustar00rootroot00000000000000#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.cpp000066400000000000000000000020761445614261000175510ustar00rootroot00000000000000#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.cpp000066400000000000000000000106041445614261000175470ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000155655ustar00rootroot00000000000000test/cpp-utils/tempfile/TempDirIncludeTest.cpp000066400000000000000000000001641445614261000220020ustar00rootroot00000000000000#include "cpp-utils/tempfile/TempDir.h" // Test the header can be included without needing additional dependencies test/cpp-utils/tempfile/TempDirTest.cpp000066400000000000000000000022321445614261000204740ustar00rootroot00000000000000#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.cpp000066400000000000000000000001651445614261000221440ustar00rootroot00000000000000#include "cpp-utils/tempfile/TempFile.h" // Test the header can be included without needing additional dependencies test/cpp-utils/tempfile/TempFileTest.cpp000066400000000000000000000053211445614261000206370ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000152275ustar00rootroot00000000000000test/cpp-utils/thread/LeftRightTest.cpp000066400000000000000000000153111445614261000204640ustar00rootroot00000000000000#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.cpp000066400000000000000000000040541445614261000207300ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000161355ustar00rootroot00000000000000test/cpp-utils/value_type/ValueTypeTest.cpp000066400000000000000000000344021445614261000214220ustar00rootroot00000000000000#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; // TODO Test that noexcept flags are set correctly 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_SUITE(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_SUITE(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_SUITE(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_SUITE(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/000077500000000000000000000000001445614261000137335ustar00rootroot00000000000000test/cryfs-cli/CMakeLists.txt000066400000000000000000000012331445614261000164720ustar00rootroot00000000000000project (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.cpp000066400000000000000000000037261445614261000205130ustar00rootroot00000000000000#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.cpp000066400000000000000000000153221445614261000210050ustar00rootroot00000000000000#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(); } // NOLINTNEXTLINE(misc-no-recursion) 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, CryConfigFile::Access::ReadWrite).right_opt().value(); configFile->config()->SetFilesystemId(CryConfig::FilesystemID::FromString("0123456789ABCDEF0123456789ABCDEF")); configFile->save(); } void modifyFilesystemKey() { FakeCryKeyProvider keyProvider; auto configFile = CryConfigFile::load(basedir / "cryfs.config", &keyProvider, CryConfigFile::Access::ReadWrite).right_opt().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.cpp000066400000000000000000000101631445614261000171670ustar00rootroot00000000000000#include "testutils/CliTest.h" using cpputils::TempFile; using cryfs::ErrorCode; namespace bf = boost::filesystem; //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, AutocreateBasedir) { TempFile notexisting_basedir(false); //Specify --cipher parameter to make it non-interactive //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that EXPECT_RUN_SUCCESS({notexisting_basedir.path().string().c_str(), mountdir.string().c_str(), "-f", "--cipher", "aes-256-gcm", "--create-missing-basedir"}, mountdir); } TEST_F(CliTest_Setup, AutocreateBasedirFail) { TempFile notexisting_basedir(false); //Specify --cipher parameter to make it non-interactive //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that EXPECT_RUN_ERROR( {notexisting_basedir.path().string().c_str(), mountdir.string().c_str(), "-f", "--cipher", "aes-256-gcm"}, "Error 16: base directory not found.", ErrorCode::InaccessibleBaseDir ); } TEST_F(CliTest_Setup, AutocreateMountpoint) { TempFile notexisting_mountpoint(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(), notexisting_mountpoint.path().string().c_str(), "-f", "--cipher", "aes-256-gcm", "--create-missing-mountpoint"}, notexisting_mountpoint.path()); } TEST_F(CliTest_Setup, AutocreateMountdirFail) { TempFile notexisting_mountdir(false); //Specify --cipher parameter to make it non-interactive //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that EXPECT_RUN_ERROR( {basedir.string().c_str(), notexisting_mountdir.path().string().c_str(), "-f", "--cipher", "aes-256-gcm"}, "Error 17: mount directory not found.", ErrorCode::InaccessibleMountDir ); } 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_F(CliTest, WorksWithCommasInBasedir) { // This test makes sure we don't regress on https://github.com/cryfs/cryfs/issues/326 //TODO Remove "-f" parameter, once EXPECT_RUN_SUCCESS can handle that auto basedir_ = basedir / "pathname,with,commas"; bf::create_directory(basedir_); EXPECT_RUN_SUCCESS({basedir_.string().c_str(), mountdir.string().c_str(), "-f"}, mountdir); } test/cryfs-cli/CliTest_ShowingHelp.cpp000066400000000000000000000020321445614261000203120ustar00rootroot00000000000000#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.cpp000066400000000000000000000246451445614261000214220ustar00rootroot00000000000000#include "testutils/CliTest.h" #include namespace bf = boost::filesystem; using ::testing::Values; using ::testing::WithParamInterface; using ::testing::Return; 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_SUITE_P(DefaultParams, CliTest_WrongEnvironment, Values(TestConfig({false, false, false}))); INSTANTIATE_TEST_SUITE_P(ExternalConfigfile, CliTest_WrongEnvironment, Values(TestConfig({true, false, false}))); INSTANTIATE_TEST_SUITE_P(LogIsNotStderr, CliTest_WrongEnvironment, Values(TestConfig({false, true, false}))); INSTANTIATE_TEST_SUITE_P(ExternalConfigfile_LogIsNotStderr, CliTest_WrongEnvironment, Values(TestConfig({true, true, false}))); INSTANTIATE_TEST_SUITE_P(RunningInForeground, CliTest_WrongEnvironment, Values(TestConfig({false, false, true}))); INSTANTIATE_TEST_SUITE_P(RunningInForeground_ExternalConfigfile, CliTest_WrongEnvironment, Values(TestConfig({true, false, true}))); INSTANTIATE_TEST_SUITE_P(RunningInForeground_LogIsNotStderr, CliTest_WrongEnvironment, Values(TestConfig({false, true, true}))); INSTANTIATE_TEST_SUITE_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?", testing::_)).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?", testing::_)).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?", testing::_)).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?", testing::_)).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?", testing::_)).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?", testing::_)).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); } #endif test/cryfs-cli/CryfsUnmountTest.cpp000066400000000000000000000016741445614261000177630ustar00rootroot00000000000000#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.cpp000066400000000000000000000056531445614261000176140ustar00rootroot00000000000000#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.cpp000066400000000000000000000110671445614261000202160ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000171555ustar00rootroot00000000000000test/cryfs-cli/program_options/ParserTest.cpp000066400000000000000000000267121445614261000217650ustar00rootroot00000000000000#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, CreateMissingBasedir_False) { ProgramOptions options = parse({"./myExecutable", basedir, "mountdir"}); EXPECT_FALSE(options.createMissingBasedir()); } TEST_F(ProgramOptionsParserTest, CreateMissingBasedir_True) { ProgramOptions options = parse({"./myExecutable", "--create-missing-basedir", basedir, "mountdir"}); EXPECT_TRUE(options.createMissingBasedir()); } TEST_F(ProgramOptionsParserTest, CreateMissingMountpoint_False) { ProgramOptions options = parse({"./myExecutable", basedir, "mountdir"}); EXPECT_FALSE(options.createMissingMountpoint()); } TEST_F(ProgramOptionsParserTest, CreateMissingMountpoint_True) { ProgramOptions options = parse({"./myExecutable", "--create-missing-mountpoint", basedir, "mountdir"}); EXPECT_TRUE(options.createMissingMountpoint()); } 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.cpp000066400000000000000000000165131445614261000235120ustar00rootroot00000000000000#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, 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, 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, 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, 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, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_FALSE(testobj.foreground()); } TEST_F(ProgramOptionsTest, ForegroundTrue) { ProgramOptions testobj("", "", none, true, false, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_TRUE(testobj.foreground()); } TEST_F(ProgramOptionsTest, AllowFilesystemUpgradeFalse) { ProgramOptions testobj("", "", none, false, false, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_FALSE(testobj.allowFilesystemUpgrade()); } TEST_F(ProgramOptionsTest, AllowFilesystemUpgradeTrue) { ProgramOptions testobj("", "", none, false, true, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_TRUE(testobj.allowFilesystemUpgrade()); } TEST_F(ProgramOptionsTest, CreateMissingBasedirFalse) { ProgramOptions testobj("", "", none, false, false, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_FALSE(testobj.createMissingBasedir()); } TEST_F(ProgramOptionsTest, CreateMissingBasedirTrue) { ProgramOptions testobj("", "", none, false, true, false, true, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_TRUE(testobj.createMissingBasedir()); } TEST_F(ProgramOptionsTest, CreateMissingMountpointFalse) { ProgramOptions testobj("", "", none, false, false, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_FALSE(testobj.createMissingMountpoint()); } TEST_F(ProgramOptionsTest, CreateMissingMountpointTrue) { ProgramOptions testobj("", "", none, false, true, false, false, true, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_TRUE(testobj.createMissingMountpoint()); } TEST_F(ProgramOptionsTest, LogfileNone) { ProgramOptions testobj("", "", none, true, false, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(none, testobj.logFile()); } TEST_F(ProgramOptionsTest, LogfileSome) { ProgramOptions testobj("", "", none, true, false, false, 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, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(none, testobj.unmountAfterIdleMinutes()); } TEST_F(ProgramOptionsTest, UnmountAfterIdleMinutesSome) { ProgramOptions testobj("", "", none, true, false, false, 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, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(none, testobj.cipher()); } TEST_F(ProgramOptionsTest, CipherSome) { ProgramOptions testobj("", "", none, true, false, false, 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, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(none, testobj.blocksizeBytes()); } TEST_F(ProgramOptionsTest, BlocksizeBytesSome) { ProgramOptions testobj("", "", none, true, false, false, 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, false, false, none, none, none, none, false, true, {"./myExecutable"}); EXPECT_TRUE(testobj.missingBlockIsIntegrityViolation().value()); } TEST_F(ProgramOptionsTest, MissingBlockIsIntegrityViolationFalse) { ProgramOptions testobj("", "", none, true, false, false, false, false, none, none, none, none, false, false, {"./myExecutable"}); EXPECT_FALSE(testobj.missingBlockIsIntegrityViolation().value()); } TEST_F(ProgramOptionsTest, MissingBlockIsIntegrityViolationNone) { ProgramOptions testobj("", "", none, true, false, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_EQ(none, testobj.missingBlockIsIntegrityViolation()); } TEST_F(ProgramOptionsTest, AllowIntegrityViolationsFalse) { ProgramOptions testobj("", "", none, true, false, false, false, false, none, none, none, none, false, none, {"./myExecutable"}); EXPECT_FALSE(testobj.allowIntegrityViolations()); } TEST_F(ProgramOptionsTest, AllowIntegrityViolationsTrue) { ProgramOptions testobj("", "", none, true, false, false, 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, 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, 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.cpp000066400000000000000000000126761445614261000216350ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000212155ustar00rootroot00000000000000test/cryfs-cli/program_options/testutils/ProgramOptionsTestBase.h000066400000000000000000000011131445614261000260000ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000157735ustar00rootroot00000000000000test/cryfs-cli/testutils/CliTest.cpp000066400000000000000000000000251445614261000200430ustar00rootroot00000000000000#include "CliTest.h" test/cryfs-cli/testutils/CliTest.h000066400000000000000000000166451445614261000175270ustar00rootroot00000000000000#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/impl/testutils/MockConsole.h" #include "../../cryfs/impl/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"); bool successfully_mounted = false; FilesystemOutput filesystem_output = run_filesystem(args, mountDir, [&] { successfully_mounted = true; onMounted(); }); EXPECT_EQ(0, filesystem_output.exit_code); if (!std::regex_search(filesystem_output.stdout_, std::regex("Mounting filesystem"))) { std::cerr << "STDOUT:\n" << filesystem_output.stdout_ << "STDERR:\n" << filesystem_output.stderr_ << std::endl; EXPECT_TRUE(false) << "Filesystem did not output the 'Mounting filesystem' message, probably wasn't successfully mounted."; } if (!successfully_mounted) { EXPECT_TRUE(false) << "Filesystem did not call onMounted callback, probably wasn't successfully mounted."; } } 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/000077500000000000000000000000001445614261000131665ustar00rootroot00000000000000test/cryfs/CMakeLists.txt000066400000000000000000000023431445614261000157300ustar00rootroot00000000000000project (cryfs-test) set(SOURCES impl/config/crypto/CryConfigEncryptorFactoryTest.cpp impl/config/crypto/outer/OuterConfigTest.cpp impl/config/crypto/outer/OuterEncryptorTest.cpp impl/config/crypto/inner/ConcreteInnerEncryptorTest.cpp impl/config/crypto/inner/InnerConfigTest.cpp impl/config/crypto/CryConfigEncryptorTest.cpp impl/config/CompatibilityTest.cpp impl/config/CryConfigCreatorTest.cpp impl/config/CryConfigFileTest.cpp impl/config/CryConfigTest.cpp impl/config/CryCipherTest.cpp impl/config/CryConfigLoaderTest.cpp impl/config/CryConfigConsoleTest.cpp impl/config/CryPasswordBasedKeyProviderTest.cpp impl/config/CryPresetPasswordBasedKeyProviderTest.cpp impl/filesystem/CryFsTest.cpp impl/filesystem/CryNodeTest.cpp impl/filesystem/FileSystemTest.cpp impl/localstate/LocalStateMetadataTest.cpp impl/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/impl/000077500000000000000000000000001445614261000141275ustar00rootroot00000000000000test/cryfs/impl/config/000077500000000000000000000000001445614261000153745ustar00rootroot00000000000000test/cryfs/impl/config/CompatibilityTest.cpp000066400000000000000000000154701445614261000215600ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "../../impl/testutils/MockConsole.h" using cpputils::Data; using cpputils::AES256_GCM; using cpputils::Serpent128_CFB; using cpputils::TempFile; using cpputils::unique_ref; 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; unique_ref loadConfigFromHex(const string &configFileContentHex) { storeHexToFile(configFileContentHex); CryPresetPasswordBasedKeyProvider keyProvider("mypassword", make_unique_ref(SCrypt::DefaultSettings)); return CryConfigFile::load(file.path(), &keyProvider, CryConfigFile::Access::ReadWrite).right(); } 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/impl/config/CryCipherTest.cpp000066400000000000000000000146761445614261000206460ustar00rootroot00000000000000#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", "mars-448-gcm", "mars-448-cfb", "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"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("mars-448-gcm"); EXPECT_CREATES_CORRECT_ENCRYPTED_BLOCKSTORE("mars-448-cfb"); 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.*")); } TEST_F(CryCipherTest, EncryptionKeyHasCorrectSize_448) { EXPECT_EQ(Mars448_GCM::STRING_KEYSIZE, CryCiphers::find("mars-448-gcm").createKey(Random::PseudoRandom()).size()); } 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/impl/config/CryConfigConsoleTest.cpp000066400000000000000000000113001445614261000221410ustar00rootroot00000000000000#include #include #include #include #include #include #include "../../impl/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::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?", testing::_)).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?", testing::_)).Times(1).WillOnce(Return(false)); \ EXPECT_CALL(*console, ask(HasSubstr("block size"), testing::_)).Times(1) #define EXPECT_ASK_FOR_MISSINGBLOCKISINTEGRITYVIOLATION() \ EXPECT_CALL(*console, askYesNo("Use default settings?", testing::_)).Times(1).WillOnce(Return(false)); \ EXPECT_CALL(*console, askYesNo(HasSubstr("missing block"), testing::_)).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?", testing::_)).Times(1).WillOnce(Return(true)); EXPECT_CALL(*console, ask(HasSubstr("block cipher"), testing::_)).Times(0); string cipher = cryconsole.askCipher(); EXPECT_EQ(CryConfigConsole::DEFAULT_CIPHER, cipher); } TEST_F(CryConfigConsoleTest_Cipher, ChooseDefaultCipherWhenNoninteractiveEnvironment) { EXPECT_CALL(*console, askYesNo(HasSubstr("default"), testing::_)).Times(0); EXPECT_CALL(*console, ask(HasSubstr("block cipher"), testing::_)).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"), testing::_)).Times(0); EXPECT_CALL(*console, ask(HasSubstr("block size"), testing::_)).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(testing::_, testing::_)).Times(0); } void EXPECT_SHOW_WARNING(const string &warning) { EXPECT_CALL(*console, askYesNo(HasSubstr(warning), testing::_)).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_SUITE_P(CryConfigConsoleTest_Cipher_Choose, CryConfigConsoleTest_Cipher_Choose, ValuesIn(CryCiphers::supportedCipherNames())); test/cryfs/impl/config/CryConfigCreatorTest.cpp000066400000000000000000000225651445614261000221550ustar00rootroot00000000000000#include #include #include #include #include #include "../../impl/testutils/MockConsole.h" #include "../../impl/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::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"), testing::_)).Times(0) #define EXPECT_ASK_FOR_BLOCKSIZE() \ EXPECT_CALL(*console, ask(HasSubstr("block size"), testing::_)).Times(1) #define EXPECT_DOES_NOT_ASK_FOR_BLOCKSIZE() \ EXPECT_CALL(*console, ask(HasSubstr("block size"), testing::_)).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"), testing::_)).WillRepeatedly(ChooseAnyCipher()); EXPECT_CALL(*console, ask(HasSubstr("block size"), testing::_)).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 } 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 } 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/impl/config/CryConfigFileTest.cpp000066400000000000000000000165131445614261000214310ustar00rootroot00000000000000#include #include #include #include #include "../../impl/testutils/FakeCryKeyProvider.h" using namespace cryfs; using cpputils::TempFile; using cpputils::unique_ref; 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; } unique_ref 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 char keySeed = 0) { FakeCryKeyProvider keyProvider(keySeed); return CryConfigFile::load(file.path(), &keyProvider, CryConfigFile::Access::ReadWrite).right_opt(); } 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) { unique_ref created = CreateAndLoadEmpty(); EXPECT_EQ("", created->config()->RootBlob()); } TEST_F(CryConfigFileTest, RootBlob_CreateAndLoad) { CryConfig cfg = Config(); cfg.SetRootBlob("rootblobid"); Create(std::move(cfg)); unique_ref loaded = std::move(Load().value()); EXPECT_EQ("rootblobid", loaded->config()->RootBlob()); } TEST_F(CryConfigFileTest, RootBlob_SaveAndLoad) { unique_ref created = CreateAndLoadEmpty(); created->config()->SetRootBlob("rootblobid"); created->save(); unique_ref loaded = std::move(Load().value()); EXPECT_EQ("rootblobid", loaded->config()->RootBlob()); } TEST_F(CryConfigFileTest, EncryptionKey_Init) { unique_ref created = CreateAndLoadEmpty(); EXPECT_EQ("", created->config()->EncryptionKey()); } TEST_F(CryConfigFileTest, EncryptionKey_CreateAndLoad) { CryConfig cfg = Config(); cfg.SetEncryptionKey("encryptionkey"); Create(std::move(cfg)); unique_ref loaded = std::move(Load().value()); EXPECT_EQ("encryptionkey", loaded->config()->EncryptionKey()); } TEST_F(CryConfigFileTest, EncryptionKey_SaveAndLoad) { unique_ref created = CreateAndLoadEmpty(); created->config()->SetEncryptionKey("encryptionkey"); created->save(); unique_ref loaded = std::move(Load().value()); EXPECT_EQ("encryptionkey", loaded->config()->EncryptionKey()); } TEST_F(CryConfigFileTest, Cipher_Init) { unique_ref 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)); unique_ref loaded = std::move(Load().value()); EXPECT_EQ("twofish-128-cfb", loaded->config()->Cipher()); } TEST_F(CryConfigFileTest, Cipher_SaveAndLoad) { unique_ref created = CreateAndLoadEmpty(); created->config()->SetCipher("twofish-128-cfb"); created->save(); unique_ref loaded = std::move(Load().value()); EXPECT_EQ("twofish-128-cfb", loaded->config()->Cipher()); } TEST_F(CryConfigFileTest, Version_Init) { unique_ref created = CreateAndLoadEmpty(); EXPECT_EQ("", created->config()->Version()); } TEST_F(CryConfigFileTest, Version_CreateAndLoad) { CryConfig cfg = Config(); cfg.SetVersion("0.9.2"); Create(std::move(cfg)); unique_ref loaded = std::move(Load().value()); EXPECT_EQ("0.9.2", loaded->config()->Version()); } TEST_F(CryConfigFileTest, Version_SaveAndLoad) { unique_ref created = CreateAndLoadEmpty(); created->config()->SetVersion("0.9.2"); created->save(); unique_ref loaded = std::move(Load().value()); EXPECT_EQ("0.9.2", loaded->config()->Version()); } TEST_F(CryConfigFileTest, CreatedWithVersion_Init) { unique_ref created = CreateAndLoadEmpty(); EXPECT_EQ("", created->config()->Version()); } TEST_F(CryConfigFileTest, CreatedWithVersion_CreateAndLoad) { CryConfig cfg = Config(); cfg.SetCreatedWithVersion("0.9.2"); Create(std::move(cfg)); unique_ref loaded = std::move(Load().value()); EXPECT_EQ("0.9.2", loaded->config()->CreatedWithVersion()); } TEST_F(CryConfigFileTest, CreatedWithVersion_SaveAndLoad) { unique_ref created = CreateAndLoadEmpty(); created->config()->SetCreatedWithVersion("0.9.2"); created->save(); unique_ref loaded = std::move(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"); unique_ref created = Load().value(); EXPECT_EQ("aes-256-gcm", created->config()->Cipher()); created->config()->SetCipher("twofish-128-cfb"); created->save(); unique_ref loaded = std::move(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/impl/config/CryConfigLoaderTest.cpp000066400000000000000000000421521445614261000217560ustar00rootroot00000000000000#include #include #include #include "../../impl/testutils/MockConsole.h" #include "../../impl/testutils/TestWithFakeHomeDirectory.h" #include #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::unique_ref; using cpputils::either; 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 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); } unique_ref 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).right().configFile; } either> LoadOrCreate(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.is_left()) { return loadResult.left(); } return std::move(loadResult.right().configFile); } either> Load(CryConfigFile::Access access = CryConfigFile::Access::ReadWrite) { EXPECT_TRUE(file.exists()); auto loadResult = loader("mypassword", false, none).load(file.path(), false, false, access); if (loadResult.is_left()) { return loadResult.left(); } return std::move(loadResult.right().configFile); } void expectLoadingModifiesFile(CryConfigFile::Access access) { Data contents_before_loading = Data::LoadFromFile(file.path()).value(); EXPECT_TRUE(Load(access).is_right()); Data contents_after_loading = Data::LoadFromFile(file.path()).value(); ASSERT_EQ(contents_before_loading.size(), contents_after_loading.size()); EXPECT_NE(0, std::memcmp(contents_before_loading.data(), contents_after_loading.data(), contents_before_loading.size())); } void expectLoadingDoesntModifyFile(CryConfigFile::Access access) { Data contents_before_loading = Data::LoadFromFile(file.path()).value(); EXPECT_TRUE(Load(access).is_right()); Data contents_after_loading = Data::LoadFromFile(file.path()).value(); ASSERT_EQ(contents_before_loading.size(), contents_after_loading.size()); EXPECT_EQ(0, std::memcmp(contents_before_loading.data(), contents_after_loading.data(), contents_before_loading.size())); } void CreateWithRootBlob(const string &rootBlob, const string &password = "mypassword") { auto cfg = loader(password, false).loadOrCreate(file.path(), false, false).right().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).right().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_TRUE(loader.loadOrCreate(file.path(), false, false).is_right()); } void ChangeEncryptionKey(const string &encKey, const string& password = "mypassword") { auto cfg = CryConfigFile::load(file.path(), keyProvider(password).get(), CryConfigFile::Access::ReadWrite).right(); 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).right().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).right().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(), CryConfigFile::Access::ReadWrite).right(); 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(); ASSERT_TRUE(file.exists()); } TEST_F(CryConfigLoaderTest, DoesntCrashIfExisting) { Create(); LoadOrCreate(); } TEST_F(CryConfigLoaderTest, DoesntLoadIfWrongPassword) { Create("mypassword"); auto loaded = LoadOrCreate("mypassword2"); EXPECT_TRUE(loaded.is_left()); } TEST_F(CryConfigLoaderTest, DoesntLoadIfDifferentCipher) { Create("mypassword", string("aes-256-gcm"), false); try { LoadOrCreate("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 { LoadOrCreate("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")); LoadOrCreate("mypassword", string("aes-256-gcm")); } TEST_F(CryConfigLoaderTest, DoesLoadIfSameCipher_Noninteractive) { Create("mypassword", string("aes-128-gcm"), true); LoadOrCreate("mypassword", string("aes-128-gcm"), true); } TEST_F(CryConfigLoaderTest, RootBlob_Load) { CreateWithRootBlob("rootblobid"); auto loaded = LoadOrCreate().right(); 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 = LoadOrCreate().right(); EXPECT_EQ("3B4682CF22F3CA199E385729B9F3CA19D325229E385729B9443CA19D325229E3", loaded->config()->EncryptionKey()); } TEST_F(CryConfigLoaderTest, EncryptionKey_Load_whenKeyChanged_thenFails) { CreateWithEncryptionKey("3B4682CF22F3CA199E385729B9F3CA19D325229E385729B9443CA19D325229E3"); ChangeEncryptionKey("3B4682CF22F3CA199E385729B9F3CA19D325229E385729B9443CA19D325229E4"); EXPECT_THROW( LoadOrCreate(), std::runtime_error ); } TEST_F(CryConfigLoaderTest, EncryptionKey_Create) { auto created = Create(); 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 = LoadOrCreate().right(); EXPECT_EQ("twofish-128-cfb", loaded->config()->Cipher()); } TEST_F(CryConfigLoaderTest, Cipher_Create) { auto created = Create(); //xchacha20-poly1305 is the default cipher chosen by mockConsole() EXPECT_EQ("xchacha20-poly1305", created->config()->Cipher()); } TEST_F(CryConfigLoaderTest, Version_Load) { CreateWithVersion("0.9.4", "0.9.4"); auto loaded = std::move(LoadOrCreate().right()); 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"); LoadOrCreate().right(); auto configFile = CryConfigFile::load(file.path(), keyProvider("mypassword").get(), CryConfigFile::Access::ReadWrite).right(); 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 = LoadOrCreate().right(); 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_TRUE(LoadOrCreate().is_right()); } 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 { LoadOrCreate(); 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_TRUE(LoadOrCreate().is_right()); } TEST_F(CryConfigLoaderTest, DoesNotAskForMigrationWhenCorrectVersion) { EXPECT_CALL(*console, askYesNo(HasSubstr("Do you want to attempt a migration now?"), testing::_)).Times(0); CreateWithVersion(gitversion::VersionString(), CryConfig::FilesystemFormatVersion); EXPECT_TRUE(LoadOrCreate().is_right()); } 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 { LoadOrCreate(); 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).right().myClientId; EXPECT_NE(myClientId, loader("mypassword", true).loadOrCreate(file2.path(), false, false).right().myClientId); } TEST_F(CryConfigLoaderTest, MyClientIdIsLoadedCorrectly) { TempFile file(false); uint32_t myClientId = loader("mypassword", true).loadOrCreate(file.path(), false, false).right().myClientId; EXPECT_EQ(myClientId, loader("mypassword", true).loadOrCreate(file.path(), false, false).right().myClientId); } TEST_F(CryConfigLoaderTest, DoesNotAskForMigrationWhenUpgradesAllowedByProgramArguments_NoninteractiveMode) { EXPECT_CALL(*console, askYesNo(HasSubstr("migrate"), testing::_)).Times(0); string version = olderVersion(); CreateWithVersion(version, version); EXPECT_TRUE(LoadOrCreate("mypassword", none, true, true).is_right()); } TEST_F(CryConfigLoaderTest, DoesNotAskForMigrationWhenUpgradesAllowedByProgramArguments_InteractiveMode) { EXPECT_CALL(*console, askYesNo(HasSubstr("migrate"), testing::_)).Times(0); string version = olderVersion(); CreateWithVersion(version, version); EXPECT_TRUE(LoadOrCreate("mypassword", none, false, true).is_right()); } TEST_F(CryConfigLoaderTest, UpdatesConfigFileWithNewVersionWhenMigrated) { EXPECT_CALL(*console, askYesNo(HasSubstr("Do you want to attempt a migration now?"), false)).Times(1).WillOnce(Return(true)); string version = olderVersion(); // this triggers a migration which should cause it to modify the config file on load CreateWithVersion(version, version); expectLoadingModifiesFile(CryConfigFile::Access::ReadWrite); // If we load it again, it shouldn't modify again because it's already updated expectLoadingDoesntModifyFile(CryConfigFile::Access::ReadWrite); } TEST_F(CryConfigLoaderTest, DoesntUpdatesConfigFileWithNewVersionWhenLoadingReadOnly) { EXPECT_CALL(*console, askYesNo(HasSubstr("Do you want to attempt a migration now?"), false)).Times(1).WillOnce(Return(true)); string version = olderVersion(); // this triggers a migration which usually would cause it to modify the config file on load CreateWithVersion(version, version); expectLoadingDoesntModifyFile(CryConfigFile::Access::ReadOnly); } test/cryfs/impl/config/CryConfigTest.cpp000066400000000000000000000154661445614261000206370ustar00rootroot00000000000000#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_AfterCopy) { cfg.SetRootBlob("rootblobid"); CryConfig copy = cfg; EXPECT_EQ("rootblobid", copy.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_AfterCopy) { cfg.SetEncryptionKey("enckey"); CryConfig copy = cfg; EXPECT_EQ("enckey", copy.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_AfterCopy) { cfg.SetCipher("mycipher"); CryConfig copy = cfg; EXPECT_EQ("mycipher", copy.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_AfterCopy) { cfg.SetVersion("0.9.1"); CryConfig copy = cfg; EXPECT_EQ("0.9.1", copy.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_AfterCopy) { cfg.SetCreatedWithVersion("0.9.3"); CryConfig copy = cfg; EXPECT_EQ("0.9.3", copy.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_AfterCopy) { cfg.SetBlocksizeBytes(32*1024); CryConfig copy = cfg; EXPECT_EQ(32*1024u, copy.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/impl/config/CryPasswordBasedKeyProviderTest.cpp000066400000000000000000000063361445614261000243530ustar00rootroot00000000000000#include #include #include "../../impl/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; namespace { class MockCallable { public: MOCK_METHOD(std::string, call, ()); }; class MockKDF : public PasswordBasedKDF { public: MOCK_METHOD(EncryptionKey, deriveExistingKey, (size_t keySize, const string& password, const Data& kdfParameters), (override)); MOCK_METHOD(KeyResult, deriveNewKey, (size_t keySize, const string& password), (override)); }; 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), testing::_)).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/impl/config/CryPresetPasswordBasedKeyProviderTest.cpp000066400000000000000000000045411445614261000255320ustar00rootroot00000000000000#include #include #include "../../impl/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; namespace { class MockKDF : public PasswordBasedKDF { public: MOCK_METHOD(EncryptionKey, deriveExistingKey, (size_t keySize, const string& password, const Data& kdfParameters), (override)); MOCK_METHOD(KeyResult, deriveNewKey, (size_t keySize, const string& password), (override)); }; 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), testing::_)).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/impl/config/crypto/000077500000000000000000000000001445614261000167145ustar00rootroot00000000000000test/cryfs/impl/config/crypto/CryConfigEncryptorFactoryTest.cpp000066400000000000000000000055551445614261000254130ustar00rootroot00000000000000#include #include #include #include #include "../../../impl/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/impl/config/crypto/CryConfigEncryptorTest.cpp000066400000000000000000000123151445614261000240530ustar00rootroot00000000000000#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/impl/config/crypto/inner/000077500000000000000000000000001445614261000200275ustar00rootroot00000000000000test/cryfs/impl/config/crypto/inner/ConcreteInnerEncryptorTest.cpp000066400000000000000000000065601445614261000260460ustar00rootroot00000000000000#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/impl/config/crypto/inner/InnerConfigTest.cpp000066400000000000000000000035401445614261000235760ustar00rootroot00000000000000#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/impl/config/crypto/outer/000077500000000000000000000000001445614261000200525ustar00rootroot00000000000000test/cryfs/impl/config/crypto/outer/OuterConfigTest.cpp000066400000000000000000000040361445614261000236450ustar00rootroot00000000000000#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/impl/config/crypto/outer/OuterEncryptorTest.cpp000066400000000000000000000052261445614261000244270ustar00rootroot00000000000000#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/impl/filesystem/000077500000000000000000000000001445614261000163135ustar00rootroot00000000000000test/cryfs/impl/filesystem/CryFsTest.cpp000066400000000000000000000063431445614261000207130ustar00rootroot00000000000000#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 std::shared_ptr; 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) { } shared_ptr 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).right().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()); dev.setContext(fspp::Context {fspp::relatime()}); } CryDevice dev(loadOrCreateConfig(), blockStore(), localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); dev.setContext(fspp::Context {fspp::relatime()}); auto rootDir = dev.LoadDir(bf::path("/")); rootDir.value()->children(); } TEST_F(CryFsTest, LoadingFilesystemDoesntModifyConfigFile) { { CryDevice dev(loadOrCreateConfig(), blockStore(), localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); dev.setContext(fspp::Context {fspp::relatime()}); } Data configAfterCreating = Data::LoadFromFile(config.path()).value(); { CryDevice dev(loadOrCreateConfig(), blockStore(), localStateDir, 0x12345678, false, false, failOnIntegrityViolation()); dev.setContext(fspp::Context {fspp::relatime()}); } Data configAfterLoading = Data::LoadFromFile(config.path()).value(); EXPECT_EQ(configAfterCreating, configAfterLoading); } } test/cryfs/impl/filesystem/CryNodeTest.cpp000066400000000000000000000072341445614261000212300ustar00rootroot00000000000000#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/impl/filesystem/FileSystemTest.cpp000066400000000000000000000037041445614261000217470ustar00rootroot00000000000000#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).right(); 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/impl/filesystem/testutils/000077500000000000000000000000001445614261000203535ustar00rootroot00000000000000test/cryfs/impl/filesystem/testutils/CryTestBase.h000066400000000000000000000036051445614261000227200ustar00rootroot00000000000000#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()); _device->setContext(fspp::Context { fspp::relatime() }); } std::shared_ptr 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/impl/localstate/000077500000000000000000000000001445614261000162625ustar00rootroot00000000000000test/cryfs/impl/localstate/BasedirMetadataTest.cpp000066400000000000000000000056731445614261000226530ustar00rootroot00000000000000#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/impl/localstate/LocalStateMetadataTest.cpp000066400000000000000000000040211445614261000233170ustar00rootroot00000000000000#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/impl/testutils/000077500000000000000000000000001445614261000161675ustar00rootroot00000000000000test/cryfs/impl/testutils/FakeCryKeyProvider.h000066400000000000000000000023541445614261000220540ustar00rootroot00000000000000#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/impl/testutils/MockConsole.h000066400000000000000000000022671445614261000205630ustar00rootroot00000000000000#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_METHOD(void, print, (const std::string&), (override)); MOCK_METHOD(unsigned int, ask, (const std::string&, const std::vector&), (override)); MOCK_METHOD(bool, askYesNo, (const std::string&, bool), (override)); MOCK_METHOD(std::string, askPassword, (const std::string&), (override)); }; 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/impl/testutils/MockCryKeyProvider.h000066400000000000000000000007371445614261000221020ustar00rootroot00000000000000#pragma once #ifndef CRYFS_MOCKCRYKEYPROVIDER_H #define CRYFS_MOCKCRYKEYPROVIDER_H #include #include class MockCryKeyProvider: public cryfs::CryKeyProvider { public: MOCK_METHOD(cpputils::EncryptionKey, requestKeyForExistingFilesystem, (size_t keySize, const cpputils::Data& kdfParameters), (override)); MOCK_METHOD(cryfs::CryKeyProvider::KeyResult, requestKeyForNewFilesystem, (size_t keySize), (override)); }; #endif test/cryfs/impl/testutils/TestWithFakeHomeDirectory.h000066400000000000000000000005261445614261000234030ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000130105ustar00rootroot00000000000000test/fspp/CMakeLists.txt000066400000000000000000000107311445614261000155520ustar00rootroot00000000000000project (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/TimestampTest.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/000077500000000000000000000000001445614261000154405ustar00rootroot00000000000000test/fspp/fs_interface/DeviceTest.cpp000066400000000000000000000002221445614261000201770ustar00rootroot00000000000000/* * 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.cpp000066400000000000000000000002171445614261000175220ustar00rootroot00000000000000/* * 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.cpp000066400000000000000000000002201445614261000176550ustar00rootroot00000000000000/* * 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.cpp000066400000000000000000000002201445614261000176630ustar00rootroot00000000000000/* * 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.cpp000066400000000000000000000002241445614261000205030ustar00rootroot00000000000000/* * Tests that the header can be included without needing additional header includes as dependencies. */ #include "fspp/fs_interface/OpenFile.h" test/fspp/fuse/000077500000000000000000000000001445614261000137525ustar00rootroot00000000000000test/fspp/fuse/BasicFuseTest.cpp000066400000000000000000000004411445614261000171610ustar00rootroot00000000000000#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.cpp000066400000000000000000000002161445614261000174410ustar00rootroot00000000000000/* * Tests that the header can be included without needing additional header includes as dependencies. */ #include "fspp/fuse/Filesystem.h" test/fspp/fuse/TimestampTest.cpp000066400000000000000000000325361445614261000172720ustar00rootroot00000000000000#include "../testutils/FuseTest.h" #include using namespace fspp::fuse; typedef FuseTest FuseTimestampTest; // Single flag TEST_F(FuseTimestampTest, whenCalledWithoutAnyAtimeFlag_thenHasRelatimeBehavior) { auto fs = TestFS({}); EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNoatimeFlag_thenHasNoatimeBehavior) { auto fs = TestFS({"-o", "noatime"}); EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithStrictatimeFlag_thenHasStrictatimeBehavior) { auto fs = TestFS({"-o", "strictatime"}); EXPECT_EQ(fspp::strictatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithRelatimeFlag_thenHasRelatimeBehavior) { auto fs = TestFS({"-o", "relatime"}); EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithAtimeFlag_thenHasRelatimeBehavior) { auto fs = TestFS({"-o", "atime"}); EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNodiratimeFlag_thenHasNoatimeBehavior) { // note: this behavior is correct because "noatime" is default and adding "nodiratime" doesn't change anything. auto fs = TestFS({"-o", "nodiratime"}); EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); } // Flag combinations TEST_F(FuseTimestampTest, whenCalledWithAtimeAtimeFlag_withCsv_thenHasRelatimeBehavior) { auto fs = TestFS({"-o", "atime,atime"}); EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithAtimeAtimeFlag_withSeparateFlags_thenHasRelatimeBehavior) { auto fs = TestFS({"-o", "atime", "-o", "atime"}); EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithAtimeNoatimeFlag_withCsv_thenFails) { EXPECT_DEATH( TestFS({"-o", "atime,noatime"}), "Cannot have both, noatime and atime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithAtimeNoatimeFlag_withSeparateFlags_thenFails) { EXPECT_DEATH( TestFS({"-o", "atime", "-o", "noatime"}), "Cannot have both, noatime and atime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithAtimeRelatimeFlag_withCsv_thenHasRelatimeBehavior) { auto fs = TestFS({"-o", "atime,relatime"}); EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithAtimeRelatimeFlag_withSeparateFlags_thenHasRelatimeBehavior) { auto fs = TestFS({"-o", "atime", "-o", "relatime"}); EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithAtimeStrictatimeFlag_withCsv_thenFails) { EXPECT_DEATH( TestFS({"-o", "atime,strictatime"}), "Cannot have both, atime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithAtimeStrictatimeFlag_withSeparateFlags_thenFails) { EXPECT_DEATH( TestFS({"-o", "atime", "-o", "strictatime"}), "Cannot have both, atime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithAtimeNodiratimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "atime,nodiratime"}); EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithAtimeNodiratimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "atime", "-o", "nodiratime"}); EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNoatimeAtime_withCsv_thenFails) { EXPECT_DEATH( TestFS({"-o", "noatime,atime"}), "Cannot have both, noatime and atime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithNoatimeAtime_withSeparateFlags_thenFails) { EXPECT_DEATH( TestFS({"-o", "noatime", "-o", "atime"}), "Cannot have both, noatime and atime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithNoatimeNoatimeFlag_withCsv_thenHasNoatimeBehavior) { auto fs = TestFS({"-o", "noatime,noatime"}); EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNoatimeNoatimeFlag_withSeparateFlags_thenHasNoatimeBehavior) { auto fs = TestFS({"-o", "noatime", "-o", "noatime"}); EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNoatimeRelatime_withCsv_thenFails) { EXPECT_DEATH( TestFS({"-o", "noatime,relatime"}), "Cannot have both, noatime and relatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithNoatimeRelatime_withSeparateFlags_thenFails) { EXPECT_DEATH( TestFS({"-o", "noatime", "-o", "relatime"}), "Cannot have both, noatime and relatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithNoatimeStrictatime_withCsv_thenFails) { EXPECT_DEATH( TestFS({"-o", "noatime,strictatime"}), "Cannot have both, noatime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithNoatimeStrictatime_withSeparateFlags_thenFails) { EXPECT_DEATH( TestFS({"-o", "noatime", "-o", "strictatime"}), "Cannot have both, noatime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithNoatimeNodiratimeFlag_withCsv_thenHasNoatimeBehavior) { auto fs = TestFS({"-o", "noatime,nodiratime"}); EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNoatimeNodiratimeFlag_withSeparateFlags_thenHasNoatimeBehavior) { auto fs = TestFS({"-o", "noatime", "-o", "nodiratime"}); EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithRelatimeAtimeFlag_withCsv_thenHasRelatimeBehavior) { auto fs = TestFS({"-o", "relatime,atime"}); EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithRelatimeAtimeFlag_withSeparateFlags_thenHasRelatimeBehavior) { auto fs = TestFS({"-o", "relatime", "-o", "atime"}); EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithRelatimeNoatime_withCsv_thenFails) { EXPECT_DEATH( TestFS({"-o", "relatime,noatime"}), "Cannot have both, noatime and relatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithRelatimeNoatime_withSeparateFlags_thenFails) { EXPECT_DEATH( TestFS({"-o", "relatime", "-o", "noatime"}), "Cannot have both, noatime and relatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithRelatimeRelatimeFlag_withCsv_thenHasRelatimeBehavior) { auto fs = TestFS({"-o", "relatime,relatime"}); EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithRelatimeRelatimeFlag_withSeparateFlags_thenHasRelatimeBehavior) { auto fs = TestFS({"-o", "relatime", "-o", "relatime"}); EXPECT_EQ(fspp::relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithRelatimeStrictatime_withCsv_thenFails) { EXPECT_DEATH( TestFS({"-o", "relatime,strictatime"}), "Cannot have both, relatime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithRelatimeStrictatime_withSeparateFlags_thenFails) { EXPECT_DEATH( TestFS({"-o", "relatime", "-o", "strictatime"}), "Cannot have both, relatime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithRelatimeNodiratimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "relatime,nodiratime"}); EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithRelatimeNodiratimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "relatime", "-o", "nodiratime"}); EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithStrictatimeAtimeFlag_withCsv_thenFails) { EXPECT_DEATH( TestFS({"-o", "strictatime,atime"}), "Cannot have both, atime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithStrictatimeAtimeFlag_withSeparateFlags_thenFails) { EXPECT_DEATH( TestFS({"-o", "strictatime", "-o", "atime"}), "Cannot have both, atime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithStrictatimeNoatimeFlag_withCsv_thenFails) { EXPECT_DEATH( TestFS({"-o", "strictatime,noatime"}), "Cannot have both, noatime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithStrictatimeNoatimeFlag_withSeparateFlags_thenFails) { EXPECT_DEATH( TestFS({"-o", "strictatime", "-o", "noatime"}), "Cannot have both, noatime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithStrictatimeRelatimeFlag_withCsv_thenFails) { EXPECT_DEATH( TestFS({"-o", "strictatime,relatime"}), "Cannot have both, relatime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithStrictatimeRelatimeFlag_withSeparateFlags_thenFails) { EXPECT_DEATH( TestFS({"-o", "strictatime", "-o", "relatime"}), "Cannot have both, relatime and strictatime flags set."); } TEST_F(FuseTimestampTest, whenCalledWithStrictatimeStrictatimeFlag_withCsv_thenHasStrictatimeBehavior) { auto fs = TestFS({"-o", "strictatime,strictatime"}); EXPECT_EQ(fspp::strictatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithStrictatimeStrictatimeFlag_withSeparateFlags_thenHasStrictatimeBehavior) { auto fs = TestFS({"-o", "strictatime", "-o", "strictatime"}); EXPECT_EQ(fspp::strictatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithStrictatimeNodiratimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "strictatime,nodiratime"}); EXPECT_EQ(fspp::nodiratime_strictatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithStrictatimeNodiratimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "strictatime", "-o", "nodiratime"}); EXPECT_EQ(fspp::nodiratime_strictatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNodiratimeAtimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "nodiratime,atime"}); EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNodiratimeAtimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "nodiratime", "-o", "atime"}); EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNodiratimeNoatimeFlag_withCsv_thenHasNoatimeBehavior) { auto fs = TestFS({"-o", "nodiratime,noatime"}); EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNodiratimeNoatimeFlag_withSeparateFlags_thenHasNoatimeBehavior) { auto fs = TestFS({"-o", "nodiratime", "-o", "noatime"}); EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNodiratimeRelatimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "nodiratime,relatime"}); EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNodiratimeRelatimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "nodiratime", "-o", "relatime"}); EXPECT_EQ(fspp::nodiratime_relatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNodiratimeStrictatimeFlag_withCsv_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "nodiratime,strictatime"}); EXPECT_EQ(fspp::nodiratime_strictatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNodiratimeStrictatimeFlag_withSeparateFlags_thenHasNodiratimeRelatimeBehavior) { auto fs = TestFS({"-o", "nodiratime", "-o", "strictatime"}); EXPECT_EQ(fspp::nodiratime_strictatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNodiratimeNodiratimeFlag_withCsv_thenHasNoatimeBehavior) { // note: this behavior is correct because "noatime" is default and adding "nodiratime" doesn't change anything. auto fs = TestFS({"-o", "nodiratime,nodiratime"}); EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); } TEST_F(FuseTimestampTest, whenCalledWithNodiratimeNodiratimeFlag_withSeparateFlags_thenHasNoatimeBehavior) { // note: this behavior is correct because "noatime" is default and adding "nodiratime" doesn't change anything. auto fs = TestFS({"-o", "nodiratime", "-o", "nodiratime"}); EXPECT_EQ(fspp::noatime().get(), context().timestampUpdateBehavior().get()); } test/fspp/fuse/access/000077500000000000000000000000001445614261000152135ustar00rootroot00000000000000test/fspp/fuse/access/FuseAccessErrorTest.cpp000066400000000000000000000014561445614261000216230ustar00rootroot00000000000000#include "testutils/FuseAccessTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Eq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::AtLeast; using namespace fspp::fuse; class FuseAccessErrorTest: public FuseAccessTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(FILENAME), testing::_)) .Times(AtLeast(1)).WillRepeatedly(Throw(FuseErrnoException(GetParam()))); int error = AccessFileReturnError(FILENAME, 0); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/access/FuseAccessFilenameTest.cpp000066400000000000000000000016351445614261000222510ustar00rootroot00000000000000#include "testutils/FuseAccessTest.h" using ::testing::Eq; using ::testing::Return; class FuseAccessFilenameTest: public FuseAccessTest { }; TEST_F(FuseAccessFilenameTest, AccessFile) { ReturnIsFileOnLstat("/myfile"); EXPECT_CALL(*fsimpl, access(Eq("/myfile"), testing::_)) .Times(1).WillOnce(Return()); AccessFile("/myfile", 0); } TEST_F(FuseAccessFilenameTest, AccessFileNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsFileOnLstat("/mydir/myfile"); EXPECT_CALL(*fsimpl, access(Eq("/mydir/myfile"), testing::_)) .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(Eq("/mydir/mydir2/myfile"), testing::_)) .Times(1).WillOnce(Return()); AccessFile("/mydir/mydir2/myfile", 0); } test/fspp/fuse/access/FuseAccessModeTest.cpp000066400000000000000000000011151445614261000214060ustar00rootroot00000000000000#include "testutils/FuseAccessTest.h" using ::testing::Eq; using ::testing::Return; using ::testing::WithParamInterface; using ::testing::Values; class FuseAccessModeTest: public FuseAccessTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(FILENAME), GetParam())) .Times(1).WillOnce(Return()); AccessFile(FILENAME, GetParam()); } test/fspp/fuse/access/testutils/000077500000000000000000000000001445614261000172535ustar00rootroot00000000000000test/fspp/fuse/access/testutils/FuseAccessTest.cpp000066400000000000000000000006761445614261000226540ustar00rootroot00000000000000#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.h000066400000000000000000000006141445614261000223110ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000156575ustar00rootroot00000000000000test/fspp/fuse/closeFile/FuseCloseTest.cpp000066400000000000000000000062051445614261000211160ustar00rootroot00000000000000#include "../../testutils/FuseTest.h" #include "../../testutils/OpenFileHandle.h" #include //TODO Figure out what's wrong and enable this test //Disabled, because it is flaky. libfuse seems to not send the release() event sometimes. /* 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_SUITE_P(FuseCloseTest, FuseCloseTest, Values(0, 1, 2, 100, 1024*1024*1024)); TEST_P(FuseCloseTest, CloseFile) { Barrier barrier; ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, openFile(Eq(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/000077500000000000000000000000001445614261000172625ustar00rootroot00000000000000test/fspp/fuse/createAndOpenFile/FuseCreateAndOpenErrorTest.cpp000066400000000000000000000026101445614261000251320ustar00rootroot00000000000000#include "testutils/FuseCreateAndOpenTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Return; using ::testing::Throw; using ::testing::Eq; using namespace fspp::fuse; class FuseCreateAndOpenErrorTest: public FuseCreateAndOpenTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(FILENAME), testing::_, testing::_, testing::_)).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(Eq(FILENAME), testing::_, testing::_, testing::_)).Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = CreateAndOpenFileReturnError(FILENAME, O_RDONLY); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/createAndOpenFile/FuseCreateAndOpenFileDescriptorTest.cpp000066400000000000000000000032171445614261000267630ustar00rootroot00000000000000#include "testutils/FuseCreateAndOpenTest.h" using ::testing::Eq; 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 = 0; int retval = ::read(fd, &buf, 1); EXPECT_EQ(1, retval) << "Reading file failed"; } }; INSTANTIATE_TEST_SUITE_P(FuseCreateAndOpenFileDescriptorTest, FuseCreateAndOpenFileDescriptorTest, Values(0, 2, 5, 1000, 1024*1024*1024)); TEST_P(FuseCreateAndOpenFileDescriptorTest, TestReturnedFileDescriptor) { ReturnDoesntExistOnLstat(FILENAME); EXPECT_CALL(*fsimpl, createAndOpenFile(Eq(FILENAME), testing::_, testing::_, testing::_)) .Times(1).WillOnce(Return(GetParam())); EXPECT_CALL(*fsimpl, read(GetParam(), testing::_, testing::_, testing::_)).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.cpp000066400000000000000000000027011445614261000255620ustar00rootroot00000000000000#include "testutils/FuseCreateAndOpenTest.h" using ::testing::Eq; using ::testing::Return; class FuseCreateAndOpenFilenameTest: public FuseCreateAndOpenTest { public: }; TEST_F(FuseCreateAndOpenFilenameTest, CreateAndOpenFile) { ReturnDoesntExistOnLstat("/myfile"); EXPECT_CALL(*fsimpl, createAndOpenFile(Eq("/myfile"), testing::_, testing::_, testing::_)) .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(Eq("/mydir/myfile"), testing::_, testing::_, testing::_)) .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(Eq("/mydir/mydir2/myfile"), testing::_, testing::_, testing::_)) .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.cpp000066400000000000000000000014321445614261000250760ustar00rootroot00000000000000#include "testutils/FuseCreateAndOpenTest.h" //TODO Disabled because it doesn't seem to work. Fuse doesn't seem to pass flags to create(). Why? /* using ::testing::WithParamInterface; using ::testing::Values; class FuseCreateAndOpenFlagsTest: public FuseCreateAndOpenTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_P(FuseCreateAndOpenFlagsTest, FuseCreateAndOpenFlagsTest, Values(O_RDWR, O_RDONLY, O_WRONLY)); TEST_P(FuseCreateAndOpenFlagsTest, testFlags) { ReturnDoesntExistOnLstat(FILENAME); EXPECT_CALL(*fsimpl, createAndOpenFile(Eq(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/000077500000000000000000000000001445614261000213225ustar00rootroot00000000000000test/fspp/fuse/createAndOpenFile/testutils/FuseCreateAndOpenTest.cpp000066400000000000000000000016061445614261000261640ustar00rootroot00000000000000#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.h000066400000000000000000000013541445614261000256310ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000157265ustar00rootroot00000000000000test/fspp/fuse/fdatasync/FuseFdatasyncErrorTest.cpp000066400000000000000000000013311445614261000230410ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000013301445614261000246650ustar00rootroot00000000000000#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_SUITE_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/000077500000000000000000000000001445614261000177665ustar00rootroot00000000000000test/fspp/fuse/fdatasync/testutils/FuseFdatasyncTest.cpp000066400000000000000000000016241445614261000240740ustar00rootroot00000000000000#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.h000066400000000000000000000010431445614261000235340ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000150735ustar00rootroot00000000000000test/fspp/fuse/flush/FuseFlushErrorTest.cpp000066400000000000000000000020541445614261000213560ustar00rootroot00000000000000#include "testutils/FuseFlushTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::WithParamInterface; using ::testing::Eq; using ::testing::Return; using ::testing::Throw; using ::testing::Values; using fspp::fuse::FuseErrnoException; class FuseFlushErrorTest: public FuseFlushTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(FILENAME), testing::_)).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.cpp000066400000000000000000000025571445614261000232130ustar00rootroot00000000000000#include "testutils/FuseFlushTest.h" 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_SUITE_P(FuseFlushFileDescriptorTest, FuseFlushFileDescriptorTest, Values(0, 1, 2, 100, 1024*1024*1024)); TEST_P(FuseFlushFileDescriptorTest, FlushOnCloseFile) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, openFile(Eq(FILENAME), testing::_)).WillOnce(Return(GetParam())); EXPECT_CALL(*fsimpl, flush(Eq(GetParam()))).Times(1); OpenAndCloseFile(FILENAME); } test/fspp/fuse/flush/testutils/000077500000000000000000000000001445614261000171335ustar00rootroot00000000000000test/fspp/fuse/flush/testutils/FuseFlushTest.cpp000066400000000000000000000012621445614261000224040ustar00rootroot00000000000000#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.h000066400000000000000000000010011445614261000220400ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000150735ustar00rootroot00000000000000test/fspp/fuse/fstat/FuseFstatErrorTest.cpp000066400000000000000000000025751445614261000213660ustar00rootroot00000000000000#include "testutils/FuseFstatTest.h" #include "fspp/fs_interface/FuseErrnoException.h" 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_SUITE_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), testing::_)).Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); auto fs = TestFS(); int error = CreateFileReturnError(fs.get(), FILENAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/fstat/FuseFstatParameterTest.cpp000066400000000000000000000020531445614261000222040ustar00rootroot00000000000000#include "testutils/FuseFstatTest.h" #include "fspp/fs_interface/FuseErrnoException.h" 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_SUITE_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()), testing::_)).Times(1).WillOnce(ReturnIsFileFstat); CallFstat(FILENAME); } test/fspp/fuse/fstat/README000066400000000000000000000005741445614261000157610ustar00rootroot00000000000000Cite 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/000077500000000000000000000000001445614261000171335ustar00rootroot00000000000000test/fspp/fuse/fstat/testutils/FuseFstatTest.cpp000066400000000000000000000020551445614261000224050ustar00rootroot00000000000000#include "FuseFstatTest.h" using ::testing::Eq; 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(Eq(filename), testing::_, testing::_, testing::_)).Times(1).WillOnce(Return(descriptor)); } test/fspp/fuse/fstat/testutils/FuseFstatTest.h000066400000000000000000000012441445614261000220510ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000150745ustar00rootroot00000000000000test/fspp/fuse/fsync/FuseFsyncErrorTest.cpp000066400000000000000000000012711445614261000213600ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000012701445614261000232040ustar00rootroot00000000000000#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_SUITE_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/000077500000000000000000000000001445614261000171345ustar00rootroot00000000000000test/fspp/fuse/fsync/testutils/FuseFsyncTest.cpp000066400000000000000000000013411445614261000224040ustar00rootroot00000000000000#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.h000066400000000000000000000010071445614261000220500ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000157455ustar00rootroot00000000000000test/fspp/fuse/ftruncate/FuseFTruncateErrorTest.cpp000066400000000000000000000016601445614261000230440ustar00rootroot00000000000000#include "testutils/FuseFTruncateTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseFTruncateErrorTest: public FuseFTruncateTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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, testing::_)) .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.cpp000066400000000000000000000015341445614261000246710ustar00rootroot00000000000000#include "testutils/FuseFTruncateTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using ::testing::Return; using namespace fspp::fuse; class FuseFTruncateFileDescriptorTest: public FuseFTruncateTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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()), testing::_)) .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.cpp000066400000000000000000000015161445614261000226650ustar00rootroot00000000000000#include "testutils/FuseFTruncateTest.h" using ::testing::Eq; using ::testing::Return; using ::testing::WithParamInterface; using ::testing::Values; class FuseFTruncateSizeTest: public FuseFTruncateTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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/000077500000000000000000000000001445614261000200055ustar00rootroot00000000000000test/fspp/fuse/ftruncate/testutils/FuseFTruncateTest.cpp000066400000000000000000000015051445614261000240700ustar00rootroot00000000000000#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.h000066400000000000000000000011231445614261000235310ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000151015ustar00rootroot00000000000000test/fspp/fuse/lstat/FuseLstatErrorTest.cpp000066400000000000000000000017571445614261000214030ustar00rootroot00000000000000#include "testutils/FuseLstatTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Eq; 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_SUITE_P(LstatErrorCodes, FuseLstatErrorTest, Values(EACCES, EBADF, EFAULT, ELOOP, ENAMETOOLONG, ENOENT, ENOMEM, ENOTDIR, EOVERFLOW, EINVAL, ENOTDIR)); TEST_F(FuseLstatErrorTest, ReturnNoError) { EXPECT_CALL(*fsimpl, lstat(Eq(FILENAME), testing::_)).Times(AtLeast(1)).WillRepeatedly(ReturnIsFile); errno = 0; int error = LstatPathReturnError(FILENAME); EXPECT_EQ(0, error); } TEST_P(FuseLstatErrorTest, ReturnError) { EXPECT_CALL(*fsimpl, lstat(Eq(FILENAME), testing::_)).Times(AtLeast(1)).WillRepeatedly(Throw(FuseErrnoException(GetParam()))); int error = LstatPathReturnError(FILENAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/lstat/FuseLstatPathParameterTest.cpp000066400000000000000000000024641445614261000230430ustar00rootroot00000000000000#include "testutils/FuseLstatTest.h" using ::testing::Eq; using ::testing::AtLeast; class FuseLstatPathParameterTest: public FuseLstatTest { }; TEST_F(FuseLstatPathParameterTest, PathParameterIsCorrectRoot) { EXPECT_CALL(*fsimpl, lstat(Eq("/"), testing::_)).Times(AtLeast(1)).WillRepeatedly(ReturnIsDir); LstatPath("/"); } TEST_F(FuseLstatPathParameterTest, PathParameterIsCorrectSimpleFile) { EXPECT_CALL(*fsimpl, lstat(Eq("/myfile"), testing::_)).Times(AtLeast(1)).WillRepeatedly(ReturnIsFile); LstatPath("/myfile"); } TEST_F(FuseLstatPathParameterTest, PathParameterIsCorrectSimpleDir) { EXPECT_CALL(*fsimpl, lstat(Eq("/mydir"), testing::_)).Times(AtLeast(1)).WillRepeatedly(ReturnIsDir); LstatPath("/mydir/"); } TEST_F(FuseLstatPathParameterTest, PathParameterIsCorrectNestedFile) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); EXPECT_CALL(*fsimpl, lstat(Eq("/mydir/mydir2/myfile"), testing::_)).Times(AtLeast(1)).WillRepeatedly(ReturnIsFile); LstatPath("/mydir/mydir2/myfile"); } TEST_F(FuseLstatPathParameterTest, PathParameterIsCorrectNestedDir) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); EXPECT_CALL(*fsimpl, lstat(Eq("/mydir/mydir2/mydir3"), testing::_)).Times(AtLeast(1)).WillRepeatedly(ReturnIsDir); LstatPath("/mydir/mydir2/mydir3/"); } test/fspp/fuse/lstat/FuseLstatReturnAtimeTest.cpp000066400000000000000000000020021445614261000225310ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000020021445614261000225330ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000013411445614261000222020ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000014321445614261000223640ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000020021445614261000225450ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000014121445614261000225510ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000016351445614261000224170ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000013411445614261000222200ustar00rootroot00000000000000#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_SUITE_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/000077500000000000000000000000001445614261000171415ustar00rootroot00000000000000test/fspp/fuse/lstat/testutils/FuseLstatReturnTest.h000066400000000000000000000032361445614261000232700ustar00rootroot00000000000000#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.cpp000066400000000000000000000034231445614261000224210ustar00rootroot00000000000000#include "FuseLstatTest.h" using std::function; using ::testing::Eq; 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(Eq(FILENAME), testing::_)).WillRepeatedly(Invoke([implementation](const boost::filesystem::path&, 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.h000066400000000000000000000040111445614261000220600ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000150605ustar00rootroot00000000000000test/fspp/fuse/mkdir/FuseMkdirDirnameTest.cpp000066400000000000000000000025351445614261000216220ustar00rootroot00000000000000#include "testutils/FuseMkdirTest.h" using ::testing::Eq; class FuseMkdirDirnameTest: public FuseMkdirTest { }; TEST_F(FuseMkdirDirnameTest, Mkdir) { ReturnDoesntExistOnLstat("/mydir"); EXPECT_CALL(*fsimpl, mkdir(Eq("/mydir"), testing::_, testing::_, testing::_)) // 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(Eq("/mydir/mysubdir"), testing::_, testing::_, testing::_)) // 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(Eq("/mydir/mydir2/mydir3"), testing::_, testing::_, testing::_)) // 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.cpp000066400000000000000000000021031445614261000213230ustar00rootroot00000000000000#include "testutils/FuseMkdirTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Eq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseMkdirErrorTest: public FuseMkdirTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(DIRNAME), testing::_, testing::_, testing::_)) .Times(1).WillOnce(FromNowOnReturnIsDirOnLstat()); int error = MkdirReturnError(DIRNAME, 0); EXPECT_EQ(0, error); } TEST_P(FuseMkdirErrorTest, ReturnedErrorIsCorrect) { ReturnDoesntExistOnLstat(DIRNAME); EXPECT_CALL(*fsimpl, mkdir(Eq(DIRNAME), testing::_, testing::_, testing::_)) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = MkdirReturnError(DIRNAME, 0); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/mkdir/FuseMkdirModeTest.cpp000066400000000000000000000011101445614261000211130ustar00rootroot00000000000000#include "testutils/FuseMkdirTest.h" using ::testing::Eq; using ::testing::WithParamInterface; using ::testing::Values; class FuseMkdirModeTest: public FuseMkdirTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(DIRNAME), GetParam(), testing::_, testing::_)) .Times(1).WillOnce(FromNowOnReturnIsDirOnLstat()); Mkdir(DIRNAME, GetParam()); } test/fspp/fuse/mkdir/testutils/000077500000000000000000000000001445614261000171205ustar00rootroot00000000000000test/fspp/fuse/mkdir/testutils/FuseMkdirTest.cpp000066400000000000000000000013231445614261000223540ustar00rootroot00000000000000#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 boost::filesystem::path& dirname, mode_t, uid_t, gid_t) { ReturnIsDirOnLstat(dirname); }); } test/fspp/fuse/mkdir/testutils/FuseMkdirTest.h000066400000000000000000000007571445614261000220330ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000155135ustar00rootroot00000000000000test/fspp/fuse/openFile/FuseOpenErrorTest.cpp000066400000000000000000000022161445614261000216160ustar00rootroot00000000000000#include "testutils/FuseOpenTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Return; using ::testing::Throw; using ::testing::Eq; using namespace fspp::fuse; class FuseOpenErrorTest: public FuseOpenTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(FILENAME), testing::_)).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(Eq(FILENAME), testing::_)).Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = OpenFileReturnError(FILENAME, O_RDONLY); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/openFile/FuseOpenFileDescriptorTest.cpp000066400000000000000000000025671445614261000234540ustar00rootroot00000000000000#include "testutils/FuseOpenTest.h" using ::testing::Eq; 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 = 0; int retval = ::read(fd, &buf, 1); EXPECT_EQ(1, retval) << "Reading file failed"; } }; INSTANTIATE_TEST_SUITE_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(Eq(FILENAME), testing::_)) .Times(1).WillOnce(Return(GetParam())); EXPECT_CALL(*fsimpl, read(GetParam(), testing::_, testing::_, testing::_)).Times(1).WillOnce(Return(fspp::num_bytes_t(1))); OpenAndReadFile(FILENAME); } test/fspp/fuse/openFile/FuseOpenFilenameTest.cpp000066400000000000000000000016531445614261000222510ustar00rootroot00000000000000#include "testutils/FuseOpenTest.h" using ::testing::Eq; using ::testing::Return; class FuseOpenFilenameTest: public FuseOpenTest { public: }; TEST_F(FuseOpenFilenameTest, OpenFile) { ReturnIsFileOnLstat("/myfile"); EXPECT_CALL(*fsimpl, openFile(Eq("/myfile"), testing::_)) .Times(1).WillOnce(Return(0)); OpenFile("/myfile", O_RDONLY); } TEST_F(FuseOpenFilenameTest, OpenFileNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsFileOnLstat("/mydir/myfile"); EXPECT_CALL(*fsimpl, openFile(Eq("/mydir/myfile"), testing::_)) .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(Eq("/mydir/mydir2/myfile"), testing::_)) .Times(1).WillOnce(Return(0)); OpenFile("/mydir/mydir2/myfile", O_RDONLY); } test/fspp/fuse/openFile/FuseOpenFlagsTest.cpp000066400000000000000000000010411445614261000215540ustar00rootroot00000000000000#include "testutils/FuseOpenTest.h" using ::testing::Eq; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Return; class FuseOpenFlagsTest: public FuseOpenTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_P(FuseOpenFlagsTest, FuseOpenFlagsTest, Values(O_RDWR, O_RDONLY, O_WRONLY)); TEST_P(FuseOpenFlagsTest, testFlags) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, openFile(Eq(FILENAME), OpenFlagsEq(GetParam()))) .Times(1).WillOnce(Return(0)); OpenFile(FILENAME, GetParam()); } test/fspp/fuse/openFile/testutils/000077500000000000000000000000001445614261000175535ustar00rootroot00000000000000test/fspp/fuse/openFile/testutils/FuseOpenTest.cpp000066400000000000000000000012551445614261000226460ustar00rootroot00000000000000#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.h000066400000000000000000000012161445614261000223100ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000146655ustar00rootroot00000000000000test/fspp/fuse/read/FuseReadErrorTest.cpp000066400000000000000000000045201445614261000207420ustar00rootroot00000000000000#include "testutils/FuseReadTest.h" #include "fspp/fs_interface/FuseErrnoException.h" 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_SUITE_P(FuseReadErrorTest, FuseReadErrorTest, Values(EAGAIN, EBADF, EFAULT, EINTR, EINVAL, EIO, EISDIR, EOVERFLOW, ESPIPE, ENXIO)); TEST_P(FuseReadErrorTest, ReturnErrorOnFirstReadCall) { EXPECT_CALL(*fsimpl, read(0, testing::_, testing::_, testing::_)) .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, testing::_, testing::_, 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, testing::_, testing::_, 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.cpp000066400000000000000000000014721445614261000225720ustar00rootroot00000000000000#include "testutils/FuseReadTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using namespace fspp::fuse; class FuseReadFileDescriptorTest: public FuseReadTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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()), testing::_, testing::_, testing::_)) .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.cpp000066400000000000000000000024441445614261000214570ustar00rootroot00000000000000#include "testutils/FuseReadTest.h" #include "fspp/fs_interface/FuseErrnoException.h" 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, testing::_, testing::_, testing::_)).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.cpp000066400000000000000000000062541445614261000222410ustar00rootroot00000000000000#include #include #include "../../testutils/InMemoryFile.h" #include "testutils/FuseReadTest.h" #include #include "fspp/fs_interface/FuseErrnoException.h" #include #include 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, testing::_, testing::_, testing::_)) .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_SUITE_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/000077500000000000000000000000001445614261000167255ustar00rootroot00000000000000test/fspp/fuse/read/testutils/FuseReadTest.cpp000066400000000000000000000017671445614261000220020ustar00rootroot00000000000000#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.h000066400000000000000000000024551445614261000214420ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000153245ustar00rootroot00000000000000test/fspp/fuse/readDir/FuseReadDirDirnameTest.cpp000066400000000000000000000020351445614261000223250ustar00rootroot00000000000000#include "testutils/FuseReadDirTest.h" using ::testing::Eq; using std::string; class FuseReadDirDirnameTest: public FuseReadDirTest { public: }; TEST_F(FuseReadDirDirnameTest, ReadRootDir) { EXPECT_CALL(*fsimpl, readDir(Eq("/"))) .Times(1).WillOnce(ReturnDirEntries({})); ReadDir("/"); } TEST_F(FuseReadDirDirnameTest, ReadDir) { ReturnIsDirOnLstat("/mydir"); EXPECT_CALL(*fsimpl, readDir(Eq("/mydir"))) .Times(1).WillOnce(ReturnDirEntries({})); ReadDir("/mydir"); } TEST_F(FuseReadDirDirnameTest, ReadDirNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); EXPECT_CALL(*fsimpl, readDir(Eq("/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(Eq("/mydir/mydir2/mydir3"))) .Times(1).WillOnce(ReturnDirEntries({})); ReadDir("/mydir/mydir2/mydir3"); } test/fspp/fuse/readDir/FuseReadDirErrorTest.cpp000066400000000000000000000021241445614261000220360ustar00rootroot00000000000000#include "testutils/FuseReadDirTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Eq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using std::string; using namespace fspp::fuse; class FuseReadDirErrorTest: public FuseReadDirTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(DIRNAME))) .Times(1).WillOnce(ReturnDirEntries({})); int error = ReadDirReturnError(DIRNAME); EXPECT_EQ(0, error); } TEST_P(FuseReadDirErrorTest, ReturnedErrorCodeIsCorrect) { ReturnIsDirOnLstat(DIRNAME); EXPECT_CALL(*fsimpl, readDir(Eq(DIRNAME))) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = ReadDirReturnError(DIRNAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/readDir/FuseReadDirReturnTest.cpp000066400000000000000000000040051445614261000222240ustar00rootroot00000000000000#include "testutils/FuseReadDirTest.h" #include #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Eq; using ::testing::WithParamInterface; using ::testing::Values; using std::vector; using std::string; using namespace fspp::fuse; vector LARGE_DIR(int num_entries) { vector result; result.reserve(num_entries); for(int i=0; i> { public: void testDirEntriesAreCorrect(const vector &direntries) { ReturnIsDirOnLstat(DIRNAME); EXPECT_CALL(*fsimpl, readDir(Eq(DIRNAME))) .Times(1).WillOnce(ReturnDirEntries(direntries)); auto returned_dir_entries = ReadDir(DIRNAME); EXPECT_EQ(direntries, returned_dir_entries); } }; INSTANTIATE_TEST_SUITE_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/000077500000000000000000000000001445614261000173645ustar00rootroot00000000000000test/fspp/fuse/readDir/testutils/FuseReadDirTest.cpp000066400000000000000000000044741445614261000230760ustar00rootroot00000000000000#include "FuseReadDirTest.h" using cpputils::make_unique_ref; using std::vector; using std::string; using ::testing::Action; using ::testing::Return; vector FuseReadDirTest::ReadDir(const char *dirname) { auto fs = TestFS(); DIR *dir = openDir(fs.get(), dirname); vector result; readDirEntries(dir, &result); 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 boost::filesystem::path&)> FuseReadDirTest::ReturnDirEntries(vector entries) { vector direntries(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(std::move(direntries)); } test/fspp/fuse/readDir/testutils/FuseReadDirTest.h000066400000000000000000000016711445614261000225370ustar00rootroot00000000000000#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"; std::vector ReadDir(const char *dirname); int ReadDirReturnError(const char *dirname); static ::testing::Action(const boost::filesystem::path&)> 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/000077500000000000000000000000001445614261000152215ustar00rootroot00000000000000test/fspp/fuse/rename/FuseRenameErrorTest.cpp000066400000000000000000000015771445614261000216430ustar00rootroot00000000000000#include "testutils/FuseRenameTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Eq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseRenameErrorTest: public FuseRenameTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(FILENAME1), Eq(FILENAME2))) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = RenameReturnError(FILENAME1, FILENAME2); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/rename/FuseRenameFilenameTest.cpp000066400000000000000000000106431445614261000222640ustar00rootroot00000000000000#include "testutils/FuseRenameTest.h" using ::testing::Eq; using ::testing::Return; class FuseRenameFilenameTest: public FuseRenameTest { }; TEST_F(FuseRenameFilenameTest, RenameFileRootToRoot) { ReturnIsFileOnLstat("/myfile"); ReturnDoesntExistOnLstat("/myrenamedfile"); EXPECT_CALL(*fsimpl, rename(Eq("/myfile"), Eq("/myrenamedfile"))) .Times(1).WillOnce(Return()); Rename("/myfile", "/myrenamedfile"); } TEST_F(FuseRenameFilenameTest, RenameFileRootToNested) { ReturnIsFileOnLstat("/myfile"); ReturnIsDirOnLstat("/mydir"); ReturnDoesntExistOnLstat("/mydir/myrenamedfile"); EXPECT_CALL(*fsimpl, rename(Eq("/myfile"), Eq("/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(Eq("/mydir/myfile"), Eq("/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(Eq("/mydir/myfile"), Eq("/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(Eq("/mydir/mydir2/myfile"), Eq("/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(Eq("/mydir/myfile"), Eq("/mydir2/myrenamedfile"))) .Times(1).WillOnce(Return()); Rename("/mydir/myfile", "/mydir2/myrenamedfile"); } TEST_F(FuseRenameFilenameTest, RenameDirRootToRoot) { ReturnIsDirOnLstat("/mydir"); ReturnDoesntExistOnLstat("/myrenameddir"); EXPECT_CALL(*fsimpl, rename(Eq("/mydir"), Eq("/myrenameddir"))) .Times(1).WillOnce(Return()); Rename("/mydir", "/myrenameddir"); } TEST_F(FuseRenameFilenameTest, RenameDirRootToNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/myrootdir"); ReturnDoesntExistOnLstat("/myrootdir/myrenameddir"); EXPECT_CALL(*fsimpl, rename(Eq("/mydir"), Eq("/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(Eq("/myrootdir/mydir"), Eq("/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(Eq("/myrootdir/mydir"), Eq("/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(Eq("/myrootdir/myrootdir2/mydir"), Eq("/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(Eq("/myrootdir/mydir"), Eq("/myrootdir2/myrenameddir"))) .Times(1).WillOnce(Return()); Rename("/myrootdir/mydir", "/myrootdir2/myrenameddir"); } test/fspp/fuse/rename/testutils/000077500000000000000000000000001445614261000172615ustar00rootroot00000000000000test/fspp/fuse/rename/testutils/FuseRenameTest.cpp000066400000000000000000000007451445614261000226650ustar00rootroot00000000000000#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.h000066400000000000000000000006601445614261000223260ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000150675ustar00rootroot00000000000000test/fspp/fuse/rmdir/FuseRmdirDirnameTest.cpp000066400000000000000000000024361445614261000216400ustar00rootroot00000000000000#include "testutils/FuseRmdirTest.h" using ::testing::Eq; class FuseRmdirDirnameTest: public FuseRmdirTest { }; TEST_F(FuseRmdirDirnameTest, Rmdir) { ReturnIsDirOnLstat("/mydir"); EXPECT_CALL(*fsimpl, rmdir(Eq("/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(Eq("/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(Eq("/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.cpp000066400000000000000000000013601445614261000213450ustar00rootroot00000000000000#include "testutils/FuseRmdirTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Eq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseRmdirErrorTest: public FuseRmdirTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(DIRNAME))) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = RmdirReturnError(DIRNAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/rmdir/testutils/000077500000000000000000000000001445614261000171275ustar00rootroot00000000000000test/fspp/fuse/rmdir/testutils/FuseRmdirTest.cpp000066400000000000000000000012151445614261000223720ustar00rootroot00000000000000#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 boost::filesystem::path& dirname) { ReturnDoesntExistOnLstat(dirname); }); } test/fspp/fuse/rmdir/testutils/FuseRmdirTest.h000066400000000000000000000007051445614261000220420ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000152565ustar00rootroot00000000000000test/fspp/fuse/statfs/FuseStatfsErrorTest.cpp000066400000000000000000000017311445614261000217250ustar00rootroot00000000000000#include "testutils/FuseStatfsTest.h" #include "fspp/fs_interface/FuseErrnoException.h" 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_SUITE_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(testing::_)).Times(1).WillOnce(Return()); int error = StatfsReturnError(FILENAME); EXPECT_EQ(0, error); } TEST_P(FuseStatfsErrorTest, ReturnError) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, statfs(testing::_)).Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = StatfsReturnError(FILENAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/statfs/FuseStatfsReturnBavailTest.cpp000066400000000000000000000011651445614261000232330ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000011561445614261000230600ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000011651445614261000232520ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000011751445614261000231120ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000011561445614261000230640ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000011561445614261000230770ustar00rootroot00000000000000#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_SUITE_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.cpp000066400000000000000000000012131445614261000234150ustar00rootroot00000000000000#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_SUITE_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/000077500000000000000000000000001445614261000173165ustar00rootroot00000000000000test/fspp/fuse/statfs/testutils/FuseStatfsReturnTest.h000066400000000000000000000025111445614261000236150ustar00rootroot00000000000000#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.cpp000066400000000000000000000022171445614261000227530ustar00rootroot00000000000000#include "FuseStatfsTest.h" using std::function; 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(testing::_)).WillRepeatedly(Invoke([implementation](struct ::statvfs *stat) { implementation(stat); })); struct ::statvfs result{}; Statfs(FILENAME, &result); return result; } test/fspp/fuse/statfs/testutils/FuseStatfsTest.h000066400000000000000000000027731445614261000224270ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000155775ustar00rootroot00000000000000test/fspp/fuse/truncate/FuseTruncateErrorTest.cpp000066400000000000000000000014741445614261000225730ustar00rootroot00000000000000#include "testutils/FuseTruncateTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Eq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseTruncateErrorTest: public FuseTruncateTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(FILENAME), testing::_)) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = TruncateFileReturnError(FILENAME, fspp::num_bytes_t(0)); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/truncate/FuseTruncateFilenameTest.cpp000066400000000000000000000017641445614261000232240ustar00rootroot00000000000000#include "testutils/FuseTruncateTest.h" using ::testing::Eq; using ::testing::Return; class FuseTruncateFilenameTest: public FuseTruncateTest { }; TEST_F(FuseTruncateFilenameTest, TruncateFile) { ReturnIsFileOnLstat("/myfile"); EXPECT_CALL(*fsimpl, truncate(Eq("/myfile"), testing::_)) .Times(1).WillOnce(Return()); TruncateFile("/myfile", fspp::num_bytes_t(0)); } TEST_F(FuseTruncateFilenameTest, TruncateFileNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsFileOnLstat("/mydir/myfile"); EXPECT_CALL(*fsimpl, truncate(Eq("/mydir/myfile"), testing::_)) .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(Eq("/mydir/mydir2/myfile"), testing::_)) .Times(1).WillOnce(Return()); TruncateFile("/mydir/mydir2/myfile", fspp::num_bytes_t(0)); } test/fspp/fuse/truncate/FuseTruncateSizeTest.cpp000066400000000000000000000013161445614261000224070ustar00rootroot00000000000000#include "testutils/FuseTruncateTest.h" using ::testing::Eq; using ::testing::Return; using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; class FuseTruncateSizeTest: public FuseTruncateTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(FILENAME), Eq(GetParam()))) .Times(1).WillOnce(Return()); TruncateFile(FILENAME, GetParam()); } test/fspp/fuse/truncate/testutils/000077500000000000000000000000001445614261000176375ustar00rootroot00000000000000test/fspp/fuse/truncate/testutils/FuseTruncateTest.cpp000066400000000000000000000007601445614261000236160ustar00rootroot00000000000000#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.h000066400000000000000000000006661445614261000232700ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000152525ustar00rootroot00000000000000test/fspp/fuse/unlink/FuseUnlinkErrorTest.cpp000066400000000000000000000013761445614261000217220ustar00rootroot00000000000000#include "testutils/FuseUnlinkTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Eq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseUnlinkErrorTest: public FuseUnlinkTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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(Eq(FILENAME))) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = UnlinkReturnError(FILENAME); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/unlink/FuseUnlinkFilenameTest.cpp000066400000000000000000000024641445614261000223500ustar00rootroot00000000000000#include "testutils/FuseUnlinkTest.h" using ::testing::Eq; class FuseUnlinkFilenameTest: public FuseUnlinkTest { }; TEST_F(FuseUnlinkFilenameTest, Unlink) { ReturnIsFileOnLstat("/mydir"); EXPECT_CALL(*fsimpl, unlink(Eq("/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(Eq("/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(Eq("/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/000077500000000000000000000000001445614261000173125ustar00rootroot00000000000000test/fspp/fuse/unlink/testutils/FuseUnlinkTest.cpp000066400000000000000000000012331445614261000227400ustar00rootroot00000000000000#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 boost::filesystem::path& filename) { ReturnDoesntExistOnLstat(filename); }); } test/fspp/fuse/unlink/testutils/FuseUnlinkTest.h000066400000000000000000000007201445614261000224050ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000154365ustar00rootroot00000000000000test/fspp/fuse/utimens/FuseUtimensErrorTest.cpp000066400000000000000000000013521445614261000222640ustar00rootroot00000000000000#include "testutils/FuseUtimensTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::Eq; using ::testing::Throw; using ::testing::WithParamInterface; using ::testing::Values; using namespace fspp::fuse; class FuseUtimensErrorTest: public FuseUtimensTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_P(FuseUtimensErrorTest, FuseUtimensErrorTest, Values(EACCES, ENOENT, EPERM, EROFS)); TEST_P(FuseUtimensErrorTest, ReturnedErrorIsCorrect) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, utimens(Eq(FILENAME), testing::_, testing::_)) .Times(1).WillOnce(Throw(FuseErrnoException(GetParam()))); int error = UtimensReturnError(FILENAME, TIMEVALUE, TIMEVALUE); EXPECT_EQ(GetParam(), error); } test/fspp/fuse/utimens/FuseUtimensFilenameTest.cpp000066400000000000000000000035371445614261000227220ustar00rootroot00000000000000#include "testutils/FuseUtimensTest.h" using ::testing::Eq; using ::testing::Return; class FuseUtimensFilenameTest: public FuseUtimensTest { }; TEST_F(FuseUtimensFilenameTest, UtimensFile) { ReturnIsFileOnLstat("/myfile"); EXPECT_CALL(*fsimpl, utimens(Eq("/myfile"), testing::_, testing::_)) .Times(1).WillOnce(Return()); Utimens("/myfile", TIMEVALUE, TIMEVALUE); } TEST_F(FuseUtimensFilenameTest, UtimensFileNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsFileOnLstat("/mydir/myfile"); EXPECT_CALL(*fsimpl, utimens(Eq("/mydir/myfile"), testing::_, testing::_)) .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(Eq("/mydir/mydir2/myfile"), testing::_, testing::_)) .Times(1).WillOnce(Return()); Utimens("/mydir/mydir2/myfile", TIMEVALUE, TIMEVALUE); } TEST_F(FuseUtimensFilenameTest, UtimensDir) { ReturnIsDirOnLstat("/mydir"); EXPECT_CALL(*fsimpl, utimens(Eq("/mydir"), testing::_, testing::_)) .Times(1).WillOnce(Return()); Utimens("/mydir", TIMEVALUE, TIMEVALUE); } TEST_F(FuseUtimensFilenameTest, UtimensDirNested) { ReturnIsDirOnLstat("/mydir"); ReturnIsDirOnLstat("/mydir/mydir2"); EXPECT_CALL(*fsimpl, utimens(Eq("/mydir/mydir2"), testing::_, testing::_)) .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(Eq("/mydir/mydir2/mydir3"), testing::_, testing::_)) .Times(1).WillOnce(Return()); Utimens("/mydir/mydir2/mydir3", TIMEVALUE, TIMEVALUE); } test/fspp/fuse/utimens/FuseUtimensTimeParameterTest.cpp000066400000000000000000000041071445614261000237330ustar00rootroot00000000000000#include "testutils/FuseUtimensTest.h" using ::testing::Eq; 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_SUITE_P(FuseUtimensTimeParameterTest, FuseUtimensTimeParameterTest, Values(TIMEVAL1, TIMEVAL2, TIMEVAL3, TIMEVAL4, TIMEVAL5, TIMEVAL6, TIMEVAL7, TIMEVAL8, TIMEVAL9, TIMEVAL10)); TEST_P(FuseUtimensTimeParameterTest, Utimens) { ReturnIsFileOnLstat(FILENAME); EXPECT_CALL(*fsimpl, utimens(Eq(FILENAME), TimeSpecEq(GetParam()[0]), TimeSpecEq(GetParam()[1]))) .Times(1).WillOnce(Return()); Utimens(FILENAME, GetParam()[0], GetParam()[1]); } test/fspp/fuse/utimens/testutils/000077500000000000000000000000001445614261000174765ustar00rootroot00000000000000test/fspp/fuse/utimens/testutils/FuseUtimensTest.cpp000066400000000000000000000013631445614261000233140ustar00rootroot00000000000000#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.h000066400000000000000000000013121445614261000227530ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000151045ustar00rootroot00000000000000test/fspp/fuse/write/FuseWriteDataTest.cpp000066400000000000000000000067631445614261000211730ustar00rootroot00000000000000#include #include "testutils/FuseWriteTest.h" #include "../../testutils/InMemoryFile.h" #include "fspp/fs_interface/FuseErrnoException.h" #include #include 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, testing::_, testing::_, testing::_)) .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_SUITE_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.cpp000066400000000000000000000046241445614261000214050ustar00rootroot00000000000000#include "testutils/FuseWriteTest.h" #include "fspp/fs_interface/FuseErrnoException.h" 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_SUITE_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, testing::_, testing::_, testing::_)) .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, testing::_, testing::_, 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, testing::_, testing::_, 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.cpp000066400000000000000000000014611445614261000232260ustar00rootroot00000000000000#include "testutils/FuseWriteTest.h" #include "fspp/fs_interface/FuseErrnoException.h" using ::testing::WithParamInterface; using ::testing::Values; using ::testing::Eq; using ::testing::Return; using namespace fspp::fuse; class FuseWriteFileDescriptorTest: public FuseWriteTest, public WithParamInterface { }; INSTANTIATE_TEST_SUITE_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()), testing::_, testing::_, testing::_)) .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.cpp000066400000000000000000000061771445614261000221240ustar00rootroot00000000000000#include #include "testutils/FuseWriteTest.h" #include "../../testutils/InMemoryFile.h" #include "fspp/fs_interface/FuseErrnoException.h" 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, testing::_, testing::_, testing::_)).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/000077500000000000000000000000001445614261000171445ustar00rootroot00000000000000test/fspp/fuse/write/testutils/FuseWriteTest.cpp000066400000000000000000000020241445614261000224230ustar00rootroot00000000000000#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.h000066400000000000000000000013501445614261000220710ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000137515ustar00rootroot00000000000000test/fspp/impl/FuseOpenFileListTest.cpp000066400000000000000000000072421445614261000205020ustar00rootroot00000000000000#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_METHOD(OpenFile::stat_info, stat, (), (const, override)); MOCK_METHOD(void, truncate, (fspp::num_bytes_t), (const, override)); MOCK_METHOD(fspp::num_bytes_t, read, (void*, fspp::num_bytes_t, fspp::num_bytes_t), (const, override)); MOCK_METHOD(void, write, (const void*, fspp::num_bytes_t, fspp::num_bytes_t), (override)); MOCK_METHOD(void, flush, (), (override)); MOCK_METHOD(void, fsync, (), (override)); MOCK_METHOD(void, fdatasync, (), (override)); }; 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.cpp000066400000000000000000000042541445614261000165120ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000150505ustar00rootroot00000000000000test/fspp/testutils/FuseTest.cpp000066400000000000000000000132521445614261000173210ustar00rootroot00000000000000#include "FuseTest.h" using ::testing::Eq; 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()), _context(boost::none) { using ::testing::_; 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()); ON_CALL(*fsimpl, setContext(_)).WillByDefault(Invoke([this] (fspp::Context context) { _context = std::move(context); })); ReturnIsDirOnLstat("/"); ReturnDoesntExistOnLstat("/.Trash"); ReturnDoesntExistOnLstat("/.Trash-1000"); } unique_ref FuseTest::TestFS(const std::vector& fuseOptions) { return make_unique_ref(fsimpl, fuseOptions); } FuseTest::TempTestFS::TempTestFS(shared_ptr fsimpl, const std::vector& fuseOptions) :_mountDir(), _fuse([fsimpl] (Fuse*) {return fsimpl;}, []{}, "fusetest", boost::none), _fuse_thread(&_fuse) { _fuse_thread.start(_mountDir.path(), fuseOptions); } 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 boost::filesystem::path&, 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 boost::filesystem::path&, 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(Eq(filename), testing::_)).Times(1).WillOnce(Return(descriptor)); } void FuseTest::ReturnIsFileOnLstat(const bf::path &path) { EXPECT_CALL(*fsimpl, lstat(Eq(path), ::testing::_)).WillRepeatedly(ReturnIsFile); } void FuseTest::ReturnIsFileOnLstatWithSize(const bf::path &path, const fspp::num_bytes_t size) { EXPECT_CALL(*fsimpl, lstat(Eq(path), ::testing::_)).WillRepeatedly(ReturnIsFileWithSize(size)); } void FuseTest::ReturnIsDirOnLstat(const bf::path &path) { EXPECT_CALL(*fsimpl, lstat(Eq(path), ::testing::_)).WillRepeatedly(ReturnIsDir); } void FuseTest::ReturnDoesntExistOnLstat(const bf::path &path) { EXPECT_CALL(*fsimpl, lstat(Eq(path), ::testing::_)).WillRepeatedly(ReturnDoesntExist); } void FuseTest::ReturnIsFileOnFstat(int descriptor) { EXPECT_CALL(*fsimpl, fstat(descriptor, testing::_)).WillRepeatedly(ReturnIsFileFstat); } void FuseTest::ReturnIsFileOnFstatWithSize(int descriptor, fspp::num_bytes_t size) { EXPECT_CALL(*fsimpl, fstat(descriptor, testing::_)).WillRepeatedly(ReturnIsFileFstatWithSize(size)); } test/fspp/testutils/FuseTest.h000066400000000000000000000116741445614261000167740ustar00rootroot00000000000000#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" class MockFilesystem: public fspp::fuse::Filesystem { public: MockFilesystem(); virtual ~MockFilesystem(); MOCK_METHOD(void, setContext, (fspp::Context&&), (override)); MOCK_METHOD(int, openFile, (const boost::filesystem::path&, int), (override)); MOCK_METHOD(void, closeFile, (int), (override)); MOCK_METHOD(void, lstat, (const boost::filesystem::path&, fspp::fuse::STAT*), (override)); MOCK_METHOD(void, fstat, (int, fspp::fuse::STAT*), (override)); MOCK_METHOD(void, truncate, (const boost::filesystem::path&, fspp::num_bytes_t), (override)); MOCK_METHOD(void, ftruncate, (int, fspp::num_bytes_t), (override)); MOCK_METHOD(fspp::num_bytes_t, read, (int, void*, fspp::num_bytes_t, fspp::num_bytes_t), (override)); MOCK_METHOD(void, write, (int, const void*, fspp::num_bytes_t, fspp::num_bytes_t), (override)); MOCK_METHOD(void, flush, (int), (override)); MOCK_METHOD(void, fsync, (int), (override)); MOCK_METHOD(void, fdatasync, (int), (override)); MOCK_METHOD(void, access, (const boost::filesystem::path&, int), (override)); MOCK_METHOD(int, createAndOpenFile, (const boost::filesystem::path&, mode_t, uid_t, gid_t), (override)); MOCK_METHOD(void, mkdir, (const boost::filesystem::path&, mode_t, uid_t, gid_t), (override)); MOCK_METHOD(void, rmdir, (const boost::filesystem::path&), (override)); MOCK_METHOD(void, unlink, (const boost::filesystem::path&), (override)); MOCK_METHOD(void, rename, (const boost::filesystem::path&, const boost::filesystem::path&), (override)); MOCK_METHOD(std::vector, readDir, (const boost::filesystem::path &path), (override)); MOCK_METHOD(void, utimens, (const boost::filesystem::path&, timespec, timespec), (override)); MOCK_METHOD(void, statfs, (struct statvfs*), (override)); MOCK_METHOD(void, chmod, (const boost::filesystem::path&, mode_t), (override)); MOCK_METHOD(void, chown, (const boost::filesystem::path&, uid_t, gid_t), (override)); MOCK_METHOD(void, createSymlink, (const boost::filesystem::path&, const boost::filesystem::path&, uid_t, gid_t), (override)); MOCK_METHOD(void, readSymlink, (const boost::filesystem::path&, char*, fspp::num_bytes_t), (override)); }; class FuseTest: public ::testing::Test { public: static constexpr const char* FILENAME = "/myfile"; FuseTest(); class TempTestFS { public: TempTestFS(std::shared_ptr fsimpl, const std::vector& fuseOptions = {}); virtual ~TempTestFS(); public: const boost::filesystem::path &mountDir() const; private: cpputils::TempDir _mountDir; fspp::fuse::Fuse _fuse; FuseThread _fuse_thread; }; cpputils::unique_ref TestFS(const std::vector& fuseOptions = {}); std::shared_ptr fsimpl; const fspp::Context& context() const { ASSERT(_context != boost::none, "Context wasn't correctly initialized"); return *_context; } private: boost::optional _context; public: //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; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static ::testing::Action ReturnIsFileWithSize(fspp::num_bytes_t size); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static ::testing::Action ReturnIsFileFstat; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static ::testing::Action ReturnIsFileFstatWithSize(fspp::num_bytes_t size); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static ::testing::Action ReturnIsDir; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static ::testing::Action ReturnDoesntExist; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) 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.cpp000066400000000000000000000022111445614261000176020ustar00rootroot00000000000000#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->runInForeground(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 macFUSE 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.h000066400000000000000000000011101445614261000172440ustar00rootroot00000000000000#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.cpp000066400000000000000000000034341445614261000201170ustar00rootroot00000000000000#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.h000066400000000000000000000020111445614261000175520ustar00rootroot00000000000000#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.cpp000066400000000000000000000000341445614261000203660ustar00rootroot00000000000000#include "OpenFileHandle.h" test/fspp/testutils/OpenFileHandle.h000066400000000000000000000021041445614261000200330ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000142315ustar00rootroot00000000000000test/gitversion/CMakeLists.txt000066400000000000000000000005161445614261000167730ustar00rootroot00000000000000project (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.cpp000066400000000000000000000225271445614261000170410ustar00rootroot00000000000000#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.cpp000066400000000000000000000054561445614261000205430ustar00rootroot00000000000000#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/000077500000000000000000000000001445614261000145335ustar00rootroot00000000000000test/my-gtest-main/CMakeLists.txt000066400000000000000000000005571445614261000173020ustar00rootroot00000000000000project (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.cpp000066400000000000000000000014041445614261000177310ustar00rootroot00000000000000#include "my-gtest-main.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include namespace { // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 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.h000066400000000000000000000001451445614261000173770ustar00rootroot00000000000000#pragma once #include const boost::filesystem::path& get_executable(); test/parallelaccessstore/000077500000000000000000000000001445614261000160735ustar00rootroot00000000000000test/parallelaccessstore/CMakeLists.txt000066400000000000000000000005501445614261000206330ustar00rootroot00000000000000project (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.cpp000066400000000000000000000000661445614261000205340ustar00rootroot00000000000000#include TEST(Dummy, DummyTest) { } test/parallelaccessstore/ParallelAccessBaseStoreTest.cpp000066400000000000000000000002001445614261000241150ustar00rootroot00000000000000#include "parallelaccessstore/ParallelAccessBaseStore.h" // Test that ParallelAccessBaseStore.h can be included without errors vendor/000077500000000000000000000000001445614261000123565ustar00rootroot00000000000000vendor/CMakeLists.txt000066400000000000000000000000701445614261000151130ustar00rootroot00000000000000add_subdirectory(googletest) add_subdirectory(cryptopp) vendor/README000066400000000000000000000013201445614261000132320ustar00rootroot00000000000000This directory contains external projects, taken from the following locations: googletest: https://github.com/google/googletest/commit/eaf9a3fd77869cf95befb87455a2e2a2e85044ff - changed: In googletest/cmake/internal_utils.cmake, add cmake_policy(SET CMP0069 NEW) to silence a warning cryptopp: https://github.com/weidai11/cryptopp/releases/tag/CRYPTOPP_8_6_0 - changed: added CMakeLists.txt and cryptopp-config.cmake from https://github.com/noloader/cryptopp-cmake/tree/675367754bf4bf2284910dcec24d068472c97990 - changed: In CMakeLists.txt, rename BUILD_TESTING to CRYPTOPP_BUILD_TESTING so it doesn't clash with our BUILD_TESTING - changed: In CMakeLists.txt, add cmake_policy(SET CMP0069 NEW) to silence a warning vendor/cryptopp/000077500000000000000000000000001445614261000142365ustar00rootroot00000000000000vendor/cryptopp/CMakeLists.txt000066400000000000000000000020211445614261000167710ustar00rootroot00000000000000project(mycryptopp) add_library(cryptopp INTERFACE) # 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 INTERFACE $<$: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) set(USE_OPENMP ON CACHE BOOL "") endif() set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "") set(BUILD_DOCUMENTATION OFF CACHE BOOL "") set(BUILD_SHARED OFF CACHE BOOL "") set(BUILD_STATIC ON CACHE BOOL "") set(USE_INTERMEDIATE_OBJECTS_TARGET OFF CACHE BOOL "") set(cryptocpp_DISPLAY_CMAKE_SUPPORT_WARNING OFF CACHE BOOL "") add_subdirectory(vendor_cryptopp EXCLUDE_FROM_ALL) target_link_libraries(cryptopp INTERFACE cryptopp-static) vendor/cryptopp/vendor_cryptopp/000077500000000000000000000000001445614261000174735ustar00rootroot00000000000000vendor/cryptopp/vendor_cryptopp/.appveyor.yml000066400000000000000000000021251445614261000221410ustar00rootroot00000000000000# Appveyor's documentation is at https://www.appveyor.com/docs/build-phase/, # and a sample configuration file is at https://www.appveyor.com/docs/appveyor-yml/. # I have to admit its a bit complex and I don't fully understand it. version: 1.0.{build} clone_depth: 5 skip_tags: true configuration: - Debug - Release platform: - Win32 - x64 image: - Visual Studio 2019 - Visual Studio 2017 - Visual Studio 2015 - Visual Studio 2013 environment: matrix: - BUILD_MODE: MSBuild # Disable build through solution file build: off # Build through commands in script below test_script: - ps: >- msbuild /t:Build /p:platform="$env:platform" /p:configuration="$env:configuration" cryptlib.vcxproj msbuild /t:Build /p:platform="$env:platform" /p:configuration="$env:configuration" cryptest.vcxproj msbuild /t:CopyCryptestToRoot cryptest.vcxproj .\cryptest.exe v .\cryptest.exe tv all notifications: - provider: Email to: - cryptopp-build@googlegroups.com on_build_success: true on_build_failure: true vendor/cryptopp/vendor_cryptopp/.cirrus.yml000066400000000000000000000032121445614261000216010ustar00rootroot00000000000000# https://cirrus-ci.org/examples/ # https://github.com/curl/curl/blob/master/.cirrus.yml # FreeBSD seems to have constant version problems, which requires IGNORE_OSVERSION. env: CIRRUS_CLONE_DEPTH: 5 IGNORE_OSVERSION: yes task: matrix: - name: Debug build, FreeBSD 12.1 freebsd_instance: image_family: freebsd-12-1 pkginstall_script: - pkg update -f - pkg install -y gmake configure_script: compile_script: - CXXFLAGS="-DDEBUG -g3 -O0" gmake -j 3 test_script: - ./cryptest.exe v - ./cryptest.exe tv all - name: Release build, FreeBSD 12.1 freebsd_instance: image_family: freebsd-12-1 pkginstall_script: - pkg update -f - pkg install -y gmake configure_script: compile_script: - gmake -j 3 test_script: - ./cryptest.exe v - ./cryptest.exe tv all - name: Debug build, FreeBSD 13.0 (snap) freebsd_instance: image_family: freebsd-13-0-snap pkginstall_script: - pkg update -f - pkg install -y gmake configure_script: compile_script: - CXXFLAGS="-DDEBUG -g3 -O0" gmake -j 3 test_script: - ./cryptest.exe v - ./cryptest.exe tv all - name: Release build, FreeBSD 13.0 (snap) freebsd_instance: image_family: freebsd-13-0-snap pkginstall_script: - pkg update -f - pkg install -y gmake configure_script: compile_script: - gmake -j 3 test_script: - ./cryptest.exe v - ./cryptest.exe tv all vendor/cryptopp/vendor_cryptopp/.gitattributes000066400000000000000000000000141445614261000223610ustar00rootroot00000000000000*.sh eol=lf vendor/cryptopp/vendor_cryptopp/.github/000077500000000000000000000000001445614261000210335ustar00rootroot00000000000000vendor/cryptopp/vendor_cryptopp/.github/issue_template.md000066400000000000000000000022021445614261000243740ustar00rootroot00000000000000### Crypto++ Issue Report Thanks for taking the time to report an issue. Reporting issues helps us improve stability and reliability for all users, so it is a valuable contribution. Please do not ask questions in the bug tracker. Please ask questions on the Crypto++ Users List at http://groups.google.com/forum/#!forum/cryptopp-users. Please _do not_ ask questions at Stack Overflow. We may not be able to answer your question. There is a wiki page with information on filing useful bug reports. If you have some time please visit http://www.cryptopp.com/wiki/Bug_Report on the wiki. The executive summary is: * State the operating system and version (Ubutnu 17 x86_64, Windows 7 Professional x64, etc) * State the version of the Crypto++ library (Crypto++ 7.0, Master, etc) * State how you built the library (Makefile, Cmake, distro, etc) * Show a typical command line (the output of the compiler for cryptlib.cpp) * Show the link command (the output of the linker for libcryptopp.so or cryptest.exe) * Show the exact error message you are receiving (copy and paste it); or * Clearly state the undesired behavior (and state the expected behavior) vendor/cryptopp/vendor_cryptopp/.github/workflows/000077500000000000000000000000001445614261000230705ustar00rootroot00000000000000vendor/cryptopp/vendor_cryptopp/.github/workflows/c-cpp.yml000066400000000000000000000004061445614261000246150ustar00rootroot00000000000000name: C/C++ CI on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: make run: make all - name: make test run: make test vendor/cryptopp/vendor_cryptopp/.gitignore000066400000000000000000000074041445614261000214700ustar00rootroot00000000000000#################################### # 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 ################# ## Emacs ################# *~ \#*\# ################# ## Vi swap ################# *.swp *.swo ################# ## 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/ Win32/ x64/ Arm/ Arm64/ 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/.travis.yml000066400000000000000000000423021445614261000216050ustar00rootroot00000000000000# In the YML below each job gets an environment that includes # BUILD_OS and BUILD_MODE. The variables are used in the driver # scripts and is used to select a test. For example, BUILD_OS=linux # and BUILD_MODE=all means run 'make all' on Linux. The Android # tests specify a ANDROID_API, ANDROID_CPU; and the iOS tests # specify IOS_SDK and IOS_CPU. They are exported for the underlying # setenv-*.sh scripts. # DO NOT create top level (global) keys like env, arch, os, compiler. # The top level/global keys invoke [unwanted] matrix expansion. Also # see https://stackoverflow.com/q/58473000/608639 and # https://docs.travis-ci.com/user/reference/overview/ and # https://docs.travis-ci.com/user/multi-cpu-architectures and # https://github.com/travis-ci/travis-yml/blob/master/schema.json. language: cpp dist: bionic git: depth: 5 # Use jobs rather than matrix since we are precisely # specifiying our test cases. Do not move any of the # keys (env, os, arch, compiler, etc) into global. # Putting them in global invokes the matrix expansion. jobs: include: - name: Standard build, GCC, Linux, amd64 os: linux arch: amd64 compiler: gcc env: - BUILD_OS=linux - BUILD_MODE=all - name: Native build, GCC, Linux, amd64 os: linux arch: amd64 compiler: gcc env: - BUILD_OS=linux - BUILD_MODE=native - name: No-asm build, GCC, Linux, amd64 os: linux arch: amd64 compiler: gcc env: - BUILD_OS=linux - BUILD_MODE=no-asm - name: Debug build, GCC, Linux, amd64 os: linux arch: amd64 compiler: gcc env: - BUILD_OS=linux - BUILD_MODE=debug - name: Asan build, GCC, Linux, amd64 os: linux arch: amd64 compiler: gcc env: - BUILD_OS=linux - BUILD_MODE=asan - name: UBsan build, GCC, Linux, amd64 os: linux arch: amd64 compiler: gcc env: - BUILD_OS=linux - BUILD_MODE=ubsan - name: PEM build, GCC, Linux, amd64 os: linux arch: amd64 compiler: gcc env: - BUILD_OS=linux - BUILD_MODE=pem - name: Autotools build, GCC, Linux, amd64 os: linux arch: amd64 compiler: gcc env: - BUILD_OS=linux - BUILD_MODE=autotools - name: CMake build, GCC, Linux, amd64 os: linux arch: amd64 compiler: gcc env: - BUILD_OS=linux - BUILD_MODE=cmake - name: Standard build, Clang, Linux, amd64 os: linux arch: amd64 compiler: clang env: - BUILD_OS=linux - BUILD_MODE=all - name: Native build, Clang, Linux, amd64 os: linux arch: amd64 compiler: clang env: - BUILD_OS=linux - BUILD_MODE=native - name: No-asm build, Clang, Linux, amd64 os: linux arch: amd64 compiler: clang env: - BUILD_OS=linux - BUILD_MODE=no-asm - name: Debug build, Clang, Linux, amd64 os: linux arch: amd64 compiler: clang env: - BUILD_OS=linux - BUILD_MODE=debug - name: Asan build, Clang, Linux, amd64 os: linux arch: amd64 compiler: clang env: - BUILD_OS=linux - BUILD_MODE=asan - name: UBsan build, Clang, Linux, amd64 os: linux arch: amd64 compiler: clang env: - BUILD_OS=linux - BUILD_MODE=ubsan - name: PEM build, Clang, Linux, amd64 os: linux arch: amd64 compiler: clang env: - BUILD_OS=linux - BUILD_MODE=pem - name: Autotools build, Clang, Linux, amd64 os: linux arch: amd64 compiler: clang env: - BUILD_OS=linux - BUILD_MODE=autotools - name: CMake build, Clang, Linux, amd64 os: linux arch: amd64 compiler: clang env: - BUILD_OS=linux - BUILD_MODE=cmake - name: Standard build, Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - BUILD_OS=osx - BUILD_MODE=all - name: Standard build (64-bit), Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - CXXFLAGS="-DNDEBUG -g2 -O3 -arch x86_64" - BUILD_OS=osx - BUILD_MODE=all - name: Standard build (32-bit), Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - CXXFLAGS="-DNDEBUG -g2 -O3 -arch i386" - BUILD_OS=osx - BUILD_MODE=all - name: Standard build (fat), Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - CXXFLAGS="-DNDEBUG -g2 -O3 -arch i386 -arch x86_64" - BUILD_OS=osx - BUILD_MODE=osx-fat - name: Native build, Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - BUILD_OS=osx - BUILD_MODE=native - name: No-asm build, Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - BUILD_OS=osx - BUILD_MODE=no-asm - name: Debug build, Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - BUILD_OS=osx - BUILD_MODE=debug - name: Asan build, Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - BUILD_OS=osx - BUILD_MODE=asan - name: UBsan build, Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - BUILD_OS=osx - BUILD_MODE=ubsan - name: PEM build, Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - BUILD_OS=osx - BUILD_MODE=pem - name: Autotools build, Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - BUILD_OS=osx - BUILD_MODE=autotools - name: CMake build, Clang, OS X, amd64 os: osx osx_image: xcode10.1 arch: amd64 compiler: clang env: - BUILD_OS=osx - BUILD_MODE=cmake - name: Standard build, GCC, Linux, arm64 os: linux arch: arm64 compiler: gcc dist: bionic env: - BUILD_OS=linux - BUILD_MODE=all - name: Native build, GCC, Linux, arm64 os: linux arch: arm64 compiler: gcc dist: bionic env: - BUILD_OS=linux - BUILD_MODE=native - name: No-asm build, GCC, Linux, arm64 os: linux arch: arm64 compiler: gcc dist: bionic env: - BUILD_OS=linux - BUILD_MODE=no-asm - name: Debug build, GCC, Linux, arm64 os: linux arch: arm64 compiler: gcc dist: bionic env: - BUILD_OS=linux - BUILD_MODE=debug - name: Asan build, GCC, Linux, arm64 os: linux arch: arm64 compiler: gcc dist: bionic env: - BUILD_OS=linux - BUILD_MODE=asan - name: UBsan build, GCC, Linux, arm64 os: linux arch: arm64 compiler: gcc dist: bionic env: - BUILD_OS=linux - BUILD_MODE=ubsan - name: PEM build, GCC, Linux, arm64 os: linux arch: arm64 compiler: gcc dist: bionic env: - BUILD_OS=linux - BUILD_MODE=pem - name: Autotools build, GCC, Linux, arm64 os: linux arch: arm64 compiler: gcc dist: bionic env: - BUILD_OS=linux - BUILD_MODE=autotools - name: CMake build, GCC, Linux, arm64 os: linux arch: arm64 compiler: gcc dist: bionic env: - BUILD_OS=linux - BUILD_MODE=cmake - name: Standard build, Clang, Linux, arm64 os: linux arch: arm64 compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=all - name: Native build, Clang, Linux, arm64 os: linux arch: arm64 compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=native - name: No-asm build, Clang, Linux, arm64 os: linux arch: arm64 compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=no-asm - name: Debug build, Clang, Linux, arm64 os: linux arch: arm64 compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=debug - name: Asan build, Clang, Linux, arm64 os: linux arch: arm64 compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=asan - name: UBsan build, Clang, Linux, arm64 os: linux arch: arm64 compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=ubsan - name: PEM build, Clang, Linux, arm64 os: linux arch: arm64 compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=pem - name: Autotools build, Clang, Linux, arm64 os: linux arch: arm64 compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=autotools - name: CMake build, Clang, Linux, arm64 os: linux arch: arm64 compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=cmake - name: Standard build, GCC, Linux, ppc64le os: linux arch: ppc64le compiler: gcc dist: bionic env: - BUILD_OS=linux - BUILD_MODE=all - name: Debug build, GCC, Linux, ppc64le os: linux arch: ppc64le compiler: gcc env: - BUILD_OS=linux - BUILD_MODE=debug - name: Standard build, Clang, Linux, ppc64le os: linux arch: ppc64le compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=all - CC=clang-8 - CXX=clang++-8 - name: Debug build, Clang, Linux, ppc64le os: linux arch: ppc64le compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=debug - CC=clang-8 - CXX=clang++-8 - name: Standard build, GCC, Linux, s390x os: linux arch: s390x compiler: gcc dist: bionic env: - BUILD_OS=linux - BUILD_MODE=all - name: Debug build, GCC, Linux, s390x os: linux arch: s390x compiler: gcc env: - BUILD_OS=linux - BUILD_MODE=debug - name: Standard build, Clang, Linux, s390x os: linux arch: s390x compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=all - CC=clang-8 - CXX=clang++-8 - name: Debug build, Clang, Linux, s390x os: linux arch: s390x compiler: clang dist: bionic env: - BUILD_OS=linux - BUILD_MODE=debug - CC=clang-8 - CXX=clang++-8 - name: Android, armv7a, Linux os: linux arch: amd64 env: - BUILD_OS=linux - BUILD_MODE=android - ANDROID_CPU=armv7a - ANDROID_API=23 - ANDROID_SDK_ROOT="/opt/android-sdk" - ANDROID_NDK_ROOT="/opt/android-ndk" - name: Android, aarch64, Linux os: linux arch: amd64 env: - BUILD_OS=linux - BUILD_MODE=android - ANDROID_CPU=aarch64 - ANDROID_API=23 - ANDROID_SDK_ROOT="/opt/android-sdk" - ANDROID_NDK_ROOT="/opt/android-ndk" - name: Android, x86, Linux os: linux arch: amd64 env: - BUILD_OS=linux - BUILD_MODE=android - ANDROID_CPU=x86 - ANDROID_API=23 - ANDROID_SDK_ROOT="/opt/android-sdk" - ANDROID_NDK_ROOT="/opt/android-ndk" - name: Android, x86_64, Linux os: linux arch: amd64 env: - BUILD_OS=linux - BUILD_MODE=android - ANDROID_CPU=x86_64 - ANDROID_API=23 - ANDROID_SDK_ROOT="/opt/android-sdk" - ANDROID_NDK_ROOT="/opt/android-ndk" - name: iPhoneOS, armv7, iOS os: osx osx_image: xcode10.1 arch: amd64 env: - BUILD_OS=osx - BUILD_MODE=ios - IOS_CPU=armv7s - IOS_SDK=iPhoneOS - name: iPhoneOS, arm64, iOS os: osx osx_image: xcode10.1 arch: amd64 env: - BUILD_OS=osx - BUILD_MODE=ios - IOS_CPU=arm64 - IOS_SDK=iPhoneOS - name: AppleTVOS, arm64, iOS os: osx osx_image: xcode10.1 arch: amd64 env: - BUILD_OS=osx - BUILD_MODE=ios - IOS_CPU=arm64 - IOS_SDK=AppleTVOS - name: WatchOS, armv7, iOS os: osx osx_image: xcode10.1 arch: amd64 env: - BUILD_OS=osx - BUILD_MODE=ios - IOS_CPU=armv7k - IOS_SDK=WatchOS - name: iPhoneSimulator, i386, OS X os: osx osx_image: xcode10.1 arch: amd64 env: - BUILD_OS=osx - BUILD_MODE=ios - IOS_CPU=i386 - IOS_SDK=iPhoneSimulator - name: iPhoneSimulator, x86_64, OS X os: osx osx_image: xcode10.1 arch: amd64 env: - BUILD_OS=osx - BUILD_MODE=ios - IOS_CPU=x86_64 - IOS_SDK=iPhoneSimulator - name: AppleTVSimulator, x86_64, OS X os: osx osx_image: xcode10.1 arch: amd64 env: - BUILD_OS=osx - BUILD_MODE=ios - IOS_CPU=x86_64 - IOS_SDK=AppleTVSimulator - name: WatchSimulator, i386, OS X os: osx osx_image: xcode10.1 arch: amd64 env: - BUILD_OS=osx - BUILD_MODE=ios - IOS_CPU=i386 - IOS_SDK=WatchSimulator allow_failures: # Clang has a fair amount of trouble # on platforms Apple does not support - os: linux arch: s390x compiler: clang # Clang 7.0 and below will likely have trouble on ppc64le # due to https://bugs.llvm.org/show_bug.cgi?id=39704. - os: linux arch: ppc64le compiler: clang before_install: - | if [[ "$BUILD_OS" == "linux" ]] && [[ "$BUILD_MODE" == "android" ]]; then # https://github.com/travis-ci/travis-ci/issues/9037 sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A145 sudo apt-get -qq -y update sudo -E TestScripts/install-ndk.sh fi if [[ "$BUILD_OS" == "linux" ]] && [[ "$BUILD_MODE" == "autotools" ]]; then # https://github.com/travis-ci/travis-ci/issues/9037 sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A145 sudo apt-get -qq -y install autoconf automake libtool fi # Clang 7 compiler is completely broken on PPC64 and s390x if [[ "$TRAVIS_CPU_ARCH" == "ppc64le" ]] || [[ "$TRAVIS_CPU_ARCH" == "s390x" ]]; then if [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$TRAVIS_COMPILER" == "clang" ]]; then # https://github.com/travis-ci/travis-ci/issues/9037 sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A145 sudo apt-get -qq -y install --no-install-recommends clang-8 fi fi script: - | if [[ "$BUILD_MODE" == "ios" ]]; then source TestScripts/setenv-ios.sh make -f GNUmakefile-cross -j 2 all static dynamic elif [[ "$BUILD_MODE" == "android" ]]; then source TestScripts/setenv-android.sh make -f GNUmakefile-cross -j 2 all static dynamic elif [[ "$BUILD_MODE" == "autotools" ]]; then bash TestScripts/cryptest-autotools.sh elif [[ "$BUILD_MODE" == "cmake" ]]; then bash TestScripts/cryptest-cmake.sh elif [[ "$BUILD_MODE" == "pem" ]]; then bash TestScripts/cryptest-pem.sh elif [[ "$BUILD_MODE" == "osx-fat" ]]; then CXXFLAGS="-DNDEBUG -g2 -O3 -arch i386 -arch x86_64" make -j 2 arch -i386 ./cryptest.exe v arch -i386 ./cryptest.exe tv all arch -x86_64 ./cryptest.exe v arch -x86_64 ./cryptest.exe tv all elif [[ "$BUILD_MODE" == "debug" ]]; then CXXFLAGS="-DDEBUG -g2 -O1" make -j 2 ./cryptest.exe v ./cryptest.exe tv all else make "$BUILD_MODE" -j 2 ./cryptest.exe v ./cryptest.exe tv all fi # Whitelist branches to avoid testing feature branches twice branches: only: - master - /\/ci$/ notifications: email: recipients: - cryptopp-build@googlegroups.com on_success: always # default: change on_failure: always # default: always vendor/cryptopp/vendor_cryptopp/3way.cpp000066400000000000000000000067601445614261000210730ustar00rootroot00000000000000// 3way.cpp - modified 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.txt000066400000000000000000001571271445614261000222500ustar00rootroot00000000000000# 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 cryptopp_DISPLAY_CMAKE_SUPPORT_WARNING) set(cryptopp_DISPLAY_CMAKE_SUPPORT_WARNING 1) endif() if(cryptopp_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}" ) message( STATUS "System ${CMAKE_SYSTEM_NAME}" ) message( STATUS "Processor ${CMAKE_SYSTEM_PROCESSOR}" ) cmake_minimum_required(VERSION 2.8.6) cmake_policy(SET CMP0069 NEW) if (${CMAKE_VERSION} VERSION_LESS "3.0.0") project(cryptopp) set(cryptopp_VERSION_MAJOR 8) set(cryptopp_VERSION_MINOR 6) set(cryptopp_VERSION_PATCH 0) else () cmake_policy(SET CMP0048 NEW) project(cryptopp VERSION 8.6.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.cpp 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.cpp) #============================================================================ # Settable options #============================================================================ option(BUILD_STATIC "Build static library" ON) option(BUILD_SHARED "Build shared library" ON) option(CRYPTOPP_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) if (${CMAKE_VERSION} VERSION_GREATER "3.1") option(USE_OPENMP "Enable OpenMP to parallelize some of the algorithms. Note that this isn't always faster, see https://www.cryptopp.com/wiki/OpenMP" OFF) endif() # These are IA-32 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_CLMUL "Disable CLMUL" OFF) option(DISABLE_SHA "Disable SHA" OFF) option(DISABLE_AVX "Disable AVX" OFF) option(DISABLE_AVX2 "Disable AVX2" OFF) # These are ARM A-32 options option(DISABLE_ARM_NEON "Disable NEON" OFF) # These are Aarch64 options option(DISABLE_ARM_AES "Disable ASIMD" OFF) option(DISABLE_ARM_AES "Disable AES" OFF) option(DISABLE_ARM_PMULL "Disable PMULL" OFF) option(DISABLE_ARM_SHA "Disable SHA" OFF) # These are PowerPC options option(DISABLE_ALTIVEC "Disable Altivec" OFF) option(DISABLE_POWER7 "Disable POWER7" OFF) option(DISABLE_POWER8 "Disable POWER8" OFF) option(DISABLE_POWER9 "Disable POWER9" 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) # Stop CMake complaining... if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(MACOSX_RPATH FALSE) endif() # 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}) 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_CLMUL) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_CLMUL) endif () if (DISABLE_AESNI) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_AESNI) endif () if (DISABLE_RDRAND) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_RDRAND) endif () if (DISABLE_RDSEED) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_RDSEED) endif () if (DISABLE_AVX) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_AVX) endif () if (DISABLE_AVX2) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_AVX2) endif () if (DISABLE_SHA) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_SHA) endif () if (DISABLE_ARM_NEON) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_ARM_NEON) endif () if (DISABLE_ARM_ASIMD) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_ARM_ASIMD) endif () if (DISABLE_ARM_AES) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_ARM_AES) endif () if (DISABLE_ARM_PMULL) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_ARM_PMULL) endif () if (DISABLE_ARM_SHA) list(APPEND CRYPTOPP_COMPILE_DEFINITIONS CRYPTOPP_DISABLE_ARM_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}) elseif (APPLE) message(STATUS "Performing Test ${var}") try_compile(COMMAND_SUCCESS ${CMAKE_BINARY_DIR} ${prog} COMPILE_DEFINITIONS ${opt}) if (COMMAND_SUCCESS) 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 () message(STATUS "Performing Test ${var}") try_compile(COMMAND_SUCCESS ${CMAKE_BINARY_DIR} ${prog} COMPILE_DEFINITIONS ${opt}) if (COMMAND_SUCCESS) 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 () if(CMAKE_SYSTEM_PROCESSOR MATCHES ${pattern}) set(${output} TRUE PARENT_SCOPE) endif() endif() endfunction(DumpMachine) # Thanks to Anonimal for MinGW; see http://github.com/weidai11/cryptopp/issues/466 DumpMachine(CRYPTOPP_AMD64 "(x86_64|AMD64|amd64)") DumpMachine(CRYPTOPP_I386 "^i.86$") DumpMachine(CRYPTOPP_MINGW32 "^mingw32") DumpMachine(CRYPTOPP_MINGW64 "(w64-mingw32|mingw64)") DumpMachine(CRYPTOPP_ARMV8 "(armv8|arm64|aarch32|aarch64)") DumpMachine(CRYPTOPP_ARM32 "(arm|armhf|arm7l|eabihf)") DumpMachine(CRYPTOPP_PPC32 "^(powerpc|ppc)") DumpMachine(CRYPTOPP_PPC64 "^ppc64") # Cleanup 32/64 bit if (CRYPTOPP_AMD64) set (CRYPTOPP_I386 0) endif () if (CRYPTOPP_ARMV8) set (CRYPTOPP_ARM32 0) endif () if (CRYPTOPP_PPC64) set (CRYPTOPP_PPC32 0) endif () ############################################################################### # 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_PPC32 AND CRYPTOPP_PPC64) unset(CRYPTOPP_PPC32) 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_PPC64 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 () ############################################################################### # 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} ) if(ANDROID) include_directories(${ANDROID_NDK}/sources/android/cpufeatures) list(APPEND cryptopp_SOURCES ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c) endif() 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 ${SRC_DIR}/rdseed.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) # For Darwin and a GCC port compiler, we need to check for -Wa,-q first. -Wa,-q # is a GCC option, and it tells GCC to use the Clang Integrated Assembler. We # need LLVM's assembler because GAS is too old on Apple platforms. GAS will # not assemble modern ISA, like AVX or AVX2. if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") CheckCompileLinkOption("-Wa,-q" CRYPTOPP_IA32_WAQ "${TEST_PROG_DIR}/test_x86_sse2.cpp") if (CRYPTOPP_IA32_WAQ) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-Wa,-q") endif () endif () # Now we can move on to normal feature testing. CheckCompileLinkOption("-msse2" CRYPTOPP_IA32_SSE2 "${TEST_PROG_DIR}/test_x86_sse2.cpp") CheckCompileLinkOption("-mssse3" CRYPTOPP_IA32_SSSE3 "${TEST_PROG_DIR}/test_x86_ssse3.cpp") CheckCompileLinkOption("-msse4.1" CRYPTOPP_IA32_SSE41 "${TEST_PROG_DIR}/test_x86_sse41.cpp") CheckCompileLinkOption("-msse4.2" CRYPTOPP_IA32_SSE42 "${TEST_PROG_DIR}/test_x86_sse42.cpp") CheckCompileLinkOption("-mssse3 -mpclmul" CRYPTOPP_IA32_CLMUL "${TEST_PROG_DIR}/test_x86_clmul.cpp") CheckCompileLinkOption("-msse4.1 -maes" CRYPTOPP_IA32_AES "${TEST_PROG_DIR}/test_x86_aes.cpp") CheckCompileLinkOption("-mavx" CRYPTOPP_IA32_AVX "${TEST_PROG_DIR}/test_x86_avx.cpp") CheckCompileLinkOption("-mavx2" CRYPTOPP_IA32_AVX2 "${TEST_PROG_DIR}/test_x86_avx2.cpp") CheckCompileLinkOption("-msse4.2 -msha" CRYPTOPP_IA32_SHA "${TEST_PROG_DIR}/test_x86_sha.cpp") if (EXISTS "${TEST_PROG_DIR}/test_asm_mixed.cpp") CheckCompileLinkOption("" CRYPTOPP_MIXED_ASM "${TEST_PROG_DIR}/test_asm_mixed.cpp") else () CheckCompileLinkOption("" CRYPTOPP_MIXED_ASM "${TEST_PROG_DIR}/test_mixed_asm.cpp") endif () # 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}/lsh256_sse.cpp PROPERTIES COMPILE_FLAGS "-mssse3") set_source_files_properties(${SRC_DIR}/lsh512_sse.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") 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_CLMUL) list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_CLMUL") elseif (CRYPTOPP_IA32_CLMUL AND NOT DISABLE_CLMUL) 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") set_source_files_properties(${SRC_DIR}/lsh256_avx.cpp PROPERTIES COMPILE_FLAGS "-mavx2") set_source_files_properties(${SRC_DIR}/lsh512_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_ARMV8) # This checks for CheckCompileLinkOption("-march=armv8-a" CRYPTOPP_ARM_ACLE_HEADER "${TEST_PROG_DIR}/test_arm_acle_header.cpp") # Use if available if (CRYPTOPP_ARM_NEON_HEADER) CheckCompileOption("-march=armv8-a -DCRYPTOPP_ARM_ACLE_HEADER=1" CRYPTOPP_ARMV8A_ASIMD) CheckCompileOption("-march=armv8-a+crc -DCRYPTOPP_ARM_ACLE_HEADER=1" CRYPTOPP_ARMV8A_CRC) CheckCompileOption("-march=armv8-a+crypto -DCRYPTOPP_ARM_ACLE_HEADER=1" CRYPTOPP_ARMV8A_CRYPTO) else () CheckCompileOption("-march=armv8-a" CRYPTOPP_ARMV8A_ASIMD) CheckCompileOption("-march=armv8-a+crc" CRYPTOPP_ARMV8A_CRC) CheckCompileOption("-march=armv8-a+crypto" CRYPTOPP_ARMV8A_CRYPTO) endif () 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}/simon128_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") set_source_files_properties(${SRC_DIR}/speck128_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a") else () list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_ARM_ASIMD") endif () if (CRYPTOPP_ARMV8A_CRC) set_source_files_properties(${SRC_DIR}/crc_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv8-a+crc") else () list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_ARM_CRC32") 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") else () list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_ARM_AES") list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_ARM_PMULL") list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_ARM_SHA") endif () elseif (CRYPTOPP_ARM32) # This checks for CheckCompileLinkOption("-march=armv7-a -mfpu=neon" CRYPTOPP_ARM_NEON_HEADER "${TEST_PROG_DIR}/test_arm_neon_header.cpp") # Use if available if (CRYPTOPP_ARM_NEON_HEADER) CheckCompileLinkOption("-march=armv7-a -mfpu=neon -DCRYPTOPP_ARM_NEON_HEADER=1" CRYPTOPP_ARMV7A_NEON "${TEST_PROG_DIR}/test_arm_neon.cpp") else () CheckCompileLinkOption("-march=armv7-a -mfpu=neon" CRYPTOPP_ARMV7A_NEON "${TEST_PROG_DIR}/test_arm_neon.cpp") endif () if (CRYPTOPP_ARMV7A_NEON) # Add Cryptogams ASM files for ARM on Linux. Linux is required due to GNU Assembler. # AES requires -mthumb under Clang. Do not add -mthumb for SHA for any files. if (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Android") list(APPEND cryptopp_SOURCES ${SRC_DIR}/aes_armv4.S) list(APPEND cryptopp_SOURCES ${SRC_DIR}/sha1_armv4.S) list(APPEND cryptopp_SOURCES ${SRC_DIR}/sha256_armv4.S) list(APPEND cryptopp_SOURCES ${SRC_DIR}/sha512_armv4.S) set_source_files_properties(${SRC_DIR}/aes_armv4.S PROPERTIES LANGUAGE CXX) set_source_files_properties(${SRC_DIR}/sha1_armv4.S PROPERTIES LANGUAGE CXX) set_source_files_properties(${SRC_DIR}/sha256_armv4.S PROPERTIES LANGUAGE CXX) set_source_files_properties(${SRC_DIR}/sha512_armv4.S PROPERTIES LANGUAGE CXX) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") set_source_files_properties(${SRC_DIR}/aes_armv4.S PROPERTIES COMPILE_FLAGS "-march=armv7-a -mthumb -mfpu=neon -Wa,--noexecstack") else () set_source_files_properties(${SRC_DIR}/aes_armv4.S PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon -Wa,--noexecstack") endif () set_source_files_properties(${SRC_DIR}/sha1_armv4.S PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon -Wa,--noexecstack") set_source_files_properties(${SRC_DIR}/sha256_armv4.S PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon -Wa,--noexecstack") set_source_files_properties(${SRC_DIR}/sha512_armv4.S PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon -Wa,--noexecstack") endif () set_source_files_properties(${SRC_DIR}/aria_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/blake2s_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/blake2b_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/chacha_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/cham_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/crc_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/lea_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/gcm_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/rijndael_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/neon_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/sha_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/simon128_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/speck128_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") set_source_files_properties(${SRC_DIR}/sm4_simd.cpp PROPERTIES COMPILE_FLAGS "-march=armv7-a -mfpu=neon") else () list(APPEND CRYPTOPP_COMPILE_OPTIONS "-DCRYPTOPP_DISABLE_ARM_NEON") endif () elseif (CRYPTOPP_PPC32 OR CRYPTOPP_PPC64) # XLC requires -qaltivec in addition to Arch or CPU option # Disable POWER9 due to https://github.com/weidai11/cryptopp/issues/986. 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_VSX_FLAG "-qarch=pwr7 -qvsx -qaltivec") set(CRYPTOPP_POWER7_PWR_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_VSX_FLAGS "-mcpu=power7 -mvsx") set(CRYPTOPP_POWER7_PWR_FLAGS "-mcpu=power7") set(CRYPTOPP_POWER8_FLAGS "-mcpu=power8") #set(CRYPTOPP_POWER9_FLAGS "-mcpu=power9") endif () CheckCompileLinkOption("${CRYPTOPP_ALTIVEC_FLAGS}" PPC_ALTIVEC_FLAG "${TEST_PROG_DIR}/test_ppc_altivec.cpp") # Hack for XLC. Find the lowest PWR architecture. if (CMAKE_CXX_COMPILER MATCHES "xlC") if (NOT PPC_ALTIVEC_FLAG) CheckCompileLinkOption("${CRYPTOPP_POWER4_FLAGS}" PPC_POWER4_FLAG "${TEST_PROG_DIR}/test_ppc_altivec.cpp") 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.cpp") 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.cpp") if (PPC_POWER6_FLAG) set(PPC_ALTIVEC_FLAG 1) set(CRYPTOPP_ALTIVEC_FLAGS "${CRYPTOPP_POWER6_FLAGS}") endif () endif () endif () # Hack for XLC and GCC. Find the right combination for PWR7 and the VSX unit. CheckCompileLinkOption("${CRYPTOPP_POWER7_VSX_FLAGS}" PPC_POWER7_FLAG "${TEST_PROG_DIR}/test_ppc_power7.cpp") if (PPC_POWER7_FLAG) set (CRYPTOPP_POWER7_FLAGS "${CRYPTOPP_POWER7_VSX_FLAGS}") else () CheckCompileLinkOption("${CRYPTOPP_POWER7_PWR_FLAGS}" PPC_POWER7_FLAG "${TEST_PROG_DIR}/test_ppc_power7.cpp") if (PPC_POWER7_FLAG) set (CRYPTOPP_POWER7_FLAGS "${CRYPTOPP_POWER7_PWR_FLAGS}") endif () endif () CheckCompileLinkOption("${CRYPTOPP_POWER8_FLAGS}" PPC_POWER8_FLAG "${TEST_PROG_DIR}/test_ppc_power8.cpp") # Disable POWER9 due to https://github.com/weidai11/cryptopp/issues/986. #CheckCompileLinkOption("${CRYPTOPP_POWER9_FLAGS}" PPC_POWER9_FLAG # "${TEST_PROG_DIR}/test_ppc_power9.cpp") #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}/aria_simd.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}/cham_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}/lea_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}) endif () if (PPC_ALTIVEC_FLAG AND NOT DISABLE_ALTIVEC) set_source_files_properties(${SRC_DIR}/ppc_simd.cpp PROPERTIES COMPILE_FLAGS ${CRYPTOPP_ALTIVEC_FLAGS}) 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}) endif () # Drop to Altivec if Power8 unavailable if (NOT PPC_POWER8_FLAG) if (PPC_ALTIVEC_FLAG) set_source_files_properties(${SRC_DIR}/gcm_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) CheckCompileLinkOption("-xarch=sse2" CRYPTOPP_IA32_SSE2 "${TEST_PROG_DIR}/test_x86_sse2.cpp") CheckCompileLinkOption("-xarch=ssse3" CRYPTOPP_IA32_SSSE3 "${TEST_PROG_DIR}/test_x86_ssse3.cpp") CheckCompileLinkOption("-xarch=sse4_1" CRYPTOPP_IA32_SSE41 "${TEST_PROG_DIR}/test_x86_sse41.cpp") CheckCompileLinkOption("-xarch=sse4_2" CRYPTOPP_IA32_SSE42 "${TEST_PROG_DIR}/test_x86_sse42.cpp") CheckCompileLinkOption("-xarch=aes" CRYPTOPP_IA32_CLMUL "${TEST_PROG_DIR}/test_x86_clmul.cpp") CheckCompileLinkOption("-xarch=aes" CRYPTOPP_IA32_AES "${TEST_PROG_DIR}/test_x86_aes.cpp") CheckCompileLinkOption("-xarch=avx" CRYPTOPP_IA32_AVX "${TEST_PROG_DIR}/test_x86_avx.cpp") CheckCompileLinkOption("-xarch=avx2" CRYPTOPP_IA32_AVX2 "${TEST_PROG_DIR}/test_x86_avx2.cpp") CheckCompileLinkOption("-xarch=sha" CRYPTOPP_IA32_SHA "${TEST_PROG_DIR}/test_x86_sha.cpp") # 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}/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(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 () #============================================================================ # 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") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}") 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 () #============================================================================ # Setup OpenMP #============================================================================ if (${CMAKE_VERSION} VERSION_GREATER "3.1" AND USE_OPENMP) find_package(OpenMP) if (OPENMP_FOUND OR OPENMP_CXX_FOUND) message(STATUS "OpenMP: 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 "OpenMP: Found libomp in macports default location.") else() message(FATAL_ERROR "OpenMP: 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 (Intel Macs) 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 "OpenMP: Found libomp in homebrew default location for Intel Macs.") else() message(FATAL_ERROR "OpenMP: Didn't find libomp. Tried homebrew default location for Intel Macs but also didn't find it.") endif() endif() # If OpenMP wasn't found, try if we can find it in the default Homebrew location (Apple Silicon Macs) if((NOT OPENMP_FOUND) AND (NOT OPENMP_CXX_FOUND) AND EXISTS "/opt/homebrew/opt/libomp/lib/libomp.dylib") set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I/opt/homebrew/opt/libomp/include") set(OpenMP_CXX_LIB_NAMES omp) set(OpenMP_omp_LIBRARY /opt/homebrew/opt/libomp/lib/libomp.dylib) find_package(OpenMP) if (OPENMP_FOUND OR OPENMP_CXX_FOUND) message(STATUS "OpenMP: Found libomp in homebrew default location for Apple Silicon Macs.") else() message(FATAL_ERROR "OpenMP: Didn't find libomp. Tried homebrew default location for Apple Silicon Macs 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 "OpenMP: 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 "OpenMP: Did not find OpenMP. Build without USE_OPENMP if you want to allow this.") endif() endif() if(NOT TARGET OpenMP::OpenMP_CXX) # We're on cmake < 3.9, handle behavior of the old FindOpenMP implementation message(STATUS "OpenMP: 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() if (BUILD_STATIC) target_link_libraries(cryptopp-static ${OpenMP_CXX_FLAGS}) # Workaround for Ubuntu 18.04 that otherwise doesn't set -fopenmp for linking target_link_libraries(cryptopp-static OpenMP::OpenMP_CXX) endif() if (BUILD_SHARED) target_link_libraries(cryptopp-shared ${OpenMP_CXX_FLAGS}) # Workaround for Ubuntu 18.04 that otherwise doesn't set -fopenmp for linking target_link_libraries(cryptopp-shared OpenMP::OpenMP_CXX) endif() endif() #============================================================================ # Tests #============================================================================ enable_testing() if (CRYPTOPP_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 (CRYPTOPP_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_ARM32) message(STATUS "Platform: ARM-32") elseif (CRYPTOPP_ARMV8) message(STATUS "Platform: ARMv8") elseif (CRYPTOPP_SPARC) message(STATUS "Platform: Sparc") elseif (CRYPTOPP_SPARC64) message(STATUS "Platform: Sparc64") elseif (CRYPTOPP_PPC32) message(STATUS "Platform: PowerPC") elseif (CRYPTOPP_PPC64) message(STATUS "Platform: PowerPC-64") elseif (CRYPTOPP_MINGW32) message(STATUS "Platform: MinGW-32") elseif (CRYPTOPP_MINGW64) message(STATUS "Platform: MinGW-64") endif () if (CRYPTOPP_ARMV7A_NEON) message(STATUS "NEON: TRUE") 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/Doxyfile000066400000000000000000003243201445614261000212050ustar00rootroot00000000000000# Doxyfile 1.8.13 # 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 (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = Crypto++ # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = 8.6 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "Free C++ class library of cryptographic schemes" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. # The logo looks really bad here. Don't use it. # PROJECT_LOGO = Logo-Steel.png # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = html-docs # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = NO # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = NO # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 0. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 0 # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = NO # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO, these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES, upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = NO # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = NO # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = NO # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = NO # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = NO # If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = NO # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete # parameter documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. # The default value is: NO. WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = . \ GNUmakefile \ GNUmakefile-cross \ rdrand.asm # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. FILE_PATTERNS = *.h \ *.cpp # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = adhoc.cpp # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = *test* \ *validat* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = . # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = NO # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see http://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES # If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the # clang parser (see: http://clang.llvm.org/) for more accurate parsing at the # cost of reduced performance. This can be particularly helpful with template # rich C++ code for which doxygen's built-in parser lacks the necessary type # information. # Note: The availability of this option depends on whether or not doxygen was # generated with the -Duse-libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = NO # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_OPTIONS = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 3 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = . # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to YES can help to show when doxygen was last run and thus if the # documentation is up to date. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = YES # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = com.cryptopp.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = com.cryptopp.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Crypto++ # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the master .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = YES # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /