diff --git a/.github/workflows/ci-tests.yaml b/.github/workflows/ci-tests.yaml index b06f95e46..724b10396 100644 --- a/.github/workflows/ci-tests.yaml +++ b/.github/workflows/ci-tests.yaml @@ -6,17 +6,20 @@ on: pull_request: branches: - master + - capio-v2 concurrency: group: build-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true jobs: codespell-check: name: "Check codespell conformance" runs-on: ubuntu-22.04 + continue-on-error: true steps: - uses: actions/checkout@v4 - name: "Run codespell" uses: codespell-project/actions-codespell@v2 + with: + exclude_file: capio-tests/unit/server/src/SourceText.hpp docker-check: name: "Check Docker image" runs-on: ubuntu-22.04 @@ -39,7 +42,9 @@ jobs: load: true tags: alphaunito/capio:latest - name: "Run unit tests with Docker" + working-directory: ${{ github.workspace }} run: | + echo "Run CAPIO POSIX Unit tests" docker run --rm \ --env CAPIO_DIR=/tmp \ @@ -49,38 +54,15 @@ jobs: capio_posix_unit_tests \ --gtest_break_on_failure \ --gtest_print_time=1 - - echo "Run CAPIO server Unit tests" - docker run --rm \ - --env CAPIO_DIR=/tmp \ - --env CAPIO_LOG_LEVEL=-1 \ - --name capio-docker \ - alphaunito/capio:latest \ - capio_server_unit_tests \ - --gtest_break_on_failure \ - --gtest_print_time=1 - - echo "Run CAPIO syscall Unit tests" - docker run --rm \ - --env CAPIO_DIR=/tmp \ - --env CAPIO_LOG_LEVEL=-1 \ - --env LD_PRELOAD=libcapio_posix.so \ - --name capio-docker \ - alphaunito/capio:latest \ - capio_server_unit_tests \ - --gtest_break_on_failure \ - --gtest_print_time=1 - - echo "Run CAPIO integration tests" - docker run --rm \ - --env CAPIO_DIR=/tmp \ - --env CAPIO_LOG_LEVEL=-1 \ - --env LD_PRELOAD=libcapio_posix.so \ - --name capio-docker \ - alphaunito/capio:latest \ - capio_integration_tests \ - --gtest_break_on_failure \ - --gtest_print_time=1 + + - name: "Run backend communication tests" + working-directory: ${{ github.workspace }} + run: docker compose -f capio-tests/multinode/backend/docker-compose.yml up --abort-on-container-exit + + - name: "Run integration tests" + working-directory: ${{ github.workspace }} + run: docker compose -f capio-tests/multinode/integration/docker-compose.yml up --abort-on-container-exit + format-check: name: "Check ${{ matrix.path }} clang-format conformance" @@ -88,25 +70,27 @@ jobs: strategy: matrix: path: - - "src" - - "tests" + - "capio-posix" + - "capio-server" + - "capio-common" + - "capio-tests" steps: - uses: actions/checkout@v4 - name: "Run clang-format style check" - uses: jidicula/clang-format-action@v4.11.0 + uses: jidicula/clang-format-action@v4.15.0 with: - clang-format-version: "16" + clang-format-version: "20" check-path: "${{ matrix.path }}" unit-tests: name: "Build ${{ matrix.build_type }} with ${{ matrix.cxx }}" runs-on: ubuntu-22.04 + continue-on-error: true strategy: matrix: build_type: - Debug - Release cxx: - - g++-9 - g++-10 - g++-11 - g++-12 @@ -126,7 +110,8 @@ jobs: libopenmpi-dev \ ninja-build \ openmpi-bin \ - pkg-config + pkg-config \ + cmake - name: "Get compiler version" run: | IFS='-' read -r -a COMPILER <<< "${{ matrix.cxx }}" @@ -161,38 +146,48 @@ jobs: -B ../build \ -S ${GITHUB_WORKSPACE} cmake --build ../build -j $(nproc) - sudo cmake --install ../build --prefix /usr/local + sudo cmake --install ../build --prefix /usr/local + - name: "Run tests" id: run-tests - timeout-minutes: 5 + timeout-minutes: 2 env: CAPIO_DIR: ${{ github.workspace }} CAPIO_LOG_LEVEL: -1 run: | export LD_LIBRARY_PATH="/usr/local/lib:${LD_LIBRARY_PATH}" - + + echo "Run CAPIO syscall Unit tests" + capio_server --no-config & + CAPIO_SERVER_PID=$! + LD_PRELOAD=libcapio_posix.so capio_syscall_unit_tests \ + --gtest_break_on_failure --gtest_print_time=1 + kill $CAPIO_SERVER_PID + sleep 2 + + + echo "Run CAPIO memory file integration tests" + capio_server --no-config & + CAPIO_SERVER_PID=$! + LD_PRELOAD=libcapio_posix.so capio_memory_file_unit_tests \ + --gtest_break_on_failure --gtest_print_time=1 + kill $CAPIO_SERVER_PID + sleep 2 + + + echo "Run CAPIO POSIX Unit tests" capio_posix_unit_tests \ --gtest_break_on_failure \ - --gtest_print_time=1 - - echo "Run CAPIO server Unit tests" + --gtest_print_time=1 + sleep 2 + + + echo "Run CAPIO SERVER unit tests" capio_server_unit_tests \ --gtest_break_on_failure \ --gtest_print_time=1 - - echo "Run CAPIO syscall Unit tests" - LD_PRELOAD=libcapio_posix.so \ - capio_syscall_unit_tests \ - --gtest_break_on_failure \ - --gtest_print_time=1 - - echo "Run CAPIO integration tests" - rm -rf /dev/shm/CAPIO* - LD_PRELOAD=libcapio_posix.so \ - capio_integration_tests \ - --gtest_break_on_failure \ - --gtest_print_time=1 + - name: "Show client logs on failure" if: ${{ always() && steps.run-tests.outcome == 'failure' && matrix.build_type == 'Debug' }} run: tail -v -n +1 capio_logs/posix/$(hostname)/posix_thread_*.log @@ -204,6 +199,7 @@ jobs: run: | pip install --upgrade gcovr gcovr \ + --gcov-ignore-parse-errors \ --exclude-throw-branches \ --xml coverage.xml \ --gcov-executable "${{ startsWith(matrix.cxx, 'clang-') && format('llvm-cov-{0} gcov', env.CXX_VERSION) || format('gcov-{0}', env.CXX_VERSION) }}" \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index e730bed93..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: "Release new version" -on: - workflow_run: - workflows: - - "CI Tests" - branches: - - master - types: - - completed -jobs: - docker: - name: "Build Docker container" - runs-on: ubuntu-22.04 - if: ${{ github.event.workflow_run.conclusion == 'success' }} - steps: - - uses: actions/checkout@v4 - - uses: docker/setup-qemu-action@v3 - - uses: docker/setup-buildx-action@v3 - - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: "Get CAPIO version" - run: echo "CAPIO_VERSION=$(cat CMakeLists.txt | grep " VERSION" | awk '{print $2}')" >> $GITHUB_ENV - - name: "Check if Docker image already exists" - run: echo "NEW_IMAGE=$(docker buildx imagetools inspect alphaunito/capio:${CAPIO_VERSION} > /dev/null 2>&1; echo $?)" >> $GITHUB_ENV - - name: "Build Docker image" - if: ${{ env.NEW_IMAGE == 1 }} - uses: docker/build-push-action@v5 - with: - build-args: | - CMAKE_BUILD_TYPE=Release - push: true - tags: | - alphaunito/capio:${{ env.CAPIO_VERSION }} - alphaunito/capio:latest - github: - name: "Create GitHub Release" - runs-on: ubuntu-22.04 - permissions: - contents: write - if: ${{ github.event.workflow_run.conclusion == 'success' }} - steps: - - uses: actions/checkout@v4 - - name: "Get CAPIO version" - run: echo "CAPIO_VERSION=$(cat CMakeLists.txt | grep " VERSION" | awk '{print $2}')" >> $GITHUB_ENV - - name: "Check tag existence" - uses: mukunku/tag-exists-action@v1.6.0 - id: check-tag - with: - tag: ${{ env.CAPIO_VERSION }} - - name: "Create Release" - id: create-release - uses: actions/create-release@v1 - if: ${{ steps.check-tag.outputs.exists == 'false' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ env.CAPIO_VERSION }} - release_name: ${{ env.CAPIO_VERSION }} - draft: false - prerelease: false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 489080712..507de2e55 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,15 @@ cmake-build-* files_location*.txt capio_logs +#Doxygen generated documentation +doxy/html +doxy/latex +doxy/doxygen-awesome-css-* +doxy/theme + # Other debug build - +.devcontainer +.DS_Store +*.alive_connection diff --git a/CMakeLists.txt b/CMakeLists.txt index 4197b3718..163667559 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ project(capio ) # Set required C++ standard -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # Generate the compile_commands.json file @@ -21,6 +21,16 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -pedantic -O0") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") +# Silence warning from G++ +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wno-terminate") +endif () + +# Silence warning from clang +if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -pedantic -O0 -Wno-gnu-zero-variadic-macro-arguments -Wno-exceptions") +endif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + ##################################### # Options ##################################### @@ -37,7 +47,6 @@ include(GNUInstallDirs) ##################################### # Dependencies ##################################### -find_package(MPI REQUIRED) find_package(Threads REQUIRED) ##################################### @@ -45,6 +54,14 @@ find_package(Threads REQUIRED) ##################################### add_compile_definitions(CAPIO_VERSION="${CMAKE_PROJECT_VERSION}") +# We check here and define only here the CAPIO_BUILD_TESTS macro +# as it is required by both src/posix and src/server targets +# Later on we check again to add the tests subproject, which cannot +# be added here for issues with include directories +IF (CAPIO_BUILD_TESTS) + add_compile_definitions(CAPIO_BUILD_TESTS) +ENDIF (CAPIO_BUILD_TESTS) + IF (CAPIO_LOG) IF (CMAKE_BUILD_TYPE STREQUAL "Debug") message(STATUS "Enabling CAPIO logger") @@ -61,8 +78,8 @@ ENDIF (CAPIO_LOG) ##################################### # Include files and directories ##################################### -file(GLOB_RECURSE CAPIO_COMMON_HEADERS "src/common/capio/*.hpp") -include_directories(src/common) +file(GLOB_RECURSE CAPIO_COMMON_HEADERS "capio-common/capio/*.hpp") +include_directories(${PROJECT_SOURCE_DIR}/capio-common) IF (CAPIO_LOG AND CMAKE_BUILD_TYPE STREQUAL "Debug") include_directories("${PROJECT_BINARY_DIR}/include/syscall") @@ -71,10 +88,28 @@ ENDIF (CAPIO_LOG AND CMAKE_BUILD_TYPE STREQUAL "Debug") ##################################### # Targets ##################################### -add_subdirectory(src/posix) -add_subdirectory(src/server) +add_subdirectory(capio-posix) +add_subdirectory(capio-server) + +##################################### +# Install capio-run +##################################### +install( + FILES ${PROJECT_SOURCE_DIR}/capio-run/capiorun + DESTINATION ${CMAKE_INSTALL_BINDIR} + PERMISSIONS + OWNER_READ + OWNER_WRITE + OWNER_EXECUTE + GROUP_READ + GROUP_EXECUTE + WORLD_READ + WORLD_EXECUTE +) + IF (CAPIO_BUILD_TESTS) message(STATUS "Building CAPIO test suite") - add_subdirectory(tests) -ENDIF (CAPIO_BUILD_TESTS) + add_compile_definitions(CAPIO_BUILD_TESTS) + add_subdirectory(capio-tests) +ENDIF (CAPIO_BUILD_TESTS) \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 514b36803..b36d637d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,8 +18,11 @@ RUN apt update \ COPY CMakeLists.txt /opt/capio/ COPY scripts /opt/capio/scripts -COPY src /opt/capio/src -COPY tests /opt/capio/tests +COPY capio-common /opt/capio/capio-common +COPY capio-posix /opt/capio/capio-posix +COPY capio-server /opt/capio/capio-server +COPY capio-tests /opt/capio/capio-tests +COPY capio-run /opt/capio/capio-run RUN mkdir -p /opt/capio/build \ && cmake \ @@ -68,7 +71,6 @@ COPY --from=builder \ "/usr/local/include/gmoc[k]" \ "/usr/local/include/gtes[t]" \ "/usr/local/include/libsyscall_intercept_hook_point.h" \ - "/usr/local/include/simdjson.h" \ /usr/local/include/ # Libraries @@ -89,11 +91,16 @@ COPY --from=builder \ # Binaries COPY --from=builder \ - "/usr/local/bin/capio_posix_unit_test[s]" \ + "/usr/local/bin/capio_posix_unit_test[s]*" \ "/usr/local/bin/capio_server" \ - "/usr/local/bin/capio_server_unit_test[s]" \ - "/usr/local/bin/capio_syscall_unit_test[s]" \ - "/usr/local/bin/capio_integration_test[s]" \ + "/usr/local/bin/capio_server_unit_test[s]*" \ + "/usr/local/bin/capio_syscall_unit_test[s]*" \ + "/usr/local/bin/capio_integration_test[s]*" \ + "/usr/local/bin/capio_backend_unit_tests*" \ + "/usr/local/bin/capio_integration_test_map*" \ + "/usr/local/bin/capio_integration_test_merge*" \ + "/usr/local/bin/capio_integration_test_split*" \ + "/opt/capio/capio-run/capiorun" \ /usr/local/bin/ # Pkgconfig diff --git a/README.md b/README.md index 95d9dbea1..333cde7c0 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,52 @@ -# CAPIO +# CAPIO: Cross Application Programmable IO -CAPIO (Cross-Application Programmable I/O), is a middleware aimed at injecting streaming capabilities to workflow steps -without changing the application codebase. It has been proven to work with C/C++ binaries, Fortran Binaries, JAVA, -python and bash. +CAPIO is a middleware aimed at injecting streaming capabilities into workflow steps +without changing the application codebase. It has been proven to work with C/C++ binaries, Fortran, Java, Python, and +Bash. -[![codecov](https://codecov.io/gh/High-Performance-IO/capio/graph/badge.svg?token=6ATRB5VJO3)](https://codecov.io/gh/High-Performance-IO/capio) -![CI-Tests](https://github.com/High-Performance-IO/capio/actions/workflows/ci-tests.yaml/badge.svg) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://raw.githubusercontent.com/High-Performance-IO/capio/master/LICENSE) +[![codecov](https://codecov.io/gh/High-Performance-IO/capio/graph/badge.svg?token=6ATRB5VJO3)](https://codecov.io/gh/High-Performance-IO/capio) ![CI-Tests](https://github.com/High-Performance-IO/capio/actions/workflows/ci-tests.yaml/badge.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://raw.githubusercontent.com/High-Performance-IO/capio/master/LICENSE) -## Build and run tests +> [!TIP] +> CAPIO is now multibackend and dynamic by nature: you do not need MPI, to benefit for the in-memory IO improvements! +> Just use a MTCL provided backend, if you want the in-memory IO, or fall back to the file system backend (default) if +> oy just want to coordinate IO operations between workflow steps! + +Compatible on: +- ![Architecture](https://img.shields.io/badge/Architecture-x86__64_/_amd64-50C878.svg) +- ![Architecture](https://img.shields.io/badge/Architecture-RISC--V_(riscv64)-50C878.svg) +- ![Architecture](https://img.shields.io/badge/Architecture-ARM64_coming_soon-red.svg) + +--- +## Automatic install with SPACK + +CAPIO is on SPACK! to install it automatically, just add the High Performance IO +repo to spack and then install CAPIO: +```bash +spack repo add https://github.com/High-Performance-IO/hpio-spack.git +spack install capio +``` + +> [!WARNING] +> To use this method, you need spack >= v1.0.0 + +## πŸ”§ Manual Build and Install ### Dependencies -CAPIO depends on the following software that needs to be manually installed: +**Required manually:** -- `cmake >=3.15` -- `c++17` or newer -- `openmpi` +- `cmake >= 3.15` +- `C++20` - `pthreads` -The following dependencies are automatically fetched during cmake configuration phase, and compiled when required. +**Fetched/compiled during configuration:** -- [syscall_intercept](https://github.com/pmem/syscall_intercept) to intercept syscalls -- [Taywee/args](https://github.com/Taywee/args) to parse server command line inputs -- [simdjson/simdjson](https://github.com/simdjson/simdjson) to parse json configuration files +- [syscall_intercept](https://github.com/pmem/syscall_intercept) - Intercept and handles LINUX system calls +- [Taywee/args](https://github.com/Taywee/args) - Parse user input arguments +- [simdjson/simdjson](https://github.com/simdjson/simdjson) - Parse fast JSON files +- [MTCL](https://github.com/ParaGroup/MTCL) - Provides abstractions over multiple communication backends -### Compile capio +### Compile CAPIO ```bash git clone https://github.com/High-Performance-IO/capio.git capio && cd capio @@ -35,169 +56,124 @@ cmake --build . -j$(nproc) sudo cmake --install . ``` -It is also possible to enable log in CAPIO, by defining `-DCAPIO_LOG=TRUE`. +To enable logging support, pass `-DCAPIO_LOG=TRUE` during the CMake configuration phase. -## Use CAPIO in your code +--- -Good news! You don't need to modify your code to benefit from the features of CAPIO. You have only to do three steps ( -the first is optional). +## πŸ§‘β€πŸ’» Using CAPIO in Your Code -1) Write a configuration file for injecting streaming capabilities to your workflow +Good news! You **don’t need to modify your application code**. Just follow these steps: -2) Launch the CAPIO daemons with MPI passing the (eventual) configuration file as argument on the machines in which you - want to execute your program (one daemon for each node). If you desire to specify a custom folder - for capio, set `CAPIO_DIR` as a environment variable. - ```bash - [CAPIO_DIR=your_capiodir] [mpiexec -N 1 --hostfile your_hostfile] capio_server -c conf.json - ``` +### 1. Create a Configuration File *(optional but recommended)* -> [!NOTE] -> if `CAPIO_DIR` is not specified when launching capio_server, it will default to the current working directory of -> capio_server. +Write a CAPIO-CL configuration file to inject streaming into your workflow. Refer to +the [CAPIO-CL Docs](https://capio.hpc4ai.it/docs/coord-language/) for details. -3) Launch your programs preloading the CAPIO shared library like this: - ```bash - CAPIO_DIR=your_capiodir \ - CAPIO_WORKFLOW_NAME=wfname \ - CAPIO_APP_NAME=appname \ - LD_PRELOAD=libcapio_posix.so \ - ./your_app - ``` +### 2 Launch the workflow with CAPIO -> [!WARNING] -> `CAPIO_DIR` must be specified when launching a program with the CAPIO library. if `CAPIO_DIR` is not specified, CAPIO -> will not intercept syscalls. +To launch your workflow with capio you can follow two routes: -### Available environment variables +#### A) Use `capiorun` for simplified operations -CAPIO can be controlled through the usage of environment variables. The available variables are listed below: +You can simplify the execution of workflow steps with CAPIO using the `capiorun` utility. See the +[`capiorun` documentation](capio-run/readme.md) for usage and examples. `capiorun` provides an easier way to manage +daemon startup and environment preparation, so that the user do not need to manually prepare the environment. -#### Global environment variable +#### B) Manually launch CAPIO -- `CAPIO_DIR` This environment variable tells to both server and application the mount point of capio; -- `CAPIO_LOG_LEVEL` this environment tells both server and application the log level to use. This variable works only - if `-DCAPIO_LOG=TRUE` was specified during cmake phase; -- `CAPIO_LOG_PREFIX` This environment variable is defined only for capio_posix applications and specifies the prefix of - the logfile name to which capio will log to. The default value is `posix_thread_`, which means that capio will log by - default to a set of files called `posix_thread_*.log`. An equivalent behaviour can be set on the capio server using - the `-l` option; -- `CAPIO_LOG_DIR` This environment variable is defined only for capio_posix applications and specifies the directory - name to which capio will be created. If this variable is not defined, capio will log by default to `capio_logs`. An - equivalent behaviour can be set on the capio server using the `-d` option; -- `CAPIO_CACHE_LINES`: This environment variable controls how many lines of cache are presents between posix and server - applications. defaults to 10 lines; -- `CAPIO_CACHE_LINE_SIZE`: This environment variable controls the size of a single cache line. defaults to 256KB; +Launch the CAPIO Daemons: start one daemon per node. Optionally set `CAPIO_DIR` to define the CAPIO mount point: -#### Server only environment variable +```bash +[CAPIO_DIR=your_capiodir] capio_server -c conf.json +``` -- `CAPIO_FILE_INIT_SIZE`: This environment variable defines the default size of pre allocated memory for a new file - handled by capio. Defaults to 4MB. Bigger sizes will reduce the overhead of malloc but will fill faster node memory. - Value has to be expressed in bytes; -- `CAPIO_PREFETCH_DATA_SIZE`: If this variable is set, then data transfers between nodes will be always, at least of the - given value in bytes; +> [!CAUTION] +> If `CAPIO_DIR` is not set, it defaults to the current working directory. -#### Posix only environment variable +You can now start your application. Just set the right environment variable and remember to set `LD_PRELOAD` to the +`libcapio_posix.so` intercepting library: -> [!WARNING] -> The following variables are mandatory. If not provided to a posix, application, CAPIO will not be able to correctly -> handle the -> application, according to the specifications given from the json configuration file! - -- `CAPIO_WORKFLOW_NAME`: This environment variable is used to define the scope of a workflow for a given step. Needs to - be the same one as the field `"name"` inside the json configuration file; -- `CAPIO_APP_NAME`: This environment variable defines the app name within a workflow for a given step; - -## How to inject streaming capabilities into your workflow - -With CAPIO is possible to run the applications of your workflow that communicates through files concurrently. CAPIO will -synchronize transparently the concurrent reads and writes on those files. If a file is never modified after it is closed -you can set the streaming semantics equals to "on_close" on the configuration file. In this way, all the reads done on -this file will hung until the writer closes the file, allowing the consumer application to read the file even if the -producer is still running. -Another supported file streaming semantics is "append" in which a read is satisfied when the producer writes the -requested data. This is the most aggressive (and efficient) form of streaming semantics (because the consumer can start -reading while the producer is writing the file). This semantic must be used only if the producer does not modify a piece -of data after it is written. -The streaming semantic on_termination tells CAPIO to not allowing streaming on that file. This is the default streaming -semantics if a semantics for a file is not specified. -The following is an example of a simple configuration: - -```json -{ - "name": "my_workflow", - "IO_Graph": [ - { - "name": "writer", - "output_stream": [ - "file0.dat", - "file1.dat", - "file2.dat" - ], - "streaming": [ - { - "name": ["file0.dat"], - "committed": "on_close" - }, - { - "name": ["file1.dat"], - "committed": "on_close", - "mode": "no_update" - }, - { - "name": ["file2.dat"], - "committed": "on_termination" - } - ] - }, - { - "name": "reader", - "input_stream": [ - "file0.dat", - "file1.dat", - "file2.dat" - ] - } - ] -} +```bash +CAPIO_DIR=your_capiodir +CAPIO_WORKFLOW_NAME=wfname +CAPIO_APP_NAME=appname +LD_PRELOAD=libcapio_posix.so +./your_app + +killall -USR1 capio_server ``` -> [!NOTE] -> We are working on an extension of the possible streaming semantics and in a detailed -> documentation about the configuration file! +> [!CAUTION] +> if `CAPIO_APP_NAME` and `CAPIO_WORKFLOW_NAME` are not set (or are set but do not match the values present in the +> CAPIO-CL configuration file), CAPIO will not be able to operate correctly! -## Examples +> [!tip] +> To gracefully shut down the capio server instance, just send the SIGUSR1 signal. +> the capio_server process will then automatically clean up and terminate itself! -The [examples](examples) folder contains some examples that shows how to use mpi_io with CAPIO. -There are also examples on how to write JSON configuration files for the semantics implemented by CAPIO: +--- -- [on_close](https://github.com/High-Performance-IO/capio/wiki/Examples#on_close-semantic): A pipeline composed by a - producer and a consumer with "on_close" semantics -- [no_update](https://github.com/High-Performance-IO/capio/wiki/Examples#noupdate-semantics): A pipeline composed by a - producer and a consumer with "no_update" semantics -- [mix_semantics](https://github.com/High-Performance-IO/capio/wiki/Examples#mixed-semantics): A pipeline composed by a - producer and a consumer with mix semantics +## βš™οΈ Environment Variables -## Report bugs + get help +### πŸ”„ Global -[Create a new issue](https://github.com/High-Performance-IO/capio/issues/new) +| Variable | Description | +|-------------------------|----------------------------------------------------| +| `CAPIO_DIR` | Shared mount point for server and application | +| `CAPIO_LOG_LEVEL` | Logging level (requires `-DCAPIO_LOG=TRUE`) | +| `CAPIO_LOG_PREFIX` | Log file name prefix (default: `posix_thread_`) | +| `CAPIO_LOG_DIR` | Directory for log files (default: `capio_logs`) | +| `CAPIO_CACHE_LINE_SIZE` | Size of a single CAPIO cache line (default: 256KB) | -[Get help](https://github.com/High-Performance-IO/capio/wiki) +### πŸ–₯️ Server-Only -> [!TIP] -> A [wiki](https://github.com/High-Performance-IO/capio/wiki) is in development! You might want to check the wiki to get -> more in depth information about CAPIO! +| Variable | Description | +|----------------------|----------------------------------------------------------------------------| +| `CAPIO_METADATA_DIR` | Directory for metadata files. Defaults to `CAPIO_DIR`. Must be accessible. | + +### πŸ“ POSIX-Only (Mandatory) + +> ⚠️ These are required by CAPIO-POSIX. Without them, your app will not behave as configured in the JSON file. + +| Variable | Description | +|-----------------------|-------------------------------------------------| +| `CAPIO_WORKFLOW_NAME` | Must match `"name"` field in your configuration | +| `CAPIO_APP_NAME` | Name of the step within your workflow | + +--- + +## πŸ“– Extended documentation + +Documentation and examples are available on the official site: + +🌐 [https://capio.hpc4ai.it/docs](https://capio.hpc4ai.it/docs) + +--- + +## 🐞 Report Bugs & Get Help + +- [Create an issue](https://github.com/High-Performance-IO/capio/issues/new) +- [Official Documentation](https://capio.hpc4ai.it/docs) + +--- + +## πŸ‘₯ CAPIO Team + +Made with ❀️ by: + +- Marco Edoardo Santimaria – (Designer & Maintainer) +- Iacopo Colonnelli – (Workflow Support & Maintainer) +- Massimo Torquati – (Designer) +- Marco Aldinucci – (Designer) -## CAPIO Team +**Former Members:** -Made with :heart: by: +- Alberto Riccardo Martinelli – (Designer & Maintainer) -Alberto Riccardo Martinelli (designer and maintainer) \ -Marco Edoardo Santimaria (Designer and maintainer) \ -Iacopo Colonnelli (Workflows expert and maintainer) \ -Massimo Torquati (Designer) \ -Marco Aldinucci (Designer) +--- -## Papers -[![CAPIO](https://img.shields.io/badge/CAPIO-10.1109/HiPC58850.2023.00031-red)]([https://arxiv.org/abs/2206.10048](https://dx.doi.org/10.1109/HiPC58850.2023.00031)) +## πŸ“š Publications +[![CAPIO](https://img.shields.io/badge/CAPIO-10.1109/HiPC58850.2023.00031-red)](https://dx.doi.org/10.1109/HiPC58850.2023.00031) +[![](https://img.shields.io/badge/CAPIO--CL-10.1007%2Fs10766--025--00789--0-green?style=flat&logo=readthedocs)](https://doi.org/10.1007/s10766-025-00789-0) \ No newline at end of file diff --git a/src/common/capio/constants.hpp b/capio-common/capio/constants.hpp similarity index 62% rename from src/common/capio/constants.hpp rename to capio-common/capio/constants.hpp index 81bf47d64..23705f856 100644 --- a/src/common/capio/constants.hpp +++ b/capio-common/capio/constants.hpp @@ -7,14 +7,20 @@ #include +// 64bit unsigned long int type for file offsets +typedef unsigned long long int capio_off64_t; + // CAPIO files constants -constexpr size_t CAPIO_DEFAULT_DIR_INITIAL_SIZE = 1024L * 1024 * 1024; -constexpr off64_t CAPIO_DEFAULT_FILE_INITIAL_SIZE = 1024L * 1024 * 1024 * 4; -constexpr std::array CAPIO_DIR_FORBIDDEN_PATHS = {std::string_view{"/proc/"}, - std::string_view{"/sys/"}}; +constexpr size_t CAPIO_DEFAULT_DIR_INITIAL_SIZE = 1024L * 1024 * 1024; +constexpr off64_t CAPIO_DEFAULT_FILE_INITIAL_SIZE = 1024L * 1024 * 1024 * 4; +[[maybe_unused]] constexpr std::array CAPIO_DIR_FORBIDDEN_PATHS = { + std::string_view{"/proc/"}, std::string_view{"/sys/"}, std::string_view{"/boot/"}, + std::string_view{"/dev/"}, std::string_view{"/var/"}, std::string_view{"/run/"}, + std::string_view("/spack/"), std::string_view{"/usr/bin/"}}; // CAPIO default values for shared memory constexpr char CAPIO_DEFAULT_WORKFLOW_NAME[] = "CAPIO"; +constexpr char CAPIO_DEFAULT_APP_NAME[] = "default_app"; constexpr char CAPIO_SHM_CANARY_ERROR[] = "FATAL ERROR: Shared memories for workflow %s already " "exists. One of two (or both) reasons are to blame: \n " @@ -25,26 +31,27 @@ constexpr char CAPIO_SHM_CANARY_ERROR[] = // CAPIO communication constants constexpr int CAPIO_REQ_BUFF_CNT = 512; // Max number of elements inside buffers constexpr int CAPIO_CACHE_LINES_DEFAULT = 10; -constexpr int CAPIO_CACHE_LINE_SIZE_DEFAULT = 256 * 1024; -constexpr size_t CAPIO_SERVER_REQUEST_MAX_SIZE = sizeof(char) * (PATH_MAX + 81920); -constexpr size_t CAPIO_REQ_MAX_SIZE = 256 * sizeof(char); -constexpr char CAPIO_SERVER_CLI_LOG_SERVER[] = "[ \033[1;32m SERVER \033[0m ] "; -constexpr char CAPIO_SERVER_CLI_LOG_SERVER_WARNING[] = "[ \033[1;33m SERVER \033[0m ] "; -constexpr char CAPIO_SERVER_CLI_LOG_SERVER_ERROR[] = "[ \033[1;31m SERVER \033[0m ] "; +constexpr int CAPIO_CACHE_LINE_SIZE_DEFAULT = 32768; // 32K of default size for cache lines +// TODO: use that in communication only uses the file descriptor instead of the path to save on the +// PATH_MAX +constexpr size_t CAPIO_REQ_MAX_SIZE = (PATH_MAX + 256) * sizeof(char); +constexpr char CAPIO_SERVER_CLI_LOG_SERVER[] = "[\033[1;32mSERVER\033[0m"; +constexpr char CAPIO_SERVER_CLI_LOG_SERVER_WARNING[] = "[\033[1;33mSERVER\033[0m"; +constexpr char CAPIO_SERVER_CLI_LOG_SERVER_ERROR[] = "[\033[1;31mSERVER\033[0m"; constexpr char LOG_CAPIO_START_REQUEST[] = "\n+++++++++++ SYSCALL %s (%d) +++++++++++"; constexpr char LOG_CAPIO_END_REQUEST[] = "----------- END SYSCALL ----------\n"; -constexpr char CAPIO_SERVER_LOG_START_REQUEST_MSG[] = "+++++++++++++++++REQUEST+++++++++++++++++"; -constexpr char CAPIO_SERVER_LOG_END_REQUEST_MSG[] = "~~~~~~~~~~~~~~~END REQUEST~~~~~~~~~~~~~~~"; -constexpr int CAPIO_LOG_MAX_MSG_LEN = 2048; -constexpr int CAPIO_SEM_RETRIES = 100; -constexpr int THEORETICAL_SIZE_DIRENT64 = sizeof(ino64_t) + sizeof(off64_t) + - sizeof(unsigned short) + sizeof(unsigned char) + - sizeof(char) * NAME_MAX; +constexpr char CAPIO_SERVER_LOG_START_REQUEST_MSG[] = "\n+++++++++++++++++REQUEST+++++++++++++++++"; +constexpr char CAPIO_SERVER_LOG_END_REQUEST_MSG[] = "~~~~~~~~~~~~~~~END REQUEST~~~~~~~~~~~~~~~"; +constexpr int CAPIO_LOG_MAX_MSG_LEN = 4096; +constexpr int CAPIO_MAX_SPSQUEUE_ELEMS = 10; +constexpr int CAPIO_MAX_SPSCQUEUE_ELEM_SIZE = 1024 * 256; // CAPIO streaming semantics constexpr char CAPIO_FILE_MODE_NO_UPDATE[] = "no_update"; constexpr char CAPIO_FILE_MODE_UPDATE[] = "update"; constexpr char CAPIO_FILE_COMMITTED_ON_CLOSE[] = "on_close"; +constexpr char CAPIO_FILE_COMMITTED_ON_FILE[] = "on_file"; +constexpr char CAPIO_FILE_COMMITTED_N_FILES[] = "n_files"; constexpr char CAPIO_FILE_COMMITTED_ON_TERMINATION[] = "on_termination"; // CAPIO POSIX return codes @@ -54,7 +61,7 @@ constexpr int CAPIO_POSIX_SYSCALL_SKIP = 1; constexpr int CAPIO_POSIX_SYSCALL_SUCCESS = 0; // CAPIO logger - common -constexpr char CAPIO_LOG_PRE_MSG[] = "at[%s]: "; +constexpr char CAPIO_LOG_PRE_MSG[] = "at[%.15llu][%.40s]: "; constexpr char CAPIO_DEFAULT_LOG_FOLDER[] = "capio_logs\0"; // CAPIO common - shared memory constant names @@ -96,51 +103,64 @@ constexpr char CAPIO_LOG_SERVER_BANNER[] = "\\______/\n\n" "\033[0m CAPIO - Cross Application Programmable IO \n" " V. " CAPIO_VERSION "\n\n"; -constexpr char CAPIO_LOG_SERVER_CLI_LEVEL_INFO[] = "[ \033[1;32m SERVER \033[0m ] "; -constexpr char CAPIO_LOG_SERVER_CLI_LEVEL_WARNING[] = "[ \033[1;33m SERVER \033[0m ] "; -constexpr char CAPIO_LOG_SERVER_CLI_LEVEL_ERROR[] = "[ \033[1;31m SERVER \033[0m ] "; +constexpr char CAPIO_LOG_SERVER_CLI_LEVEL_INFO[] = "[\033[1;32mSERVER\033[0m"; +constexpr char CAPIO_LOG_SERVER_CLI_LEVEL_WARNING[] = "[\033[1;33mSERVER\033[0m"; +constexpr char CAPIO_LOG_SERVER_CLI_LEVEL_ERROR[] = "[\033[1;31mSERVER\033[0m"; +constexpr char CAPIO_LOG_SERVER_CLI_LEVEL_JSON[] = "[\033[1;34mSERVER\033[0m"; constexpr char CAPIO_LOG_SERVER_CLI_LOGGING_ENABLED_WARNING[] = - "[ \033[1;33m SERVER \033[0m ] " + "[\033[1;33mSERVER\033[0m] " "|==================================================================|\n" - "[ \033[1;33m SERVER \033[0m ] | you are running a build of CAPIO with " + "[\033[1;33mSERVER\033[0m] | you are running a build of CAPIO with " "logging enabled. |\n" - "[ \033[1;33m SERVER \033[0m ] | this will have impact on performance. " - "you " - "should recompile CAPIO |\n" - "[ \033[1;33m SERVER \033[0m ] | with -DCAPIO_LOG=FALSE " - " " - " |\n" - "[ \033[1;33m SERVER \033[0m ] " + "[\033[1;33mSERVER\033[0m] | this will have impact on performance. " + "you should recompile CAPIO |\n" + "[\033[1;33mSERVER\033[0m] | with -DCAPIO_LOG=FALSE " + " |\n" + "[\033[1;33mSERVER\033[0m] " "|==================================================================|\n"; constexpr char CAPIO_LOG_SERVER_CLI_LOGGING_NOT_AVAILABLE[] = "CAPIO_LOG set but log support was not compiled into CAPIO!"; -constexpr char CAPIO_LOG_SERVER_REQUEST_START[] = "\n+++++++++++ REQUEST +++++++++++"; +constexpr char CAPIO_LOG_SERVER_REQUEST_START[] = "+++++++++++ REQUEST +++++++++++"; constexpr char CAPIO_LOG_SERVER_REQUEST_END[] = "~~~~~~~~~ END REQUEST ~~~~~~~~~\n"; // CAPIO server argument parser constexpr char CAPIO_SERVER_ARG_PARSER_PRE[] = - "Cross Application Programmable IO application. developed by Alberto " - "Riccardo Martinelli (UniTO), Massimo Torquati(UniPI), Marco Aldinucci (UniTO), Iacopo " - "Colonnelli(UniTO) and Marco Edoardo Santimaria (UniTO)."; + "Cross Application Programmable IO application. developed by Marco Edoardo Santimaria (UniTO), " + "Iacopo Colonnelli(UniTO), Massimo Torquati(UniPI) and Marco Aldinucci (UniTO), "; constexpr char CAPIO_SERVER_ARG_PARSER_EPILOGUE[] = "For further help, a full list of the available ENVIRONMENT VARIABLES," " and a guide on config JSON file structure, please visit " "https://github.com/High-Performance-IO/capio"; -constexpr char CAPIO_SERVER_ARG_PARSER_PRE_COMMAND[] = "{ENVIRONMENT_VARS} mpirun -n 1"; +constexpr char CAPIO_SERVER_ARG_PARSER_PRE_COMMAND[] = "{ENVIRONMENT_VARS} "; constexpr char CAPIO_SERVER_ARG_PARSER_LOGILE_DIR_OPT_HELP[] = "Name of the folder to which CAPIO server will put log files into"; constexpr char CAPIO_SERVER_ARG_PARSER_LOGILE_OPT_HELP[] = "Filename to which capio_server will log to, without extension"; constexpr char CAPIO_SERVER_ARG_PARSER_CONFIG_OPT_HELP[] = "JSON Configuration file for capio_server"; +constexpr char CAPIO_SERVER_ARG_PARSER_BACKEND_OPT_HELP[] = + "Select a valid communication backend for CAPIO. current supported values are: [fs | tcp | mpi " + "| ucx | mqtt ]"; +constexpr char CAPIO_SERVER_ARG_PARSER_BACKEND_PORT_OPT_HELP[] = + "A valid PORT for the Communication backend"; constexpr char CAPIO_SERVER_ARG_PARSER_CONFIG_NO_CONF_FILE_HELP[] = "If specified, server application will start without a config file, using default settings."; +constexpr char CAPIO_SERVER_ARG_PARSER_MEM_STORAGE_ONLY_HELP[] = + "If set, all files will be stored inside the home node server memory and never on file system " + "(unless memory limit is reached, or server instance terminates)."; constexpr char CAPIO_SERVER_ARG_PARSER_CONFIG_NCONTINUE_ON_ERROR_HELP[] = "If specified, Capio will try to continue its execution to continue even if it has reached a " "fatal termination point. This flag should be used only to debug capio. If this flag is " "specified, and a fatal termination point is reached, the behaviour of capio is undefined and " "should not be taken as valid"; +constexpr char CAPIO_SERVER_ARG_PARSER_CONFIG_RESOLVE_RELATIVE_TO_HELP[] = + "When finding relative paths in the CAPIO-CL configuration file, resolve them relative to the " + "provided path"; + +constexpr char CAPIO_SERVER_ARG_PARSER_CONFIG_CONTROL_PLANE_BACKEND[] = + "Which control plane backend to used. Options: . Defaults to "; + constexpr char CAPIO_LOG_SERVER_CLI_CONT_ON_ERR_WARNING[] = "[ \033[1;33m SERVER \033[0m ]\033[1;31m " "|==================================================================|\033[0m\n" @@ -163,4 +183,17 @@ constexpr char CAPIO_SERVER_ARG_PARSER_CONFIG_BACKEND_HELP[] = "Backend used in CAPIO. The value [backend] can be one of the following implemented backends: " "\n\t> mpi (default)\n\t> mpisync"; -#endif // CAPIO_COMMON_CONSTANTS_HPP +// CAPIO backend constant values + +constexpr int DEFAULT_CAPIO_BACKEND_PORT = 2222; +constexpr int CAPIO_BACKEND_DEFAULT_SLEEP_TIME = 300; +constexpr char MULTICAST_DISCOVERY_ADDR[] = "234.234.234.1"; +constexpr char MULTICAST_CONTROLPL_ADDR[] = "234.234.234.2"; +constexpr int MULTICAST_DISCOVERY_PORT = 2223; +constexpr int MULTICAST_CONTROLPL_PORT = 2224; + +// hostname + : + sizeof(port) +constexpr int MULTICAST_ALIVE_TOKEN_MESSAGE_SIZE = HOST_NAME_MAX + 10; +constexpr int MULTICAST_CONTROLPL_MESSAGE_SIZE = HOST_NAME_MAX + PATH_MAX + 10; + +#endif // CAPIO_COMMON_CONSTANTS_HPP \ No newline at end of file diff --git a/src/common/capio/env.hpp b/capio-common/capio/env.hpp similarity index 69% rename from src/common/capio/env.hpp rename to capio-common/capio/env.hpp index 50bb565d5..9e6d52c60 100644 --- a/src/common/capio/env.hpp +++ b/capio-common/capio/env.hpp @@ -9,10 +9,17 @@ #include +#ifndef __CAPIO_POSIX +#include "capiocl.hpp" +#include "capiocl/engine.h" +#include +extern capiocl::engine::Engine *capio_cl_engine; +#endif + #include "logger.hpp" #include "syscall.hpp" -const std::filesystem::path &get_capio_dir() { +inline const std::filesystem::path &get_capio_dir() { static std::filesystem::path capio_dir{}; START_LOG(capio_syscall(SYS_gettid), "call()"); // TODO: if CAPIO_DIR is not set, it should be left as null @@ -22,20 +29,10 @@ const std::filesystem::path &get_capio_dir() { auto buf = std::unique_ptr(new char[PATH_MAX]); if (val == nullptr) { - - std::cout << "\n" - << CAPIO_LOG_SERVER_CLI_LEVEL_ERROR << "Fatal: CAPIO_DIR not provided!" - << std::endl; ERR_EXIT("Fatal: CAPIO_DIR not provided!"); - } else { - const char *realpath_res = capio_realpath(val, buf.get()); if (realpath_res == nullptr) { - std::cout << "\n" - << CAPIO_LOG_SERVER_CLI_LEVEL_ERROR - << "Fatal: CAPIO_DIR set, but folder does not exists on filesystem!" - << std::endl; ERR_EXIT("error CAPIO_DIR: directory %s does not " "exist. [buf=%s]", val, buf.get()); @@ -44,7 +41,7 @@ const std::filesystem::path &get_capio_dir() { capio_dir = std::filesystem::path(buf.get()); for (auto &forbidden_path : CAPIO_DIR_FORBIDDEN_PATHS) { if (capio_dir.native().rfind(forbidden_path, 0) == 0) { - ERR_EXIT("CAPIO_DIR inside %s file system is not supported", forbidden_path); + ERR_EXIT("CAPIO_DIR inside %s file system is not supported", forbidden_path.data()); } } } @@ -53,40 +50,26 @@ const std::filesystem::path &get_capio_dir() { return capio_dir; } -inline long get_cache_lines() { +inline const std::filesystem::path &get_capio_metadata_path() { + static std::filesystem::path metadata_path{}; START_LOG(capio_syscall(SYS_gettid), "call()"); - static long data_bufs_size = -1; - if (data_bufs_size == -1) { - LOG("Value not set. getting value"); - char *value = std::getenv("CAPIO_CACHE_LINES"); - if (value != nullptr) { - LOG("Getting value from environment variable"); - data_bufs_size = strtol(value, nullptr, 10); + if (metadata_path.empty()) { + const char *val = std::getenv("CAPIO_METADATA_DIR"); + auto buf = std::unique_ptr(new char[PATH_MAX]); + + if (val == nullptr) { + metadata_path = std::filesystem::current_path() / ".capio_metadata"; } else { - LOG("Getting default value"); - data_bufs_size = CAPIO_CACHE_LINES_DEFAULT; + metadata_path = std::filesystem::path(val) / ".capio_metadata"; } - } - LOG("data_bufs_size=%ld", data_bufs_size); - return data_bufs_size; -} -inline long get_cache_line_size() { - START_LOG(capio_syscall(SYS_gettid), "call()"); - static long data_bufs_count = -1; - if (data_bufs_count == -1) { - LOG("Value not set. getting value"); - char *value = std::getenv("CAPIO_CACHE_LINE_SIZE"); - if (value != nullptr) { - LOG("Getting value from environment variable"); - data_bufs_count = strtol(value, nullptr, 10); - } else { - LOG("Getting default value"); - data_bufs_count = CAPIO_CACHE_LINE_SIZE_DEFAULT; + if (!std::filesystem::exists(metadata_path)) { + std::filesystem::create_directories(metadata_path); } } - LOG("data_bufs_count=%ld", data_bufs_count); - return data_bufs_count; + LOG("CAPIO_METADATA_DIR=%s", metadata_path.c_str()); + + return metadata_path; } inline int get_capio_log_level() { @@ -98,8 +81,6 @@ inline int get_capio_log_level() { } else { auto [ptr, ec] = std::from_chars(log_level, log_level + strlen(log_level), level); if (ec != std::errc()) { - std::cout << CAPIO_LOG_SERVER_CLI_LEVEL_WARNING << "invalid CAPIO_LOG_LEVEL value" - << std::endl; level = 0; } } @@ -109,15 +90,66 @@ inline int get_capio_log_level() { inline std::string get_capio_workflow_name() { static std::string name; + + START_LOG(capio_syscall(SYS_gettid), "call()"); + if (name.empty()) { + LOG("name is empty"); +#ifdef __CAPIO_POSIX + LOG("Fetching name from std::env"); auto tmp = std::getenv("CAPIO_WORKFLOW_NAME"); if (tmp != nullptr) { name = tmp; } else { name = CAPIO_DEFAULT_WORKFLOW_NAME; } +#else + if (capio_cl_engine != nullptr) { + name = capio_cl_engine->getWorkflowName(); + } else { + name = capiocl::CAPIO_CL_DEFAULT_WF_NAME; + } + +#endif } + + LOG("Returning workflow_name=%s", name.c_str()); return name; } -#endif // CAPIO_COMMON_ENV_HPP +inline long get_cache_lines() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + static long data_bufs_size = -1; + if (data_bufs_size == -1) { + LOG("Value not set. getting value"); + char *value = std::getenv("CAPIO_CACHE_LINES"); + if (value != nullptr) { + LOG("Getting value from environment variable"); + data_bufs_size = strtol(value, nullptr, 10); + } else { + LOG("Getting default value"); + data_bufs_size = CAPIO_CACHE_LINES_DEFAULT; + } + } + LOG("data_bufs_size=%ld", data_bufs_size); + return data_bufs_size; +} + +inline long get_cache_line_size() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + static long data_bufs_count = -1; + if (data_bufs_count == -1) { + LOG("Value not set. getting value"); + char *value = std::getenv("CAPIO_CACHE_LINE_SIZE"); + if (value != nullptr) { + LOG("Getting value from environment variable"); + data_bufs_count = strtol(value, nullptr, 10); + } else { + LOG("Getting default value"); + data_bufs_count = CAPIO_CACHE_LINE_SIZE_DEFAULT; + } + } + LOG("data_bufs_count=%ld", data_bufs_count); + return data_bufs_count; +} +#endif // CAPIO_COMMON_ENV_HPP \ No newline at end of file diff --git a/src/common/capio/filesystem.hpp b/capio-common/capio/filesystem.hpp similarity index 50% rename from src/common/capio/filesystem.hpp rename to capio-common/capio/filesystem.hpp index 8d4f8f502..7b06a2f8c 100644 --- a/src/common/capio/filesystem.hpp +++ b/capio-common/capio/filesystem.hpp @@ -14,7 +14,9 @@ #include "logger.hpp" #include "syscall.hpp" -std::filesystem::path get_parent_dir_path(const std::filesystem::path &file_path) { +#include + +inline std::filesystem::path get_parent_dir_path(const std::filesystem::path &file_path) { START_LOG(capio_syscall(SYS_gettid), "call(file_path=%s)", file_path.c_str()); if (file_path == file_path.root_path()) { return file_path; @@ -33,8 +35,8 @@ inline bool in_dir(const std::string &path, const std::string &glob) { inline bool is_directory(int dirfd) { START_LOG(capio_syscall(SYS_gettid), "call(dirfd=%d)", dirfd); + struct stat path_stat = {0}; - struct stat path_stat {}; int tmp = fstat(dirfd, &path_stat); if (tmp != 0) { LOG("Error at is_directory(dirfd=%d) -> %d: %d (%s)", dirfd, tmp, errno, @@ -49,10 +51,13 @@ inline bool is_prefix(const std::filesystem::path &path_1, const std::filesystem return !relpath.empty() && relpath.native().rfind("..", 0) != 0; } +/* + * Returns true if any of the paths in the list contains any of the forbidden paths. + */ inline bool is_forbidden_path(const std::string_view &path) { return std::any_of(CAPIO_DIR_FORBIDDEN_PATHS.cbegin(), CAPIO_DIR_FORBIDDEN_PATHS.cend(), [&path](const std::string_view &forbidden_path) { - return path.rfind(forbidden_path, 0) == 0; + return path.find(forbidden_path) != std::string_view::npos; }); } @@ -67,10 +72,49 @@ inline bool is_capio_dir(const std::filesystem::path &path_to_check) { inline bool is_capio_path(const std::filesystem::path &path_to_check) { START_LOG(capio_syscall(SYS_gettid), "call(path_to_check=%s)", path_to_check.c_str()); - // check if path_to_check begins with CAPIO_DIR - const auto res = is_prefix(get_capio_dir(), path_to_check); - LOG("is_capio_path:%s", res ? "yes" : "no"); - return res; + // check if path_to_check begins with CAPIO_DIR and is not within the forbidden paths + const auto is_capio_path = + is_prefix(get_capio_dir(), path_to_check) && !is_forbidden_path(path_to_check.string()); + + LOG("is_capio_path:%s", is_capio_path ? "yes" : "no"); + return is_capio_path; +} + +/** + * Given a string, replace a single character with a string. This function is used + * when converting a CAPIO-CL wildcard into a C++ valid regular expression + * @param str + * @param symbol + * @param replacement + * @return + */ +[[nodiscard]] static std::string replaceSymbol(const std::string &str, char symbol, + const std::string &replacement) { + std::string result = str; + size_t pos = 0; + + // Find the symbol and replace it + while ((pos = result.find(symbol, pos)) != std::string::npos) { + result.replace(pos, 1, replacement); + pos += replacement.length(); // Move past the replacement + } + + return result; +} + +/** + * Convert a CAPIO-CL regular expression into a c++ valid regular expression + * @param capio_path String to convert + * @return std::regex compiled with the corresponding c++ regex + */ +[[maybe_unused]] [[nodiscard]] static std::regex generateCapioRegex(const std::string &capio_path) { + START_LOG(gettid(), "call(capio_path=%s)", capio_path.c_str()); + auto computed = replaceSymbol(capio_path, '.', "\\."); + computed = replaceSymbol(computed, '/', "\\/"); + computed = replaceSymbol(computed, '*', R"([a-zA-Z0-9\/\.\-_:]*)"); + computed = replaceSymbol(computed, '+', "."); + LOG("Computed regex: %s", computed.c_str()); + return std::regex(computed); } -#endif // CAPIO_COMMON_FILESYSTEM_HPP +#endif // CAPIO_COMMON_FILESYSTEM_HPP \ No newline at end of file diff --git a/src/common/capio/logger.hpp b/capio-common/capio/logger.hpp similarity index 71% rename from src/common/capio/logger.hpp rename to capio-common/capio/logger.hpp index b171c1409..d71705a01 100644 --- a/src/common/capio/logger.hpp +++ b/capio-common/capio/logger.hpp @@ -9,12 +9,11 @@ #include #include #include -#include #include "constants.hpp" #include "syscall.hpp" -bool continue_on_error = false; // change behaviour of ERR_EXIT to continue if set to true +inline bool continue_on_error = false; // change behaviour of ERR_EXIT to continue if set to true #if defined(CAPIO_LOG) && defined(__CAPIO_POSIX) #include "syscallnames.h" @@ -22,30 +21,35 @@ bool continue_on_error = false; // change behaviour of ERR_EXIT to continue if s #ifndef __CAPIO_POSIX #include -thread_local std::ofstream logfile; // if building for server, self contained logfile -std::string log_master_dir_name = CAPIO_DEFAULT_LOG_FOLDER; -std::string logfile_prefix = CAPIO_SERVER_DEFAULT_LOG_FILE_PREFIX; +inline thread_local std::ofstream logfile; // if building for server, self-contained logfile +inline std::string log_master_dir_name = CAPIO_DEFAULT_LOG_FOLDER; +inline std::string logfile_prefix = CAPIO_SERVER_DEFAULT_LOG_FILE_PREFIX; #else -thread_local bool logfileOpen = false; -thread_local int logfileFD = -1; -thread_local char logfile_path[PATH_MAX]{'\0'}; +inline thread_local bool logfileOpen = false; +inline thread_local int logfileFD = -1; +inline thread_local char logfile_path[PATH_MAX]{'\0'}; #endif -thread_local int current_log_level = 0; -thread_local bool logging_syscall = false; // this variable tells the logger that syscall logging - // has started and we are not in setup phase +inline thread_local int current_log_level = 0; +inline thread_local bool logging_syscall = + false; // this variable tells the logger that syscall logging +// has started and we are not in setup phase #ifndef CAPIO_MAX_LOG_LEVEL // capio max log level. defaults to -1, where everything is logged #define CAPIO_MAX_LOG_LEVEL -1 #endif -int CAPIO_LOG_LEVEL = CAPIO_MAX_LOG_LEVEL; +inline int CAPIO_LOG_LEVEL = CAPIO_MAX_LOG_LEVEL; #ifndef __CAPIO_POSIX inline auto open_server_logfile() { auto hostname = new char[HOST_NAME_MAX]; gethostname(hostname, HOST_NAME_MAX); + if (log_master_dir_name.empty()) { + log_master_dir_name = CAPIO_DEFAULT_LOG_FOLDER; + } + const std::filesystem::path output_folder = std::string{log_master_dir_name + "/server/" + hostname}; @@ -128,7 +132,19 @@ inline void setup_posix_log_filename() { } #endif -void log_write_to(char *buffer, size_t bufflen) { +inline long long current_time_in_millis() { + timespec ts{}; + static long long start_time = -1; + if (start_time == -1) { + capio_syscall(SYS_clock_gettime, CLOCK_REALTIME, &ts); + start_time = static_cast(ts.tv_sec) * 1000 + (ts.tv_nsec) / 1000000; + } + capio_syscall(SYS_clock_gettime, CLOCK_REALTIME, &ts); + auto time_now = static_cast(ts.tv_sec) * 1000 + (ts.tv_nsec) / 1000000; + return time_now - start_time; +} + +inline void log_write_to(char *buffer, size_t bufflen) { #ifdef __CAPIO_POSIX if (current_log_level < CAPIO_MAX_LOG_LEVEL || CAPIO_MAX_LOG_LEVEL < 0) { capio_syscall(SYS_write, logfileFD, buffer, bufflen); @@ -136,19 +152,29 @@ void log_write_to(char *buffer, size_t bufflen) { } #else if (current_log_level < CAPIO_LOG_LEVEL || CAPIO_LOG_LEVEL < 0) { - logfile << buffer << "\n"; + logfile << buffer << std::endl; logfile.flush(); } #endif } +/** + * @brief Class used to suspend the logging capabilities of CAPIO, by setting the logging_syscall + * flag to false at instantiation, and restarting the logging at destruction + * + */ class SyscallLoggingSuspender { public: SyscallLoggingSuspender() { logging_syscall = false; } ~SyscallLoggingSuspender() { logging_syscall = true; } }; +/** + * @brief Class that provides logging capabilities to CAPIO. It uses the STL it the component is not + * the intercepting library, otherwise it uses POSIX defined systemcalls. + * + */ class Logger { private: char invoker[256]{0}; @@ -167,12 +193,22 @@ class Logger { #else if (!logfileOpen) { setup_posix_log_filename(); - + current_log_level = 0; // reset after clone log level, so to not inherit it +#if defined(SYS_mkdir) capio_syscall(SYS_mkdir, get_log_dir(), 0755); capio_syscall(SYS_mkdir, get_posix_log_dir(), 0755); capio_syscall(SYS_mkdir, get_host_log_dir(), 0755); - +#elif defined(SYS_mkdirat) + capio_syscall(SYS_mkdirat, AT_FDCWD, get_log_dir(), 0755); + capio_syscall(SYS_mkdirat, AT_FDCWD, get_posix_log_dir(), 0755); + capio_syscall(SYS_mkdirat, AT_FDCWD, get_host_log_dir(), 0755); +#endif +#if defined(SYS_open) logfileFD = capio_syscall(SYS_open, logfile_path, O_CREAT | O_WRONLY | O_TRUNC, 0644); +#elif defined(SYS_openat) + logfileFD = capio_syscall(SYS_openat, AT_FDCWD, logfile_path, + O_CREAT | O_WRONLY | O_TRUNC, 0644); +#endif if (logfileFD == -1) { capio_syscall(SYS_write, fileno(stdout), @@ -182,9 +218,8 @@ class Logger { capio_syscall(SYS_write, fileno(stdout), strerror(errno), strlen(strerror(errno))); capio_syscall(SYS_write, fileno(stdout), "\n", 1); exit(EXIT_FAILURE); - } else { - logfileOpen = true; } + logfileOpen = true; } #endif strncpy(this->invoker, invoker, sizeof(this->invoker)); @@ -192,7 +227,7 @@ class Logger { va_list argp, argpc; - sprintf(format, CAPIO_LOG_PRE_MSG, this->invoker); + sprintf(format, CAPIO_LOG_PRE_MSG, current_time_in_millis(), this->invoker); size_t pre_msg_len = strlen(format); strcpy(format + pre_msg_len, message); @@ -202,8 +237,15 @@ class Logger { #if defined(CAPIO_LOG) && defined(__CAPIO_POSIX) if (current_log_level == 0 && logging_syscall) { - int syscallNumber = va_arg(argp, int); - auto buf1 = reinterpret_cast(capio_syscall( + int syscallNumber; + + if (strcmp(invoker, "hook_clone_child") == 0) { + syscallNumber = SYS_clone; + } else { + syscallNumber = va_arg(argp, int); + } + + auto buf1 = reinterpret_cast(capio_syscall( SYS_mmap, nullptr, 50, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); sprintf(buf1, CAPIO_LOG_POSIX_SYSCALL_START, sys_num_to_string(syscallNumber), syscallNumber); @@ -230,7 +272,7 @@ class Logger { inline ~Logger() { current_log_level--; - sprintf(format, CAPIO_LOG_PRE_MSG, this->invoker); + sprintf(format, CAPIO_LOG_PRE_MSG, current_time_in_millis(), this->invoker); size_t pre_msg_len = strlen(format); strcpy(format + pre_msg_len, "returned"); @@ -246,7 +288,7 @@ class Logger { inline void log(const char *message, ...) { va_list argp, argpc; - sprintf(format, CAPIO_LOG_PRE_MSG, this->invoker); + sprintf(format, CAPIO_LOG_PRE_MSG, current_time_in_millis(), this->invoker); size_t pre_msg_len = strlen(format); strcpy(format + pre_msg_len, message); @@ -281,7 +323,13 @@ class Logger { #define ERR_EXIT(message, ...) \ log.log(message, ##__VA_ARGS__); \ if (!continue_on_error) { \ - exit(EXIT_FAILURE); \ + char tmp_buf[5120]; \ + sprintf(tmp_buf, message, ##__VA_ARGS__); \ + char node_name[HOST_NAME_MAX]{0}; \ + gethostname(node_name, HOST_NAME_MAX); \ + printf("%s %s] %s\n", CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, node_name, tmp_buf); \ + fflush(stdout); \ + throw std::runtime_error(std::string(tmp_buf)); \ } #define LOG(message, ...) log.log(message, ##__VA_ARGS__) #define START_LOG(tid, message, ...) \ @@ -317,9 +365,22 @@ class Logger { #else -#define ERR_EXIT(message, ...) \ - if (!continue_on_error) \ - exit(EXIT_FAILURE) +#ifndef __CAPIO_POSIX +inline bool syscall_no_intercept_flag = false; +#endif + +#define ERR_EXIT(fmt, ...) \ + if (!continue_on_error) { \ + syscall_no_intercept_flag = true; \ + char tmp_buf[5120]; \ + sprintf(tmp_buf, fmt, ##__VA_ARGS__); \ + char node_name[HOST_NAME_MAX]{0}; \ + gethostname(node_name, HOST_NAME_MAX); \ + printf("%s [ %s ] %s\n", CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, node_name, tmp_buf); \ + fflush(stdout); \ + exit(EXIT_FAILURE); \ + } + #define LOG(message, ...) #define START_LOG(tid, message, ...) #define START_SYSCALL_LOGGING() @@ -332,4 +393,4 @@ class Logger { #endif -#endif // CAPIO_COMMON_LOGGER_HPP +#endif // CAPIO_COMMON_LOGGER_HPP \ No newline at end of file diff --git a/src/common/capio/queue.hpp b/capio-common/capio/queue.hpp similarity index 69% rename from src/common/capio/queue.hpp rename to capio-common/capio/queue.hpp index bb90cb82a..73cccb414 100644 --- a/src/common/capio/queue.hpp +++ b/capio-common/capio/queue.hpp @@ -11,32 +11,38 @@ #include "capio/semaphore.hpp" #include "capio/shm.hpp" +/** + * @brief Generic shared memory queue class + * + * @tparam T Type of data that is being transported + * @tparam Mutex Type of semaphore + */ template class Queue { - private: void *_shm; const long int _max_num_elems, _elem_size; // elements size in bytes long int _buff_size; // buffer size in bytes long int *_first_elem = nullptr, *_last_elem = nullptr; const std::string _shm_name, _first_elem_name, _last_elem_name; + bool require_cleanup; Mutex _mutex; NamedSemaphore _sem_num_elems, _sem_num_empty; - inline void _read(T *buff_recv, int num_bytes) { + inline void _read(T *buff_recv, capio_off64_t num_bytes) { _sem_num_elems.lock(); std::lock_guard lg(_mutex); - memcpy(reinterpret_cast(buff_recv), reinterpret_cast(_shm) + *_first_elem, + memcpy(reinterpret_cast(buff_recv), static_cast(_shm) + *_first_elem, num_bytes); *_first_elem = (*_first_elem + _elem_size) % _buff_size; _sem_num_empty.unlock(); } - inline void _write(const T *data, int num_bytes) { + inline void _write(const T *data, unsigned long long int num_bytes) { _sem_num_empty.lock(); std::lock_guard lg(_mutex); - memcpy(reinterpret_cast(_shm) + *_last_elem, reinterpret_cast(data), + memcpy(static_cast(_shm) + *_last_elem, reinterpret_cast(data), num_bytes); *_last_elem = (*_last_elem + _elem_size) % _buff_size; @@ -45,19 +51,22 @@ template class Queue { public: Queue(const std::string &shm_name, const long int max_num_elems, const long int elem_size, - const std::string &workflow_name = get_capio_workflow_name()) + const std::string &workflow_name = get_capio_workflow_name(), bool cleanup = true) : _max_num_elems(max_num_elems), _elem_size(elem_size), _buff_size(_max_num_elems * _elem_size), _shm_name(workflow_name + "_" + shm_name), _first_elem_name(workflow_name + SHM_FIRST_ELEM + shm_name), - _last_elem_name(workflow_name + SHM_LAST_ELEM + shm_name), - _mutex(workflow_name + SHM_MUTEX_PREFIX + shm_name, 1), - _sem_num_elems(workflow_name + SHM_SEM_ELEMS + shm_name, 0), - _sem_num_empty(workflow_name + SHM_SEM_EMPTY + shm_name, max_num_elems) { + _last_elem_name(workflow_name + SHM_LAST_ELEM + shm_name), require_cleanup(cleanup), + _mutex(workflow_name + SHM_MUTEX_PREFIX + shm_name, 1, cleanup), + _sem_num_elems(workflow_name + SHM_SEM_ELEMS + shm_name, 0, cleanup), + _sem_num_empty(workflow_name + SHM_SEM_EMPTY + shm_name, max_num_elems, cleanup) { START_LOG(capio_syscall(SYS_gettid), "call(shm_name=%s, _max_num_elems=%ld, elem_size=%ld, " - "workflow_name=%s)", - shm_name.data(), max_num_elems, elem_size, workflow_name.data()); - + "workflow_name=%s, cleanup=%s)", + shm_name.data(), max_num_elems, elem_size, workflow_name.data(), + cleanup ? "yes" : "no"); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; +#endif _first_elem = (long int *) create_shm(_first_elem_name, sizeof(long int)); _last_elem = (long int *) create_shm(_last_elem_name, sizeof(long int)); _shm = get_shm_if_exist(_shm_name); @@ -66,17 +75,33 @@ template class Queue { *_last_elem = 0; _shm = create_shm(_shm_name, _buff_size); } +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif } Queue(const Queue &) = delete; Queue &operator=(const Queue &) = delete; + ~Queue() { START_LOG(capio_syscall(SYS_gettid), "call(_shm_name=%s, _first_elem_name=%s, _last_elem_name=%s)", _shm_name.c_str(), _first_elem_name.c_str(), _last_elem_name.c_str()); - SHM_DESTROY_CHECK(_shm_name.c_str()); - SHM_DESTROY_CHECK(_first_elem_name.c_str()); - SHM_DESTROY_CHECK(_last_elem_name.c_str()); + if (require_cleanup) { + LOG("Performing cleanup of allocated resources"); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; +#endif + SHM_DESTROY_CHECK(_shm_name.c_str()); + LOG("Removed %s", _shm_name.c_str()); + SHM_DESTROY_CHECK(_first_elem_name.c_str()); + LOG("Removed %s", _first_elem_name.c_str()); + SHM_DESTROY_CHECK(_last_elem_name.c_str()); + LOG("Removed %s", _last_elem_name.c_str()); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif + } } inline T *fetch() { @@ -84,7 +109,7 @@ template class Queue { _sem_num_elems.lock(); - std::lock_guard lg(_mutex); + const std::lock_guard lg(_mutex); T *segment = reinterpret_cast(_shm) + *_first_elem; *_first_elem = (*_first_elem + _elem_size) % _buff_size; @@ -93,10 +118,10 @@ template class Queue { return segment; } - inline auto get_name() { return this->_shm_name; } + inline auto get_name() const { return this->_shm_name; } - inline void read(T *buff_rcv, long int num_bytes) { - START_LOG(capio_syscall(SYS_gettid), "call(buff_rcv=0x%08x, num_bytes=%ld)", buff_rcv, + inline void read(T *buff_rcv, unsigned long long int num_bytes) { + START_LOG(capio_syscall(SYS_gettid), "call(buff_rcv=0x%08x, num_bytes=%llu)", buff_rcv, num_bytes); off64_t n_reads = num_bytes / _elem_size; @@ -106,7 +131,7 @@ template class Queue { _read(buff_rcv + i * _elem_size, _elem_size); } if (r) { - _read(buff_rcv + n_reads * _elem_size, r); + _read(buff_rcv + (n_reads * _elem_size), r); } } @@ -120,7 +145,7 @@ template class Queue { _sem_num_empty.lock(); - std::lock_guard lg(_mutex); + const std::lock_guard lg(_mutex); T *segment = reinterpret_cast(_shm) + *_last_elem; *_last_elem = (*_last_elem + _elem_size) % _buff_size; @@ -129,8 +154,8 @@ template class Queue { return segment; } - inline void write(const T *data, long int num_bytes) { - START_LOG(capio_syscall(SYS_gettid), "call(data=0x%08x, num_bytes=%ld)", data, num_bytes); + inline void write(const T *data, unsigned long long int num_bytes) { + START_LOG(capio_syscall(SYS_gettid), "call(data=0x%08x, num_bytes=%llu)", data, num_bytes); off64_t n_writes = num_bytes / _elem_size; size_t r = num_bytes % _elem_size; @@ -154,4 +179,4 @@ template using CircularBuffer = Queue; // Single Producer Single Consumer queue using SPSCQueue = Queue; -#endif // CAPIO_QUEUE_HPP +#endif // CAPIO_QUEUE_HPP \ No newline at end of file diff --git a/capio-common/capio/requests.hpp b/capio-common/capio/requests.hpp new file mode 100644 index 000000000..25ec7e11d --- /dev/null +++ b/capio-common/capio/requests.hpp @@ -0,0 +1,21 @@ +#ifndef CAPIO_COMMON_REQUESTS_HPP +#define CAPIO_COMMON_REQUESTS_HPP + +constexpr const int CAPIO_REQUEST_CONSENT = 0; +constexpr const int CAPIO_REQUEST_CLOSE = 1; +constexpr const int CAPIO_REQUEST_CREATE = 2; +constexpr const int CAPIO_REQUEST_EXIT_GROUP = 3; +constexpr const int CAPIO_REQUEST_HANDSHAKE = 4; +constexpr const int CAPIO_REQUEST_MKDIR = 5; +constexpr const int CAPIO_REQUEST_OPEN = 6; +constexpr const int CAPIO_REQUEST_READ = 7; +constexpr const int CAPIO_REQUEST_READ_MEM = 8; +constexpr const int CAPIO_REQUEST_RENAME = 9; +constexpr const int CAPIO_REQUEST_WRITE = 10; +constexpr const int CAPIO_REQUEST_WRITE_MEM = 11; +constexpr const int CAPIO_REQUEST_QUERY_MEM_FILE = 12; +constexpr const int CAPIO_REQUEST_POSIX_DIR_COMMITTED = 13; + +constexpr const int CAPIO_NR_REQUESTS = 14; + +#endif // CAPIO_COMMON_REQUESTS_HPP \ No newline at end of file diff --git a/capio-common/capio/response_queue.hpp b/capio-common/capio/response_queue.hpp new file mode 100644 index 000000000..9df2b57b1 --- /dev/null +++ b/capio-common/capio/response_queue.hpp @@ -0,0 +1,71 @@ +#ifndef CAPIO_RESPONSE_QUEUE_HPP +#define CAPIO_RESPONSE_QUEUE_HPP + +#include +#include + +#include + +#include "capio/env.hpp" +#include "capio/logger.hpp" +#include "capio/semaphore.hpp" +#include "capio/shm.hpp" + +/** + * @brief Efficient implementation of class Queue for offset responses + * + * @tparam T Type of data that is being transported + */ +class ResponseQueue { + void *_shm; + const std::string _shm_name; + bool require_cleanup; + NamedSemaphore _shared_mutex; + + public: + explicit ResponseQueue(const std::string &shm_name, bool cleanup = true) + : _shm_name(get_capio_workflow_name() + "_" + shm_name), require_cleanup(cleanup), + _shared_mutex(get_capio_workflow_name() + SHM_SEM_ELEMS + shm_name, 0, cleanup) { + START_LOG(capio_syscall(SYS_gettid), "call(shm_name=%s, workflow_name=%s, cleanup=%s)", + shm_name.data(), get_capio_workflow_name().data(), cleanup ? "yes" : "no"); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; +#endif + + _shm = get_shm_if_exist(_shm_name); + if (_shm == nullptr) { + _shm = create_shm(_shm_name, sizeof(capio_off64_t)); + } +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif + } + + ResponseQueue(const ResponseQueue &) = delete; + ResponseQueue &operator=(const ResponseQueue &) = delete; + + ~ResponseQueue() { + START_LOG(capio_syscall(SYS_gettid), "call(_shm_name=%s)", _shm_name.c_str()); + if (require_cleanup) { + LOG("Performing cleanup of allocated resources"); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; +#endif + SHM_DESTROY_CHECK(_shm_name.c_str()); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif + } + } + + auto read() { + _shared_mutex.lock(); + return *static_cast(_shm); + } + + void write(const capio_off64_t data) { + *static_cast(_shm) = data; + _shared_mutex.unlock(); + } +}; +#endif // CAPIO_RESPONSE_QUEUE_HPP \ No newline at end of file diff --git a/src/common/capio/semaphore.hpp b/capio-common/capio/semaphore.hpp similarity index 50% rename from src/common/capio/semaphore.hpp rename to capio-common/capio/semaphore.hpp index d5f1c7c9e..a97ce3b03 100644 --- a/src/common/capio/semaphore.hpp +++ b/capio-common/capio/semaphore.hpp @@ -7,33 +7,51 @@ #include "capio/logger.hpp" +/** + * @brief Class that implements the C++20 semaphore interface but provides no locking whatsoever + * + */ class NoLock { public: - NoLock(const std::string &name, unsigned int init_value) { + NoLock(const std::string &name, unsigned int init_value, bool cleanup) { START_LOG(capio_syscall(SYS_gettid), "call(name=%s, initial_value=%d)", name.c_str(), init_value); } NoLock(const NoLock &) = delete; NoLock &operator=(const NoLock &) = delete; - ~NoLock() = default; + + ~NoLock() { START_LOG(capio_syscall(SYS_gettid), "call()"); }; static inline void lock() { START_LOG(capio_syscall(SYS_gettid), "call()"); }; static inline void unlock() { START_LOG(capio_syscall(SYS_gettid), "call()"); }; }; +/** + * @brief Class that provides the C++20 interface of std::semaphore but uses named semaphores on + * shared memory + * + */ class NamedSemaphore { private: const std::string _name; sem_t *_sem; + bool _require_cleanup; public: - NamedSemaphore(std::string name, unsigned int init_value) : _name(std::move(name)) { - START_LOG(capio_syscall(SYS_gettid), " call(name=%s, init_value=%d)", _name.c_str(), - init_value); - + NamedSemaphore(std::string name, unsigned int init_value, bool cleanup = true) + : _name(std::move(name)), _require_cleanup(cleanup) { + START_LOG(capio_syscall(SYS_gettid), " call(name=%s, init_value=%d, cleanup=%s)", + _name.c_str(), init_value, _require_cleanup ? "true" : "false"); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; +#endif _sem = sem_open(_name.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, init_value); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif + if (_sem == SEM_FAILED) { ERR_EXIT(" sem_open %s failed", _name.c_str()); } @@ -41,23 +59,42 @@ class NamedSemaphore { NamedSemaphore(const NamedSemaphore &) = delete; NamedSemaphore &operator=(const NamedSemaphore &) = delete; + ~NamedSemaphore() { START_LOG(capio_syscall(SYS_gettid), "call()"); - if (sem_destroy(_sem) != 0) { - ERR_EXIT(" destruction of semaphore %s failed", _name.c_str()); - } - LOG(" Destroyed shared semaphore %s", _name.c_str()); - if (sem_unlink(_name.c_str()) != 0) { - ERR_EXIT(" destruction of semaphore %s failed", _name.c_str()); + if (_require_cleanup) { + LOG("Performing cleanup of shared semaphore!"); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; +#endif + if (sem_destroy(_sem) != 0) { + if (errno != ENOENT) { + ERR_EXIT(" destruction of semaphore %s failed: %s", _name.c_str(), + strerror(errno)); + } else { + LOG("Warn: no shuch file for sem %s", _name.c_str()); + } + } + LOG(" Destroyed shared semaphore %s", _name.c_str()); + if (sem_unlink(_name.c_str()) != 0) { + if (errno != ENOENT) { + ERR_EXIT(" unlink of semaphore %s failed", _name.c_str()); + } else { + LOG("Warn: no shuch file for sem %s", _name.c_str()); + } + } + LOG(" Unlinked shared semaphore %s", _name.c_str()); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif } - LOG(" Unlinked shared semaphore %s", _name.c_str()); } inline void lock() { START_LOG(capio_syscall(SYS_gettid), "call(name=%s)", _name.c_str()); if (sem_wait(_sem) == -1) { - ERR_EXIT(" unable to acquire %s", _name.c_str()); + ERR_EXIT("Unable to acquire %s", _name.c_str()); } } @@ -70,12 +107,17 @@ class NamedSemaphore { } }; +/** + * @brief C++20 backport of std::semaphore + * + */ class Semaphore { private: sem_t _sem{}; + bool _require_cleanup; public: - explicit Semaphore(unsigned int init_value) { + explicit Semaphore(unsigned int init_value, bool cleanup = true) { START_LOG(capio_syscall(SYS_gettid), "call(init_value=%d)", init_value); if (sem_init(&_sem, 0, init_value) != 0) { @@ -85,10 +127,19 @@ class Semaphore { Semaphore(const Semaphore &) = delete; Semaphore &operator=(const Semaphore &) = delete; + ~Semaphore() { START_LOG(capio_syscall(SYS_gettid), "call()"); - if (sem_destroy(&_sem) != 0) { - ERR_EXIT("destruction of unnamed semaphore failed"); + if (_require_cleanup) { +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; +#endif + if (sem_destroy(&_sem) != 0) { + ERR_EXIT("destruction of unnamed semaphore failed"); + } +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif } } diff --git a/src/common/capio/shm.hpp b/capio-common/capio/shm.hpp similarity index 50% rename from src/common/capio/shm.hpp rename to capio-common/capio/shm.hpp index 7b39d5427..b18097479 100644 --- a/src/common/capio/shm.hpp +++ b/capio-common/capio/shm.hpp @@ -15,28 +15,34 @@ #define SHM_DESTROY_CHECK(source_name) \ if (shm_unlink(source_name) == -1) { \ - ERR_EXIT("Unable to destroy shared mem: ", source_name); \ - }; + if (errno != ENOENT) { \ + ERR_EXIT("Unable to destroy shared mem: %s. Error is %s", source_name, \ + strerror(errno)); \ + } else { \ + LOG("Warn: shm segment %s is not present on FS", source_name); \ + } \ + } #define SHM_CREATE_CHECK(condition, source) \ if (condition) { \ - ERR_EXIT("Unable to open shm: %s", source); \ + ERR_EXIT("Unable to open shm: %s: %s", source, strerror(errno)); \ }; #else #define SHM_DESTROY_CHECK(source_name) \ if (shm_unlink(source_name) == -1) { \ - std::cout << CAPIO_LOG_SERVER_CLI_LEVEL_WARNING << "Unable to destroy shared mem: '" \ - << source_name << "' (" << strerror(errno) << ")" << std::endl; \ + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, "Unable to destroy shared mem: '" + \ + std::string(source_name) + "' (" + \ + strerror(errno) + ")"); \ }; #define SHM_CREATE_CHECK(condition, source) \ if (condition) { \ - LOG("error while creating %s", source); \ - std::cout << CAPIO_SERVER_CLI_LOG_SERVER_ERROR << "Unable to create shm: " << source \ - << std::endl; \ - ERR_EXIT("Unable to open shm: %s", source); \ + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, "Unable to create shared mem: '" + \ + std::string(source) + "' (" + \ + strerror(errno) + ")"); \ + ERR_EXIT("Unable to open shm %s: %s", source, strerror(errno)); \ }; #endif @@ -46,9 +52,11 @@ class CapioShmCanary { std::string _canary_name; public: - explicit CapioShmCanary(std::string capio_workflow_name) : _canary_name(capio_workflow_name) { + explicit CapioShmCanary(std::string capio_workflow_name = CAPIO_DEFAULT_WORKFLOW_NAME) + : _canary_name(get_capio_workflow_name()) { START_LOG(capio_syscall(SYS_gettid), "call(capio_workflow_name: %s)", _canary_name.data()); if (_canary_name.empty()) { + LOG("Empty _canary_name"); _canary_name = get_capio_workflow_name(); } _shm_id = shm_open(_canary_name.data(), O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); @@ -66,18 +74,28 @@ class CapioShmCanary { ~CapioShmCanary() { START_LOG(capio_syscall(SYS_gettid), "call()"); -#ifndef __CAPIO_POSIx - std::cout << CAPIO_LOG_SERVER_CLI_LEVEL_WARNING << "Removing shared memory canary flag" - << std::endl; +#ifndef __CAPIO_POSIX + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "Removing shared memory canary flag"); +#endif + +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; #endif close(_shm_id); SHM_DESTROY_CHECK(_canary_name.c_str()); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif + +#ifndef __CAPIO_POSIX + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "Shutdown completed"); +#endif } }; -CapioShmCanary *shm_canary; +inline CapioShmCanary *shm_canary; -void *create_shm(const std::string &shm_name, const long int size) { +inline void *create_shm(const std::string &shm_name, const long int size) { START_LOG(capio_syscall(SYS_gettid), "call(shm_name=%s, size=%ld)", shm_name.c_str(), size); // if we are not creating a new object, mode is equals to 0 @@ -93,30 +111,55 @@ void *create_shm(const std::string &shm_name, const long int size) { ERR_EXIT("mmap create_shm %s", shm_name.c_str()); } if (close(fd) == -1) { - ERR_EXIT("close"); } return p; } -void *get_shm(const std::string &shm_name) { +inline auto get_shm_size(int shm_fd, const char *shm_name) { + START_LOG(capio_syscall(SYS_gettid), "call(fd=%ld)", shm_fd); + struct stat sb = {0}; + /* Open existing object */ + /* Use shared memory object size as length argument for mmap() + and as number of bytes to write() */ + if (fstat(shm_fd, &sb) == -1) { + ERR_EXIT("fstat %s", shm_name); + } + + if (sb.st_size <= 0) { + LOG("WARN: size of stat is %ld. Retry once.", sb.st_size); + if (fstat(shm_fd, &sb) == -1) { + ERR_EXIT("fstat %s", shm_name); + } + if (sb.st_size <= 0) { + LOG("WARN: retry no. 2 gave a size of %ld", sb.st_size); + ERR_EXIT("FATAL: unable to obtain size of shm object %s after two tries...", shm_name); + } + } + + LOG("Size of shm object %s : %ld", shm_name, sb.st_size); + return sb.st_size; +} + +inline void *get_shm(const std::string &shm_name) { START_LOG(capio_syscall(SYS_gettid), "call(shm_name=%s)", shm_name.c_str()); // if we are not creating a new object, mode is equals to 0 int fd = shm_open(shm_name.c_str(), O_RDWR, 0); // to be closed - struct stat sb {}; if (fd == -1) { ERR_EXIT("get_shm shm_open %s", shm_name.c_str()); } - /* Open existing object */ - /* Use shared memory object size as length argument for mmap() - and as number of bytes to write() */ - if (fstat(fd, &sb) == -1) { - ERR_EXIT("fstat %s", shm_name.c_str()); - } - void *p = mmap(nullptr, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + auto size = get_shm_size(fd, shm_name.c_str()); + + void *p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { - ERR_EXIT("mmap get_shm %s", shm_name.c_str()); + LOG("ERROR MMAP arg dump:"); + LOG("mmap-size: %ld", size); + LOG("mmap-prot: %ld", PROT_READ | PROT_WRITE); + LOG("mmap-flags: %ld", MAP_SHARED); + LOG("mmap-fd: %ld", fd); + ERR_EXIT("ERROR: mmap failed at get_shm(%s): %s", shm_name.c_str(), strerror(errno)); } if (close(fd) == -1) { ERR_EXIT("close"); @@ -124,27 +167,30 @@ void *get_shm(const std::string &shm_name) { return p; } -void *get_shm_if_exist(const std::string &shm_name) { +inline void *get_shm_if_exist(const std::string &shm_name) { START_LOG(capio_syscall(SYS_gettid), "call(shm_name=%s)", shm_name.c_str()); // if we are not creating a new object, mode is equals to 0 int fd = shm_open(shm_name.c_str(), O_RDWR, 0); // to be closed - struct stat sb {}; + if (fd == -1) { if (errno == ENOENT) { return nullptr; } - ERR_EXIT("get_shm shm_open %s", shm_name.c_str()); + ERR_EXIT("ERROR: unable to open shared memory %s: %s", shm_name.c_str(), strerror(errno)); } - /* Open existing object */ - /* Use shared memory object size as length argument for mmap() - and as number of bytes to write() */ - if (fstat(fd, &sb) == -1) { - ERR_EXIT("fstat %s", shm_name.c_str()); - } - void *p = mmap(nullptr, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + auto size = get_shm_size(fd, shm_name.c_str()); + + void *p = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { - ERR_EXIT("mmap get_shm %s", shm_name.c_str()); + LOG("ERROR MMAP arg dump:"); + LOG("mmap-size: %ld", size); + LOG("mmap-prot: %ld", PROT_READ | PROT_WRITE); + LOG("mmap-flags: %ld", MAP_SHARED); + LOG("mmap-fd: %ld", fd); + ERR_EXIT("ERROR: mmap failed at get_shm_if_exist(%s): %s", shm_name.c_str(), + strerror(errno)); } if (close(fd) == -1) { ERR_EXIT("close"); @@ -152,4 +198,4 @@ void *get_shm_if_exist(const std::string &shm_name) { return p; } -#endif // CAPIO_COMMON_SHM_HPP +#endif // CAPIO_COMMON_SHM_HPP \ No newline at end of file diff --git a/src/common/capio/syscall.hpp b/capio-common/capio/syscall.hpp similarity index 95% rename from src/common/capio/syscall.hpp rename to capio-common/capio/syscall.hpp index a4c182dc6..d282ccad4 100644 --- a/src/common/capio/syscall.hpp +++ b/capio-common/capio/syscall.hpp @@ -30,4 +30,4 @@ inline char *syscall_no_intercept_realpath(const char *path, char *resolved) { #define gettid() capio_syscall(SYS_gettid) #endif -#endif // CAPIO_COMMON_SYSCALL_HPP +#endif // CAPIO_COMMON_SYSCALL_HPP \ No newline at end of file diff --git a/src/posix/CMakeLists.txt b/capio-posix/CMakeLists.txt similarity index 100% rename from src/posix/CMakeLists.txt rename to capio-posix/CMakeLists.txt diff --git a/src/posix/handlers.hpp b/capio-posix/handlers.hpp similarity index 72% rename from src/posix/handlers.hpp rename to capio-posix/handlers.hpp index 3211be803..4fe0f53eb 100644 --- a/src/posix/handlers.hpp +++ b/capio-posix/handlers.hpp @@ -1,19 +1,23 @@ #ifndef CAPIO_POSIX_HANDLERS_HPP #define CAPIO_POSIX_HANDLERS_HPP +/********************/ +// SYSCALL HANDLERS // +/********************/ + #include "handlers/access.hpp" #include "handlers/chdir.hpp" #include "handlers/close.hpp" +#include "handlers/copy_file_range.hpp" #include "handlers/dup.hpp" #include "handlers/execve.hpp" -#include "handlers/exit_group.hpp" +#include "handlers/exit.hpp" #include "handlers/fchmod.hpp" #include "handlers/fchown.hpp" #include "handlers/fcntl.hpp" #include "handlers/fgetxattr.hpp" #include "handlers/fork.hpp" #include "handlers/getcwd.hpp" -#include "handlers/getdents.hpp" #include "handlers/ioctl.hpp" #include "handlers/lseek.hpp" #include "handlers/mkdir.hpp" @@ -26,4 +30,10 @@ #include "handlers/unlink.hpp" #include "handlers/write.hpp" -#endif // CAPIO_POSIX_HANDLERS_HPP +/********************/ +// POSIX HANDLERS // +/********************/ + +#include "handlers/posix_readdir.hpp" + +#endif // CAPIO_POSIX_HANDLERS_HPP \ No newline at end of file diff --git a/src/posix/handlers/access.hpp b/capio-posix/handlers/access.hpp similarity index 58% rename from src/posix/handlers/access.hpp rename to capio-posix/handlers/access.hpp index 0f56b2f7a..33a312f4b 100644 --- a/src/posix/handlers/access.hpp +++ b/capio-posix/handlers/access.hpp @@ -1,21 +1,42 @@ #ifndef CAPIO_POSIX_HANDLERS_ACCESS_HPP #define CAPIO_POSIX_HANDLERS_ACCESS_HPP -#if defined(SYS_access) || defined(SYS_faccessat) || defined(SYS_faccessat2) - #include "utils/common.hpp" #include "utils/filesystem.hpp" -inline off64_t capio_faccessat(int dirfd, const std::string_view &pathname, mode_t mode, int flags, - long tid) { - START_LOG(tid, "call(dirfd=%d, pathname=%s, mode=%o, flags=%X)", dirfd, pathname.data(), mode, - flags); - - if (is_forbidden_path(pathname)) { +#if defined(SYS_access) +int access_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + const std::string_view pathname(reinterpret_cast(arg0)); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + START_LOG(tid, "call()"); + if (!is_capio_path(pathname)) { LOG("Path %s is forbidden: skip", pathname.data()); return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } + std::filesystem::path path(pathname); + if (path.is_relative()) { + path = capio_posix_realpath(pathname); + } + + consent_request_cache_fs->consent_request(path, tid, __FUNCTION__); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; +} +#endif // SYS_access + +#if defined(SYS_faccessat) || defined(SYS_faccessat2) +int faccessat_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, + long *result) { + auto dirfd = static_cast(arg0); + const std::string_view pathname(reinterpret_cast(arg1)); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + START_LOG(tid, "call()"); + + if (!is_capio_path(pathname)) { + LOG("Path %s is forbidden or is not a capio path: skip", pathname.data()); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + std::filesystem::path path(pathname); if (path.is_relative()) { if (dirfd == AT_FDCWD) { @@ -34,35 +55,12 @@ inline off64_t capio_faccessat(int dirfd, const std::string_view &pathname, mode return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } path = (dir_path / path).lexically_normal(); - return is_capio_path(path) ? access_request(path, tid) : -2; } } - if (is_capio_path(path)) { - return access_request(path, tid); - } else { - return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; - } -} - -int access_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { - const std::string_view pathname(reinterpret_cast(arg0)); - auto mode = static_cast(arg1); - long tid = syscall_no_intercept(SYS_gettid); - - return posix_return_value(capio_faccessat(AT_FDCWD, pathname, mode, 0, tid), result); -} - -int faccessat_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, - long *result) { - auto dirfd = static_cast(arg0); - const std::string_view pathname(reinterpret_cast(arg1)); - auto mode = static_cast(arg2); - auto flags = static_cast(arg3); - long tid = syscall_no_intercept(SYS_gettid); - - return posix_return_value(capio_faccessat(dirfd, pathname, mode, flags, tid), result); + consent_request_cache_fs->consent_request(path, tid, __FUNCTION__); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } +#endif // SYS_faccessat -#endif // SYS_access || SYS_faccessat || SYS_faccessat2 -#endif // CAPIO_POSIX_HANDLERS_ACCESS_HPP +#endif // CAPIO_POSIX_HANDLERS_ACCESS_HPP \ No newline at end of file diff --git a/src/posix/handlers/chdir.hpp b/capio-posix/handlers/chdir.hpp similarity index 56% rename from src/posix/handlers/chdir.hpp rename to capio-posix/handlers/chdir.hpp index 179beb196..52f542ed7 100644 --- a/src/posix/handlers/chdir.hpp +++ b/capio-posix/handlers/chdir.hpp @@ -1,45 +1,35 @@ #ifndef CAPIO_POSIX_HANDLERS_CHDIR_HPP #define CAPIO_POSIX_HANDLERS_CHDIR_HPP +#include "utils/requests.hpp" #if defined(SYS_chdir) #include "utils/filesystem.hpp" -/* - * chdir could be done to a CAPIO dir that is not present in the filesystem. - * For this reason if chdir is done to a CAPIO directory we don't give control - * to the kernel. - */ - int chdir_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { const std::string_view pathname(reinterpret_cast(arg0)); - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); START_LOG(tid, "call(path=%s)", pathname.data()); - if (is_forbidden_path(pathname)) { + syscall_no_intercept_flag = true; + if (!is_capio_path(pathname)) { LOG("Path %s is forbidden: skip", pathname.data()); + syscall_no_intercept_flag = false; return CAPIO_POSIX_SYSCALL_SKIP; } std::filesystem::path path(pathname); if (path.is_relative()) { path = capio_posix_realpath(path); - if (path.empty()) { - *result = -errno; - return CAPIO_POSIX_SYSCALL_SUCCESS; - } } - if (is_capio_path(path)) { - set_current_dir(path); - errno = 0; - return CAPIO_POSIX_SYSCALL_SUCCESS; - } + consent_request_cache_fs->consent_request(path, tid, __FUNCTION__); + syscall_no_intercept_flag = false; // if not a capio path, then control is given to kernel return CAPIO_POSIX_SYSCALL_SKIP; } #endif // SYS_chdir -#endif // CAPIO_POSIX_HANDLERS_CHDIR_HPP +#endif // CAPIO_POSIX_HANDLERS_CHDIR_HPP \ No newline at end of file diff --git a/src/posix/handlers/close.hpp b/capio-posix/handlers/close.hpp similarity index 68% rename from src/posix/handlers/close.hpp rename to capio-posix/handlers/close.hpp index 6889002a6..d04608ecc 100644 --- a/src/posix/handlers/close.hpp +++ b/capio-posix/handlers/close.hpp @@ -7,17 +7,17 @@ int close_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { int fd = static_cast(arg0); - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); START_LOG(tid, "call(fd=%ld)", fd); if (exists_capio_fd(fd)) { - get_write_cache(tid).flush(); - close_request(fd, tid); + close_request(get_capio_fd_path(fd), tid); + write_request_cache_mem->flush(); delete_capio_fd(fd); - *result = 0; } + return CAPIO_POSIX_SYSCALL_SKIP; } #endif // SYS_close -#endif // CAPIO_POSIX_HANDLERS_CLOSE_HPP +#endif // CAPIO_POSIX_HANDLERS_CLOSE_HPP \ No newline at end of file diff --git a/capio-posix/handlers/copy_file_range.hpp b/capio-posix/handlers/copy_file_range.hpp new file mode 100644 index 000000000..01e4d4fce --- /dev/null +++ b/capio-posix/handlers/copy_file_range.hpp @@ -0,0 +1,24 @@ +#ifndef CAPIO_COPY_FILE_RANGE_HPP +#define CAPIO_COPY_FILE_RANGE_HPP + +int copy_file_range_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, + long *result) { + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + auto fd_in = static_cast(arg0); + auto off_in = static_cast(arg1); + + // auto fd_out = static_cast(arg2); + // auto off_out = static_cast(arg3); + + START_LOG(tid, "call()"); + + // TODO: support in memory read / write + if (exists_capio_fd(fd_in)) { + auto path = get_capio_fd_path(fd_in); + LOG("Handling copy for source file: %s", path.c_str()); + read_request_cache_fs->read_request(path, off_in, tid, fd_in); + } + + return CAPIO_POSIX_SYSCALL_SKIP; +} +#endif // CAPIO_COPY_FILE_RANGE_HPP \ No newline at end of file diff --git a/src/posix/handlers/dup.hpp b/capio-posix/handlers/dup.hpp similarity index 67% rename from src/posix/handlers/dup.hpp rename to capio-posix/handlers/dup.hpp index fba955675..17f1a371d 100644 --- a/src/posix/handlers/dup.hpp +++ b/capio-posix/handlers/dup.hpp @@ -1,59 +1,57 @@ #ifndef CAPIO_POSIX_HANDLERS_DUP_HPP #define CAPIO_POSIX_HANDLERS_DUP_HPP -#if defined(SYS_dup) || defined(SYS_dup2) || defined(SYS_dup3) - #include "capio/syscall.hpp" #include "utils/requests.hpp" +#if defined(SYS_dup) int dup_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); int fd = static_cast(arg0); START_LOG(tid, "call(fd=%d)", fd); if (exists_capio_fd(fd)) { - int res = open("/dev/null", O_WRONLY); + auto res = static_cast(syscall_no_intercept(SYS_dup, fd)); if (res == -1) { *result = -errno; return CAPIO_POSIX_SYSCALL_SUCCESS; } - dup_request(fd, res, tid); dup_capio_fd(tid, fd, res, false); - *result = res; - return CAPIO_POSIX_SYSCALL_SUCCESS; + return posix_return_value(res, result); } return CAPIO_POSIX_SYSCALL_SKIP; } +#endif // SYS_dup +#if defined(SYS_dup2) int dup2_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { - long tid = syscall_no_intercept(SYS_gettid); - int fd = static_cast(arg0); - int fd2 = static_cast(arg1); - - // int res = capio_dup2(fd, fd2, tid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + auto fd = static_cast(arg0); + auto fd2 = static_cast(arg1); START_LOG(tid, "call(fd=%d, fd2=%d)", fd, fd2); if (exists_capio_fd(fd)) { - int res = static_cast(syscall_no_intercept(SYS_dup2, fd, fd2)); + auto res = static_cast(syscall_no_intercept(SYS_dup2, fd, fd2)); if (res == -1) { *result = -errno; return CAPIO_POSIX_SYSCALL_SUCCESS; } if (fd != res) { - dup_request(fd, res, tid); dup_capio_fd(tid, fd, res, false); } - *result = res; - return CAPIO_POSIX_SYSCALL_SUCCESS; + + return posix_return_value(res, result); } return CAPIO_POSIX_SYSCALL_SKIP; } +#endif // SYS_dup2 +#if defined(SYS_dup3) int dup3_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); int fd = static_cast(arg0); int fd2 = static_cast(arg1); int flags = static_cast(arg2); @@ -74,14 +72,12 @@ int dup3_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg return CAPIO_POSIX_SYSCALL_SUCCESS; } bool is_cloexec = (flags & O_CLOEXEC) == O_CLOEXEC; - dup_request(fd, res, tid); dup_capio_fd(tid, fd, res, is_cloexec); - *result = res; - return CAPIO_POSIX_SYSCALL_SUCCESS; + return posix_return_value(res, result); } return CAPIO_POSIX_SYSCALL_SKIP; } +#endif // SYS_dup3 -#endif // SYS_dup || SYS_dup2 || SYS_dup3 -#endif // CAPIO_POSIX_HANDLERS_DUP_HPP +#endif // CAPIO_POSIX_HANDLERS_DUP_HPP \ No newline at end of file diff --git a/src/posix/handlers/execve.hpp b/capio-posix/handlers/execve.hpp similarity index 76% rename from src/posix/handlers/execve.hpp rename to capio-posix/handlers/execve.hpp index 865003365..47c526a70 100644 --- a/src/posix/handlers/execve.hpp +++ b/capio-posix/handlers/execve.hpp @@ -6,7 +6,7 @@ #include "utils/snapshot.hpp" int execve_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); START_LOG(tid, "call()"); create_snapshot(tid); @@ -15,4 +15,4 @@ int execve_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long a } #endif // SYS_execve -#endif // CAPIO_POSIX_HANDLERS_EXECVE_HPP +#endif // CAPIO_POSIX_HANDLERS_EXECVE_HPP \ No newline at end of file diff --git a/src/posix/handlers/exit_group.hpp b/capio-posix/handlers/exit.hpp similarity index 58% rename from src/posix/handlers/exit_group.hpp rename to capio-posix/handlers/exit.hpp index ff609f8c5..1c81f8cef 100644 --- a/src/posix/handlers/exit_group.hpp +++ b/capio-posix/handlers/exit.hpp @@ -15,19 +15,31 @@ */ int exit_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); START_LOG(tid, "call()"); + syscall_no_intercept_flag = true; + LOG("syscall_no_intercept_flag = true"); + + delete_caches(); + LOG("Removed caches"); + if (is_capio_tid(tid)) { LOG("Thread %d is a CAPIO thread: clean up", tid); - get_read_cache(tid).flush(); - get_write_cache(tid).flush(); exit_group_request(tid); remove_capio_tid(tid); } + if (const auto itm = bufs_response->find(tid); itm != bufs_response->end()) { + delete itm->second; + bufs_response->erase(tid); + LOG("Removed response buffer"); + } + syscall_no_intercept_flag = false; + LOG("syscall_no_intercept_flag = false"); + return CAPIO_POSIX_SYSCALL_SKIP; } #endif // SYS_exit || SYS_exit_group -#endif // CAPIO_POSIX_HANDLERS_EXIT_GROUP_HPP +#endif // CAPIO_POSIX_HANDLERS_EXIT_GROUP_HPP \ No newline at end of file diff --git a/src/posix/handlers/fchmod.hpp b/capio-posix/handlers/fchmod.hpp similarity index 52% rename from src/posix/handlers/fchmod.hpp rename to capio-posix/handlers/fchmod.hpp index f8f27f8fd..cc9e926f5 100644 --- a/src/posix/handlers/fchmod.hpp +++ b/capio-posix/handlers/fchmod.hpp @@ -1,10 +1,11 @@ #ifndef CAPIO_POSIX_HANDLERS_FCHMOD_HPP #define CAPIO_POSIX_HANDLERS_FCHMOD_HPP -#if defined(SYS_chmod) +#if defined(SYS_chmod) || defined(SYS_fchmod) int fchmod_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { - int fd = static_cast(arg0); + int fd = static_cast(arg0); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); START_LOG(syscall_no_intercept(SYS_gettid), "call(fd=%d)", fd); if (!exists_capio_fd(fd)) { @@ -12,13 +13,10 @@ int fchmod_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long a return CAPIO_POSIX_SYSCALL_SKIP; } - // Upon success fchmod shall return 0 - // Since capio does not handle permission, we will be - // Upon the assumption that all the operations occurs with success - *result = 0; - LOG("File is present in capio. Ignoring fchmod operation"); - return CAPIO_POSIX_SYSCALL_SUCCESS; + consent_request_cache_fs->consent_request(get_capio_fd_path(fd), tid, __FUNCTION__); + + return CAPIO_POSIX_SYSCALL_SKIP; } #endif // SYS_chmod -#endif // CAPIO_POSIX_HANDLERS_FCHMOD_HPP +#endif // CAPIO_POSIX_HANDLERS_FCHMOD_HPP \ No newline at end of file diff --git a/capio-posix/handlers/fchown.hpp b/capio-posix/handlers/fchown.hpp new file mode 100644 index 000000000..b40398c0b --- /dev/null +++ b/capio-posix/handlers/fchown.hpp @@ -0,0 +1,23 @@ +#ifndef CAPIO_POSIX_HANDLERS_FCHOWN_HPP +#define CAPIO_POSIX_HANDLERS_FCHOWN_HPP +#include "utils/requests.hpp" + +#if defined(SYS_chown) || defined(SYS_fchown) + +int fchown_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + int fd = static_cast(arg0); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + START_LOG(syscall_no_intercept(SYS_gettid), "call(fd=%d)", fd); + + if (!exists_capio_fd(fd)) { + LOG("Syscall refers to file not handled by capio. Skipping it!"); + return CAPIO_POSIX_SYSCALL_SKIP; + } + + consent_request_cache_fs->consent_request(get_capio_fd_path(fd), tid, __FUNCTION__); + + return CAPIO_POSIX_SYSCALL_SKIP; +} + +#endif // SYS_chown +#endif // CAPIO_POSIX_HANDLERS_FCHOWN_HPP \ No newline at end of file diff --git a/capio-posix/handlers/fcntl.hpp b/capio-posix/handlers/fcntl.hpp new file mode 100644 index 000000000..2780f8856 --- /dev/null +++ b/capio-posix/handlers/fcntl.hpp @@ -0,0 +1,25 @@ +#ifndef CAPIO_POSIX_HANDLERS_FCNTL_HPP +#define CAPIO_POSIX_HANDLERS_FCNTL_HPP + +#if defined(SYS_fcntl) + +#include "utils/requests.hpp" + +#if defined(SYS_fcntl) || defined(SYS_fcntl64) +int fcntl_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + auto fd = static_cast(arg0); + auto cmd = static_cast(arg1); + auto arg = static_cast(arg2); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + + START_LOG(tid, "call(fd=%d, cmd=%d, arg=%d)", fd, cmd, arg); + + if (exists_capio_fd(fd)) { + consent_request_cache_fs->consent_request(get_capio_fd_path(fd), tid, __FUNCTION__); + } + return CAPIO_POSIX_SYSCALL_SKIP; +} +#endif + +#endif // SYS_fcntl +#endif // CAPIO_POSIX_HANDLERS_FCNTL_HPP \ No newline at end of file diff --git a/src/posix/handlers/fgetxattr.hpp b/capio-posix/handlers/fgetxattr.hpp similarity index 60% rename from src/posix/handlers/fgetxattr.hpp rename to capio-posix/handlers/fgetxattr.hpp index 1e4ba4acd..3554d799e 100644 --- a/src/posix/handlers/fgetxattr.hpp +++ b/capio-posix/handlers/fgetxattr.hpp @@ -8,21 +8,15 @@ int fgetxattr_handler(long arg0, long arg1, long arg2, long arg3, long arg4, lon std::string name(reinterpret_cast(arg1)); auto *value = reinterpret_cast(arg2); auto size = static_cast(arg3); - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); auto fd = static_cast(arg0); START_LOG(tid, "call(name=%s, value=0x%08x, size=%ld)", name.c_str(), value, size); if (exists_capio_fd(fd)) { - if (std::equal(name.begin(), name.end(), "system.posix_acl_access")) { - errno = ENODATA; - *result = -errno; - return CAPIO_POSIX_SYSCALL_SUCCESS; - } else { - ERR_EXIT("fgetxattr with name %s is not yet supported in CAPIO", name.c_str()); - } + consent_request_cache_fs->consent_request(get_capio_fd_path(fd), tid, __FUNCTION__); } return CAPIO_POSIX_SYSCALL_SKIP; } #endif // SYS_fgetxattr -#endif // CAPIO_POSIX_HANDLERS_FGETXATTR_HPP +#endif // CAPIO_POSIX_HANDLERS_FGETXATTR_HPP \ No newline at end of file diff --git a/src/posix/handlers/fork.hpp b/capio-posix/handlers/fork.hpp similarity index 65% rename from src/posix/handlers/fork.hpp rename to capio-posix/handlers/fork.hpp index f4067186c..e707c0994 100644 --- a/src/posix/handlers/fork.hpp +++ b/capio-posix/handlers/fork.hpp @@ -12,17 +12,16 @@ int fork_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg START_LOG(parent_tid, "call(pid=%ld)", pid); - if (pid == 0) { // child - auto child_tid = static_cast(syscall_no_intercept(SYS_gettid)); + if (pid == 0) { + // child + const auto child_tid = static_cast(syscall_no_intercept(SYS_gettid)); init_process(child_tid); - clone_request(parent_tid, child_tid); *result = 0; - } else { - *result = pid; + return posix_return_value(0, result); } - return CAPIO_POSIX_SYSCALL_SUCCESS; + return posix_return_value(pid, result); } #endif // SYS_fork -#endif // CAPIO_POSIX_HANDLERS_FORK_HPP +#endif // CAPIO_POSIX_HANDLERS_FORK_HPP \ No newline at end of file diff --git a/capio-posix/handlers/getcwd.hpp b/capio-posix/handlers/getcwd.hpp new file mode 100644 index 000000000..2431d73ed --- /dev/null +++ b/capio-posix/handlers/getcwd.hpp @@ -0,0 +1,16 @@ +#ifndef CAPIO_POSIX_HANDLERS_GETCWD_HPP +#define CAPIO_POSIX_HANDLERS_GETCWD_HPP + +#if defined(SYS_getcwd) + +int getcwd_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + auto buf = reinterpret_cast(arg0); + auto size = static_cast(arg1); + + START_LOG(syscall_no_intercept(SYS_gettid), "call(buf=0x%08x, size=%ld)", buf, size); + + return CAPIO_POSIX_SYSCALL_SKIP; +} + +#endif // SYS_getcwd +#endif // CAPIO_POSIX_HANDLERS_GETCWD_HPP \ No newline at end of file diff --git a/src/posix/handlers/ioctl.hpp b/capio-posix/handlers/ioctl.hpp similarity index 69% rename from src/posix/handlers/ioctl.hpp rename to capio-posix/handlers/ioctl.hpp index 9bd6b923e..86d192f7d 100644 --- a/src/posix/handlers/ioctl.hpp +++ b/capio-posix/handlers/ioctl.hpp @@ -6,16 +6,14 @@ int ioctl_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { auto fd = static_cast(arg0); auto request = static_cast(arg1); - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); START_LOG(tid, "call(fd=%d, request=%ld)", fd, request, tid); if (exists_capio_fd(fd)) { - errno = ENOTTY; - *result = -errno; - return CAPIO_POSIX_SYSCALL_SUCCESS; + consent_request_cache_fs->consent_request(get_capio_fd_path(fd), tid, __FUNCTION__); } return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } #endif // SYS_ioctl -#endif // CAPIO_POSIX_HANDLERS_IOCTL_HPP +#endif // CAPIO_POSIX_HANDLERS_IOCTL_HPP \ No newline at end of file diff --git a/capio-posix/handlers/lseek.hpp b/capio-posix/handlers/lseek.hpp new file mode 100644 index 000000000..814dd6b4d --- /dev/null +++ b/capio-posix/handlers/lseek.hpp @@ -0,0 +1,33 @@ +#ifndef CAPIO_POSIX_HANDLERS_LSEEK_HPP +#define CAPIO_POSIX_HANDLERS_LSEEK_HPP + +#if defined(SYS_lseek) || defined(SYS_llseek) + +#include "utils/common.hpp" + +int lseek_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + int fd = static_cast(arg0); + auto offset = static_cast(arg1); + int whence = static_cast(arg2); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + + START_LOG(tid, "call(fd=%d, offset=%ld, whence=%d)", fd, offset, whence); + if (exists_capio_fd(fd)) { + capio_off64_t computed_offset = 0; + + if (whence == SEEK_CUR) { + computed_offset = get_capio_fd_offset(fd) + offset; + } else { + computed_offset = offset; + } + + // computed_offset = seek_request(get_capio_fd_path(fd), computed_offset, whence, tid, fd); + + set_capio_fd_offset(fd, computed_offset); + } + + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; +} + +#endif // SYS_lseek || SYS_llseek +#endif // CAPIO_POSIX_HANDLERS_LSEEK_HPP \ No newline at end of file diff --git a/src/posix/handlers/mkdir.hpp b/capio-posix/handlers/mkdir.hpp similarity index 52% rename from src/posix/handlers/mkdir.hpp rename to capio-posix/handlers/mkdir.hpp index b2331b3fb..c7303f958 100644 --- a/src/posix/handlers/mkdir.hpp +++ b/capio-posix/handlers/mkdir.hpp @@ -1,15 +1,13 @@ #ifndef CAPIO_POSIX_HANDLERS_MKDIR_HPP #define CAPIO_POSIX_HANDLERS_MKDIR_HPP -#if defined(SYS_mkdir) || defined(SYS_mkdirat) || defined(SYS_rmdir) - #include "utils/common.hpp" #include "utils/filesystem.hpp" -inline off64_t capio_mkdirat(int dirfd, const std::string_view &pathname, mode_t mode, long tid) { +inline off64_t capio_mkdirat(int dirfd, const std::string_view &pathname, mode_t mode, pid_t tid) { START_LOG(tid, "call(dirfd=%d, pathname=%s, mode=%o)", dirfd, pathname.data(), mode); - if (is_forbidden_path(pathname)) { + if (!is_capio_path(pathname)) { LOG("Path %s is forbidden: skip", pathname.data()); return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } @@ -34,85 +32,46 @@ inline off64_t capio_mkdirat(int dirfd, const std::string_view &pathname, mode_t } if (is_capio_path(path)) { - if (exists_capio_path(path)) { - errno = EEXIST; - return CAPIO_POSIX_SYSCALL_ERRNO; - } - off64_t res = mkdir_request(path, tid); - if (res == 1) { - return CAPIO_POSIX_SYSCALL_ERRNO; - } else { - LOG("Adding %s to capio_files_path", path.c_str()); - add_capio_path(path); - return res; - } - } else { - return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + create_request(-1, path, tid); } + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } -inline off64_t capio_rmdir(const std::string_view &pathname, long tid) { +inline off64_t capio_rmdir(const std::string_view &pathname, pid_t tid) { START_LOG(tid, "call(pathname=%s)", pathname.data()); - if (is_forbidden_path(pathname)) { - LOG("Path %s is forbidden: skip", pathname.data()); - return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; - } - - std::filesystem::path path(pathname); - if (path.is_relative()) { - path = capio_posix_realpath(path); - if (path.empty()) { - LOG("path_to_check.len = 0!"); - return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; - } - } - - if (is_capio_path(path)) { - if (!exists_capio_path(path)) { - LOG("capio_files_path.find == end. errno = " - "ENOENT"); - errno = ENOENT; - return CAPIO_POSIX_SYSCALL_ERRNO; - } - off64_t res = rmdir_request(path, tid); - if (res == 2) { - LOG("res == 2. errno = ENOENT"); - errno = ENOENT; - return CAPIO_POSIX_SYSCALL_ERRNO; - } else { - delete_capio_path(path); - return res; - } - } else { - return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; - } + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } +#if defined(SYS_mkdir) int mkdir_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { const std::string_view pathname(reinterpret_cast(arg0)); auto mode = static_cast(arg1); - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); return posix_return_value(capio_mkdirat(AT_FDCWD, pathname, mode, tid), result); } +#endif // SYS_mkdir +#if defined(SYS_mkdirat) int mkdirat_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { int dirfd = static_cast(arg0); const std::string_view pathname(reinterpret_cast(arg1)); auto mode = static_cast(arg2); - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); return posix_return_value(capio_mkdirat(dirfd, pathname, mode, tid), result); } +#endif // SYS_mkdirat +#if defined(SYS_rmdir) int rmdir_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { const std::string_view pathname(reinterpret_cast(arg0)); - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); return posix_return_value(capio_rmdir(pathname, tid), result); } +#endif // SYS_rmdir -#endif // SYS_mkdir || SYS_mkdirat || SYS_rmdir -#endif // CAPIO_POSIX_HANDLERS_MKDIR_HPP +#endif // CAPIO_POSIX_HANDLERS_MKDIR_HPP \ No newline at end of file diff --git a/capio-posix/handlers/open.hpp b/capio-posix/handlers/open.hpp new file mode 100644 index 000000000..de3792fcc --- /dev/null +++ b/capio-posix/handlers/open.hpp @@ -0,0 +1,183 @@ +#ifndef CAPIO_POSIX_HANDLERS_OPENAT_HPP +#define CAPIO_POSIX_HANDLERS_OPENAT_HPP + +#include "utils/common.hpp" +#include "utils/filesystem.hpp" + +std::string compute_abs_path(char *pathname, int dirfd) { + START_LOG(syscall_no_intercept(SYS_gettid), "call(pathname=%s, dirfd=%d)", pathname, dirfd); + std::filesystem::path path(pathname); + if (path.is_relative()) { + if (dirfd == AT_FDCWD) { + path = capio_posix_realpath(path); + if (path.empty()) { + LOG("path empty AT_FDCWD"); + return ""; + } + } else { + if (!is_directory(dirfd)) { + LOG("dirfd does not point to a directory"); + return ""; + } + const std::filesystem::path dir_path = get_dir_path(dirfd); + if (dir_path.empty()) { + LOG("path empty"); + return ""; + } + + path = (dir_path / path).lexically_normal(); + LOG("path = %s", path.c_str()); + } + } + return path; +} + +#if defined(SYS_creat) +int creat_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + std::string pathname(reinterpret_cast(arg0)); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + int flags = O_CREAT | O_WRONLY | O_TRUNC; + mode_t mode = static_cast(arg2); + START_LOG(tid, "call(path=%s, flags=%d, mode=%d)", pathname.data(), flags, mode); + + const auto name_length = pathname.size(); + for (auto i = 0; i < name_length; i++) { + if (pathname[i] == '(' || pathname[i] == ')' || pathname[i] < 32 || pathname[i] > 126) { + LOG("Path %s contains invalid ASCII chars: %d. Skippind", pathname.c_str(), + pathname[i]); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + } + + std::string path = compute_abs_path(pathname.data(), -1); + + if (!is_capio_path(path)) { + LOG("Path %s is forbidden: skip", path.data()); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + + create_request(-1, path.data(), tid); + LOG("Create request sent"); + + const int fd = + static_cast(syscall_no_intercept(SYS_creat, arg0, arg1, arg2, arg3, arg4, arg5)); + + if (fd < 0) { + return CAPIO_POSIX_SYSCALL_ERRNO; + } + + LOG("fd=%d", fd); + + if (is_capio_path(path) && fd >= 0) { + LOG("Registering path and fd"); + add_capio_fd(tid, path, fd, 0, (flags & O_CLOEXEC) == O_CLOEXEC); + } + + return posix_return_value(fd, result); +} +#endif // SYS_creat + +#if defined(SYS_open) +int open_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + std::string pathname(reinterpret_cast(arg0)); + int flags = static_cast(arg1); + mode_t mode = static_cast(arg2); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + START_LOG(tid, "call(path=%s, flags=%d, mode=%d)", pathname.data(), flags, mode); + + const auto name_length = pathname.size(); + for (auto i = 0; i < name_length; i++) { + if (pathname[i] == '(' || pathname[i] == ')' || pathname[i] < 32 || pathname[i] > 126) { + LOG("Path %s contains invalid ASCII chars: %d. Skippind", pathname.c_str(), + pathname[i]); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + } + + std::string path = compute_abs_path(pathname.data(), -1); + + if (!is_capio_path(path)) { + LOG("Path %s is not a capio path: skip", path.data()); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + + std::string resolved_path = resolve_possible_symlink(path); + if ((flags & O_CREAT) == O_CREAT) { + LOG("O_CREAT"); + create_request(-1, resolved_path.data(), tid); + } else { + LOG("not O_CREAT"); + if (open_request(-1, resolved_path.data(), tid) == 0) { + LOG("File is excluded! Skipping open of file!"); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + } + + const int fd = + static_cast(syscall_no_intercept(SYS_open, arg0, arg1, arg2, arg3, arg4, arg5)); + if (fd < 0) { + return CAPIO_POSIX_SYSCALL_ERRNO; + } + + LOG("Adding capio path"); + add_capio_fd(tid, resolved_path, fd, 0, (flags & O_CLOEXEC) == O_CLOEXEC); + LOG("fd=%d", fd); + + return posix_return_value(fd, result); +} +#endif // SYS_open + +#if defined(SYS_openat) +int openat_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + int dirfd = static_cast(arg0); + std::string pathname(reinterpret_cast(arg1)); + int flags = static_cast(arg2); + mode_t mode = static_cast(arg3); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + START_LOG(tid, "call(dirfd=%ld, path=%s, flags=%d, mode=%d)", dirfd, pathname.data(), flags, + mode); + + const auto name_length = pathname.size(); + for (auto i = 0; i < name_length; i++) { + if (pathname[i] == '(' || pathname[i] == ')' || pathname[i] < 32 || pathname[i] > 126) { + LOG("Path %s contains invalid ASCII chars: %d. Skippind", pathname.c_str(), + pathname[i]); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + } + + std::string path = compute_abs_path(pathname.data(), dirfd); + if (!is_capio_path(path)) { + LOG("Path %s is not a capio path: skip", path.data()); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + + std::string resolved_path = resolve_possible_symlink(path); + + if ((flags & O_CREAT) == O_CREAT) { + LOG("O_CREAT"); + create_request(-1, resolved_path.data(), tid); + } else { + LOG("not O_CREAT"); + if (open_request(-1, resolved_path.data(), tid) == 0) { + LOG("File is excluded! Skipping open of file!"); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + } + + const int fd = + static_cast(syscall_no_intercept(SYS_openat, arg0, arg1, arg2, arg3, arg4, arg5)); + LOG("fd=%d", fd); + + if (fd < 0) { + return CAPIO_POSIX_SYSCALL_ERRNO; + } + + LOG("Adding resolved capio path (%s)", resolved_path.c_str()); + add_capio_fd(tid, resolved_path, fd, 0, (flags & O_CLOEXEC) == O_CLOEXEC); + + return posix_return_value(fd, result); +} +#endif // SYS_openat + +#endif // CAPIO_POSIX_HANDLERS_OPENAT_HPP \ No newline at end of file diff --git a/capio-posix/handlers/posix_readdir.hpp b/capio-posix/handlers/posix_readdir.hpp new file mode 100644 index 000000000..919915d1f --- /dev/null +++ b/capio-posix/handlers/posix_readdir.hpp @@ -0,0 +1,426 @@ +#ifndef POSIX_READDIR_HPP +#define POSIX_READDIR_HPP + +#include +#include +#include +#include +#include +#include +#include + +// Map &DIR -> +inline std::unordered_map> + *opened_directory = nullptr; + +inline std::unordered_map *> *directory_items; + +inline std::unordered_map directory_commit_token_path; + +inline timespec dirent_await_sleep_time{0, 100 * 1000000L}; // 100ms + +inline dirent64 *(*real_readdir64)(DIR *) = nullptr; +inline dirent *(*real_readdir)(DIR *) = nullptr; +inline DIR *(*real_opendir)(const char *) = nullptr; +inline int (*real_closedir)(DIR *) = nullptr; + +inline dirent64 *dirent_curr_dir; +inline dirent64 *dirent_parent_dir; + +inline void init_posix_dirent() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + syscall_no_intercept_flag = true; + if (!real_readdir64) { + LOG("Loading real readdir64 method"); + real_readdir64 = (dirent64 * (*) (DIR *) ) dlsym(RTLD_NEXT, "readdir64"); + } + + if (!real_opendir) { + LOG("Loading real opendir method"); + real_opendir = (DIR * (*) (const char *) ) dlsym(RTLD_NEXT, "opendir"); + } + + if (!real_closedir) { + LOG("Loading real closedir method"); + real_closedir = (int (*)(DIR *)) dlsym(RTLD_NEXT, "closedir"); + } + + if (!real_readdir) { + LOG("Loading real readdir method"); + real_readdir = (dirent * (*) (DIR *) ) dlsym(RTLD_NEXT, "readdir"); + } + + directory_items = new std::unordered_map *>(); + opened_directory = + new std::unordered_map>(); + + dirent_curr_dir = new dirent64(); + dirent_parent_dir = new dirent64(); + + dirent_curr_dir->d_type = DT_DIR; + dirent_parent_dir->d_type = DT_DIR; + + memcpy(dirent_curr_dir->d_name, ".\0", 2); + memcpy(dirent_parent_dir->d_name, "..\0", 3); + + syscall_no_intercept_flag = false; +} + +inline unsigned long int load_files_from_directory(const char *path) { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s)", path); + + syscall_no_intercept_flag = true; + dirent64 *entry; + DIR *dir = real_opendir(path); + unsigned long int count = 0; + + if (directory_items->find(path) == directory_items->end()) { + LOG("Directory vector not present. Adding it at path %s", path); + directory_items->emplace(path, new std::vector()); + directory_items->at(path)->emplace_back(dirent_curr_dir); + directory_items->at(path)->emplace_back(dirent_parent_dir); + } + + while ((entry = real_readdir64(dir)) != NULL) { + std::filesystem::path dir_abs_path(entry->d_name); + + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + LOG("Skipping entry %s", entry->d_name); + continue; + } + + LOG("Entry name is %s. computing absolute path", entry->d_name); + dir_abs_path = std::filesystem::path(path) / entry->d_name; + LOG("Directory abs path = %s", dir_abs_path.c_str()); + + auto directory_object = directory_items->at(path); + + auto itm = std::find_if(directory_object->begin(), directory_object->end(), + [&](const dirent64 *_scope_entry) { + return std::string(entry->d_name) == _scope_entry->d_name; + }); + + if (itm == directory_object->end()) { + LOG("Item %s is not stored within internal capio data structure. adding it", + dir_abs_path.c_str()); + auto new_entry = new dirent64(); + memcpy(new_entry, entry, sizeof(dirent64)); + directory_object->emplace_back(new_entry); + } + count++; + } + + LOG("Found %ld items.", count); + + real_closedir(dir); + syscall_no_intercept_flag = false; + return count; +} + +inline struct dirent64 *capio_internal_readdir(DIR *dirp, long pid) { + START_LOG(pid, "call(dirp=%ld)", dirp); + + auto directory_path = + std::get<0>(opened_directory->at(reinterpret_cast(dirp))); + + const auto &committed_directory_toke_path = directory_commit_token_path.at(directory_path); + + if (const auto item = opened_directory->find(reinterpret_cast(dirp)); + item != opened_directory->end() || std::filesystem::exists(committed_directory_toke_path)) { + LOG("Found dirp."); + const auto dir_path_name = std::get<0>(item->second); + const auto capio_internal_offset = std::get<1>(item->second); + + LOG("Getting files inside directory %s", dir_path_name.c_str()); + + if (capio_internal_offset >= directory_items->at(dir_path_name)->size()) { + LOG("Internal offset for dir reached end of vector. Loading files from FS."); + auto loaded_files = load_files_from_directory(dir_path_name.c_str()); + LOG("There are %ld files inside %s", loaded_files, dir_path_name.c_str()); + while (loaded_files <= capio_internal_offset) { + LOG("Not enough files: expected %ld, got %ld... waiting", loaded_files, + capio_internal_offset); + LOG("Checking for commit token existence (%s)", + committed_directory_toke_path.c_str()); + syscall_no_intercept_flag = true; + bool is_committed = std::filesystem::exists(committed_directory_toke_path); + syscall_no_intercept_flag = false; + LOG("File %s committed", is_committed ? "is" : "is not"); + if (is_committed) { + LOG("Returning NULL as result"); + errno = 0; + return NULL; + } + + syscall_no_intercept(SYS_nanosleep, &dirent_await_sleep_time, NULL); + loaded_files = load_files_from_directory(dir_path_name.c_str()); + LOG("There are %ld files inside %s", loaded_files, dir_path_name.c_str()); + } + } + + LOG("Returning item %d", std::get<1>(item->second)); + + const auto return_value = directory_items->at(dir_path_name)->at(std::get<1>(item->second)); + std::get<1>(item->second)++; + + LOG("Returned dirent structure:"); + LOG("dirent.d_name = %s", return_value->d_name); + LOG("dirent.d_type = %d", return_value->d_type); + LOG("dirent.d_ino = %d", return_value->d_ino); + LOG("dirent.d_off = %d", return_value->d_off); + LOG("dirent.d_reclen = %d", return_value->d_reclen); + return return_value; + } + LOG("Reached end of branch... something might be amiss.. returning EOS"); + errno = 0; + return NULL; +} + +DIR *opendir(const char *name) { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s)", name); + + const auto name_length = strlen(name); + for (auto i = 0; i < name_length; i++) { + if (name[i] == '(' || name[i] == ')' || name[i] < 32 || name[i] > 126) { + LOG("Path %s contains invalid ASCII chars: %d. Skippind", name, name[i]); + syscall_no_intercept_flag = true; + auto dir = real_opendir(name); + syscall_no_intercept_flag = false; + + return dir; + } + } + + LOG("Path %s does not contain '(' or ')': proceeding with resolution", name); + + auto absolute_path = capio_absolute(name); + + LOG("Resolved absolute path = %s", absolute_path.c_str()); + + if (!is_capio_path(absolute_path)) { + LOG("Not a CAPIO path. continuing execution"); + syscall_no_intercept_flag = true; + auto dir = real_opendir(absolute_path.c_str()); + syscall_no_intercept_flag = false; + + return dir; + } + + LOG("Performing consent request to open directory %s", absolute_path.c_str()); + consent_request_cache_fs->consent_request(absolute_path.c_str(), gettid(), __FUNCTION__); + + syscall_no_intercept_flag = true; + auto dir = real_opendir(absolute_path.c_str()); + syscall_no_intercept_flag = false; + + if (directory_commit_token_path.find(absolute_path) == directory_commit_token_path.end()) { + LOG("Commit token path was not found for path %s", absolute_path.c_str()); + auto token_path = new char[PATH_MAX]{0}; + posix_directory_committed_request(capio_syscall(SYS_gettid), absolute_path, token_path); + // FIXME: This is a flying patch to implement the exclude with little to no overhead. + // If the commit token is , then the file is excluded and hence the directory + // should not be handled by CAPIO... + // TODO: Find a better solution + if (strcmp(token_path, "") == 0) { + LOG("File is excluded. Opening as a simple directory and not registering CAPIO fd!"); + return dir; + } + LOG("Inserting token path %s", token_path); + directory_commit_token_path.insert({absolute_path, token_path}); + } + + LOG("Opened directory with offset %ld", dir); + opened_directory->insert( + {reinterpret_cast(dir), {std::string(absolute_path), 0}}); + directory_items->emplace(std::string(absolute_path), new std::vector()); + + auto fd = dirfd(dir); + LOG("File descriptor for directory %s is %d", absolute_path.c_str(), fd); + + add_capio_fd(capio_syscall(SYS_gettid), absolute_path.c_str(), fd, 0, 0); + + return dir; +} + +int closedir(DIR *dirp) { + START_LOG(capio_syscall(SYS_gettid), "call(dir=%ld)", dirp); + + static int (*real_closedir)(DIR *) = NULL; + if (!real_closedir) { + syscall_no_intercept_flag = true; + real_closedir = (int (*)(DIR *)) dlsym(RTLD_NEXT, "closedir"); + syscall_no_intercept_flag = false; + if (!real_closedir) { + ERR_EXIT("Failed to find original closedir: %s\n", dlerror()); + } + } + + if (const auto pos = opened_directory->find(reinterpret_cast(dirp)); + pos != opened_directory->end()) { + LOG("Closing directory with path %s", pos->second.first.c_str()); + close_request(pos->second.first.c_str(), capio_syscall(SYS_gettid)); + syscall_no_intercept_flag = true; + delete_capio_fd(dirfd(dirp)); + syscall_no_intercept_flag = false; + + if (auto pos1 = directory_items->find(pos->second.first); pos1 != directory_items->end()) { + directory_items->erase(pos1); + } + opened_directory->erase(pos); + LOG("removed dir from map of opened files"); + } + + syscall_no_intercept_flag = true; + auto return_code = real_closedir(dirp); + syscall_no_intercept_flag = false; + LOG("Return code of closedir = %d", return_code); + + return return_code; +} + +struct dirent *readdir(DIR *dirp) { + long pid = capio_syscall(SYS_gettid); + START_LOG(pid, "call(dir=%ld)", dirp); + + if (opened_directory->find(reinterpret_cast(dirp)) == + opened_directory->end()) { + LOG("Directory is not handled by CAPIO. Returning read readdir"); + syscall_no_intercept_flag = true; + const auto result = real_readdir(dirp); + syscall_no_intercept_flag = false; + + return result; + } + + dirent64 *capio_internal_dirent64 = capio_internal_readdir(dirp, pid); + LOG("return value == NULL ? %s", capio_internal_dirent64 == NULL ? "TRUE" : "FALSE"); + return reinterpret_cast(capio_internal_dirent64); +} + +struct dirent64 *readdir64(DIR *dirp) { + long pid = capio_syscall(SYS_gettid); + START_LOG(pid, "call(dir=%ld)", dirp); + + if (opened_directory->find(reinterpret_cast(dirp)) == + opened_directory->end()) { + LOG("Directory is not handled by CAPIO. Returning read readdir"); + syscall_no_intercept_flag = true; + auto result = real_readdir64(dirp); + syscall_no_intercept_flag = false; + + return result; + } + + auto capio_internal_dirent64 = capio_internal_readdir(dirp, pid); + LOG("return value == NULL ? %s", capio_internal_dirent64 == NULL ? "TRUE" : "FALSE"); + return capio_internal_dirent64; +} + +void rewinddir(DIR *dirp) { + long pid = capio_syscall(SYS_gettid); + START_LOG(pid, "call(dir=%ld)", dirp); + + static void (*real_rewinddir)(DIR *) = NULL; + if (!real_rewinddir) { + LOG("Loading real glibc method"); + syscall_no_intercept_flag = true; + real_rewinddir = (void (*)(DIR *)) dlsym(RTLD_NEXT, "rewinddir"); + syscall_no_intercept_flag = false; + } + + if (opened_directory->find(reinterpret_cast(dirp)) == + opened_directory->end()) { + LOG("Directory is not handled by CAPIO. Returning false"); + syscall_no_intercept_flag = true; + real_rewinddir(dirp); + syscall_no_intercept_flag = false; + } else { + LOG("File handled by CAPIO. Resetting internal CAPIO offset"); + opened_directory->at(reinterpret_cast(dirp)).second = 0; + } +} + +long int telldir(DIR *dirp) { + long pid = capio_syscall(SYS_gettid); + START_LOG(pid, "call(dir=%ld)", dirp); + + static long int (*real_telldir)(DIR *) = NULL; + if (!real_telldir) { + LOG("Loading real glibc method"); + syscall_no_intercept_flag = true; + real_telldir = (long int (*)(DIR *)) dlsym(RTLD_NEXT, "telldir"); + syscall_no_intercept_flag = false; + } + + if (opened_directory->find(reinterpret_cast(dirp)) == + opened_directory->end()) { + LOG("Directory is not handled by CAPIO. Returning false"); + syscall_no_intercept_flag = true; + auto result = real_telldir(dirp); + syscall_no_intercept_flag = false; + LOG("Telldir returned %ld", result); + return result; + } + + LOG("File handled by CAPIO. Returning internal CAPIO offset (which is %ld)", + opened_directory->at(reinterpret_cast(dirp)).second); + return opened_directory->at(reinterpret_cast(dirp)).second; +} + +void seekdir(DIR *dirp, long int loc) { + long pid = capio_syscall(SYS_gettid); + START_LOG(pid, "call(dir=%ld, loc=%ld)", dirp, loc); + + static void (*real_seekdir)(DIR *, long int) = NULL; + if (!real_seekdir) { + LOG("Loading real glibc method"); + syscall_no_intercept_flag = true; + real_seekdir = (void (*)(DIR *, long int)) dlsym(RTLD_NEXT, "seekdir"); + syscall_no_intercept_flag = false; + } + + if (opened_directory->find(reinterpret_cast(dirp)) == + opened_directory->end()) { + LOG("Directory is not handled by CAPIO. Using real seekdir"); + syscall_no_intercept_flag = true; + real_seekdir(dirp, loc); + syscall_no_intercept_flag = false; + } else { + LOG("Directory is handled by CAPIO. Setting internal offset to %ld", loc); + opened_directory->at(reinterpret_cast(dirp)).second = loc; + } +} + +int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) { + /* + * WARN: I have not yet clear the usage of this function, as such bugs are surely presents + * TODO: implement the correct handling logic for this method + */ + long pid = capio_syscall(SYS_gettid); + START_LOG(pid, "call(dir=%ld)", dirp); + + static int (*real_readdir_r)(DIR *, struct dirent *, struct dirent **) = NULL; + if (!real_readdir_r) { + LOG("Loading real glibc method"); + syscall_no_intercept_flag = true; + real_readdir_r = + (int (*)(DIR *, struct dirent *, struct dirent **)) dlsym(RTLD_NEXT, "readdir_r"); + syscall_no_intercept_flag = false; + } + + if (opened_directory->find(reinterpret_cast(dirp)) == + opened_directory->end()) { + LOG("Directory is not handled by CAPIO. Using real readdir_r"); + syscall_no_intercept_flag = true; + int res = real_readdir_r(dirp, entry, result); + syscall_no_intercept_flag = false; + return res; + } + + *result = reinterpret_cast(capio_internal_readdir(dirp, pid)); + + LOG("CAPIO returned a directory entry: %s", entry->d_name); + return 0; +} + +#endif // POSIX_READDIR_HPP \ No newline at end of file diff --git a/capio-posix/handlers/read.hpp b/capio-posix/handlers/read.hpp new file mode 100644 index 000000000..684d9f8da --- /dev/null +++ b/capio-posix/handlers/read.hpp @@ -0,0 +1,72 @@ +#ifndef CAPIO_POSIX_HANDLERS_READ_HPP +#define CAPIO_POSIX_HANDLERS_READ_HPP + +inline off64_t capio_read_fs(int fd, size_t count, pid_t tid) { + START_LOG(capio_syscall(SYS_gettid), "call(fd=%d, count=%ld, tid=%ld)", fd, count, tid); + if (exists_capio_fd(fd)) { + auto computed_offset = get_capio_fd_offset(fd) + count; + + LOG("Handling read on file %s up to byte %ld", get_capio_fd_path(fd).c_str(), + computed_offset); + + read_request_cache_fs->read_request(get_capio_fd_path(fd), computed_offset, tid, fd); + + set_capio_fd_offset(fd, computed_offset); + } + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; +} + +inline off64_t capio_read_mem(int fd, size_t count, void *buffer, long *result) { + START_LOG(capio_syscall(SYS_gettid), "call(fd=%d, count=%ld)", fd, count); + if (exists_capio_fd(fd)) { + const auto computed_offset = get_capio_fd_offset(fd) + count; + + LOG("Handling read on file %s up to byte %ld", get_capio_fd_path(fd).c_str(), + computed_offset); + + const auto res = read_request_cache_mem->read(fd, buffer, count); + LOG("Result of read is %lu", res); + return posix_return_value(res, result); + } + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; +} + +#if defined(SYS_read) +int read_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + int fd = static_cast(arg0); + auto count = static_cast(arg2); + auto buffer = reinterpret_cast(arg1); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + + START_LOG(capio_syscall(SYS_gettid), "call(fd=%d, tid=%d, count=%ld)", fd, tid, count); + if (exists_capio_fd(fd)) { + auto read_result = store_file_in_memory(get_capio_fd_path(fd), tid) + ? capio_read_mem(fd, count, buffer, result) + : capio_read_fs(fd, count, tid); + + LOG("read result: %ld", read_result); + return read_result; + } + LOG("Not a CAPIO fd... skipping..."); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; +} +#endif // SYS_read + +#if defined(SYS_readv) +int readv_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + auto fd = static_cast(arg0); + auto iovcnt = static_cast(arg2); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + + if (exists_capio_fd(fd)) { + auto computed_offset = get_capio_fd_offset(fd) + iovcnt * sizeof(iovec); + + read_request_cache_fs->read_request(get_capio_fd_path(fd), computed_offset, tid, fd); + + set_capio_fd_offset(fd, computed_offset); + } + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; +} +#endif // SYS_readv + +#endif // CAPIO_POSIX_HANDLERS_READ_HPP \ No newline at end of file diff --git a/capio-posix/handlers/rename.hpp b/capio-posix/handlers/rename.hpp new file mode 100644 index 000000000..4c61e8a5d --- /dev/null +++ b/capio-posix/handlers/rename.hpp @@ -0,0 +1,41 @@ +#ifndef CAPIO_POSIX_HANDLERS_RENAME_HPP +#define CAPIO_POSIX_HANDLERS_RENAME_HPP + +#if defined(SYS_rename) + +#include "utils/filesystem.hpp" + +int rename_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + std::filesystem::path oldpath(reinterpret_cast(arg0)); + std::filesystem::path newpath(reinterpret_cast(arg1)); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + START_LOG(tid, "call(oldpath=%s, newpath=%s)", oldpath.c_str(), newpath.c_str()); + LOG("Compute paths"); + auto oldpath_abs = capio_absolute(oldpath); + LOG("oldpath absolute: %s", oldpath_abs.c_str()); + auto newpath_abs = capio_absolute(newpath); + LOG("newpath absolute: %s", newpath_abs.c_str()); + + if (is_prefix(oldpath_abs, newpath_abs)) { + // TODO: The check is more complex + errno = EINVAL; + *result = -errno; + return CAPIO_POSIX_SYSCALL_SUCCESS; + } + LOG("newpath is not prefix of old"); + + if (is_capio_path(oldpath_abs)) { + LOG("Oldpath is capio_path"); + rename_capio_path(oldpath_abs, newpath_abs); + } + + if (is_capio_path(oldpath_abs) || is_capio_path(newpath_abs)) { + LOG("Either old or new or both paths are capio_paths. sending request"); + rename_request(oldpath_abs, newpath_abs, tid); + } + + return CAPIO_POSIX_SYSCALL_SKIP; +} + +#endif // SYS_rename +#endif // CAPIO_POSIX_HANDLERS_RENAME_HPP \ No newline at end of file diff --git a/src/posix/handlers/stat.hpp b/capio-posix/handlers/stat.hpp similarity index 51% rename from src/posix/handlers/stat.hpp rename to capio-posix/handlers/stat.hpp index 4e6f598e8..a7f9028cb 100644 --- a/src/posix/handlers/stat.hpp +++ b/capio-posix/handlers/stat.hpp @@ -1,8 +1,6 @@ #ifndef CAPIO_POSIX_HANDLERS_STAT_HPP #define CAPIO_POSIX_HANDLERS_STAT_HPP -#if defined(SYS_fstat) || defined(SYS_lstat) || defined(SYS_newfstatat) || defined(SYS_stat) - #include #include "capio/env.hpp" @@ -11,82 +9,34 @@ #include "utils/filesystem.hpp" #include "utils/requests.hpp" -inline blkcnt_t get_nblocks(off64_t file_size) { - return (file_size % 4096 == 0) ? (file_size / 512) : (file_size / 512 + 8); -} - -inline void fill_statbuf(struct stat *statbuf, off_t file_size, bool is_dir, ino_t inode) { - START_LOG(syscall_no_intercept(SYS_gettid), - "call(statbuf=0x%08x, file_size=%ld, is_dir=%s, inode=%ul)", statbuf, file_size, - is_dir ? "true" : "false", inode); - - struct timespec time { - 1, 1 - }; - if (is_dir == 1) { - statbuf->st_mode = S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; - file_size = 4096; - } else { - statbuf->st_mode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; - } - statbuf->st_dev = 100; - statbuf->st_ino = inode; - statbuf->st_nlink = 1; - statbuf->st_uid = syscall_no_intercept(SYS_getuid); - statbuf->st_gid = syscall_no_intercept(SYS_getgid); - statbuf->st_rdev = 0; - statbuf->st_size = file_size; - statbuf->st_blksize = 4096; - statbuf->st_blocks = (file_size < 4096) ? 8 : get_nblocks(file_size); - statbuf->st_atim = time; - statbuf->st_mtim = time; - statbuf->st_ctim = time; -} - -inline int capio_fstat(int fd, struct stat *statbuf, long tid) { +inline int capio_fstat(int fd, struct stat *statbuf, pid_t tid) { START_LOG(tid, "call(fd=%d, statbuf=0x%08x)", fd, statbuf); if (exists_capio_fd(fd)) { - get_write_cache(tid).flush(); - auto [file_size, is_dir] = fstat_request(fd, tid); - if (file_size == -1) { - errno = ENOENT; - return CAPIO_POSIX_SYSCALL_ERRNO; - } - fill_statbuf(statbuf, file_size, is_dir, std::hash{}(get_capio_fd_path(fd))); - return CAPIO_POSIX_SYSCALL_SUCCESS; - } else { - return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + consent_request_cache_fs->consent_request(get_capio_fd_path(fd), tid, __FUNCTION__); } + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } -inline int capio_lstat(const std::string_view &pathname, struct stat *statbuf, long tid) { +inline int capio_lstat(const std::string_view &pathname, struct stat *statbuf, pid_t tid) { START_LOG(tid, "call(absolute_path=%s, statbuf=0x%08x)", pathname.data(), statbuf); - if (is_forbidden_path(pathname)) { + if (!is_capio_path(pathname)) { LOG("Path %s is forbidden: skip", pathname.data()); return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } const std::filesystem::path absolute_path(pathname); if (is_capio_path(absolute_path)) { - get_write_cache(tid).flush(); - auto [file_size, is_dir] = stat_request(absolute_path, tid); - if (file_size == -1) { - errno = ENOENT; - return CAPIO_POSIX_SYSCALL_ERRNO; - } - fill_statbuf(statbuf, file_size, is_dir, std::hash{}(absolute_path)); - return CAPIO_POSIX_SYSCALL_SUCCESS; - } else { - return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + consent_request_cache_fs->consent_request(pathname, tid, __FUNCTION__); } + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } -inline int capio_lstat_wrapper(const std::string_view &pathname, struct stat *statbuf, long tid) { +inline int capio_lstat_wrapper(const std::string_view &pathname, struct stat *statbuf, pid_t tid) { START_LOG(tid, "call(path=%s, buf=0x%08x)", pathname.data(), statbuf); - if (is_forbidden_path(pathname)) { + if (!is_capio_path(pathname)) { LOG("Path %s is forbidden: skip", pathname.data()); return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } @@ -100,70 +50,77 @@ inline int capio_lstat_wrapper(const std::string_view &pathname, struct stat *st } inline int capio_fstatat(int dirfd, const std::string_view &pathname, struct stat *statbuf, - int flags, long tid) { + int flags, pid_t tid) { START_LOG(tid, "call(dirfd=%ld, pathname=%s, statbuf=0x%08x, flags=%X)", dirfd, pathname.data(), statbuf, flags); - if (is_forbidden_path(pathname)) { + if (!is_capio_path(pathname)) { LOG("Path %s is forbidden: skip", pathname.data()); return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; } std::filesystem::path path(pathname); if (path.empty() && (flags & AT_EMPTY_PATH) == AT_EMPTY_PATH) { - if (dirfd == AT_FDCWD) { // operate on currdir + if (dirfd == AT_FDCWD) { + // operate on currdir return capio_lstat(get_current_dir().native(), statbuf, tid); - } else { // operate on dirfd. in this case dirfd can refer to any type of file - return capio_fstat(dirfd, statbuf, tid); } - } else if (path.is_relative()) { + // operate on dirfd. in this case dirfd can refer to any type of file + return capio_fstat(dirfd, statbuf, tid); + } + if (path.is_relative()) { if (dirfd == AT_FDCWD) { // pathname is interpreted relative to currdir return capio_lstat_wrapper(path.native(), statbuf, tid); - } else { - if (!is_directory(dirfd)) { - errno = ENOTDIR; - return CAPIO_POSIX_SYSCALL_ERRNO; - } - const std::filesystem::path dir_path = get_dir_path(dirfd); - if (dir_path.empty()) { - return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; - } - path = (dir_path / path).lexically_normal(); - return capio_lstat(path.native(), statbuf, tid); } - } else { + if (!is_directory(dirfd)) { + errno = ENOTDIR; + return CAPIO_POSIX_SYSCALL_ERRNO; + } + const std::filesystem::path dir_path = get_dir_path(dirfd); + if (dir_path.empty()) { + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + path = (dir_path / path).lexically_normal(); return capio_lstat(path.native(), statbuf, tid); } + return capio_lstat(path.native(), statbuf, tid); } +#if defined(SYS_fstat) || defined(SYS_fstat64) int fstat_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { auto fd = static_cast(arg0); auto *buf = reinterpret_cast(arg1); - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); return posix_return_value(capio_fstat(fd, buf, tid), result); } +#endif // SYS_fstat || SYS_fstat64 +#if defined(SYS_fstatat) || defined(SYS_newfstatat) || defined(SYS_fstatat64) int fstatat_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { auto dirfd = static_cast(arg0); const std::string_view pathname(reinterpret_cast(arg1)); auto *statbuf = reinterpret_cast(arg2); auto flags = static_cast(arg3); - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); return posix_return_value(capio_fstatat(dirfd, pathname, statbuf, flags, tid), result); } +#endif // SYS_fstatat || SYS_newfstatat || SYS_fstatat64 +#if defined(SYS_lstat) || defined(SYS_lstat64) int lstat_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { const std::string_view pathname(reinterpret_cast(arg0)); auto *buf = reinterpret_cast(arg1); - long tid = syscall_no_intercept(SYS_gettid); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); return posix_return_value(capio_lstat_wrapper(pathname, buf, tid), result); } +#endif // SYS_lstat || SYS_lstat64 +#if defined(SYS_stat) || defined(SYS_stat64) int stat_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { const std::string_view pathname(reinterpret_cast(arg0)); auto *buf = reinterpret_cast(arg1); @@ -171,6 +128,6 @@ int stat_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg return posix_return_value(capio_lstat_wrapper(pathname, buf, tid), result); } +#endif // SYS_stat || SYS_stat64 -#endif // SYS_fstat || SYS_lstat || SYS_newfstatat || SYS_stat -#endif // CAPIO_POSIX_HANDLERS_STAT_HPP +#endif // CAPIO_POSIX_HANDLERS_STAT_HPP \ No newline at end of file diff --git a/capio-posix/handlers/statfs.hpp b/capio-posix/handlers/statfs.hpp new file mode 100644 index 000000000..7c4488ff3 --- /dev/null +++ b/capio-posix/handlers/statfs.hpp @@ -0,0 +1,20 @@ +#ifndef CAPIO_POSIX_HANDLERS_STATFS_HPP +#define CAPIO_POSIX_HANDLERS_STATFS_HPP + +#if defined(SYS_fstatfs) || defined(SYS_fstatfs64) + +int fstatfs_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, + long *result) { + auto fd = static_cast(arg0); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + + START_LOG(tid, "call(fd=%d)", fd); + + if (exists_capio_fd(fd)) { + consent_request_cache_fs->consent_request(get_capio_fd_path(fd), tid, __FUNCTION__); + } + return CAPIO_POSIX_SYSCALL_SKIP; +} + +#endif // SYS_fstatfs || SYS_fstatfs64 +#endif // CAPIO_POSIX_HANDLERS_STATFS_HPP \ No newline at end of file diff --git a/capio-posix/handlers/statx.hpp b/capio-posix/handlers/statx.hpp new file mode 100644 index 000000000..ba7d5bc2c --- /dev/null +++ b/capio-posix/handlers/statx.hpp @@ -0,0 +1,35 @@ +#ifndef CAPIO_POSIX_HANDLERS_STATX_HPP +#define CAPIO_POSIX_HANDLERS_STATX_HPP + +#if defined(SYS_statx) + +#include "utils/common.hpp" + +inline int capio_statx(int dirfd, const std::string_view &pathname, int flags, int mask, + struct statx *statxbuf, pid_t tid) { + START_LOG(tid, "call(dirfd=%d, pathname=%s, flags=%d, mask=%d, statxbuf=0x%08x)", dirfd, + pathname.data(), flags, mask, statxbuf); + + if (!is_capio_path(pathname)) { + LOG("Path %s is forbidden: skip", pathname.data()); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + + consent_request_cache_fs->consent_request(pathname, tid, __FUNCTION__); + + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; +} + +int statx_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + auto dirfd = static_cast(arg0); + const std::string_view pathname(reinterpret_cast(arg1)); + auto flags = static_cast(arg2); + auto mask = static_cast(arg3); + auto *buf = reinterpret_cast(arg4); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + + return posix_return_value(capio_statx(dirfd, pathname, flags, mask, buf, tid), result); +} + +#endif // SYS_statx +#endif // CAPIO_POSIX_HANDLERS_STATX_HPP \ No newline at end of file diff --git a/capio-posix/handlers/unlink.hpp b/capio-posix/handlers/unlink.hpp new file mode 100644 index 000000000..58e5cbba1 --- /dev/null +++ b/capio-posix/handlers/unlink.hpp @@ -0,0 +1,39 @@ +#ifndef CAPIO_POSIX_HANDLERS_UNLINK_HPP +#define CAPIO_POSIX_HANDLERS_UNLINK_HPP + +#include "utils/common.hpp" + +#if defined(SYS_unlink) +int unlink_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + std::string_view pathname(reinterpret_cast(arg0)); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + + START_LOG(tid, "call(path=%s)", pathname.data()); + + if (is_capio_path(pathname)) { + LOG("Deleting path"); + delete_capio_path(pathname.data()); + } + + return CAPIO_POSIX_SYSCALL_SKIP; +} +#endif // SYS_unlink + +#if defined(SYS_unlinkat) +int unlinkat_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, + long *result) { + const std::string_view pathname(reinterpret_cast(arg1)); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + + START_LOG(tid, "call(path=%s)", pathname.data()); + auto path = capio_posix_realpath(pathname); + if (is_capio_path(path)) { + LOG("Deleting path"); + delete_capio_path(path); + } + + return CAPIO_POSIX_SYSCALL_SKIP; +} +#endif // SYS_unlinkat + +#endif // CAPIO_POSIX_HANDLERS_UNLINK_HPP \ No newline at end of file diff --git a/capio-posix/handlers/write.hpp b/capio-posix/handlers/write.hpp new file mode 100644 index 000000000..c640b18df --- /dev/null +++ b/capio-posix/handlers/write.hpp @@ -0,0 +1,72 @@ +#ifndef CAPIO_POSIX_HANDLERS_WRITE_HPP +#define CAPIO_POSIX_HANDLERS_WRITE_HPP + +#include "utils/common.hpp" +#include "utils/requests.hpp" + +inline off64_t capio_write_mem(int fd, char *buffer, capio_off64_t count, pid_t tid) { + START_LOG(tid, "call(fd=%d, count=%ld)", fd, count); + write_request_cache_mem->write(fd, buffer, count); + return count; +} + +#if defined(SYS_write) +int write_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + auto fd = static_cast(arg0); + auto buffer = reinterpret_cast(arg1); + auto count = static_cast(arg2); + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + START_LOG(tid, "call(fd=%d, buffer=%p, count=%ld, id=%ld)", fd, buffer, count, tid); + if (!exists_capio_fd(fd)) { + LOG("FD %d is not handled by capio... skipping syscall", fd); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + + if (!store_file_in_memory(get_capio_fd_path(fd), tid)) { + LOG("File is to be handled in FS. skipping write"); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + + auto write_result = capio_write_mem(fd, buffer, count, tid); + + LOG("Write result: %ld", write_result); + + return posix_return_value(write_result, result); +} +#endif // SYS_write + +#if defined(SYS_writev) +int writev_handler(long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long *result) { + auto fd = static_cast(arg0); + auto io_vec = reinterpret_cast(arg1); + auto iovcnt = static_cast(arg2); + long tid = syscall_no_intercept(SYS_gettid); + START_LOG(tid, "call(fd=%d, buffer=%p, count=%ld, pid=%ld)", fd, io_vec->iov_base, + io_vec->iov_len, tid); + if (!exists_capio_fd(fd)) { + LOG("FD %d is not handled by CAPIO... skipping syscall", fd); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + + if (!store_file_in_memory(get_capio_fd_path(fd), tid)) { + LOG("File is to be handled in FS. skipping write"); + return CAPIO_POSIX_SYSCALL_REQUEST_SKIP; + } + + LOG("Need to handle %ld IOVEC objects", iovcnt); + int write_result = 0; + for (auto i = 0; i < iovcnt; ++i) { + const auto [iov_base, iov_len] = io_vec[i]; + if (iov_len == 0) { + LOG("Size of IOVEC is 0. Skipping write request"); + continue; + } + LOG("Handling IOVEC elements %d of size %ld", i, iov_len); + write_result += capio_write_mem(fd, static_cast(iov_base), iov_len, tid); + } + + return posix_return_value(write_result, result); +} +#endif // SYS_writev + +#endif // CAPIO_POSIX_HANDLERS_WRITE_HPP \ No newline at end of file diff --git a/src/posix/libcapio_posix.cpp b/capio-posix/libcapio_posix.cpp similarity index 78% rename from src/posix/libcapio_posix.cpp rename to capio-posix/libcapio_posix.cpp index 7acda3fdf..57ad15ce2 100644 --- a/src/posix/libcapio_posix.cpp +++ b/capio-posix/libcapio_posix.cpp @@ -80,6 +80,9 @@ static constexpr long CAPIO_NR_SYSCALLS = 1 + std::max({ #ifdef SYS_fcntl SYS_fcntl, #endif +#ifdef SYS_fcntl64 + SYS_fcntl64, +#endif #ifdef SYS_fgetxattr SYS_fgetxattr, #endif @@ -92,9 +95,21 @@ static constexpr long CAPIO_NR_SYSCALLS = 1 + std::max({ #ifdef SYS_fstat SYS_fstat, #endif +#ifdef SYS_fstat64 + SYS_fstat64, +#endif +#ifdef SYS_fstatat + SYS_fstatat, +#endif +#ifdef SYS_fstatat64 + SYS_fstatat64, +#endif #ifdef SYS_fstatfs SYS_fstatfs, #endif +#ifdef SYS_fstatfs64 + SYS_fstatfs64, +#endif #ifdef SYS_getcwd SYS_getcwd, #endif @@ -116,9 +131,15 @@ static constexpr long CAPIO_NR_SYSCALLS = 1 + std::max({ #ifdef SYS_lseek SYS_lseek, #endif +#ifdef SYS_llseek + SYS_llseek, +#endif #ifdef SYS_lstat SYS_lstat, #endif +#ifdef SYS_lstat64 + SYS_lstat64, +#endif #ifdef SYS_mkdir SYS_mkdir, #endif @@ -149,6 +170,9 @@ static constexpr long CAPIO_NR_SYSCALLS = 1 + std::max({ #ifdef SYS_stat SYS_stat, #endif +#ifdef SYS_stat64 + SYS_stat64, +#endif #ifdef SYS_statx SYS_statx, #endif @@ -163,6 +187,9 @@ static constexpr long CAPIO_NR_SYSCALLS = 1 + std::max({ #endif #ifdef SYS_writev SYS_writev, +#endif +#ifdef SYS_copy_file_range + SYS_copy_file_range, #endif }); @@ -230,6 +257,9 @@ static constexpr std::array build_syscall_table( #ifdef SYS_fcntl _syscallTable[SYS_fcntl] = fcntl_handler; #endif +#ifdef SYS_fcntl64 + _syscallTable[SYS_fcntl64] = fcntl_handler; +#endif #ifdef SYS_fgetxattr _syscallTable[SYS_fgetxattr] = fgetxattr_handler; #endif @@ -242,18 +272,24 @@ static constexpr std::array build_syscall_table( #ifdef SYS_fstat _syscallTable[SYS_fstat] = fstat_handler; #endif +#ifdef SYS_fstat64 + _syscallTable[SYS_fstat64] = fstat_handler; +#endif +#ifdef SYS_fstatat + _syscallTable[SYS_fstatat] = fstatat_handler; +#endif +#ifdef SYS_fstatat64 + _syscallTable[SYS_fstatat64] = fstatat_handler; +#endif #ifdef SYS_fstatfs _syscallTable[SYS_fstatfs] = fstatfs_handler; #endif +#ifdef SYS_fstatfs64 + _syscallTable[SYS_fstatfs64] = fstatfs_handler; +#endif #ifdef SYS_getcwd _syscallTable[SYS_getcwd] = getcwd_handler; #endif -#ifdef SYS_getdents - _syscallTable[SYS_getdents] = getdents_handler; -#endif -#ifdef SYS_getdents64 - _syscallTable[SYS_getdents64] = getdents64_handler; -#endif #ifdef SYS_getxattr _syscallTable[SYS_getxattr] = not_implemented_handler; #endif @@ -266,9 +302,15 @@ static constexpr std::array build_syscall_table( #ifdef SYS_lseek _syscallTable[SYS_lseek] = lseek_handler; #endif +#ifdef SYS_llseek + _syscallTable[SYS_llseek] = lseek_handler; +#endif #ifdef SYS_lstat _syscallTable[SYS_lstat] = lstat_handler; #endif +#ifdef SYS_lstat64 + _syscallTable[SYS_lstat64] = lstat_handler; +#endif #ifdef SYS_mkdir _syscallTable[SYS_mkdir] = mkdir_handler; #endif @@ -299,6 +341,9 @@ static constexpr std::array build_syscall_table( #ifdef SYS_stat _syscallTable[SYS_stat] = stat_handler; #endif +#ifdef SYS_stat64 + _syscallTable[SYS_stat64] = stat_handler; +#endif #ifdef SYS_statx _syscallTable[SYS_statx] = statx_handler; #endif @@ -314,6 +359,9 @@ static constexpr std::array build_syscall_table( #ifdef SYS_writev _syscallTable[SYS_writev] = writev_handler; #endif +#ifdef SYS_copy_file_range + _syscallTable[SYS_copy_file_range] = copy_file_range_handler; +#endif return _syscallTable; } @@ -360,14 +408,41 @@ static int hook(long syscall_number, long arg0, long arg1, long arg2, long arg3, return 1; } - return syscallTable[syscall_number](arg0, arg1, arg2, arg3, arg4, arg5, result); + LOG("Handling syscall NO %ld (max num is %ld)", syscall_number, CAPIO_NR_SYSCALLS); + try { + return syscallTable[syscall_number](arg0, arg1, arg2, arg3, arg4, arg5, result); + } catch (const std::exception &exception) { + syscall_no_intercept_flag = true; + + std::cout + << std::endl + << "~~~~~~~~~~~~~~[\033[31mlibcapio_posix.so: FATAL EXCEPTION\033[0m]~~~~~~~~~~~~~~" + << std::endl + << "| Exception thrown while handling syscall " << syscall_number << std::endl + << "| TID of offending thread: " << syscall_no_intercept(SYS_gettid) << std::endl + << "| PID of offending thread: " << syscall_no_intercept(SYS_getpid) << std::endl + << "| PPID of offending thread: " << syscall_no_intercept(SYS_getppid) << std::endl + << "| " << std::endl + << "| `" << typeid(exception).name() << ": " << exception.what() << std::endl + << "|" << std::endl + << "~~~~~~~~~~~~~~[\033[31mlibcapio_posix.so: FATAL EXCEPTION\033[0m]~~~~~~~~~~~~~~" + << std::endl + << std::endl; + + ERR_EXIT("%s", exception.what()); + } + return 1; } -static __attribute__((constructor)) void init() { +static + __attribute__((constructor)) + + void + init() { init_client(); - init_data_plane(); init_filesystem(); init_threading_support(); + init_posix_dirent(); long tid = syscall_no_intercept(SYS_gettid); @@ -379,8 +454,11 @@ static __attribute__((constructor)) void init() { init_process(tid); register_capio_tid(tid); + // TODO: use var to set cache size + init_caches(); + intercept_hook_point_clone_child = hook_clone_child; intercept_hook_point_clone_parent = hook_clone_parent; intercept_hook_point = hook; START_SYSCALL_LOGGING(); -} +} \ No newline at end of file diff --git a/src/posix/readme.md b/capio-posix/readme.md similarity index 100% rename from src/posix/readme.md rename to capio-posix/readme.md diff --git a/src/posix/syscall_intercept/CMakeLists.txt b/capio-posix/syscall_intercept/CMakeLists.txt similarity index 79% rename from src/posix/syscall_intercept/CMakeLists.txt rename to capio-posix/syscall_intercept/CMakeLists.txt index 517f76217..6215c0dde 100644 --- a/src/posix/syscall_intercept/CMakeLists.txt +++ b/capio-posix/syscall_intercept/CMakeLists.txt @@ -14,12 +14,13 @@ include(ExternalProject) # Import external project from git ##################################### ExternalProject_Add(syscall_intercept - GIT_REPOSITORY https://github.com/pmem/syscall_intercept.git - GIT_TAG ca4b13531f883597c2f04a40e095f76f6c3a6d22 + GIT_REPOSITORY https://github.com/alpha-unito/syscall_intercept.git + GIT_TAG b05ff8037de2eb7b44dfb7fa372cfe08d565ba84 PREFIX ${CMAKE_CURRENT_BINARY_DIR} CMAKE_ARGS + -DSTATIC_CAPSTONE=ON -DBUILD_TESTS=OFF -DBUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -) \ No newline at end of file +) diff --git a/capio-posix/utils/cache.hpp b/capio-posix/utils/cache.hpp new file mode 100644 index 000000000..8d9fe4067 --- /dev/null +++ b/capio-posix/utils/cache.hpp @@ -0,0 +1,37 @@ +#ifndef CAPIO_CACHE_HPP +#define CAPIO_CACHE_HPP +#include "capio/requests.hpp" +#include "env.hpp" + +#include "cache/consent_request_cache.hpp" +#include "cache/read_request_cache_fs.hpp" +#include "cache/read_request_cache_mem.hpp" +#include "cache/write_request_cache_mem.hpp" + +inline thread_local ConsentRequestCache *consent_request_cache_fs; +inline thread_local ReadRequestCacheFS *read_request_cache_fs; +inline thread_local WriteRequestCacheMEM *write_request_cache_mem; +inline thread_local ReadRequestCacheMEM *read_request_cache_mem; + +inline void init_caches() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + read_request_cache_fs = new ReadRequestCacheFS(); + consent_request_cache_fs = new ConsentRequestCache(); + write_request_cache_mem = new WriteRequestCacheMEM(); + read_request_cache_mem = new ReadRequestCacheMEM(); +} + +inline void delete_caches() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + delete read_request_cache_fs; + delete consent_request_cache_fs; + delete write_request_cache_mem; + delete read_request_cache_mem; + + delete cts_queue; + LOG("Removed cts_queue"); + delete stc_queue; + LOG("Removed stc_queue"); +} + +#endif // CAPIO_CACHE_HPP \ No newline at end of file diff --git a/capio-posix/utils/cache/consent_request_cache.hpp b/capio-posix/utils/cache/consent_request_cache.hpp new file mode 100644 index 000000000..54a8d6c83 --- /dev/null +++ b/capio-posix/utils/cache/consent_request_cache.hpp @@ -0,0 +1,58 @@ +#ifndef CONSENT_REQUEST_CACHE_HPP +#define CONSENT_REQUEST_CACHE_HPP + +class ConsentRequestCache { + std::unordered_map *available_consent; + + // Block until the server allows for proceeding to a generic request + static capio_off64_t _consent_to_proceed_request(const std::filesystem::path &path, + const long tid, + const std::string &source_func) { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s, tid=%ld, source_func=%s)", path.c_str(), + tid, source_func.c_str()); + char req[CAPIO_REQ_MAX_SIZE]; + sprintf(req, "%04d %ld %s %s", CAPIO_REQUEST_CONSENT, tid, path.c_str(), + source_func.c_str()); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); + capio_off64_t res = bufs_response->at(tid)->read(); + LOG("Obtained from server %llu", res); + return res; + } + + public: + explicit ConsentRequestCache() { + available_consent = new std::unordered_map; + }; + + ~ConsentRequestCache() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + delete available_consent; + }; + + void consent_request(const std::filesystem::path &path, long tid, + const std::string &source_func) const { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s, tid=%ld, source=%s)", path.c_str(), tid, + source_func.c_str()); + + const auto resolved_path = resolve_possible_symlink(path); + + if (!is_capio_path(resolved_path)) { + LOG("PATH is forbidden. Skipping request!"); + return; + } + + /** + * If entry is not present in cache, then proceed to perform request. othrewise if present, + * there is no need to perform request to server and can proceed + */ + if (!available_consent->contains(resolved_path)) { + LOG("File not present in cache. performing request"); + auto res = _consent_to_proceed_request(resolved_path, tid, source_func); + LOG("Registering new file for consent to proceed"); + available_consent->emplace(resolved_path, res); + } + LOG("Unlocking thread"); + } +}; + +#endif // CONSENT_REQUEST_CACHE_HPP \ No newline at end of file diff --git a/capio-posix/utils/cache/read_request_cache_fs.hpp b/capio-posix/utils/cache/read_request_cache_fs.hpp new file mode 100644 index 000000000..89c2c1949 --- /dev/null +++ b/capio-posix/utils/cache/read_request_cache_fs.hpp @@ -0,0 +1,81 @@ +#ifndef READ_REQUEST_CACHE_FS_HPP +#define READ_REQUEST_CACHE_FS_HPP + +class ReadRequestCacheFS { + int current_fd = -1; + capio_off64_t max_read = 0; + std::unordered_map *available_read_cache; + + std::filesystem::path current_path; + + // return amount of readable bytes + static capio_off64_t _read_request(const std::filesystem::path &path, const off64_t end_of_Read, + const long tid, const long fd) { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s, end_of_Read=%ld, tid=%ld, fd=%ld)", + path.c_str(), end_of_Read, tid, fd); + char req[CAPIO_REQ_MAX_SIZE]; + sprintf(req, "%04d %ld %ld %s %ld", CAPIO_REQUEST_READ, tid, fd, path.c_str(), end_of_Read); + LOG("Sending read request %s", req); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); + const capio_off64_t res = bufs_response->at(tid)->read(); + LOG("Response to request is %llu", res); + return res; + } + + public: + explicit ReadRequestCacheFS() { + available_read_cache = new std::unordered_map; + }; + + ~ReadRequestCacheFS() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + delete available_read_cache; + }; + + void read_request(std::filesystem::path path, const long end_of_read, int tid, const int fd) { + START_LOG(capio_syscall(SYS_gettid), "[cache] call(path=%s, end_of_read=%ld, tid=%ld)", + path.c_str(), end_of_read, tid); + if (fd != current_fd || path.compare(current_path) != 0) { + LOG("[cache] %s changed from previous state. updating", + fd != current_fd ? "File descriptor" : "File path"); + current_path = std::move(path); + current_fd = fd; + + if (const auto item = available_read_cache->find(current_path); + item != available_read_cache->end()) { + LOG("[cache] Found file entry in cache"); + max_read = item->second; + } else { + LOG("[cache] Entry not found, initializing new entry to offset 0"); + max_read = 0; + available_read_cache->emplace(current_path, 0); + } + LOG("[cache] Max read value is %llu %s", max_read, + max_read == ULLONG_MAX ? "(ULLONG_MAX)" : ""); + } + + // File is committed if server reports its size to be ULLONG_MAX + if (max_read == ULLONG_MAX) { + LOG("[cache] Returning as file is committed"); + return; + } + + if (static_cast(end_of_read) > max_read) { + LOG("[cache] end_of_read > max_read. Performing server request"); + max_read = _read_request(current_path, end_of_read, tid, fd); + LOG("[cache] Obtained value from server is %llu", max_read); + if (available_read_cache->find(path) == available_read_cache->end()) { + LOG("[cache] Cound not find entry in cache. Adding new entry to cache"); + available_read_cache->emplace(path, max_read); + } else { + available_read_cache->at(path) = max_read; + LOG("[cache] Updating max read value in cache. new value: %llu", + available_read_cache->at(path)); + } + LOG("[cache] completed update from server of max read for file. returning control to " + "application"); + } + }; +}; + +#endif // READ_REQUEST_CACHE_FS_HPP \ No newline at end of file diff --git a/capio-posix/utils/cache/read_request_cache_mem.hpp b/capio-posix/utils/cache/read_request_cache_mem.hpp new file mode 100644 index 000000000..5fbc8da8d --- /dev/null +++ b/capio-posix/utils/cache/read_request_cache_mem.hpp @@ -0,0 +1,197 @@ +#ifndef READ_REQUEST_CACHE_MEM_HPP +#define READ_REQUEST_CACHE_MEM_HPP + +class ReadRequestCacheMEM { + char *_cache; + long _tid; + int _fd; + capio_off64_t _max_line_size, _actual_size, _cache_offset; + capio_off64_t _last_read_end, _real_file_size_commmitted; + bool committed = false; + + /** + * Copy data from the cache internal buffer to the target buffer + * @param buffer + * @param count + */ + void _read(void *buffer, capio_off64_t count) { + START_LOG(capio_syscall(SYS_gettid), "call(count=%ld)", count); + + if (count > 0) { + memcpy(buffer, _cache + _cache_offset, count); + LOG("Read %ld. adding it to _cache_offset of value %ld", count, _cache_offset); + _cache_offset += count; + } + } + + protected: + [[nodiscard]] capio_off64_t read_request(const int fd, const capio_off64_t count, + const long tid, bool use_cache = true) { + START_LOG(capio_syscall(SYS_gettid), "call(fd=%ld, count=%llu, tid=%ld, load_data=%s)", fd, + count, tid, use_cache ? "true" : "false"); + char req[CAPIO_REQ_MAX_SIZE]; + + // send as the last parameter to the server the maximum amount of data that can be read into + // a single line of cache + + auto read_begin_offset = get_capio_fd_offset(fd); + + sprintf(req, "%04d %ld %llu %llu %llu %d %s", CAPIO_REQUEST_READ_MEM, tid, + read_begin_offset, count, _max_line_size, use_cache, get_capio_fd_path(fd).c_str()); + LOG("Sending read request %s", req); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); + capio_off64_t stc_queue_read = bufs_response->at(tid)->read(); + LOG("Response to request is %llu", stc_queue_read); + + if (stc_queue_read >= 0x8000000000000000) { + committed = true; + stc_queue_read -= 0x8000000000000000; + _real_file_size_commmitted = stc_queue_read; + LOG("File is committed. Actual offset is: %ld", stc_queue_read); + } + + if (use_cache) { + stc_queue->read(_cache, stc_queue_read); + _cache_offset = 0; + LOG("Completed fetch of data from server"); + } else { + _actual_size = 0; + _cache_offset = 0; + LOG("Data has not been loaded from server, as load_data==false." + " Load will occur independently"); + } + + return stc_queue_read; + } + + public: + explicit ReadRequestCacheMEM(const long line_size = get_posix_read_cache_line_size()) + : _cache(nullptr), _tid(capio_syscall(SYS_gettid)), _fd(-1), _max_line_size(line_size), + _actual_size(0), _cache_offset(0), _last_read_end(-1) { + _cache = new char[_max_line_size]; + } + + ~ReadRequestCacheMEM() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + delete[] _cache; + } + + void flush() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + if (_cache_offset != _actual_size) { + _actual_size = _cache_offset = 0; + } + committed = false; + _real_file_size_commmitted = -1; + } + + long read(const int fd, void *buffer, capio_off64_t count) { + START_LOG(capio_syscall(SYS_gettid), "call(fd=%d, count=%ld)", fd, count); + + long actual_read_size = 0; + + if (_fd != fd) { + LOG("changed fd from %d to %d: flushing", _fd, fd); + flush(); + _fd = fd; + _last_read_end = get_capio_fd_offset(fd); + } + + // Check if a seek has occurred before and in case in which case flush the cache + // and update the offset to the new value + if (_last_read_end != get_capio_fd_offset(_fd)) { + LOG("A seek() has occurred (_last_read_end=%llu, get_capio_fd_offset=%llu). Performing " + "flush().", + _last_read_end, get_capio_fd_offset(_fd)); + flush(); + _last_read_end = get_capio_fd_offset(_fd); + } + + if (committed && _real_file_size_commmitted == _last_read_end) { + LOG("All file content has been read. Returning 0"); + return 0; + } + + /* + * Check: if read size is greater than the capability of the cache line, bypass + * the cache and perform a read directly to the provided buffer + */ + if (count > _max_line_size) { + LOG("count > _max_line_size. Bypassing cache. Performing read() directly to buffer."); + const auto _read_size = read_request(_fd, count, _tid, false); + stc_queue->read(static_cast(buffer), _read_size); + return _read_size; + } + + // Check if cache is empty or if all the content of the cache has been already consumed + if (_actual_size == 0 || _actual_size == _cache_offset) { + LOG("No data is present locally. performing request."); + const auto size = count < _max_line_size ? count : _max_line_size; + _actual_size = read_request(_fd, size, _tid); + + // Update count for current request. If count exceeds _actual_size, resize it to not + // exceeds the available size on posix application + count = std::min(static_cast(count), _actual_size); + } + + if (count <= _max_line_size - _cache_offset) { + // There is enough data to perform a read + LOG("The requested amount of data can be served without performing a request"); + _read(buffer, count); + actual_read_size = count; + _last_read_end = get_capio_fd_offset(_fd) + count; + set_capio_fd_offset(fd, _last_read_end); + } else { + // There could be some data available already on the cache. Copy that first and then + // proceed to request the other missing data + + const auto first_copy_size = + std::min(_actual_size - _cache_offset, static_cast(count)); + + LOG("Data (or part of it) might be already present. performing first copy of" + " std::min(_actual_size(%llu) - _cache_offset(%llu), count(%llu) = %ld", + _actual_size, _cache_offset, count, first_copy_size); + + _read(buffer, first_copy_size); + _last_read_end = get_capio_fd_offset(_fd) + first_copy_size; + set_capio_fd_offset(fd, get_capio_fd_offset(fd) + first_copy_size); + actual_read_size = first_copy_size; + LOG("actual_read_size incremented to: %ld", actual_read_size); + + // Compute the remaining amount of data to send to client + auto remaining_size = count - first_copy_size; + capio_off64_t copy_offset = first_copy_size; + + while (copy_offset < count && !committed) { + LOG("Need to request still %ld of data from server component", count - copy_offset); + // request a line from the server component through a request + _actual_size = read_request(_fd, remaining_size, _tid); + + if (committed) { + LOG("File has resulted in a commit message. Exiting loop"); + break; + } + + LOG("Available size after request: %ld", _actual_size); + // compute the amount of data that is going to be sent to the client application + auto size_to_send_to_client = + remaining_size < _actual_size ? remaining_size : _actual_size; + + LOG("Sending %ld of data to posix application", size_to_send_to_client); + _read(static_cast(buffer) + copy_offset, size_to_send_to_client); + actual_read_size += size_to_send_to_client; + LOG("actual_read_size incremented to: %ld", actual_read_size); + + copy_offset += size_to_send_to_client; + remaining_size -= size_to_send_to_client; + + _last_read_end = get_capio_fd_offset(_fd) + size_to_send_to_client; + set_capio_fd_offset(fd, _last_read_end); + } + } + + LOG("Read return value: %ld (_last_Read_end = %llu)", actual_read_size, _last_read_end); + return actual_read_size; + } +}; +#endif // READ_REQUEST_CACHE_MEM_HPP \ No newline at end of file diff --git a/capio-posix/utils/cache/write_request_cache_mem.hpp b/capio-posix/utils/cache/write_request_cache_mem.hpp new file mode 100644 index 000000000..fbe7b85a5 --- /dev/null +++ b/capio-posix/utils/cache/write_request_cache_mem.hpp @@ -0,0 +1,100 @@ +#ifndef WRITE_REQUEST_CACHE_MEM_HPP +#define WRITE_REQUEST_CACHE_MEM_HPP + +class WriteRequestCacheMEM { + char *_cache; + long _tid; + int _fd; + off64_t _max_line_size, _actual_size; + capio_off64_t _last_write_end, _last_write_begin; + + void _write(const off64_t count, const void *buffer) { + START_LOG(capio_syscall(SYS_gettid), "call(count=%ld)", count); + + if (count > 0) { + if (_cache == nullptr) { + _cache = cts_queue->reserve(); + } + memcpy(_cache + _actual_size, buffer, count); + _actual_size += count; + if (_actual_size == _max_line_size) { + flush(); + } + } + } + + protected: + void write_request(const off64_t count, const long tid, const char *path, + const capio_off64_t offset) const { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s, count=%ld, offset=%llu)", path, count, + offset); + char req[CAPIO_REQ_MAX_SIZE]; + + sprintf(req, "%04d %ld %s %llu %ld", CAPIO_REQUEST_WRITE_MEM, tid, path, offset, count); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); + } + + public: + explicit WriteRequestCacheMEM(off64_t line_size = get_cache_line_size()) + : _cache(nullptr), _tid(capio_syscall(SYS_gettid)), _fd(-1), _max_line_size(line_size), + _actual_size(0), _last_write_end(-1), _last_write_begin(0) {} + + ~WriteRequestCacheMEM() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + this->flush(); + } + + void flush() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + if (_actual_size != 0) { + LOG("Actual size: %ld", _actual_size); + write_request(_actual_size, _tid, get_capio_fd_path(_fd).c_str(), _last_write_begin); + _cache = nullptr; + _actual_size = 0; + } + LOG("Flush completed"); + } + + void write(int fd, const void *buffer, off64_t count) { + START_LOG(capio_syscall(SYS_gettid), "call(fd=%d, buffer=0x%08x, count=%ld)", fd, buffer, + count); + + if (_fd != fd) { + LOG("changed fd from %d to %d: flushing", _fd, fd); + flush(); + _fd = fd; + _last_write_end = -1; + } + + // Check if a seek has occurred before and in case in which case flush the cache + // and update the offset to the new value + if (_last_write_end != get_capio_fd_offset(_fd)) { + flush(); + _last_write_begin = get_capio_fd_offset(_fd) + _actual_size; + } + + if (count <= _max_line_size - _actual_size) { + LOG("count %ld <= _max_line_size - _actual_size %ld", count, + _max_line_size - _actual_size); + _write(count, buffer); + } else { + LOG("count %ld > _max_line_size - _actual_size %ld", count, + _max_line_size - _actual_size); + flush(); + if (count - _actual_size > _max_line_size) { + LOG("count - _actual_size %ld > _max_line_size %ld", count - _actual_size, + _max_line_size); + write_request(count, _tid, get_capio_fd_path(_fd).c_str(), _last_write_begin); + cts_queue->write(static_cast(buffer), count); + } else { + LOG("count - _actual_size %ld <= _max_line_size %ld", count - _actual_size, + _max_line_size); + _write(count, buffer); + } + } + _last_write_end = get_capio_fd_offset(fd) + count; + set_capio_fd_offset(fd, _last_write_end); + } +}; + +#endif // WRITE_REQUEST_CACHE_MEM_HPP \ No newline at end of file diff --git a/capio-posix/utils/clone.hpp b/capio-posix/utils/clone.hpp new file mode 100644 index 000000000..650ddc317 --- /dev/null +++ b/capio-posix/utils/clone.hpp @@ -0,0 +1,117 @@ +#ifndef CAPIO_POSIX_UTILS_CLONE_HPP +#define CAPIO_POSIX_UTILS_CLONE_HPP + +#include +#include + +#include "capio/syscall.hpp" + +#include "requests.hpp" + +inline std::mutex clone_mutex; +inline std::condition_variable clone_cv; +inline std::unordered_set *tids; + +inline bool is_capio_tid(const pid_t tid) { + const std::lock_guard lg(clone_mutex); + return tids->find(tid) != tids->end(); +} + +inline void register_capio_tid(const pid_t tid) { + START_LOG(syscall_no_intercept(SYS_gettid), "call(tid=%ld)", tid); + const std::lock_guard lg(clone_mutex); + tids->insert(tid); +} + +inline void remove_capio_tid(const pid_t tid) { + START_LOG(syscall_no_intercept(SYS_gettid), "call(tid=%ld)", tid); + std::lock_guard lg(clone_mutex); + if (tids->find(tid) != tids->end()) { + tids->erase(tid); + } +} + +inline void init_threading_support() { tids = new std::unordered_set{}; } + +inline void init_process(pid_t tid) { + START_LOG(syscall_no_intercept(SYS_gettid), "call(tid=%ld)", tid); + + syscall_no_intercept_flag = true; + + auto *p_buf_response = new ResponseQueue(SHM_COMM_CHAN_NAME_RESP + std::to_string(tid)); + bufs_response->insert(std::make_pair(tid, p_buf_response)); + + LOG("Created request response buffer with name: %s", + (SHM_COMM_CHAN_NAME_RESP + std::to_string(tid)).c_str()); + + const char *capio_app_name = get_capio_app_name(); + auto pid = static_cast(syscall_no_intercept(SYS_gettid)); + + /** + * The previous if, for an anonymous handshake was present, however the get_capio_app_name() + * never returns a nullptr, as there is a default name, thus rendering the + * handshake_anonymous_request() useless + */ + handshake_request(tid, pid, capio_app_name); + + syscall_no_intercept_flag = false; +} + +inline void hook_clone_child() { + auto tid = static_cast(syscall_no_intercept(SYS_gettid)); + +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; + + /* + * This piece of code is aimed at addressing issues with applications that spawn several + * thousand threads that only do computations. When this occurs, under some circumstances CAPIO + * might fail to allocate shared memory objects. As such, if child threads ONLY do computation, + * we can safely ignore them with CAPIO. + */ + thread_local char *skip_child = std::getenv("CAPIO_IGNORE_CHILD_THREADS"); + if (skip_child != nullptr) { + auto skip_child_str = std::string(skip_child); + if (skip_child_str == "ON" || skip_child_str == "TRUE" || skip_child_str == "YES") { + return; + } + } + +#endif + std::unique_lock lock(clone_mutex); + clone_cv.wait(lock, [&tid] { return tids->find(tid) != tids->end(); }); + + /** + * Freeing memory here through `tids.erase()` can cause a SIGSEGV error + * in the libc, which tries to load the `__ctype_b_loc` table but fails + * because it is not initialized yet. For this reason, a thread's `tid` + * is removed from the `tids` set only when the thread terminates. + */ + lock.unlock(); + START_SYSCALL_LOGGING(); + START_LOG(tid, "call()"); + LOG("Initializing child thread %d", tid); + init_process(tid); + LOG("Child thread %d initialized", tid); + LOG("Starting child thread %d", tid); + init_caches(); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif +} + +inline void hook_clone_parent(long child_tid) { + SUSPEND_SYSCALL_LOGGING(); + auto parent_tid = static_cast(syscall_no_intercept(SYS_gettid)); + START_LOG(parent_tid, "call(parent_tid=%d, child_tid=%d)", parent_tid, child_tid); + + if (child_tid < 0) { + LOG("Skipping clone as child tid is set to %d: %s", child_tid, std::strerror(child_tid)); + return; + } + + register_capio_tid(child_tid); + clone_cv.notify_all(); +} + +#endif // CAPIO_POSIX_UTILS_CLONE_HPP \ No newline at end of file diff --git a/capio-posix/utils/common.hpp b/capio-posix/utils/common.hpp new file mode 100644 index 000000000..5f6be981a --- /dev/null +++ b/capio-posix/utils/common.hpp @@ -0,0 +1,27 @@ +#include + +#ifndef CAPIO_FUNCTIONS_H +#define CAPIO_FUNCTIONS_H + +inline int posix_return_value(long res, long *result) { + START_LOG(capio_syscall(SYS_gettid), "cal(res=%ld)", res); + if (res != CAPIO_POSIX_SYSCALL_REQUEST_SKIP) { + *result = (res < 0 ? -errno : res); + LOG("SYSCALL handled by capio. errno is: %s", res < 0 ? strerror(-errno) : "none"); + return CAPIO_POSIX_SYSCALL_SUCCESS; + } + LOG("SYSCALL delegated to the kernel"); + return CAPIO_POSIX_SYSCALL_SKIP; +} + +inline bool is_file_to_store_in_memory(std::filesystem::path &path, const long pid) { + if (paths_to_store_in_memory == nullptr) { + file_in_memory_request(pid); + } + + return std::any_of( + paths_to_store_in_memory->begin(), paths_to_store_in_memory->end(), + [&path](const std::regex ®ex) { return std::regex_match(path.string(), regex); }); +} + +#endif // CAPIO_FUNCTIONS_H \ No newline at end of file diff --git a/capio-posix/utils/env.hpp b/capio-posix/utils/env.hpp new file mode 100644 index 000000000..cba743fa0 --- /dev/null +++ b/capio-posix/utils/env.hpp @@ -0,0 +1,63 @@ +#ifndef CAPIO_POSIX_UTILS_ENV_HPP +#define CAPIO_POSIX_UTILS_ENV_HPP + +#include +#include + +#include "capio/logger.hpp" + +inline const char *get_capio_app_name() { + static char *capio_app_name = std::getenv("CAPIO_APP_NAME"); + + if (capio_app_name == nullptr) { + return CAPIO_DEFAULT_APP_NAME; + } + return capio_app_name; +} + +inline capio_off64_t get_capio_write_cache_size() { + static char *cache_size_str = std::getenv("CAPIO_WRITER_CACHE_SIZE"); + + static capio_off64_t cache_size = cache_size_str == nullptr + ? CAPIO_CACHE_LINE_SIZE_DEFAULT + : std::strtoull(cache_size_str, nullptr, 10); + return cache_size; +} + +inline long get_posix_read_cache_line_size() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + static long data_bufs_count = -1; + if (data_bufs_count == -1) { + LOG("Value not set. getting value"); + char *value = std::getenv("CAPIO_POSIX_CACHE_LINE_SIZE"); + if (value != nullptr) { + LOG("Getting value from environment variable"); + data_bufs_count = strtol(value, nullptr, 10); + } else { + LOG("Getting default value"); + data_bufs_count = CAPIO_CACHE_LINE_SIZE_DEFAULT; + } + } + LOG("data_bufs_count=%ld", data_bufs_count); + return data_bufs_count; +} + +inline bool store_in_memory_only() { + START_LOG(capio_syscall(SYS_gettid), "call()"); + static bool *store_in_memory = nullptr; + + if (store_in_memory == nullptr) { + store_in_memory = new bool; + char *value = std::getenv("CAPIO_STORE_ONLY_MEMORY"); + if (value != nullptr) { + *store_in_memory = strcmp("ON", value) == 0; + } else { + *store_in_memory = false; + } + } + + LOG("Store files exclusively in memory? %s", *store_in_memory ? "YES" : "NO"); + return *store_in_memory; +} + +#endif // CAPIO_POSIX_UTILS_ENV_HPP \ No newline at end of file diff --git a/capio-posix/utils/filesystem.hpp b/capio-posix/utils/filesystem.hpp new file mode 100644 index 000000000..04f3b64f1 --- /dev/null +++ b/capio-posix/utils/filesystem.hpp @@ -0,0 +1,362 @@ +#ifndef CAPIO_POSIX_UTILS_FILESYSTEM_HPP +#define CAPIO_POSIX_UTILS_FILESYSTEM_HPP + +#include +#include +#include +#include +#include +#include + +#include + +#include "capio/env.hpp" +#include "capio/filesystem.hpp" +#include "capio/logger.hpp" +#include "capio/syscall.hpp" + +#include "types.hpp" + +inline CPFileDescriptors_t *capio_files_descriptors; +inline CPFilesPaths_t *capio_files_paths; +inline std::unique_ptr current_dir; +inline CPFiles_t *files; + +/** + * Set the CLOEXEC property of a file descriptor in metadata structures + * @param fd + * @param is_cloexec + * @return + */ +inline void set_capio_fd_cloexec(int fd, bool is_cloexec) { + std::get<3>(files->at(fd)) = is_cloexec; +} + +/** + * Get the current directory + * @return the current directory + */ +inline const std::filesystem::path &get_current_dir() { return *current_dir; } + +/** + * Add a path to metadata structures + * @param path + * @return + */ +inline void add_capio_path(const std::string &path) { + capio_files_paths->emplace(path, std::unordered_set{}); +} + +/** + * Add a file descriptor to metadata structures. is_cloexec is used to handle whether the fd needs + * to be closed before executing an execve or not (fd persists if is_cloexec == true) + * @param path + * @param fd + * @return + */ +inline void add_capio_fd(pid_t tid, const std::string &path, int fd, capio_off64_t offset, + bool is_cloexec) { + START_LOG(tid, "call(path=%s, fd=%d)", path.c_str(), fd); + add_capio_path(path); + LOG("Added capio path %s", path.c_str()); + capio_files_paths->at(path).insert(fd); + LOG("Inserted tid %d for path %s", tid, path.c_str()); + capio_files_descriptors->insert({fd, path}); + LOG("Inserted file descriptor tuple"); + files->insert({fd, {std::make_shared(offset), 0, 0, is_cloexec}}); + LOG("Registered file"); +} + +/** + * Compute the absolute path for @pathname + * @param pathname + * @return + */ +inline std::filesystem::path capio_posix_realpath(const std::filesystem::path &pathname) { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s)", pathname.c_str()); + char *posix_real_path = capio_realpath((char *) pathname.c_str(), nullptr); + + // if capio_realpath fails, then it should be a capio_file + if (posix_real_path == nullptr) { + LOG("path is null due to errno='%s'", strerror(errno)); + + if (pathname.is_absolute()) { + LOG("Path=%s is already absolute", pathname.c_str()); + return {pathname}; + } + + std::filesystem::path new_path = (*current_dir / pathname).lexically_normal(); + if (is_capio_path(new_path)) { + LOG("Computed absolute path = %s", new_path.c_str()); + return new_path; + } + + LOG("file %s is not a posix file, nor a capio file!", pathname.c_str()); + return {}; + } + + // if not, then check for realpath through libc implementation + LOG("Computed realpath = %s", posix_real_path); + return {posix_real_path}; +} + +/** + * Return the absolute path for the @path argument + * + * @param path + * @return + */ +inline std::filesystem::path capio_absolute(const std::filesystem::path &path) { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s)", path.c_str()); + if (!path.is_absolute()) { + LOG("PATH is not absolute"); + auto absolute = capio_posix_realpath(path); + LOG("Computed absolute path is %s", absolute.c_str()); + return absolute; + } + LOG("Path is absolute"); + return path; +} + +/** + * Delete a file descriptor from metadata structures + * @param fd + * @return + */ +inline void delete_capio_fd(pid_t fd) { + START_LOG(capio_syscall(SYS_gettid), "call(fd=%d)", fd); + auto &path = capio_files_descriptors->at(fd); + capio_files_paths->at(path).erase(fd); + capio_files_descriptors->erase(fd); + files->erase(fd); +} + +/** + * Delete a file or folder from metadata structures + * @param path + * @return + */ +inline void delete_capio_path(const std::string &path) { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s)", path.c_str()); + if (capio_files_paths->find(path) != capio_files_paths->end()) { + auto it = capio_files_paths->at(path).begin(); + LOG("Proceeding to remove fds"); + while (it != capio_files_paths->at(path).end()) { + delete_capio_fd(*it++); + } + LOG("Proceeding to remove path from capio_files_paths"); + capio_files_paths->erase(path); + LOG("Cleanup completed"); + } +} + +/** + * Destroy metadata structures + * @return + */ +inline void destroy_filesystem() { + current_dir.reset(); + delete capio_files_descriptors; + delete capio_files_paths; + delete files; +} + +/** + * Clone a file descriptor in metadata structures + * @param tid + * @param oldfd + * @param newfd + * @return + */ +inline void dup_capio_fd(pid_t tid, int oldfd, int newfd, bool is_cloexec) { + const std::string &path = capio_files_descriptors->at(oldfd); + capio_files_paths->at(path).insert(newfd); + files->insert({newfd, files->at(oldfd)}); + set_capio_fd_cloexec(newfd, is_cloexec); + capio_files_descriptors->insert({newfd, capio_files_descriptors->at(oldfd)}); +} + +/** + * Check if a file descriptor exists in metadata structures + * @param fd + * @return if the file descriptor exists + */ +inline bool exists_capio_fd(pid_t fd) { + START_LOG(capio_syscall(SYS_gettid), "call(fd=%d)", fd); + return files->find(fd) != files->end(); +} + +/** + * Get the CLOEXEC property of a file descriptor in metadata structures + * @param fd + * @return the CLOEXEC property + */ +inline bool get_capio_fd_cloexec(int fd) { return std::get<3>(files->at(fd)); } + +/** + * Get the path of a file descriptor + * @param fd + * @return the file descriptor path + */ +inline const std::string &get_capio_fd_path(int fd) { return capio_files_descriptors->at(fd); } + +/** + * Get the current offset of a file descriptor + * @param fd + * @return the current offset + */ +inline capio_off64_t get_capio_fd_offset(int fd) { return *std::get<0>(files->at(fd)); } + +/** + * Get all the file descriptors stored in metadata structures + * @return a vector of file descriptors + */ +inline std::vector get_capio_fds() { + std::vector fds; + fds.reserve(files->size()); + for (auto &file : *files) { + fds.push_back(file.first); + } + return fds; +} + +/** + * Get the corresponding path from a dirfd file descriptor + * @param dirfd + * @return the corresponding path + */ +inline std::filesystem::path get_dir_path(int dirfd) { + START_LOG(capio_syscall(SYS_gettid), "call(dirfd=%d)", dirfd); + + auto it = capio_files_descriptors->find(dirfd); + if (it != capio_files_descriptors->end()) { + LOG("dirfd %d points to path %s", dirfd, it->second.c_str()); + return it->second; + } + LOG("dirfd %d not found. Computing it through proclnk", dirfd); + char proclnk[128] = {}; + char dir_pathname[PATH_MAX] = {}; + sprintf(proclnk, "/proc/self/fd/%d", dirfd); + if (capio_syscall(SYS_readlinkat, AT_FDCWD, proclnk, dir_pathname, PATH_MAX) < 0) { + LOG("failed to readlink\n"); + return {}; + } + LOG("dirfd %d points to path %s", dirfd, dir_pathname); + return {dir_pathname}; +} + +/** + * Initialize metadata structures + * @return + */ +inline void init_filesystem() { + std::unique_ptr buf(new char[PATH_MAX]); + capio_syscall(SYS_getcwd, buf.get(), PATH_MAX); + current_dir = std::make_unique(buf.get()); + capio_files_descriptors = new CPFileDescriptors_t(); + capio_files_paths = new CPFilesPaths_t(); + files = new CPFiles_t(); +} + +/** + * Rename a file or folder in metadata structures + * @param oldpath + * @param newpath + * @return + */ +inline void rename_capio_path(const std::string &oldpath, const std::string &newpath) { + START_LOG(capio_syscall(SYS_gettid), "call(oldpath=%s, newpath=%s)", oldpath.c_str(), + newpath.c_str()); + if (capio_files_paths->find(oldpath) != capio_files_paths->end()) { + auto entry = capio_files_paths->extract(oldpath); + entry.key() = newpath; + capio_files_paths->insert(std::move(entry)); + for (auto fd : capio_files_paths->at(newpath)) { + capio_files_descriptors->at(fd).assign(newpath); + } + } else { + LOG("Warning: olpath not found in capio_files_paths"); + } +} + +/** + * Set the offset of a file descriptor in metadata structures + * @param fd + * @param offset + * @return + */ +inline void set_capio_fd_offset(int fd, capio_off64_t offset) { + START_LOG(capio_syscall(SYS_gettid), "call(fd=%d, offset=%lld)", fd, offset); + LOG("Previous offset = %lld", *std::get<0>(files->at(fd))); + *std::get<0>(files->at(fd)) = offset; + LOG("New offset = %lld", *std::get<0>(files->at(fd))); +} + +/** + * Change the current directory + * @return the current directory + */ +inline void set_current_dir(const std::filesystem::path &cwd) { + current_dir = std::make_unique(cwd); +} + +/** + * Resolve a possible symbolic link to the absolute path that it points to + * @param input_path + * @return + */ +[[maybe_unused]] [[nodiscard]] static std::string +resolve_possible_symlink(const std::filesystem::path &input_path) { + + // Cache for resolved symbolic links: link -> realpath + static std::unordered_map resolved_symlinks_cache; + + if (!resolved_symlinks_cache.contains(input_path)) { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s)", input_path.c_str()); + + LOG("Absolute path = %s", input_path.c_str()); + +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; +#endif + + std::filesystem::path resolved; + + for (std::filesystem::path input_abs_path = std::filesystem::absolute(input_path); + const auto &part : input_abs_path) { + resolved /= part; + + if (part == "." || part.empty()) { + continue; + } + if (part == "..") { + resolved = resolved.parent_path(); + continue; + } + if (std::filesystem::is_symlink(resolved)) { + char buf[PATH_MAX]{0}; + auto result = + capio_syscall(SYS_readlinkat, AT_FDCWD, resolved.c_str(), buf, sizeof(buf) - 1); + if (result == -1) { + LOG("File might not exist. path was %s and Error is %s", resolved.c_str(), + strerror(errno)); + continue; + } + + if (std::filesystem::path target(buf); target.is_relative()) { + resolved = resolved.parent_path() / target; + } else { + resolved = target; + } + } + } +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif + + resolved_symlinks_cache[input_path] = resolved; + } + return resolved_symlinks_cache[input_path]; +} + +#endif // CAPIO_POSIX_UTILS_FILESYSTEM_HPP \ No newline at end of file diff --git a/capio-posix/utils/requests.hpp b/capio-posix/utils/requests.hpp new file mode 100644 index 000000000..8b3c3c14e --- /dev/null +++ b/capio-posix/utils/requests.hpp @@ -0,0 +1,161 @@ +#ifndef CAPIO_POSIX_UTILS_REQUESTS_HPP +#define CAPIO_POSIX_UTILS_REQUESTS_HPP + +#include + +#include "capio/requests.hpp" +#include +#include + +#include "env.hpp" +#include "filesystem.hpp" +#include "types.hpp" + +inline thread_local std::vector *paths_to_store_in_memory = nullptr; + +inline CircularBuffer *buf_requests; +inline std::unordered_map *bufs_response; + +inline thread_local SPSCQueue *cts_queue; +inline thread_local SPSCQueue *stc_queue; + +#include "cache.hpp" + +/** + * Initialize request and response buffers + * @return + */ +inline void init_client() { + buf_requests = + new CircularBuffer(SHM_COMM_CHAN_NAME, CAPIO_REQ_BUFF_CNT, CAPIO_REQ_MAX_SIZE); + bufs_response = new std::unordered_map(); +} + +/** + * Perform handshake. + * @param tid + * @param pid + * @param app_name + */ +inline void handshake_request(const long tid, const long pid, const std::string &app_name) { + START_LOG(capio_syscall(SYS_gettid), "call(tid=%ld, pid=%ld, app_name=%s)", tid, pid, + app_name.c_str()); + + cts_queue = new SPSCQueue("queue-" + std::to_string(tid) + ".cts", get_cache_lines(), + get_cache_line_size(), get_capio_workflow_name(), true); + stc_queue = new SPSCQueue("queue-" + std::to_string(tid) + ".stc", get_cache_lines(), + get_cache_line_size(), get_capio_workflow_name(), true); + LOG("Initialized data transfer queues"); + + char req[CAPIO_REQ_MAX_SIZE]; + sprintf(req, "%04d %ld %ld %s", CAPIO_REQUEST_HANDSHAKE, tid, pid, app_name.c_str()); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); + + LOG("Waiting for response from capio_server"); + if (bufs_response->at(pid)->read() == 0) { + ERR_EXIT("Error: handshake request sent while capio_server is shutting down!"); + } +} + +/** + * File in memory requests: server returns the amount of paths that needs to be obtained from the + * server to know which files are going to be treated during write and read operations inside memory + * @param pid + * @return + */ +inline std::vector *file_in_memory_request(const long pid) { + START_LOG(capio_syscall(SYS_gettid), "call(pid=%ld)", pid); + char req[CAPIO_REQ_MAX_SIZE]; + + sprintf(req, "%04d %ld ", CAPIO_REQUEST_QUERY_MEM_FILE, pid); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); + LOG("Sent query for which file to store in memory"); + capio_off64_t files_to_read_from_queue = bufs_response->at(pid)->read(); + LOG("Need to read %llu files from data queues", files_to_read_from_queue); + const auto regex_vector = new std::vector; + for (capio_off64_t i = 0; i < files_to_read_from_queue; i++) { + LOG("Reading file number %d", i); + auto file = new char[PATH_MAX]{}; + stc_queue->read(file, PATH_MAX); + LOG("Obtained path %s", file); + + if (file[0] == '*') { + LOG("Obtained all file regex. converting it to be coherent with CAPIO paths"); + auto c_dir = get_capio_dir().string(); + memcpy(file, c_dir.c_str(), c_dir.length()); + memcpy(file + c_dir.size(), "/*", 2); + LOG("Generated path relative to CAPIO_DIR: %s", file); + } + + regex_vector->emplace_back(generateCapioRegex(file)); + delete[] file; + } + return regex_vector; +} + +inline capio_off64_t posix_directory_committed_request(const long pid, + const std::filesystem::path &path, + char *token_path) { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s)", path.c_str()); + char req[CAPIO_REQ_MAX_SIZE]; + + sprintf(req, "%04d %ld %s ", CAPIO_REQUEST_POSIX_DIR_COMMITTED, pid, path.c_str()); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); + LOG("Sent query for directory committement"); + capio_off64_t path_len = bufs_response->at(pid)->read(); + LOG("Directory %s has the token length of %llu", path.c_str(), path_len); + + stc_queue->read(token_path, path_len); + LOG("commit token path will exist at %s", token_path); + return path_len; +} + +// non blocking +inline void close_request(const std::filesystem::path &path, const long tid) { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s, tid=%ld)", path.c_str(), tid); + char req[CAPIO_REQ_MAX_SIZE]; + sprintf(req, "%04d %ld %s", CAPIO_REQUEST_CLOSE, tid, path.c_str()); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); +} + +// non blocking +inline void create_request(const int fd, const std::filesystem::path &path, const long tid) { + START_LOG(capio_syscall(SYS_gettid), "call(fd=%ld, path=%s, tid=%ld)", fd, path.c_str(), tid); + char req[CAPIO_REQ_MAX_SIZE]; + sprintf(req, "%04d %ld %d %s", CAPIO_REQUEST_CREATE, tid, fd, path.c_str()); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); +} + +// non blocking +inline void exit_group_request(const long tid) { + START_LOG(capio_syscall(SYS_gettid), "call(tid=%ld)", tid); + char req[CAPIO_REQ_MAX_SIZE]; + sprintf(req, "%04d %ld", CAPIO_REQUEST_EXIT_GROUP, tid); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); +} + +// block until open is possible +[[nodiscard]] inline capio_off64_t open_request(const int fd, const std::filesystem::path &path, + const long tid) { + START_LOG(capio_syscall(SYS_gettid), "call(fd=%ld, path=%s, tid=%ld)", fd, path.c_str(), tid); + char req[CAPIO_REQ_MAX_SIZE]; + sprintf(req, "%04d %ld %d %s", CAPIO_REQUEST_OPEN, tid, fd, path.c_str()); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); + const capio_off64_t res = bufs_response->at(tid)->read(); + LOG("Obtained from server %llu. File is %s exclude", res, res == 0 ? "" : "NOT"); + return res; +} + +// non blocking +inline void rename_request(const std::filesystem::path &old_path, + const std::filesystem::path &new_path, const long tid) { + START_LOG(capio_syscall(SYS_gettid), "call(old=%s, new=%s, tid=%ld)", old_path.c_str(), + new_path.c_str(), tid); + char req[CAPIO_REQ_MAX_SIZE]; + sprintf(req, "%04d %ld %s %s", CAPIO_REQUEST_RENAME, tid, old_path.c_str(), new_path.c_str()); + buf_requests->write(req, CAPIO_REQ_MAX_SIZE); +} + +#include "utils/storage.hpp" + +#endif // CAPIO_POSIX_UTILS_REQUESTS_HPP \ No newline at end of file diff --git a/src/posix/utils/snapshot.hpp b/capio-posix/utils/snapshot.hpp similarity index 53% rename from src/posix/utils/snapshot.hpp rename to capio-posix/utils/snapshot.hpp index ac5fe8d51..4399ab0f4 100644 --- a/src/posix/utils/snapshot.hpp +++ b/capio-posix/utils/snapshot.hpp @@ -9,34 +9,58 @@ #include "types.hpp" +/** + * EXPLANATION OF SNAPSHOT. + * From the execve manual: By default, file descriptors remain open across an execve(). + * File descriptors that are marked close-on-exec are closed; + * + * This means that the snapshot is used to move file descriptor metadata (that is handled by capio) + * forward to the new process through shared memory. at the execve syscall, a snapshot is created, + * by storing the required information and then it gets inherited by the new process, removing the + * FDs opened with FD_CLOEXEC + */ + inline int *get_fd_snapshot(long tid) { - return static_cast(get_shm_if_exist("capio_snapshot_" + std::to_string(tid))); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; +#endif + const auto return_value = + static_cast(get_shm_if_exist("capio_snapshot_" + std::to_string(tid))); +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif + return return_value; } -void initialize_from_snapshot(const int *fd_shm, long tid) { +inline void initialize_from_snapshot(const int *fd_shm, pid_t tid) { START_LOG(tid, "call(%ld)", fd_shm); int i = 0; std::string shm_name; int fd; - off64_t *p_shm; + capio_off64_t *p_shm; char *path_shm; std::string pid = std::to_string(tid); - +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; +#endif while ((fd = fd_shm[i]) != -1) { shm_name = "capio_snapshot_path_" + pid + "_" + std::to_string(fd); path_shm = static_cast(get_shm(shm_name)); + if (munmap(path_shm, PATH_MAX) == -1) { ERR_EXIT("munmap initialize_from_snapshot"); } + if (shm_unlink(shm_name.c_str()) == -1) { ERR_EXIT("shm_unlink snapshot %s", shm_name.c_str()); } + shm_name = "capio_snapshot_" + pid + "_" + std::to_string(fd); - p_shm = static_cast(get_shm(shm_name)); - add_capio_fd(tid, path_shm, fd, p_shm[1], p_shm[2], static_cast(p_shm[3]), - static_cast(p_shm[4])); - if (munmap(p_shm, 6 * sizeof(off64_t)) == -1) { + p_shm = static_cast(get_shm(shm_name)); + add_capio_fd(tid, path_shm, fd, p_shm[1], static_cast(p_shm[2])); + + if (munmap(p_shm, 3 * sizeof(capio_off64_t)) == -1) { ERR_EXIT("munmap 2 initialize_from_snapshot"); } if (shm_unlink(shm_name.c_str()) == -1) { @@ -48,11 +72,14 @@ void initialize_from_snapshot(const int *fd_shm, long tid) { if (shm_unlink(shm_name.c_str()) == -1) { ERR_EXIT("shm_unlink snapshot %s", shm_name.c_str()); } +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif } -void create_snapshot(long tid) { +inline void create_snapshot(long tid) { START_LOG(tid, "call()"); - off64_t *p_shm; + capio_off64_t *p_shm; char *path_shm; std::string pid = std::to_string(tid); @@ -60,20 +87,20 @@ void create_snapshot(long tid) { if (fds.empty()) { return; } - +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = true; +#endif int *fd_shm = static_cast(create_shm("capio_snapshot_" + pid, (fds.size() + 1) * sizeof(int))); int i = 0; for (auto &fd : fds) { fd_shm[i] = fd; - p_shm = (off64_t *) create_shm("capio_snapshot_" + pid + "_" + std::to_string(fd), - 5 * sizeof(off64_t)); + p_shm = (capio_off64_t *) create_shm("capio_snapshot_" + pid + "_" + std::to_string(fd), + 3 * sizeof(capio_off64_t)); p_shm[0] = fd; p_shm[1] = get_capio_fd_offset(fd); - p_shm[2] = get_capio_fd_size(fd); - p_shm[3] = get_capio_fd_flags(fd); - p_shm[4] = get_capio_fd_cloexec(fd); + p_shm[2] = get_capio_fd_cloexec(fd); std::string shm_name = "capio_snapshot_path_" + pid + "_" + std::to_string(fd); path_shm = (char *) create_shm(shm_name, PATH_MAX * sizeof(char)); @@ -81,6 +108,9 @@ void create_snapshot(long tid) { ++i; } fd_shm[i] = -1; +#ifdef __CAPIO_POSIX + syscall_no_intercept_flag = false; +#endif } -#endif // CAPIO_POSIX_UTILS_SNAPSHOT_HPP +#endif // CAPIO_POSIX_UTILS_SNAPSHOT_HPP \ No newline at end of file diff --git a/capio-posix/utils/storage.hpp b/capio-posix/utils/storage.hpp new file mode 100644 index 000000000..6370af8f8 --- /dev/null +++ b/capio-posix/utils/storage.hpp @@ -0,0 +1,23 @@ +#ifndef STORAGE_HPP +#define STORAGE_HPP + +/** + * Check if the given path needs to be stored in memory. + * Request is sent lazily + */ +inline bool store_file_in_memory(const std::filesystem::path &file_name, const long pid) { + START_LOG(capio_syscall(SYS_gettid), "call(path=%s, pid=%ld)", file_name.c_str(), pid); + if (paths_to_store_in_memory == nullptr) { + /* + * Request which files need to be handled in memory instead of file system + */ + LOG("Vector is empty and not allocated. performing request"); + paths_to_store_in_memory = file_in_memory_request(pid); + } + + return std::any_of( + paths_to_store_in_memory->begin(), paths_to_store_in_memory->end(), + [file_name](const std::regex &rx) { return std::regex_match(file_name.string(), rx); }); +} + +#endif // STORAGE_HPP \ No newline at end of file diff --git a/src/posix/utils/types.hpp b/capio-posix/utils/types.hpp similarity index 58% rename from src/posix/utils/types.hpp rename to capio-posix/utils/types.hpp index 1308da32f..ba0e6e3a6 100644 --- a/src/posix/utils/types.hpp +++ b/capio-posix/utils/types.hpp @@ -6,12 +6,13 @@ #include "capio/queue.hpp" -typedef std::unordered_map, off64_t, int, bool>> CPFiles_t; -typedef std::pair CPStatResponse_t; -typedef std::unordered_map *> CPBufResponse_t; +typedef std::unordered_map, capio_off64_t, int, bool>> + CPFiles_t; +typedef std::unordered_map *> CPBufResponse_t; typedef std::unordered_map CPFileDescriptors_t; typedef std::unordered_map> CPFilesPaths_t; typedef int (*CPHandler_t)(long, long, long, long, long, long, long *); -#endif // CAPIO_POSIX_UTILS_TYPES_HPP +#endif // CAPIO_POSIX_UTILS_TYPES_HPP \ No newline at end of file diff --git a/capio-run/.gitignore b/capio-run/.gitignore new file mode 100644 index 000000000..f92bc354f --- /dev/null +++ b/capio-run/.gitignore @@ -0,0 +1,5 @@ +.venv +.idea +*.json +*.sh +__pycache__ \ No newline at end of file diff --git a/capio-run/capiorun b/capio-run/capiorun new file mode 100755 index 000000000..44291b839 --- /dev/null +++ b/capio-run/capiorun @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 + +import argparse +import json +import os +import signal +import subprocess +import sys +import time + +parser = argparse.ArgumentParser( + prog="capiorun", + description=""" +capiorun - Simplified launcher for CAPIO-based applications. + +This utility streamlines the setup and execution of CAPIO workflows by automatically configuring +the environment and managing capio_server instances. It allows running specific application steps +defined in a CAPIO-CL configuration without manual setup of environment variables. + +Typical usage: + capiorun -d /mnt/capio -n myapp -c config.json -- +""", + epilog=""" +Developed by Marco Edoardo Santimaria + +For more information, refer to the CAPIO documentation or repository. +""", + formatter_class=argparse.RawTextHelpFormatter, +) + +# Required arguments +parser.add_argument( + "-d", + "--capio-dir", + required=True, + help="CAPIO virtual mount point (e.g., /mnt/capio)", +) +parser.add_argument( + "-n", + "--app-name", + required=True, + help="Name of the CAPIO application step to launch. Must match an entry in the CAPIO-CL config.", +) + +# Optional but commonly used +parser.add_argument( + "-c", + "--capiocl", + default="--no-config", + help="Path to the CAPIO-CL configuration file (default: --no-config)", +) +parser.add_argument( + "-m", "--metadata-dir", default="", help="Custom directory for metadata" +) + +# Debug and logging +parser.add_argument( + "-L", + "--log-level", + default="-1", + help="CAPIO log level. Useful when running in debug mode (default: -1)", +) +parser.add_argument( + "--log-dir", default="", help="Custom directory for CAPIO log output" +) +parser.add_argument("--log-prefix", default="", help="Prefix for CAPIO log files") + +# Tuning and advanced +parser.add_argument( + "--cache-lines", + default="", + help="Number of CAPIO shm-queue cache lines (optional tuning parameter)", +) +parser.add_argument( + "--init-file-size", + default="", + help="Default file size (in bytes) when pre-allocating memory for new files", +) + +# Binary locations +parser.add_argument( + "-l", + "--libcapio", + default="libcapio_posix.so", + help="Path to libcapio_posix.so shared library (default: libcapio_posix.so)", +) +parser.add_argument( + "-s", + "--server", + default="capio_server", + help="Path to capio_server executable (default: capio_server)", +) + +# Positional arguments +parser.add_argument( + "args", nargs=argparse.REMAINDER, help="Command to launch with capio" +) + + +def build_env(args, workflow_name): + env = os.environ.copy() + if args.log_dir: + env["CAPIO_LOG_DIR"] = args.log_dir + if args.log_prefix: + env["CAPIO_LOG_PREFIX"] = args.log_prefix + if args.cache_lines: + env["CAPIO_CACHE_LINES"] = args.cache_lines + if args.init_file_size: + env["CAPIO_FILE_INIT_SIZE"] = args.init_file_size + + env["CAPIO_DIR"] = args.capio_dir + env["CAPIO_LOG_LEVEL"] = args.log_level + env["CAPIO_WORKFLOW_NAME"] = workflow_name + env["CAPIO_METADATA_DIR"] = args.metadata_dir + + return env + + +def count_files_starting_with(prefix): + return sum( + 1 + for filename in os.listdir("/dev/shm") + if os.path.isfile(os.path.join("/dev/shm", filename)) + and filename.startswith(prefix) + ) + + +server_process = None +step_process = None + +if __name__ == "__main__": + args = parser.parse_args() + + workflow_name = "CAPIO" + if args.capiocl != "--no-config": + with open(args.capiocl, "r") as f: + workflow_name = json.load(f)["name"] + + if not os.path.exists(f"/dev/shm/{workflow_name}"): + print(f"Starting capio server with config file: {args.capiocl}") + print(f"CAPIO_LOG_LEVEL = {args.log_level}") + print(f"CAPIO_WORKFLOW_NAME = {workflow_name}") + print(f"CAPIO_APP_NAME = {args.app_name}") + print(f"CAPIO_DIR = {args.capio_dir}") + print(f"CAPIO-CL CONFIG = {args.capiocl}") + if not os.path.exists(args.capiocl) and args.capiocl != "--no-config": + print(f"File {args.capiocl} does not exists. aborting execution...") + exit(1) + server_env = build_env(args, workflow_name) + + server_process = subprocess.Popen( + [ + args.server, + ( + ("--config " + args.capiocl) + if args.capiocl != "--no-config" + else args.capiocl + ), + ], + env=server_env, + stdout="server.log", + stderr="server.err", + cwd=args.log_dir, + start_new_session=True, + ) + + print(f"capio_server PID: {server_process.pid}") + time.sleep(1) + + else: + print( + f"An instance of capio_server with workflow name {workflow_name} already exists!" + ) + + step_env = build_env(args, workflow_name) + step_env["CAPIO_APP_NAME"] = args.app_name + step_env["LD_PRELOAD"] = args.libcapio + + print(f"Starting workflow steps with following environment variables:") + print(f"CAPIO_LOG_LEVEL = {args.log_level}") + print(f"CAPIO_WORKFLOW_NAME = {workflow_name}") + print(f"CAPIO_APP_NAME = {args.app_name}") + print(f"CAPIO_DIR = {args.capio_dir}") + print(f"LD_PRELOAD = {args.libcapio}") + print(f"command = {' '.join(args.args)}") + try: + step_process = subprocess.Popen( + args.args, + env=step_env, + stdout=sys.stdout, + stderr=sys.stderr, + ) + + step_process.wait() + print(f"Step {args.app_name} terminated successfully") + except Exception as e: + print( + f"An error occurred in startup/execution of workflow app <{args.app_name}>: {e}" + ) + + if server_process is not None: + if count_files_starting_with(workflow_name) >= 4: + print( + "Server instance is used by other applications... skipping server termination" + ) + else: + print(f"Terminating instance of capio_server") + server_process.send_signal(signal.SIGTERM) + time.sleep(2) + print("Terminated CAPIO server instance") + else: + if count_files_starting_with(workflow_name) < 4: + print( + "Terminating instance of capio_server started by other capiorun command" + ) + result = subprocess.run( + ["killall", "capio_server"], stdout=sys.stdout, stderr=sys.stderr + ) + if result.returncode == 0: + print("Terminated CAPIO server instance") + else: + print("Error terminating capio_server instance!") + else: + print("Skipping termination of capio_server") diff --git a/capio-run/readme.md b/capio-run/readme.md new file mode 100644 index 000000000..4e518a14f --- /dev/null +++ b/capio-run/readme.md @@ -0,0 +1,42 @@ +# **`capiorun` – Simplified Launch of CAPIO Applications** + +`capiorun` is a lightweight Python utility designed to streamline the execution of workflow steps with **CAPIO**. It automates the setup of required environment variables and manages the lifecycle of the `capio_server` instance, reducing the manual configuration needed by users. + +If a `capio_server` instance is not already running for a given workflow, `capiorun` will automatically start and manage it. + +--- + +## πŸ“Œ **Parameters** + +| Flag | Description | +|------|-------------| +| `--capio-dir` *(required)* | Specifies the CAPIO virtual mount point. | +| `--capiocl` | Path to the CAPIO-CL configuration file. | +| `--app-name` *(required)* | The name of the application step being launched. Must match an entry in the CAPIO-CL configuration file (`--capiocl`). | +| `--workflow-name` | The name of the workflow to which the application step (`--app-name`) belongs. | +| `--capio-log-level` *(optional)* | Sets the log level if CAPIO is executed in debug mode. | +| `args` | Remaining parameters that represent the executable and its arguments to be run with CAPIO. | + +--- + +## βš™οΈ **Optional Overrides** + +You can also explicitly specify the locations of both `libcapio_posix.so` and the `capio_server` binary: + +| Flag | Description | +|------|-------------| +| `--libcapio` | Path to the `libcapio_posix.so` shared library. | +| `--server` | Path to the `capio_server` executable. | + +--- + +## πŸ§ͺ **Advanced Configuration** + +These optional flags allow fine-tuned control over CAPIO runtime behavior: + +| Flag | Description | +|------|-------------| +| `--log-dir` | Directory where CAPIO should store log files. | +| `--log-prefix` | Prefix to prepend to CAPIO-generated log files. | +| `--cache-lines` | Number of cache lines to be used by CAPIO. Useful for performance tuning. | +| `--init-file-size` | Initial size of CAPIO-managed files upon creation. | diff --git a/capio-run/requirements.txt b/capio-run/requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/server/CMakeLists.txt b/capio-server/CMakeLists.txt similarity index 71% rename from src/server/CMakeLists.txt rename to capio-server/CMakeLists.txt index 4c51715e0..e1630bd01 100644 --- a/src/server/CMakeLists.txt +++ b/capio-server/CMakeLists.txt @@ -2,9 +2,10 @@ # Target information ##################################### set(TARGET_NAME capio_server) -set(TARGET_INCLUDE_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}) -set(TARGET_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/capio_server.cpp + +set(TARGET_INCLUDE_FOLDER .) +file(GLOB_RECURSE TARGET_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" ) ##################################### @@ -13,14 +14,36 @@ set(TARGET_SOURCES FetchContent_Declare( args GIT_REPOSITORY https://github.com/Taywee/args.git - GIT_TAG 6.4.6 + GIT_TAG 6.4.7 ) +set(ARGS_BUILD_EXAMPLE OFF CACHE INTERNAL "") +set(ARGS_BUILD_UNITTESTS OFF CACHE INTERNAL "") + FetchContent_Declare( simdjson GIT_REPOSITORY https://github.com/simdjson/simdjson.git - GIT_TAG v3.3.0 + GIT_TAG v3.10.1 +) +FetchContent_Declare( + mtcl + GIT_REPOSITORY https://github.com/ParaGroup/MTCL + GIT_TAG fbd8aa924b916c6578963af315fdb5f10c3969ad +) + +FetchContent_Declare( + httplib + GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git + GIT_TAG v0.26.0 +) + +FetchContent_Declare( + capio_cl + GIT_REPOSITORY https://github.com/High-Performance-IO/CAPIO-CL.git + GIT_TAG main ) -FetchContent_MakeAvailable(args simdjson) + + +FetchContent_MakeAvailable(args simdjson mtcl httplib capio_cl) ##################################### # Target definition @@ -40,23 +63,16 @@ target_include_directories(${TARGET_NAME} PRIVATE ${MPI_INCLUDE_PATH} ${args_SOURCE_DIR} ${simdjson_SOURCE_DIR} + ${mtcl_SOURCE_DIR}/include + ${httplib_SOURCE_DIR} + ${capio_cl_SOURCE_DIR} + ${capio_cl_SOURCE_DIR}/include ) -##################################### -# Target properties -##################################### -IF (MPI_COMPILE_FLAGS) - set_target_properties(${TARGET_NAME} PROPERTIES COMPILE_FLAGS "${MPI_COMPILE_FLAGS}") -ENDIF (MPI_COMPILE_FLAGS) - -IF (MPI_LINK_FLAGS) - set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "${MPI_LINK_FLAGS}") -ENDIF (MPI_LINK_FLAGS) - ##################################### # Link libraries ##################################### -target_link_libraries(${TARGET_NAME} PRIVATE ${MPI_LIBRARIES} pthread rt stdc++fs) +target_link_libraries(${TARGET_NAME} PRIVATE ${MPI_LIBRARIES} pthread rt stdc++fs libcapio_cl) ##################################### # Install rules diff --git a/capio-server/capio_server.cpp b/capio-server/capio_server.cpp new file mode 100644 index 000000000..eabbcd0f8 --- /dev/null +++ b/capio-server/capio_server.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +capiocl::engine::Engine *capio_cl_engine; + +#include +CapioGlobalConfiguration *capio_global_configuration; + +#include "capio/env.hpp" +#include "capio/logger.hpp" +#include "capio/semaphore.hpp" +#include "include/api-server/api-server.hpp" + +#include +#include +#include +#include +#include +#include +#include + +ClientManager *client_manager; +CapioAPIServer *api_server; +CapioFileManager *file_manager; +FileSystemMonitor *fs_monitor; +RequestHandlerEngine *request_handlers_engine; +CapioStorageService *storage_service; + +int main(int argc, char **argv) { + std::cout << CAPIO_LOG_SERVER_BANNER; + capio_global_configuration = new CapioGlobalConfiguration(); + capio_global_configuration->CAPIO_SERVER_MAIN_PID = gettid(); + + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, + "Started server with PID: " + + std::to_string(capio_global_configuration->CAPIO_SERVER_MAIN_PID)); + + char resolve_prefix[PATH_MAX]{0}; + const std::string config_path = parseCLI(argc, argv, resolve_prefix); + + START_LOG(gettid(), "call()"); + setup_signal_handlers(); + + capio_cl_engine = + config_path.empty() + ? new capiocl::engine::Engine() + : capiocl::parser::Parser::parse(config_path, std::filesystem::path(resolve_prefix)); + + capio_global_configuration->workflow_name = capio_cl_engine->getWorkflowName(); + + shm_canary = new CapioShmCanary(capio_global_configuration->workflow_name); + api_server = new CapioAPIServer(6666); + file_manager = new CapioFileManager(); + fs_monitor = new FileSystemMonitor(); + client_manager = new ClientManager(); + request_handlers_engine = new RequestHandlerEngine(); + storage_service = new CapioStorageService(); + + capio_cl_engine->print(); + + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "server initialization completed!"); + + request_handlers_engine->start(); + + sig_term_handler(SIGTERM, nullptr, nullptr); + + exit(EXIT_SUCCESS); +} \ No newline at end of file diff --git a/capio-server/include/api-server/api-server.hpp b/capio-server/include/api-server/api-server.hpp new file mode 100644 index 000000000..a717e4540 --- /dev/null +++ b/capio-server/include/api-server/api-server.hpp @@ -0,0 +1,58 @@ +#ifndef CAPIO_API_SERVER_HPP +#define CAPIO_API_SERVER_HPP +#include +#include +#include +#include +#include + +inline std::unordered_map api_server_routes_descriptions; + +#define REGISTER_GET_ROUTE(route_name, route_description, callback) \ + api_server_routes_descriptions[route_name] = route_description; \ + svr->Get(route_name, callback); + +class CapioAPIServer { + typedef std::unordered_map ResponseMap; + + std::thread *th; + httplib::Server httplib_server_instance; + static void api_server_main_func(int server_port, httplib::Server *svr); + + static std::string + build_json_response(const std::unordered_map &map) { + + // TODO: implement a correct json mapping in c++ + + ResponseMap response_map = map; + response_map["identity"] = std::string("{ \"hostname\":\"") + + capio_global_configuration->node_name + "\",\"wf_name\":\"" + + capio_global_configuration->workflow_name + "\"},"; + + std::string json_response = "{"; + + for (auto &[key, value] : response_map) { + if (key == "identity") { + json_response += "\"" + key + "\":" + value; + } else { + json_response += "\"" + key + "\":\""; + json_response += value + "\","; + } + } + + // Remove last comma to ensure json validity + if (json_response.back() == ',') { + json_response.pop_back(); + } + + json_response += "}\n"; + return json_response; + } + + public: + explicit CapioAPIServer(int server_port); + ~CapioAPIServer(); +}; + + +#endif // CAPIO_API_SERVER_HPP diff --git a/capio-server/include/client-manager/client_manager.hpp b/capio-server/include/client-manager/client_manager.hpp new file mode 100644 index 000000000..037867241 --- /dev/null +++ b/capio-server/include/client-manager/client_manager.hpp @@ -0,0 +1,74 @@ +#ifndef CLIENT_MANAGER_HPP +#define CLIENT_MANAGER_HPP +#include +#include +#include + +/** + * @brief Class to handle libcapio_posix clients applications + */ +class ClientManager { + std::unordered_map *bufs_response; + std::unordered_map *app_names; + + /** + * Files that are produced by a given pid. Used for Commit On Termination fallback rule + */ + std::unordered_map *> *files_created_by_producer; + + public: + ClientManager(); + + ~ClientManager(); + + /** + * Add a new response buffer for thread @param tid + * @param tid + * @return + */ + void register_client(const std::string &app_name, pid_t tid) const; + + /** + * Delete the response buffer associated with thread @param tid + * @param tid + * @return + */ + void remove_client(pid_t tid) const; + + /** + * Write offset to response buffer of process @param tid + * @param tid + * @param offset + * @return + */ + void reply_to_client(const pid_t tid, const capio_off64_t offset) const; + + /** + * @brief Add a file that is not yet ready to be consumed by a process to a list of files + * waiting to be ready + * + * @param tid + * @param path + */ + void register_produced_file(pid_t tid, std::string &path) const; + + /** + * @brief Get the files that a given pid is waiting to be produced + * + * @param tid + * @return auto + */ + [[nodiscard]] std::vector *get_produced_files(pid_t tid) const; + + /** + * @brief Get the app name given a process pid + * + * @param tid + * @return std::string + */ + [[nodiscard]] std::string get_app_name(pid_t tid) const; + + size_t get_connected_posix_client(); +}; + +#endif // CLIENT_MANAGER_HPP \ No newline at end of file diff --git a/capio-server/include/client-manager/handlers.hpp b/capio-server/include/client-manager/handlers.hpp new file mode 100644 index 000000000..1986ca65f --- /dev/null +++ b/capio-server/include/client-manager/handlers.hpp @@ -0,0 +1,94 @@ +#ifndef HANDLERS_HPP +#define HANDLERS_HPP + +/** + * @brief Handle the close systemcall + * + * @param str raw request as read from the shared memory interface stripped of the request number + * (first parameter of the request) + */ +void close_handler(const char *const str); + +/** + * @brief Handle the consent to proceed request. This handler only checks whether the conditions for + * a systemcall to continue are met. + * + * @param str raw request as read from the shared memory interface stripped of the request number + * (first parameter of the request) + */ +void consent_to_proceed_handler(const char *const str); + +/** + * @brief Handle the create systemcall + * + * @param str raw request as read from the shared memory interface stripped of the request number + * (first parameter of the request) + */ +void create_handler(const char *const str); + +/** + * @brief Handle the exit systemcall + * + * @param str raw request as read from the shared memory interface stripped of the request number + * (first parameter of the request) + */ +void exit_handler(const char *const str); + +/** + * @brief Handle the exit systemcall + * + * @param str raw request as read from the shared memory interface stripped of the request number + * (first parameter of the request) + */ +void files_to_store_in_memory_handler(const char *const str); + +/** + * @brief Perform handshake while providing the posix application name + * + * @param str raw request as read from the shared memory interface stripped of the request number + * (first parameter of the request) + */ +void handshake_handler(const char *const str); + +/** + * @brief Handle the open systemcall + * + * @param str raw request as read from the shared memory interface stripped of the request number + * (first parameter of the request) + */ +void open_handler(const char *const str); + +/** + * + */ +void posix_readdir_handler(const char *const str); + +/** + * @brief Handle the read systemcall + * + * @param str raw request as read from the shared memory interface stripped of the request number + * (first parameter of the request) + */ +void read_handler(const char *const str); + +void read_mem_handler(const char *const str); + +/** + * @brief Handle the rename systemcall + * + * @param str raw request as read from the shared memory interface stripped of the request number + * (first parameter of the request) + */ +void rename_handler(const char *const str); + +/** + * @brief Handle the write systemcall + * + * @param str raw request as read from the shared memory interface stripped of the request number + * (first parameter of the request) + */ +void write_handler(const char *const str); + +void write_mem_handler(const char *const str); + +#endif // HANDLERS_HPP diff --git a/capio-server/include/client-manager/request_handler_engine.hpp b/capio-server/include/client-manager/request_handler_engine.hpp new file mode 100644 index 000000000..95d79ae69 --- /dev/null +++ b/capio-server/include/client-manager/request_handler_engine.hpp @@ -0,0 +1,38 @@ +#ifndef CAPIO_CL_ENGINE_MAIN_HPP +#define CAPIO_CL_ENGINE_MAIN_HPP + +#include "capio/requests.hpp" + +#include + +/** + * @brief Class that handles the system calls received from the posix client application + * + */ +class RequestHandlerEngine { + std::array request_handlers{}; + CSBufRequest_t *buf_requests; + + static constexpr std::array build_request_handlers_table(); + + /** + * Read next incoming request into @param str and returns the request code + * @param str + * @return request code + */ + inline auto read_next_request(char *str) const; + + public: + explicit RequestHandlerEngine(); + + ~RequestHandlerEngine(); + + /** + * @brief Start the main loop on the main thread that will read each request one by one from all + * the posix clients (aggregated) and handle the response + * + */ + void start() const; +}; + +#endif // CAPIO_CL_ENGINE_MAIN_HPP \ No newline at end of file diff --git a/capio-server/include/communication-service/capio_communication_service.hpp b/capio-server/include/communication-service/capio_communication_service.hpp new file mode 100644 index 000000000..75dbb4d88 --- /dev/null +++ b/capio-server/include/communication-service/capio_communication_service.hpp @@ -0,0 +1,17 @@ +#ifndef CAPIOCOMMUNICATIONSERVICE_HPP +#define CAPIOCOMMUNICATIONSERVICE_HPP + +#include + +class CapioCommunicationService { + + public: + ~CapioCommunicationService(); + + CapioCommunicationService(std::string &backend_name, const int port, + const std::string &control_backend_name); +}; + +inline CapioCommunicationService *capio_communication_service; + +#endif // CAPIOCOMMUNICATIONSERVICE_HPP diff --git a/capio-server/include/communication-service/control-plane/capio_control_plane.hpp b/capio-server/include/communication-service/control-plane/capio_control_plane.hpp new file mode 100644 index 000000000..11c83d9ce --- /dev/null +++ b/capio-server/include/communication-service/control-plane/capio_control_plane.hpp @@ -0,0 +1,30 @@ +#ifndef CAPIO_CONTROL_PLANE_HPP +#define CAPIO_CONTROL_PLANE_HPP +#include + +class CapioControlPlane { + public: + typedef enum { CREATE, WRITE, CLOSE, COMMIT } event_type; + + virtual ~CapioControlPlane() = default; + + /** + * Notify a single host of the occurrence of an event + * @param event + * @param path + * @param hostname_target + */ + void notify(event_type event, const std::filesystem::path &path, + const std::string &hostname_target) {} + + /** + * Notify all nodes of the occurrence of an event + * @param event + * @param path + */ + virtual void notify_all(event_type event, const std::filesystem::path &path) = 0; +}; + +inline CapioControlPlane *capio_control_plane; + +#endif // CAPIO_CONTROL_PLANE_HPP diff --git a/capio-server/include/communication-service/control-plane/fs_control_plane.hpp b/capio-server/include/communication-service/control-plane/fs_control_plane.hpp new file mode 100644 index 000000000..651eb65d8 --- /dev/null +++ b/capio-server/include/communication-service/control-plane/fs_control_plane.hpp @@ -0,0 +1,36 @@ +#ifndef FS_CONTROL_PLANE_HPP +#define FS_CONTROL_PLANE_HPP + +#include +#include +#include +#include + +class FSControlPlane : public CapioControlPlane { + char ownHostname[HOST_NAME_MAX] = {0}; + int _backend_port; + bool *continue_execution; + std::thread *thread; + std::vector token_used_to_connect; + std::mutex *token_used_to_connect_mutex; + + void generate_aliveness_token(int port) const; + + void delete_aliveness_token(); + + /* + * Monitor the file system for the presence of tokens. + */ + static void fs_server_aliveness_detector_thread(const bool *continue_execution, + std::vector *token_used_to_connect, + std::mutex *token_used_to_connect_mutex); + + public: + explicit FSControlPlane(int backend_port); + + ~FSControlPlane(); + + void notify_all(event_type event, const std::filesystem::path &path); +}; + +#endif // FS_CONTROL_PLANE_HPP diff --git a/capio-server/include/communication-service/control-plane/multicast_control_plane.hpp b/capio-server/include/communication-service/control-plane/multicast_control_plane.hpp new file mode 100644 index 000000000..431e33e4a --- /dev/null +++ b/capio-server/include/communication-service/control-plane/multicast_control_plane.hpp @@ -0,0 +1,31 @@ +#ifndef MULTICAST_CONTROL_PLANE_HPP +#define MULTICAST_CONTROL_PLANE_HPP +#include +#include +#include +#include +#include + +class MulticastControlPlane : public CapioControlPlane { + bool *continue_execution; + std::thread *discovery_thread, *controlpl_incoming; + std::vector token_used_to_connect; + std::mutex *token_used_to_connect_mutex; + char ownHostname[HOST_NAME_MAX] = {0}; + + static void multicast_server_aliveness_thread(const bool *continue_execution, + std::vector *token_used_to_connect, + std::mutex *token_used_to_connect_mutex, + int dataplane_backend_port); + + static void multicast_control_plane_incoming_thread(const bool *continue_execution); + + public: + explicit MulticastControlPlane(int dataplane_backend_port); + + ~MulticastControlPlane() override; + + void notify_all(event_type event, const std::filesystem::path &path) override; +}; + +#endif // MULTICAST_CONTROL_PLANE_HPP diff --git a/capio-server/include/communication-service/data-plane/backend_interface.hpp b/capio-server/include/communication-service/data-plane/backend_interface.hpp new file mode 100644 index 000000000..d9ff1d2c6 --- /dev/null +++ b/capio-server/include/communication-service/data-plane/backend_interface.hpp @@ -0,0 +1,144 @@ +#ifndef BACKEND_INTERFACE_HPP +#define BACKEND_INTERFACE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class MessageQueue { + class ResponseToRequest { + public: + std::string original_request; + char *response; + capio_off64_t response_size; + + ResponseToRequest(std::string request, char *buf, capio_off64_t buff_size) + : original_request(std::move(request)), response(buf), response_size(buff_size) {} + }; + + std::queue request_queue; + std::queue response_queue; + + std::mutex request_mutex; + std::mutex response_mutex; + + std::condition_variable request_cv; + std::condition_variable response_cv; + + public: + void push_request(const std::string &request) { + START_LOG(gettid(), "call(req=%s)", request.c_str()); + { + LOG("Locking request_mutex"); + std::lock_guard lg(request_mutex); + LOG("Obtained lock"); + request_queue.emplace(request); + } + request_cv.notify_one(); + } + + std::optional try_get_request() { + START_LOG(gettid(), "call()"); + LOG("Locking request_mutex"); + std::lock_guard lg(request_mutex); + LOG("Obtained lock"); + if (request_queue.empty()) { + return std::nullopt; + } + std::string req = std::move(request_queue.front()); + request_queue.pop(); + return req; + } + + void push_response(char *buffer, capio_off64_t buff_size, std::string origin) { + START_LOG(gettid(), "call(origin=%s, buff_size=%ld)", origin.c_str(), buff_size); + { + std::lock_guard lg(response_mutex); + LOG("Obtained lock"); + response_queue.emplace(std::move(origin), buffer, buff_size); + } + response_cv.notify_one(); + } + + std::tuple get_response() { + START_LOG(gettid(), "call()"); + std::unique_lock lk(response_mutex); + response_cv.wait(lk, [this] { return !response_queue.empty(); }); + LOG("Obtained lock"); + auto response = std::move(response_queue.front()); + response_queue.pop(); + return {response.response_size, response.response}; + } +}; + +class NotImplementedBackendMethod : public std::exception { + public: + [[nodiscard]] const char *what() const noexcept override { + auto msg = new char[1024]{}; + sprintf(msg, "The chosen backend does not implement method: %s", __func__); + return msg; + } +}; + +class BackendInterface { + protected: + typedef enum { HAVE_FINISH_SEND_REQUEST, FETCH_FROM_REMOTE } BackendRequest_t; + + public: + virtual ~BackendInterface() = default; + + /** + * @param hostname_port who to connect to in the form of hostname:port + */ + virtual void connect_to(std::string hostname_port) { throw NotImplementedBackendMethod(); }; + + /** Fetch a chunk of CapioFile internal data from remote host + * + * @param hostname Hostname to request data from + * @param filepath Path of the file targeted by the request + * @param offset Offset relative to the beginning of the file from which to read from + * @param count Size of @param buffer and hence size of the fetch operation + * @return Tuple of size of buffer and pointer to char* with the actual buffer + */ + virtual std::tuple fetchFromRemoteHost(const std::string &hostname, + const std::filesystem::path &filepath, + capio_off64_t offset, + capio_off64_t count) { + throw NotImplementedBackendMethod(); + }; + + /** + * + * @return A vector of hostnames for which a connection exists + */ + virtual std::vector get_open_connections() { throw NotImplementedBackendMethod(); } +}; + +/* + * This class implements a placeholder for backend interface, whenever CAPIO is only providing IO + * coordination + */ +class NoBackend final : public BackendInterface { + public: + void connect_to(std::string hostname_port) override { return; }; + std::tuple fetchFromRemoteHost(const std::string &hostname, + const std::filesystem::path &filepath, + capio_off64_t offset, + capio_off64_t count) override { + return {-1, nullptr}; + }; + + std::vector get_open_connections() override { return {}; } +}; + +inline BackendInterface *capio_backend; +#endif // BACKEND_INTERFACE_HPP diff --git a/capio-server/include/communication-service/data-plane/mtcl_backend.hpp b/capio-server/include/communication-service/data-plane/mtcl_backend.hpp new file mode 100644 index 000000000..8d2330fdb --- /dev/null +++ b/capio-server/include/communication-service/data-plane/mtcl_backend.hpp @@ -0,0 +1,82 @@ +#ifndef MTCL_BACKEND_HPP +#define MTCL_BACKEND_HPP + +#include +#include +#include +#include + +/** + * This avoids it to include the MTCL library here as it is a header-only library. + * this is equivalent to use extern in C but for class + */ +namespace MTCL { +class HandleUser; +} + +class MTCLBackend : public BackendInterface { + + std::string selfToken, connectedHostname, ownPort, usedProtocol; + std::unordered_map open_connections; + char ownHostname[HOST_NAME_MAX] = {0}; + int thread_sleep_times = 0; + bool *continue_execution = new bool; + std::mutex *_guard; + std::thread *th; + std::vector connection_threads; + bool *terminate; + + /** + * This thread handles a single p2p connection with another capio_server instance + * @param HandlerPointer A MTCL valid HandlePointer + * @param remote_hostname The remote endpoint of the connection handled by this thread + * @param queue A pointer to a queue to communicate with other components. Queue has pointers to + * inbound and outbound sub-queues for inbound and outbound messages + * @param sleep_time How long to sleep between thread cycle + * @param terminate A reference to a boolean heap allocated variable that is controlled by the + * main thread that tells when to terminate the execution + */ + void static serverConnectionHandler(MTCL::HandleUser HandlerPointer, + const std::string &remote_hostname, MessageQueue *queue, + int sleep_time, const bool *terminate); + + /** + * Waits for incoming new requests to connect to new server instances. When a new request + * arrives, it then handshakes with the remote servers, opening a new connection, and starting a + * new thread that will handle remote requests + * + * @param continue_execution + * @param sleep_time + * @param open_connections + * @param guard + * @param _connection_threads + * @param terminate + */ + void static incomingConnectionListener( + const bool *continue_execution, int sleep_time, + std::unordered_map *open_connections, std::mutex *guard, + std::vector *_connection_threads, bool *terminate); + + /** + * Explode request into request code and request arguments + * @param req Original request string as received from the remote node + * @param args output string with parameters of the request + * @return request code + */ + static int read_next_request(char *req, char *args); + + public: + explicit MTCLBackend(const std::string &proto, const std::string &port, int sleep_time); + + ~MTCLBackend() override; + + void connect_to(std::string hostname_port) override; + + std::vector get_open_connections() override; + std::tuple fetchFromRemoteHost(const std::string &hostname, + const std::filesystem::path &filepath, + capio_off64_t offset, + capio_off64_t count) override; +}; + +#endif // MTCL_BACKEND_HPP diff --git a/capio-server/include/file-manager/file_manager.hpp b/capio-server/include/file-manager/file_manager.hpp new file mode 100644 index 000000000..ef0078976 --- /dev/null +++ b/capio-server/include/file-manager/file_manager.hpp @@ -0,0 +1,139 @@ +#ifndef FILE_MANAGER_HEADER_HPP +#define FILE_MANAGER_HEADER_HPP + +#include +#include +#include +#include + +inline std::mutex creation_mutex; +inline std::mutex data_mutex; +/** + * @brief Class that handle all the information related to the files handled by capio, as well + * metadata for such files. + * + */ +class CapioFileManager { + std::unordered_map> thread_awaiting_file_creation; + std::unordered_map> thread_awaiting_data; + + /** + * @brief Creates the directory structure for the metadata file and proceed to return the path + * pointing to the metadata token file. For improvements in performances, a hash map is included + * to cache the computed paths. For thread safety concerns, see + * https://en.cppreference.com/w/cpp/container#Thread_safety + * + * @param path real path of the file + * @return std::string with the translated capio token metadata path + */ + static std::string getAndCreateMetadataPath(const std::string &path); + + /** + * @brief Awakes all threads waiting for the creation of a file + * + * @param path file that has just been created + * @param pids + */ + static void _unlockThreadAwaitingCreation(const std::string &path, + const std::vector &pids); + + /** + * @brief Loop between all thread registered on the file path, and check for each + * one if enough data has been produced. If so, unlock and remove the thread + * + * @param path + * @param pids_awaiting + */ + static void _unlockThreadAwaitingData(const std::string &path, + std::unordered_map &pids_awaiting); + + public: + CapioFileManager() { + START_LOG(gettid(), "call()"); + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "CapioFileManager initialization completed."); + } + + ~CapioFileManager() { + START_LOG(gettid(), "call()"); + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "CapioFileManager cleanup completed."); + } + + /** + * @brief Get the file size + * + * @param path + * @return uintmax_t file size if file exists, 0 otherwise + */ + static uintmax_t get_file_size_if_exists(const std::filesystem::path &path); + static std::string getMetadataPath(const std::string &path); + + /** + * @brief Update the CAPIO metadata n_close option by adding one to the current value + * + * @param path + */ + static void increaseCloseCount(const std::filesystem::path &path); + + /** + * @brief Returns whether the file is committed or not + * + * @param path + * @return true if is committed + * @return false if it is not + */ + static bool isCommitted(const std::filesystem::path &path); + + /** + * @brief Set a CAPIO handled file to be committed + * + * @param path + */ + static void setCommitted(const std::filesystem::path &path); + + /** + * @brief Set all the files that are currently open, or have been open by a given process to be + * committed + * + * @param tid + */ + static void setCommitted(pid_t tid); + + /** + * @brief Register a process waiting on a file to exist and with a file size of at least the + * expected_size parameter. + * + * @param path + * @param tid + * @param expected_size + */ + void addThreadAwaitingData(const std::string &path, int tid, size_t expected_size); + + /** + * @brief Register a thread to the threads waiting for a file to exists (inside the + * CapioFSMonitor) for a given file path to exists + * + * @param path + * @param tid + */ + void addThreadAwaitingCreation(const std::string &path, pid_t tid); + + /** + * @brief Check for threads awaiting file creation and unlock threads waiting on them + * + */ + void checkFilesAwaitingCreation(); + + /** + * @brief check if there are threads waiting for data, and for each one of them check if the + * file has enough data + * + */ + void checkFileAwaitingData(); + + /** + * @brief commit directories that have NFILES inside them if their commit rule is n_files + */ + void checkDirectoriesNFiles() const; +}; + +#endif // FILE_MANAGER_HEADER_HPP \ No newline at end of file diff --git a/capio-server/include/file-manager/fs_monitor.hpp b/capio-server/include/file-manager/fs_monitor.hpp new file mode 100644 index 000000000..a8330d59b --- /dev/null +++ b/capio-server/include/file-manager/fs_monitor.hpp @@ -0,0 +1,35 @@ +#ifndef CAPIO_FS_FILE_SYSTEM_MONITOR_HPP +#define CAPIO_FS_FILE_SYSTEM_MONITOR_HPP +#include +#include + +/** + * @brief Class that monitors the composition of the CAPIO_DIR directory. + * + */ +class FileSystemMonitor { + std::thread *th; + + bool *continue_execution = new bool; + + static void print_message_error(const std::string &func, const std::exception &exception); + + public: + /** + * @brief Main thread execution loop. Main idea is to check + * whether the files exists on the file system. Then if they exists, wake both thread waiting + * for file existence and files waiting for data, as the check on the file size (ie. if there is + * enough data) is carried out by the CapioFileManager class and not by the file_system monitor + * component itself. At creation a thread is spawned that will continue until a process signals + * it to stop by setting the continue_execution parameter to false. + * + * @param continue_execution + */ + static void _main(const bool *continue_execution); + + explicit FileSystemMonitor(); + + ~FileSystemMonitor(); +}; + +#endif // CAPIO_FS_FILE_SYSTEM_MONITOR_HPP \ No newline at end of file diff --git a/capio-server/include/storage-service/capio_file.hpp b/capio-server/include/storage-service/capio_file.hpp new file mode 100644 index 000000000..60521d7d0 --- /dev/null +++ b/capio-server/include/storage-service/capio_file.hpp @@ -0,0 +1,188 @@ +#ifndef CAPIO_FILE_HPP +#define CAPIO_FILE_HPP + +#include +#include +#include +#include +#include +#include + +class CapioFile { + protected: + const std::string fileName, homeNode; + std::size_t totalSize; + + public: + explicit CapioFile(const std::string &filePath, + const std::string &home_node = capio_global_configuration->node_name) + : fileName(filePath), homeNode(home_node), totalSize(0) {}; + virtual ~CapioFile() = default; + + virtual bool is_remote() { return false; } + + [[nodiscard]] std::size_t getSize() const { + START_LOG(gettid(), "call()"); + return totalSize; + } + + [[nodiscard]] const std::string &getFileName() const { return fileName; } + + /** + * Write data to a file stored inside the memory + * @param buffer buffer to store inside memory (i.e. content of the file) + * @param file_offset offset internal to the file + * @param buffer_length Size of the buffer. + */ + virtual std::size_t writeData(const char *buffer, std::size_t file_offset, + std::size_t buffer_length) = 0; + /** + * Read from Capio File + * @param buffer Buffer to read to + * @param file_offset Starting offset of read operation from CapioMemFile + * @param buffer_size Length of buffer + * @return number of bytes read from CapioMemoryFile + */ + virtual std::size_t readData(char *buffer, std::size_t file_offset, + std::size_t buffer_size) = 0; + + /** + * Store data inside the CapioMemoryFile by reading it from a SPSCQueue object. Behaves just + * like the writeData method + * @param queue + * @param offset + * @param length + */ + virtual void readFromQueue(SPSCQueue &queue, std::size_t offset, std::size_t length) = 0; + + /** + * Write the content of the capioFile to a SPSCQueue object + * @param queue + * @param offset + * @param length + * @return + */ + virtual std::size_t writeToQueue(SPSCQueue &queue, std::size_t offset, + std::size_t length) const = 0; + + /** + * + * @return HomeNode of file + */ + virtual std::string getHomeNode() { return homeNode; }; +}; + +class CapioMemoryFile : public CapioFile { + std::map> memoryBlocks; + + // Static file sizes of file pages + static constexpr u_int32_t _pageSizeMB = 4; + static constexpr u_int64_t _pageSizeBytes = _pageSizeMB * 1024 * 1024; + + char *cross_page_buffer_view; + + /** + * Compute the offsets required to handle write operations onto CapioMemoryFile + * @param offset Offset from the start of the file, on which the write operation will begin + * @param length Size of the buffer that will be written into memory + * @return tuple + */ + static auto compute_offsets(const std::size_t offset, std::size_t length); + + /** + * Retrieve a block with memory already reserved at a given offset + * @param id + * @return + */ + std::vector &get_block(u_int64_t id); + + public: + explicit CapioMemoryFile(const std::string &filePath); + + ~CapioMemoryFile(); + + /** + * Write data to a file stored inside the memory + * @param buffer buffer to store inside memory (i.e. content of the file) + * @param file_offset offset internal to the file + * @param buffer_length Size of the buffer. + */ + std::size_t writeData(const char *buffer, const std::size_t file_offset, + std::size_t buffer_length) override; + + /** + * Read from Capio File + * @param buffer Buffer to read to + * @param file_offset Starting offset of read operation from CapioMemFile + * @param buffer_size Length of buffer + * @return number of bytes read from CapioMemoryFile + */ + std::size_t readData(char *buffer, std::size_t file_offset, std::size_t buffer_size); + + /** + * Store data inside the CapioMemoryFile by reading it from a SPSCQueue object. Behaves just + * like the writeData method + * @param queue + * @param offset + * @param length + */ + void readFromQueue(SPSCQueue &queue, std::size_t offset, std::size_t length) override; + + /** + * Write the content of the capioFile to a SPSCQueue object + * @param queue + * @param offset + * @param length + * @return + */ + std::size_t writeToQueue(SPSCQueue &queue, std::size_t offset, + std::size_t length) const override; +}; + +class CapioRemoteFile : public CapioFile { + public: + explicit CapioRemoteFile(const std::string &filePath, const std::string &home_node); + + ~CapioRemoteFile() override; + + bool is_remote() override { return true; }; + + /** + * Write data to a file stored inside the memory + * @param buffer buffer to store inside memory (i.e. content of the file) + * @param file_offset offset internal to the file + * @param buffer_length Size of the buffer. + */ + std::size_t writeData(const char *buffer, const std::size_t file_offset, + std::size_t buffer_length) override; + + /** + * Read from Capio File + * @param buffer Buffer to read to + * @param file_offset Starting offset of read operation from CapioMemFile + * @param buffer_size Length of buffer + * @return number of bytes read from CapioMemoryFile + */ + std::size_t readData(char *buffer, std::size_t file_offset, std::size_t buffer_size); + + /** + * Store data inside the CapioMemoryFile by reading it from a SPSCQueue object. Behaves just + * like the writeData method + * @param queue + * @param offset + * @param length + */ + void readFromQueue(SPSCQueue &queue, std::size_t offset, std::size_t length) override; + + /** + * Write the content of the capioFile to a SPSCQueue object + * @param queue + * @param offset + * @param length + * @return + */ + std::size_t writeToQueue(SPSCQueue &queue, std::size_t offset, + std::size_t length) const override; +}; + +#endif // CAPIO_FILE_HPP diff --git a/capio-server/include/storage-service/capio_storage_service.hpp b/capio-server/include/storage-service/capio_storage_service.hpp new file mode 100644 index 000000000..7f76c5a51 --- /dev/null +++ b/capio-server/include/storage-service/capio_storage_service.hpp @@ -0,0 +1,129 @@ +#ifndef CAPIO_STORAGE_SERVICE_H +#define CAPIO_STORAGE_SERVICE_H + +#include +#include +#include + +class CapioStorageService { + // TODO: put all of this conde on a different thread + + std::unordered_map *_client_to_server_queue; + std::unordered_map *_server_to_client_queue; + std::unordered_map *_stored_files; + + std::unordered_map>> + *_threads_waiting_for_memory_data; + + /** + * Return a file if exists. if not, create it and then return it + * @param file_name + * @return + */ + [[nodiscard]] auto getFile(const std::string &file_name) const; + + public: + CapioStorageService(); + + ~CapioStorageService(); + + void createMemoryFile(const std::string &file_name) const; + + /** + * Create a CapioRemoteFile, after checking that an instance of CapioMemoryFile (meaning a local + * file) is not present + * @param file_name file path + * @param home_node + */ + void createRemoteFile(const std::string &file_name, const std::string &home_node) const; + + void deleteFile(const std::string &file_name) const; + + /** + * Notify the occurrence of an operation on a given file, for which other servers running at a + * certain point might be required to know. This function is used to allow CAPIO to function in + * the event that a distributed file system (or at least a shared directory) is not available + */ + void notifyEvent(const std::string &event_name, const std::filesystem::path &filename) const; + + /** + * Add a new thread in the list of thhreads awaiting for expected_size to be available + * @param tid + * @param path + * @param offset + * @param size + */ + void addThreadWaitingForData(pid_t tid, const std::string &path, capio_off64_t offset, + capio_off64_t size) const; + + void check_and_unlock_thread_awaiting_data(const std::string &path); + + /** + * Return size of given path as contained inside memory + * @param path + * @return + */ + size_t sizeOf(const std::string &path) const; + + /** + * Initialize a new client data structure + * @param app_name + * @param pid + */ + void register_client(const std::string &app_name, pid_t pid) const; + + /** + * Send the file content to a client application + * @param pid + * @param file + * @param offset + * @param size + * @return size sent to client + */ + size_t reply_to_client(pid_t pid, const std::string &file, capio_off64_t offset, + capio_off64_t size) const; + + /** + * Send raw data to client without fetching from the storage manager itself + * @param pid + * @param data + * @param len + */ + void reply_to_client_raw(pid_t pid, const char *data, capio_off64_t len) const; + + /** + * Receive the file content from the client application + * @param tid + * @param file + * @param offset + * @param size + */ + void recive_from_client(pid_t tid, const std::string &file, capio_off64_t offset, + off64_t size) const; + + /** + * Return a list of regex used to match files that need to be stored inside memory first + * to a posix application + * @param pid + * @return + */ + [[nodiscard]] size_t sendFilesToStoreInMemory(long pid) const; + + void remove_client(pid_t pid) const; + + void storeData(const std::filesystem::path &path, capio_off64_t offset, capio_off64_t buff_size, + const char *buffer) const; + + /** + * Read data from file and store it inside buffer + * @param filepath filepath + * @param offset starting read offset + * @param buffer targeted read buffer + * @param count Requested read size + * @return Actual size of read + */ + size_t readFromFileToBuffer(const std::filesystem::path &filepath, capio_off64_t offset, + char *buffer, capio_off64_t count) const; +}; + +#endif // CAPIO_STORAGE_SERVICE_H \ No newline at end of file diff --git a/capio-server/include/utils/command_line_parser.hpp b/capio-server/include/utils/command_line_parser.hpp new file mode 100644 index 000000000..ec5ec19f5 --- /dev/null +++ b/capio-server/include/utils/command_line_parser.hpp @@ -0,0 +1,15 @@ +#ifndef COMMAND_LINE_PARSER_HPP +#define COMMAND_LINE_PARSER_HPP +#include +#include + +/** + * Parse command line arguments. + * + * @param return value with string that contains the resolve prefix to use when parsing capio_cl + * file + * @return capio_cl configuration path + */ +std::string parseCLI(int argc, char **argv, char *resolve_prefix); + +#endif // COMMAND_LINE_PARSER_HPP diff --git a/capio-server/include/utils/configuration.hpp b/capio-server/include/utils/configuration.hpp new file mode 100644 index 000000000..cbb9e2221 --- /dev/null +++ b/capio-server/include/utils/configuration.hpp @@ -0,0 +1,45 @@ +#ifndef CAPIO_CONFIGURATION_HPP +#define CAPIO_CONFIGURATION_HPP + +#include "capio/constants.hpp" + +#include +#include +#include +#include +#include + +/* + * Variables required to be globally available + * to all classes and subclasses. + */ +class CapioGlobalConfiguration { + public: + bool termination_phase, StoreOnlyInMemory; + std::string workflow_name; + pid_t CAPIO_SERVER_MAIN_PID = -1; + char node_name[HOST_NAME_MAX]{0}; + + CapioGlobalConfiguration() { + gethostname(node_name, HOST_NAME_MAX); + termination_phase = false; + StoreOnlyInMemory = false; + CAPIO_SERVER_MAIN_PID = -1; + workflow_name = CAPIO_DEFAULT_WORKFLOW_NAME; + } +}; + +extern CapioGlobalConfiguration* capio_global_configuration; + +inline void server_println(const std::string &message_type = "", + const std::string &message_line = "") { + if (message_type.empty()) { + std::cout << std::endl; + } else { + std::cout << message_type << " " << capio_global_configuration->node_name << "] " + << message_line << std::endl + << std::flush; + } +} + +#endif // CAPIO_CONFIGURATION_HPP \ No newline at end of file diff --git a/capio-server/include/utils/distributed_semaphore.hpp b/capio-server/include/utils/distributed_semaphore.hpp new file mode 100644 index 000000000..cd73bee5c --- /dev/null +++ b/capio-server/include/utils/distributed_semaphore.hpp @@ -0,0 +1,37 @@ +#ifndef DISTRIBUTEDSEMAPHORE_HPP +#define DISTRIBUTEDSEMAPHORE_HPP + +#include + +/** + * @brief Class to provide mutually exclusive access to files on distributed file systems by + * providing file based locking + */ +class DistributedSemaphore { + std::string name; + timespec sleep{}; + bool locked; + int fp; + + void lock(); + + void unlock() const; + + public: + /** + * @brief Construct a new Distributed Semaphore object. At instantiation of the object, the + * _lock() method is created this locking the resource. If the locking fails, a new attempt is + * done after sleep_time nanoseconds + * + * @param locking The file that is the target to mutual exclusive access + * @param sleep_time Sleep time between lock tries in nano seconds + */ + DistributedSemaphore(std::string locking, int sleep_time); + + /** + * @brief Destroy the Distributed Semaphore object thus unlocking the resource + * + */ + ~DistributedSemaphore(); +}; +#endif // DISTRIBUTEDSEMAPHORE_HPP \ No newline at end of file diff --git a/capio-server/include/utils/signals.hpp b/capio-server/include/utils/signals.hpp new file mode 100644 index 000000000..352a9a954 --- /dev/null +++ b/capio-server/include/utils/signals.hpp @@ -0,0 +1,87 @@ +#ifndef CAPIO_SERVER_HANDLERS_SIGNALS_HPP +#define CAPIO_SERVER_HANDLERS_SIGNALS_HPP + +#include +#include + +#include + +extern ClientManager *client_manager; +extern CapioAPIServer *api_server; +extern CapioFileManager *file_manager; +extern FileSystemMonitor *fs_monitor; +extern RequestHandlerEngine *request_handlers_engine; +extern CapioStorageService *storage_service; + +#ifdef CAPIO_COVERAGE +extern "C" void __gcov_dump(void); +#endif + +inline void sig_usr1_handler(int signum, siginfo_t *info, void *ptr) { + // Empty function used to Wake up sleeping threads when the API server has received a + // Termination request. This way the termination phase condition is re-evaluated and the server + // can shut down properly +} + +/** + * @brief Generic handler for incoming signals + * + * @param signum + * @param info + * @param ptr + */ +inline void sig_term_handler(int signum, siginfo_t *info, void *ptr) { + if (gettid() != capio_global_configuration->CAPIO_SERVER_MAIN_PID) { + return; + } + START_LOG(gettid(), "call(signal=[%d] (%s) from process with pid=%ld)", signum, + strsignal(signum), info != nullptr ? info->si_pid : -1); + server_println(); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, "shutting down server"); + + if (signum == SIGSEGV) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, "Segfault detected!"); + } + +#ifdef CAPIO_COVERAGE + __gcov_dump(); +#endif + + delete request_handlers_engine; + delete fs_monitor; + delete capio_communication_service; + delete shm_canary; + delete api_server; + + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "Bye!"); + exit(EXIT_SUCCESS); +} + +/** + * @brief Set the up signal handlers. Note: sigusr1 is only used to wake up from sleep threads + * waiting on queues + * + */ +inline void setup_signal_handlers() { + START_LOG(gettid(), "call()"); + static struct sigaction sigact, sigact_usr1; + + memset(&sigact, 0, sizeof(sigact)); + memset(&sigact_usr1, 0, sizeof(sigact)); + + sigact.sa_sigaction = sig_term_handler; + sigact.sa_flags = SA_SIGINFO; + + sigact_usr1.sa_sigaction = sig_usr1_handler; + sigact_usr1.sa_flags = SA_SIGINFO; + + if ((sigaction(SIGTERM, &sigact, nullptr) | sigaction(SIGILL, &sigact, nullptr) | + sigaction(SIGABRT, &sigact, nullptr) | sigaction(SIGFPE, &sigact, nullptr) | + sigaction(SIGSEGV, &sigact, nullptr) | sigaction(SIGQUIT, &sigact, nullptr) | + sigaction(SIGPIPE, &sigact, nullptr) | sigaction(SIGINT, &sigact, nullptr) | + sigaction(SIGUSR1, &sigact_usr1, nullptr)) == -1) { + ERR_EXIT("sigaction for SIGTERM"); + } +} + +#endif // CAPIO_SERVER_HANDLERS_SIGNALS_HPP diff --git a/capio-server/include/utils/types.hpp b/capio-server/include/utils/types.hpp new file mode 100644 index 000000000..05fbb3431 --- /dev/null +++ b/capio-server/include/utils/types.hpp @@ -0,0 +1,30 @@ +#ifndef CAPIO_SERVER_UTILS_TYPES_HPP +#define CAPIO_SERVER_UTILS_TYPES_HPP + +#include +#include +#include +#include + +#include "capio/queue.hpp" + +/** + * @brief Typedef to store the map of response buffers indexed by process tid + * + */ +typedef std::unordered_map *> CSBufResponse_t; + +/** + * @brief Typedef for the shared memory queue object for incoming requests + * + */ +typedef CircularBuffer CSBufRequest_t; + +/** + * @brief Typedef for the generic capio_server systemcall handler. Required to create direct access + * array of handlers with common interface + * + */ +typedef void (*CSHandler_t)(const char *const); + +#endif // CAPIO_SERVER_UTILS_TYPES_HPP \ No newline at end of file diff --git a/capio-server/src/api-server/api-server.cpp b/capio-server/src/api-server/api-server.cpp new file mode 100644 index 000000000..5d38223ae --- /dev/null +++ b/capio-server/src/api-server/api-server.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +extern ClientManager *client_manager; +extern CapioAPIServer *api_server; + +CapioAPIServer::CapioAPIServer(int server_port) { + th = new std::thread(api_server_main_func, server_port, &httplib_server_instance); + + // Register callback for unknown routes + httplib_server_instance.set_error_handler( + [](const httplib::Request &req, httplib::Response &res) { + ResponseMap map; + map["status"] = std::to_string(res.status); + map["message"] = "Error: Unknown request: " + req.path; + res.set_content(build_json_response(map).c_str(), "application/json"); + }); +} + +CapioAPIServer::~CapioAPIServer() { + httplib_server_instance.stop(); + th->join(); + delete th; + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "API server correctly terminated"); +} + +void CapioAPIServer::api_server_main_func(const int server_port, httplib::Server *svr) { + + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, + "Started API server on port: " + std::to_string(server_port)); + + REGISTER_GET_ROUTE("/", "Get server instance information", + [](const httplib::Request &req, httplib::Response &res) { + ResponseMap map; + map["endpoints"] = "/routes"; + res.set_content(build_json_response(map).c_str(), "application/json"); + }); + + REGISTER_GET_ROUTE("/clients", "Get number connected POSIX clients", + [](const httplib::Request &req, httplib::Response &res) { + ResponseMap map; + map["connected_clients"] = + std::to_string(client_manager->get_connected_posix_client()); + + res.set_content(build_json_response(map).c_str(), "application/json"); + }); + + REGISTER_GET_ROUTE("/terminate", "Terminate gracefully server instance", + [](const httplib::Request &req, httplib::Response &res) { + server_println(CAPIO_SERVER_CLI_LOG_SERVER_WARNING, + "Received shutdown request from API Server"); + capio_global_configuration->termination_phase = true; + ResponseMap map; + map["status"] = "shutting-down"; + res.set_content(build_json_response(map).c_str(), "application/json"); + kill(capio_global_configuration->CAPIO_SERVER_MAIN_PID, + SIGUSR1); // Wake parent child and children + }); + + REGISTER_GET_ROUTE("/routes", "Get all available API-SERVER routes", + [](const httplib::Request &req, httplib::Response &res) { + res.set_content( + build_json_response(api_server_routes_descriptions).c_str(), + "application/json"); + }); + + REGISTER_GET_ROUTE("/status", "Get current server status", + [](const httplib::Request &req, httplib::Response &res) { + ResponseMap map; + map["status"] = capio_global_configuration->termination_phase + ? "shutting-down" + : "running"; + res.set_content(build_json_response(map).c_str(), "application/json"); + }); + + svr->listen("127.0.0.1", server_port); + server_println(CAPIO_SERVER_CLI_LOG_SERVER_ERROR, "API server terminated unexpectedly"); +} diff --git a/capio-server/src/client-manager/client_manager.cpp b/capio-server/src/client-manager/client_manager.cpp new file mode 100644 index 000000000..bbfefd10e --- /dev/null +++ b/capio-server/src/client-manager/client_manager.cpp @@ -0,0 +1,74 @@ +#include + +ClientManager::ClientManager() { + START_LOG(gettid(), "call()"); + bufs_response = new std::unordered_map(); + app_names = new std::unordered_map; + files_created_by_producer = new std::unordered_map *>; + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "ClientManager initialization completed."); +} + +ClientManager::~ClientManager() { + START_LOG(gettid(), "call()"); + delete bufs_response; + delete app_names; + delete files_created_by_producer; + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, "ClientManager cleanup completed."); +} + +void ClientManager::register_client(const std::string &app_name, pid_t tid) const { + START_LOG(gettid(), "call(tid=%ld, app_name=%s)", tid, app_name.c_str()); + // TODO: replace numbers with constexpr + auto *p_buf_response = new ResponseQueue(SHM_COMM_CHAN_NAME_RESP + std::to_string(tid), false); + + bufs_response->insert(std::make_pair(tid, p_buf_response)); + app_names->emplace(tid, app_name); + files_created_by_producer->emplace(tid, new std::vector); +} + +void ClientManager::remove_client(pid_t tid) const { + START_LOG(gettid(), "call(tid=%ld)", tid); + if (const auto it_resp = bufs_response->find(tid); it_resp != bufs_response->end()) { + delete it_resp->second; + bufs_response->erase(it_resp); + } + files_created_by_producer->erase(tid); +} + +void ClientManager::reply_to_client(const pid_t tid, const capio_off64_t offset) const { + START_LOG(gettid(), "call(tid=%ld, offset=%llu)", tid, offset); + if (const auto out = bufs_response->find(tid); out != bufs_response->end()) { + out->second->write(offset); + return; + } + LOG("Err: no such buffer for provided tid"); +} + +void ClientManager::register_produced_file(pid_t tid, std::string &path) const { + START_LOG(gettid(), "call(tid=%ld, path=%s)", tid, path.c_str()); + if (const auto itm = files_created_by_producer->find(tid); + itm != files_created_by_producer->end()) { + itm->second->emplace_back(path); + return; + } + LOG("Error: tis is not present in files_created_by_producers map"); +} + +std::vector *ClientManager::get_produced_files(pid_t tid) const { + START_LOG(gettid(), "call(tid=%ld)", tid); + if (const auto itm = files_created_by_producer->find(tid); + itm == files_created_by_producer->end()) { + files_created_by_producer->emplace(tid, new std::vector()); + } + return files_created_by_producer->at(tid); +} + +std::string ClientManager::get_app_name(pid_t tid) const { + START_LOG(gettid(), "call(tid=%ld)", tid); + if (const auto itm = app_names->find(tid); itm != app_names->end()) { + return itm->second; + } + return CAPIO_DEFAULT_APP_NAME; +} + +size_t ClientManager::get_connected_posix_client() { return bufs_response->size(); } \ No newline at end of file diff --git a/capio-server/src/client-manager/handlers/close.cpp b/capio-server/src/client-manager/handlers/close.cpp new file mode 100644 index 000000000..b6c175b73 --- /dev/null +++ b/capio-server/src/client-manager/handlers/close.cpp @@ -0,0 +1,42 @@ +#include "include/client-manager/client_manager.hpp" + +#include +#include +#include +#include +#include +#include +extern capiocl::engine::Engine *capio_cl_engine; +extern ClientManager *client_manager; +extern CapioFileManager *file_manager; + +void close_handler(const char *const str) { + pid_t tid; + char path[PATH_MAX]; + sscanf(str, "%d %s", &tid, path); + + START_LOG(gettid(), "call(tid=%d, path=%s)", tid, path); + + const std::filesystem::path filename(path); + const auto app_name = client_manager->get_app_name(tid); + + LOG("File needs handling"); + + // Call the set_committed method only if the commit rule is on_close and calling thread is a + // producer + if (capio_cl_engine->getCommitRule(filename) == CAPIO_FILE_COMMITTED_ON_CLOSE && + capio_cl_engine->isProducer(filename, app_name)) { + CapioFileManager::setCommitted(path); + /** + * The increase close count is called only on explicit close() sc, as defined by the + * CAPIO-CL specification. If it were to be called every time the file is committed, then + * an extra increase would occur as by default, at termination all files are committed. + * By calling this only when close sc are occurred, we guarantee the correct count of + * how many close sc occurs. Also, checks are computed to increase the count only if the + * commit count is greater than 1 to avoid unnecessary overhead. + */ + if (capio_cl_engine->getCommitCloseCount(filename) > 1) { + CapioFileManager::increaseCloseCount(path); + } + } +} diff --git a/capio-server/src/client-manager/handlers/consent.cpp b/capio-server/src/client-manager/handlers/consent.cpp new file mode 100644 index 000000000..65a29aec8 --- /dev/null +++ b/capio-server/src/client-manager/handlers/consent.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include +#include +extern capiocl::engine::Engine *capio_cl_engine; +extern ClientManager *client_manager; +extern CapioFileManager *file_manager; + +void consent_to_proceed_handler(const char *const str) { + pid_t tid; + char path[1024], source_func[1024]; + sscanf(str, "%d %s %s", &tid, path, source_func); + START_LOG(gettid(), "call(tid=%d, path=%s, source=%s)", tid, path, source_func); + + const auto app_name = client_manager->get_app_name(tid); + + // Skip operations on CAPIO_DIR + if (!capio_cl_engine->contains(path)) { + LOG("Ignore calls as file should not be treated by CAPIO"); + client_manager->reply_to_client(tid, 1); + return; + } + + if (capio_cl_engine->isExcluded(path)) { + LOG("File should NOT be handled by CAPIO as it is marked as EXCLUDED"); + client_manager->reply_to_client(tid, 1); + return; + } + + if (capio_cl_engine->isProducer(path, app_name)) { + LOG("Application is producer. continuing"); + client_manager->reply_to_client(tid, 1); + return; + } + + if (!std::filesystem::exists(path)) { + LOG("Requested file %s does not exists yet. awaiting for creation", path); + file_manager->addThreadAwaitingCreation(path, tid); + return; + } + + if (capio_cl_engine->isFirable(path)) { + LOG("Mode for file %s is no_update. allowing process to continue", path); + client_manager->reply_to_client(tid, 1); + return; + } + + if (CapioFileManager::isCommitted(path)) { + LOG("It is possible to unlock waiting thread"); + client_manager->reply_to_client(tid, 1); + return; + } + + LOG("File %s is not yet committed. Adding to threads waiting for committed with ULLONG_MAX", + path); + file_manager->addThreadAwaitingData(path, tid, ULLONG_MAX); +} diff --git a/capio-server/src/client-manager/handlers/create.cpp b/capio-server/src/client-manager/handlers/create.cpp new file mode 100644 index 000000000..e93544c9b --- /dev/null +++ b/capio-server/src/client-manager/handlers/create.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +extern capiocl::engine::Engine *capio_cl_engine; +extern ClientManager *client_manager; +extern CapioFileManager *file_manager; +extern CapioStorageService *storage_service; + +void create_handler(const char *const str) { + pid_t tid, fd; + char path[PATH_MAX]; + sscanf(str, "%d %d %s", &tid, &fd, path); + std::string path_str(path); + std::string name(client_manager->get_app_name(tid)); + + START_LOG(gettid(), "call(tid=%d, path=%s)", tid, path); + /** + * See write.hpp for a reason for which the method on file manager is not being invoked + */ + // file_manager->unlockThreadAwaitingCreation(path); + + capio_cl_engine->addProducer(path, name); + client_manager->register_produced_file(tid, path_str); + storage_service->createMemoryFile(path); + capio_control_plane->notify_all(CapioControlPlane::CREATE, path_str); +} diff --git a/capio-server/src/client-manager/handlers/exit.cpp b/capio-server/src/client-manager/handlers/exit.cpp new file mode 100644 index 000000000..c3315ea5d --- /dev/null +++ b/capio-server/src/client-manager/handlers/exit.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +extern ClientManager *client_manager; +extern CapioFileManager *file_manager; +extern CapioStorageService *storage_service; +void exit_handler(const char *const str) { + // TODO: register files open for each tid ti register a close + pid_t tid; + sscanf(str, "%d", &tid); + START_LOG(gettid(), "call(tid=%d)", tid); + + /** + * At exit, all files are considered to be committed. hence, call the set_committed + * method. The increase_close_count method is not called, as it would add a close count + * to a file that might have already been closing (hence increasing the close count by an extra + * close + */ + CapioFileManager::setCommitted(tid); + storage_service->remove_client(tid); + client_manager->remove_client(tid); +} diff --git a/capio-server/src/client-manager/handlers/files_in_memory.cpp b/capio-server/src/client-manager/handlers/files_in_memory.cpp new file mode 100644 index 000000000..4de4f23d7 --- /dev/null +++ b/capio-server/src/client-manager/handlers/files_in_memory.cpp @@ -0,0 +1,18 @@ +#include +#include +#include +#include +#include +extern ClientManager *client_manager; +extern CapioStorageService *storage_service; +void files_to_store_in_memory_handler(const char *const str) { + // TODO: register files open for each tid ti register a close + pid_t tid; + sscanf(str, "%d", &tid); + START_LOG(gettid(), "call(tid=%d)", tid); + + auto count = storage_service->sendFilesToStoreInMemory(tid); + + LOG("Need to tell client to read %llu files from data queue", count); + client_manager->reply_to_client(tid, count); +} \ No newline at end of file diff --git a/capio-server/src/client-manager/handlers/handshake.cpp b/capio-server/src/client-manager/handlers/handshake.cpp new file mode 100644 index 000000000..f4eac424a --- /dev/null +++ b/capio-server/src/client-manager/handlers/handshake.cpp @@ -0,0 +1,28 @@ +#include "capio/constants.hpp" +#include +#include +extern ClientManager *client_manager; +extern CapioStorageService *storage_service; + +void handshake_handler(const char *const str) { + pid_t tid, pid; + char app_name[1024]; + sscanf(str, "%d %d %s", &tid, &pid, app_name); + START_LOG(gettid(), "call(tid=%ld, pid=%ld, app_name=%s)", tid, pid, app_name); + + if (!capio_global_configuration->termination_phase) { + client_manager->register_client(app_name, tid); + storage_service->register_client(app_name, tid); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, + "Registered new app: " + std::string(app_name)); + + // Unlock client waiting to start + LOG("Allowing handshake to continue"); + client_manager->reply_to_client(tid, 1); + } else { + LOG("Termination phase is in progress. ignoring further handshakes."); + client_manager->reply_to_client(tid, 0); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + "Termination phase is in progress. Ignoring further handshakes."); + } +} \ No newline at end of file diff --git a/capio-server/src/client-manager/handlers/open.cpp b/capio-server/src/client-manager/handlers/open.cpp new file mode 100644 index 000000000..036e0db1a --- /dev/null +++ b/capio-server/src/client-manager/handlers/open.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include +#include +#include +extern capiocl::engine::Engine *capio_cl_engine; +extern ClientManager *client_manager; +extern CapioFileManager *file_manager; +extern CapioStorageService *storage_service; +void open_handler(const char *const str) { + pid_t tid; + int fd; + char path[PATH_MAX]; + sscanf(str, "%d %d %s", &tid, &fd, path); + START_LOG(gettid(), "call(tid=%d, fd=%d, path=%s", tid, fd, path); + + if (capio_cl_engine->isExcluded(path)) { + LOG("File should not be handled as it is excluded!"); + client_manager->reply_to_client(tid, 0); + return; + } + + const auto app_name = client_manager->get_app_name(tid); + + if (capio_cl_engine->isProducer(path, app_name)) { + LOG("Thread is producer. allowing to continue with open"); + client_manager->reply_to_client(tid, 1); + storage_service->createMemoryFile(path); + return; + } + + if (std::filesystem::exists(path)) { + LOG("File already exists! allowing to continue with open"); + client_manager->reply_to_client(tid, 1); + + /* + * At this point, the file that needs to be created more likely than not is not local to the + * machine. As such, we call the creation of a new CapioRemoteFile + */ + storage_service->createRemoteFile(path, {}); + return; + } + + LOG("File does not yet exists. halting operation and adding it to queue"); + file_manager->addThreadAwaitingCreation(path, tid); +} \ No newline at end of file diff --git a/capio-server/src/client-manager/handlers/posix_readdir.cpp b/capio-server/src/client-manager/handlers/posix_readdir.cpp new file mode 100644 index 000000000..fc3637f99 --- /dev/null +++ b/capio-server/src/client-manager/handlers/posix_readdir.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include +#include +#include +extern capiocl::engine::Engine *capio_cl_engine; +extern ClientManager *client_manager; +extern CapioFileManager *file_manager; +extern CapioStorageService *storage_service; +void posix_readdir_handler(const char *const str) { + pid_t pid; + char path[PATH_MAX]; + sscanf(str, "%d %s", &pid, path); + START_LOG(gettid(), "call(pid=%d, path=%s", pid, path); + + if (capio_cl_engine->isExcluded(path)) { + LOG("Path is excluded. Creating commit token to avoid starvation"); + CapioFileManager::setCommitted(path); + client_manager->reply_to_client(pid, 6); + storage_service->reply_to_client_raw(pid, "", 6); + } + + const auto metadata_token = CapioFileManager::getMetadataPath(path); + LOG("sending to pid %ld token path of %s", pid, metadata_token.c_str()); + + client_manager->reply_to_client(pid, metadata_token.length()); + storage_service->reply_to_client_raw(pid, metadata_token.c_str(), metadata_token.length()); +} diff --git a/capio-server/src/client-manager/handlers/read.cpp b/capio-server/src/client-manager/handlers/read.cpp new file mode 100644 index 000000000..2047375e7 --- /dev/null +++ b/capio-server/src/client-manager/handlers/read.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +extern capiocl::engine::Engine *capio_cl_engine; +extern ClientManager *client_manager; +extern CapioFileManager *file_manager; +extern CapioStorageService *storage_service; + +void read_handler(const char *const str) { + long tid; + int fd; + capio_off64_t end_of_read; + char path[PATH_MAX]; + sscanf(str, "%ld %d %s %llu", &tid, &fd, path, &end_of_read); + START_LOG(gettid(), "call(path=%s, tid=%ld, end_of_read=%llu)", path, tid, end_of_read); + + const std::filesystem::path path_fs(path); + const auto app_name = client_manager->get_app_name(tid); + + /** + * If process is producer OR fire rule is no update and there is enough data, allow the process + * to continue in its execution + */ + const uintmax_t file_size = CapioFileManager::get_file_size_if_exists(path); + if (capio_cl_engine->isProducer(path, app_name) || + (capio_cl_engine->isFirable(path_fs) && file_size >= end_of_read)) { + LOG("File can be consumed as it is either the producer, or the fire rule is FNU and there " + "is enough data"); + client_manager->reply_to_client(tid, file_size); + return; + } + + /** + * return ULLONG_MAX to signal client cache that file is committed and no more requests are + * required + */ + if (CapioFileManager::isCommitted(path)) { + LOG("File is committed, and hence can be consumed"); + client_manager->reply_to_client(tid, ULLONG_MAX); + return; + } + + /** + * Otherwise, file cannot yet be consumed, and hence add thread to wait for data... + */ + LOG("File cannot yet be consumed. Adding thread to wait list"); + file_manager->addThreadAwaitingData(path, tid, end_of_read); +} + +void read_mem_handler(const char *const str) { + long int tid; + capio_off64_t read_size, client_cache_line_size, read_begin_offset; + int use_cache; + char path[PATH_MAX]; + sscanf(str, "%ld %llu %llu %llu %d %s", &tid, &read_begin_offset, &read_size, + &client_cache_line_size, &use_cache, path); + START_LOG(gettid(), + "call(tid=%d, read_begin_offset=%llu, read_size=%llu, client_cache_line_size=%llu, " + "use_cache=%s, path=%s)", + tid, read_begin_offset, read_size, client_cache_line_size, + use_cache ? "true" : "false", path); + + capio_off64_t size_to_send = std::min({client_cache_line_size, read_size}); + LOG("Will try to send to client up to %ld bytes", size_to_send); + auto size_sent = storage_service->reply_to_client(tid, path, read_begin_offset, size_to_send); + + LOG("Sending to posix app the offset up to which read."); + if (file_manager->isCommitted(path)) { + LOG("File is committed, setting MSB to 1"); + size_sent |= 0x8000000000000000; + } + + LOG("Telling client to read %ld bytes", size_sent); + client_manager->reply_to_client(tid, size_sent); +} diff --git a/capio-server/src/client-manager/handlers/rename.cpp b/capio-server/src/client-manager/handlers/rename.cpp new file mode 100644 index 000000000..34bb9b3cf --- /dev/null +++ b/capio-server/src/client-manager/handlers/rename.cpp @@ -0,0 +1,16 @@ +#include +#include +#include + +void rename_handler(const char *const str) { + pid_t tid; + char old_path[PATH_MAX], new_path[PATH_MAX]; + sscanf(str, "%d %s %s", &tid, old_path, new_path); + START_LOG(gettid(), "call(tid=%d, old=%s, new=%s)", tid, old_path, new_path); + /** + * SEE write.hpp with the explanation on why the call below is commented away + */ + // file_manager->unlockThreadAwaitingCreation(new_path); + + // TODO: check what happen when old or new is to be handled in memory +} diff --git a/capio-server/src/client-manager/handlers/write.cpp b/capio-server/src/client-manager/handlers/write.cpp new file mode 100644 index 000000000..aaebedd96 --- /dev/null +++ b/capio-server/src/client-manager/handlers/write.cpp @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +extern CapioStorageService *storage_service; + +void write_mem_handler(const char *const str) { + long int tid; + char path[PATH_MAX]; + off64_t write_size; + capio_off64_t offset; + sscanf(str, "%ld %s %llu %ld", &tid, path, &offset, &write_size); + START_LOG(gettid(), "call(tid=%d, path=%s, offset=%lld, write_size=%lld)", tid, path, offset, + write_size); + + storage_service->recive_from_client(tid, path, offset, write_size); +} diff --git a/capio-server/src/client-manager/request_handler_engine.cpp b/capio-server/src/client-manager/request_handler_engine.cpp new file mode 100644 index 000000000..de2330bd6 --- /dev/null +++ b/capio-server/src/client-manager/request_handler_engine.cpp @@ -0,0 +1,118 @@ +#include +#include +#include +#include + +extern ClientManager *client_manager; +extern RequestHandlerEngine *request_handlers_engine; + +constexpr std::array +RequestHandlerEngine::build_request_handlers_table() { + std::array _request_handlers{0}; + + _request_handlers[CAPIO_REQUEST_CONSENT] = consent_to_proceed_handler; + _request_handlers[CAPIO_REQUEST_CLOSE] = close_handler; + _request_handlers[CAPIO_REQUEST_CREATE] = create_handler; + _request_handlers[CAPIO_REQUEST_EXIT_GROUP] = exit_handler; + _request_handlers[CAPIO_REQUEST_HANDSHAKE] = handshake_handler; + _request_handlers[CAPIO_REQUEST_MKDIR] = create_handler; + _request_handlers[CAPIO_REQUEST_OPEN] = open_handler; + _request_handlers[CAPIO_REQUEST_READ] = read_handler; + _request_handlers[CAPIO_REQUEST_RENAME] = rename_handler; + _request_handlers[CAPIO_REQUEST_QUERY_MEM_FILE] = files_to_store_in_memory_handler; + _request_handlers[CAPIO_REQUEST_READ_MEM] = read_mem_handler; + _request_handlers[CAPIO_REQUEST_WRITE_MEM] = write_mem_handler; + _request_handlers[CAPIO_REQUEST_POSIX_DIR_COMMITTED] = posix_readdir_handler; + + return _request_handlers; +} + +auto RequestHandlerEngine::read_next_request(char *str) const { + char req[CAPIO_REQ_MAX_SIZE]; + START_LOG(gettid(), "call()"); + buf_requests->read(req); + LOG("req=%s", req); + int code = -1; + auto [ptr, ec] = std::from_chars(req, req + 4, code); + if (ec == std::errc()) { + strcpy(str, ptr + 1); + } else { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + "Received invalid code: " + std::to_string(code)); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + "Offending request: " + std::string(ptr) + " / " + req); + ERR_EXIT("Invalid request %d:%s", code, ptr); + } + return code; +} + +RequestHandlerEngine::RequestHandlerEngine() { + START_LOG(gettid(), "call()"); + request_handlers = build_request_handlers_table(); + buf_requests = new CSBufRequest_t(SHM_COMM_CHAN_NAME, CAPIO_REQ_BUFF_CNT, CAPIO_REQ_MAX_SIZE, + capio_global_configuration->workflow_name); + + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "RequestHandlerEngine initialization completed."); +} + +RequestHandlerEngine::~RequestHandlerEngine() { + START_LOG(gettid(), "call()"); + delete buf_requests; + + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "RequestHandlerEngine cleanup completed."); +} + +void RequestHandlerEngine::start() const { + START_LOG(gettid(), "call()\n\n"); + + const auto str = std::unique_ptr(new char[CAPIO_REQ_MAX_SIZE]); + int code; + + /* When in termination_phase, we empty all requests while clients are connected. as soon + * as queues are empty and the server ha removed all requests, it calls the termination + * handler to stop the server execution + */ + while (!capio_global_configuration->termination_phase || + client_manager->get_connected_posix_client() > 0) { + LOG(CAPIO_LOG_SERVER_REQUEST_START); + try { + code = read_next_request(str.get()); + } catch (const std::exception &e) { + if (capio_global_configuration->termination_phase) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Termination phase is in progress... " + "Ignoring Exception likely thrown while waking up threads"); + continue; + } + throw; + } + + try { + request_handlers[code](str.get()); + } catch (const std::exception &exception) { + std::cout << std::endl + << "~~~~~~~~~~~~~~[\033[31mRequestHandlerEngine::start(): FATAL " + "EXCEPTION\033[0m]~~~~~~~~~~~~~~" + << std::endl + << "| Exception thrown while handling request number: " << code << " : " + << str.get() << std::endl + << "| TID of offending thread: " << gettid() << std::endl + << "| PID of offending thread: " << getpid() << std::endl + << "| PPID of offending thread: " << getppid() << std::endl + << "| " << std::endl + << "| `" << typeid(exception).name() << ": " << exception.what() << std::endl + << "|" << std::endl + << "~~~~~~~~~~~~~~[\033[31mRequestHandlerEngine::start(): FATAL " + "EXCEPTION\033[0m]~~~~~~~~~~~~~~" + << std::endl + << std::endl; + + capio_global_configuration->termination_phase = true; + kill(getpid(), SIGUSR1); + } + + LOG(CAPIO_LOG_SERVER_REQUEST_END); + } + + LOG("Terminated handling of posix clients"); +} \ No newline at end of file diff --git a/capio-server/src/communication-service/capio_communication_service.cpp b/capio-server/src/communication-service/capio_communication_service.cpp new file mode 100644 index 000000000..2ab92397a --- /dev/null +++ b/capio-server/src/communication-service/capio_communication_service.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +CapioCommunicationService::~CapioCommunicationService() { + delete capio_control_plane; + delete capio_backend; +}; + +CapioCommunicationService::CapioCommunicationService(std::string &backend_name, const int port, + const std::string &control_backend_name) { + START_LOG(gettid(), "call(backend_name=%s)", backend_name.c_str()); + + LOG("My hostname is %s. Starting to listen on connection", + capio_global_configuration->node_name); + + if (backend_name == "MQTT" || backend_name == "MPI") { + server_println( + CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Warn: selected backend is not yet officially supported. Setting backend to TCP"); + backend_name = "TCP"; + } + + if (backend_name == "TCP" || backend_name == "UCX") { + + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "Selected backend is " + backend_name); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, + "Selected backend port is " + std::to_string(port)); + capio_backend = + new MTCLBackend(backend_name, std::to_string(port), CAPIO_BACKEND_DEFAULT_SLEEP_TIME); + } else if (backend_name == "FS") { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "Selected backend is File System"); + capio_backend = new NoBackend(); + } else if (backend_name == "none") { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "Skipping communication backend startup"); + } else { + START_LOG(gettid(), "call()"); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + "Provided communication backend " + backend_name + " is invalid"); + ERR_EXIT("No valid backend was provided"); + } + + server_println(CAPIO_SERVER_CLI_LOG_SERVER, + "CapioCommunicationService initialization completed."); + + if (control_backend_name == "fs") { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "Starting FS control plane"); + capio_control_plane = new FSControlPlane(port); + } else if (control_backend_name == "multicast") { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "Starting multicast control plane"); + capio_control_plane = new MulticastControlPlane(port); + } else { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, + "Error: unknown control plane backend: " + control_backend_name); + } +} diff --git a/capio-server/src/communication-service/control-plane/fs_control_plane.cpp b/capio-server/src/communication-service/control-plane/fs_control_plane.cpp new file mode 100644 index 000000000..47c9fbced --- /dev/null +++ b/capio-server/src/communication-service/control-plane/fs_control_plane.cpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include +#include +#include + +void FSControlPlane::generate_aliveness_token(const int port) const { + START_LOG(gettid(), "call(port=d)", port); + + std::string token_filename(ownHostname); + token_filename += ".alive_connection"; + + LOG("Creating alive token %s", token_filename.c_str()); + + std::ofstream FilePort(token_filename); + FilePort << port; + FilePort.close(); + + LOG("Saved self token info to FS"); + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "Generated token at " + token_filename); +} + +void FSControlPlane::delete_aliveness_token() { + START_LOG(gettid(), "call()"); + + std::string token_filename(ownHostname); + token_filename += ".alive_connection"; + if (!std::filesystem::exists(token_filename)) { + LOG("Token does not exists. Skipping delettion"); + return; + } + + LOG("Removing alive token %s", token_filename.c_str()); + std::filesystem::remove(token_filename); + LOG("Removed token"); +} + +void FSControlPlane::fs_server_aliveness_detector_thread( + const bool *continue_execution, std::vector *token_used_to_connect, + std::mutex *token_used_to_connect_mutex) { + START_LOG(gettid(), "call()"); + + if (!continue_execution) { + LOG("Terminating execution"); + return; + } + + auto dir_iterator = std::filesystem::directory_iterator(std::filesystem::current_path()); + for (const auto &entry : dir_iterator) { + const auto token_path = entry.path(); + + if (!entry.is_regular_file() || token_path.extension() != ".alive_connection") { + LOG("Filename %s is not valid", entry.path().c_str()); + continue; + } + + LOG("Found token %s", token_path.c_str()); + + std::ifstream MyReadFile(token_path.filename()); + std::string remoteHost = entry.path().stem(), remotePort; + LOG("Testing for file: %s (hostname: %s, port=%s)", entry.path().filename().c_str(), + remoteHost.c_str(), remotePort.c_str()); + + getline(MyReadFile, remotePort); + MyReadFile.close(); + + const auto hostname_port = std::string(remoteHost) + ":" + remotePort; + std::lock_guard lock(*token_used_to_connect_mutex); + if (std::find(token_used_to_connect->begin(), token_used_to_connect->end(), + hostname_port) != token_used_to_connect->end()) { + LOG("Token already handled... skipping it!"); + continue; + }; + + // TODO: as of now we will not connect with servers + // TODO: that terminates and then comes back up online... + token_used_to_connect->push_back(hostname_port); + capio_backend->connect_to(std::string(remoteHost) + ":" + remotePort); + } + LOG("Terminated loop. sleeping one second"); + sleep(1); +} + +FSControlPlane::FSControlPlane(int backend_port) : _backend_port(backend_port) { + gethostname(ownHostname, HOST_NAME_MAX); + generate_aliveness_token(backend_port); + continue_execution = new bool(true); + token_used_to_connect_mutex = new std::mutex(); + thread = new std::thread(fs_server_aliveness_detector_thread, std::ref(continue_execution), + &token_used_to_connect, token_used_to_connect_mutex); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, "FSControlPlane initialization completed."); +}; + +FSControlPlane::~FSControlPlane() { + delete_aliveness_token(); + pthread_cancel(thread->native_handle()); + thread->join(); + delete thread; + delete continue_execution; + delete token_used_to_connect_mutex; + + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, "FSControlPlane cleanup completed."); +} + +void FSControlPlane::notify_all(FSControlPlane::event_type event, + const std::filesystem::path &path) {} diff --git a/capio-server/src/communication-service/control-plane/multicast_control_plane.cpp b/capio-server/src/communication-service/control-plane/multicast_control_plane.cpp new file mode 100644 index 000000000..fe6bc47d0 --- /dev/null +++ b/capio-server/src/communication-service/control-plane/multicast_control_plane.cpp @@ -0,0 +1,166 @@ +#include "include/storage-service/capio_storage_service.hpp" +#include "multicast_utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +extern CapioStorageService *storage_service; +void MulticastControlPlane::multicast_server_aliveness_thread( + const bool *continue_execution, std::vector *token_used_to_connect, + std::mutex *token_used_to_connect_mutex, int dataplane_backend_port) { + + START_LOG(gettid(), "call(data_plane_backend_port=%d)", dataplane_backend_port); + + char incomingMessage[MULTICAST_ALIVE_TOKEN_MESSAGE_SIZE]; + + const std::string SELF_TOKEN = std::string(capio_global_configuration->node_name) + ":" + + std::to_string(dataplane_backend_port); + + sockaddr_in addr = {}; + socklen_t addrlen = {}; + const auto discovery_socket = + open_outgoing_socket(MULTICAST_DISCOVERY_ADDR, MULTICAST_DISCOVERY_PORT, addr, addrlen); + + server_println(CAPIO_SERVER_CLI_LOG_SERVER, std::string("Multicast discovery service @ ") + + MULTICAST_DISCOVERY_ADDR + ":" + + std::to_string(MULTICAST_DISCOVERY_PORT)); + + while (*continue_execution) { + send_multicast_alive_token(dataplane_backend_port); + LOG("Waiting for incoming token..."); + + do { + bzero(incomingMessage, sizeof(incomingMessage)); + const auto recv_sice = + recvfrom(discovery_socket, incomingMessage, MULTICAST_ALIVE_TOKEN_MESSAGE_SIZE, 0, + reinterpret_cast(&addr), &addrlen); + LOG("Received multicast data of size %ld and content %s", recv_sice, incomingMessage); + if (recv_sice < 0) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + std::string("WARNING: received 0 bytes from multicast socket: ")); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Execution will continue only with FS discovery support"); + return; + } + } while (std::string(incomingMessage) == SELF_TOKEN); + + std::lock_guard lg(*token_used_to_connect_mutex); + if (std::find(token_used_to_connect->begin(), token_used_to_connect->end(), + incomingMessage) == token_used_to_connect->end()) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, + "Multicast adv: " + std::string(incomingMessage)); + LOG("Received message: %s", incomingMessage); + token_used_to_connect->push_back(incomingMessage); + capio_backend->connect_to(incomingMessage); + } + + sleep(1); + } +} + +void MulticastControlPlane::multicast_control_plane_incoming_thread( + const bool *continue_execution) { + START_LOG(gettid(), "Call(multicast_control_plane_incoming_thread)"); + char incoming_msg[MULTICAST_CONTROLPL_MESSAGE_SIZE] = {0}; + sockaddr_in addr = {}; + socklen_t addrlen = {}; + const auto discovery_socket = + open_outgoing_socket(MULTICAST_CONTROLPL_ADDR, MULTICAST_CONTROLPL_PORT, addr, addrlen); + + server_println(CAPIO_SERVER_CLI_LOG_SERVER, std::string("Multicast control plane @ ") + + MULTICAST_CONTROLPL_ADDR + ":" + + std::to_string(MULTICAST_CONTROLPL_PORT)); + + while (*continue_execution) { + bzero(incoming_msg, sizeof(incoming_msg)); + const auto recv_sice = + recvfrom(discovery_socket, incoming_msg, MULTICAST_CONTROLPL_MESSAGE_SIZE, 0, + reinterpret_cast(&addr), &addrlen); + LOG("Received multicast data of size %ld and content %s", recv_sice, incoming_msg); + if (recv_sice < 0) { + LOG("WARNING: received size less than zero. An error might have occurred: %s", + strerror(errno)); + LOG("Skipping iteration and returning to listening for incoming paxkets"); + continue; + } + + int event; + char source_hostname[HOST_NAME_MAX]; + char source_path[PATH_MAX]; + + sscanf(incoming_msg, "%d %s %s", &event, source_hostname, source_path); + + LOG("event=%d, source=%s, path=%s", event, source_hostname, source_path); + + if (strcmp(capio_global_configuration->node_name, source_hostname) == 0) { + continue; + } + + switch (event) { + case CREATE: + LOG("Handling remote CREATE event"); + storage_service->createRemoteFile(source_path, source_hostname); + break; + + default: + LOG("WARNING: unknown / unhandled event: %s", incoming_msg); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Unknown/Unhandled message received: " + std::string(incoming_msg)); + } + LOG("Completed handling of event"); + } + + close(discovery_socket); +} + +MulticastControlPlane::MulticastControlPlane(int dataplane_backend_port) { + START_LOG(gettid(), "call(dataplane_backend_port=%d)", dataplane_backend_port); + gethostname(ownHostname, HOST_NAME_MAX); + continue_execution = new bool(true); + token_used_to_connect_mutex = new std::mutex(); + + discovery_thread = new std::thread(multicast_server_aliveness_thread, continue_execution, + &token_used_to_connect, token_used_to_connect_mutex, + dataplane_backend_port); + + controlpl_incoming = + new std::thread(multicast_control_plane_incoming_thread, continue_execution); +} + +MulticastControlPlane::~MulticastControlPlane() { + *continue_execution = false; + pthread_cancel(discovery_thread->native_handle()); + discovery_thread->join(); + pthread_cancel(controlpl_incoming->native_handle()); + controlpl_incoming->join(); + delete token_used_to_connect_mutex; + delete discovery_thread; + delete continue_execution; + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "MulticastControlPlane correctly terminated"); +} + +void MulticastControlPlane::notify_all(const event_type event, const std::filesystem::path &path) { + START_LOG(gettid(), "call(event=%s, path=%s)", event, path.string().c_str()); + sockaddr_in addr = {}; + const auto socket = + open_outgoing_multicast_socket(MULTICAST_CONTROLPL_ADDR, MULTICAST_CONTROLPL_PORT, &addr); + + char message[MULTICAST_CONTROLPL_MESSAGE_SIZE]; + sprintf(message, "%03d %s %s", event, ownHostname, path.string().c_str()); + + LOG("Sending message: %s", message); + if (sendto(socket, message, strlen(message), 0, reinterpret_cast(&addr), + sizeof(addr)) < 0) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + "WARNING: unable to send message(" + std::string(message) + + ") to multicast address!: " + strerror(errno)); + } + LOG("Sent message"); + + close(socket); +} diff --git a/capio-server/src/communication-service/control-plane/multicast_utils.hpp b/capio-server/src/communication-service/control-plane/multicast_utils.hpp new file mode 100644 index 000000000..ee37f800e --- /dev/null +++ b/capio-server/src/communication-service/control-plane/multicast_utils.hpp @@ -0,0 +1,122 @@ +#ifndef CAPIO_MULTICAST_UTILS_HPP +#define CAPIO_MULTICAST_UTILS_HPP + +#include +#include +#include +#include +#include +#include +#include + +static int open_outgoing_multicast_socket(const char *address, const int port, sockaddr_in *addr) { + int transmission_socket = socket(AF_INET, SOCK_DGRAM, 0); + if (transmission_socket < 0) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + std::string("WARNING: unable to bind multicast socket: ") + strerror(errno)); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Execution will continue only with FS discovery support"); + + return -1; + } + + addr->sin_family = AF_INET; + addr->sin_addr.s_addr = inet_addr(address); + addr->sin_port = htons(port); + return transmission_socket; +}; + +static void send_multicast_alive_token(const int data_plane_backend_port) { + START_LOG(gettid(), "call(data_plane_backend_port=%d)", data_plane_backend_port); + + sockaddr_in addr = {}; + const auto socket = + open_outgoing_multicast_socket(MULTICAST_DISCOVERY_ADDR, MULTICAST_DISCOVERY_PORT, &addr); + + char message[MULTICAST_ALIVE_TOKEN_MESSAGE_SIZE]; + sprintf(message, "%s:%d", capio_global_configuration->node_name, data_plane_backend_port); + + LOG("Sending token: %s", message); + + if (sendto(socket, message, strlen(message), 0, reinterpret_cast(&addr), + sizeof(addr)) < 0) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + "WARNING: unable to send alive token(" + std::string(message) + + ") to multicast address!: " + strerror(errno)); + } + LOG("Sent multicast token"); + close(socket); +} + +static int open_outgoing_socket(const char *address_ip, const int port, sockaddr_in &addr, + socklen_t &addrlen) { + START_LOG(gettid(), "call(address=%s, port=%d)", address_ip, port); + int loopback = 0; // disable receive loopback messages + u_int multiple_socket_on_same_address = 1; // enable multiple sockets on same address + + int outgoing_socket = socket(AF_INET, SOCK_DGRAM, 0); + if (outgoing_socket < 0) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + std::string("WARNING: unable to open multicast socket: ") + strerror(errno)); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Execution will continue only with FS discovery support"); + return -1; + } + LOG("Created socket"); + + if (setsockopt(outgoing_socket, SOL_SOCKET, SO_REUSEADDR, + (char *) &multiple_socket_on_same_address, + sizeof(multiple_socket_on_same_address)) < 0) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + std::string("WARNING: unable to multiple sockets to same address: ") + + strerror(errno)); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Execution will continue only with FS discovery support"); + return -1; + } + LOG("Set IP address to accept multiple sockets on same address"); + + if (setsockopt(outgoing_socket, IPPROTO_IP, IP_MULTICAST_LOOP, &loopback, sizeof(loopback)) < + 0) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + std::string("WARNING: unable to filter out loopback incoming messages: ") + + strerror(errno)); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Execution will continue only with FS discovery support"); + return -1; + } + LOG("Disabled reception of loopback messages from socket"); + + addr = {}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + addrlen = sizeof(addr); + LOG("Set socket on IP: %s - PORT: %d", address_ip, port); + + // bind to receive address + if (bind(outgoing_socket, reinterpret_cast(&addr), sizeof(addr)) < 0) { + + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + std::string("WARNING: unable to bind multicast socket: ") + strerror(errno)); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Execution will continue only with FS discovery support"); + return -1; + } + LOG("Binded socket"); + + ip_mreq mreq{}; + mreq.imr_multiaddr.s_addr = inet_addr(address_ip); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + if (setsockopt(outgoing_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + std::string("WARNING: unable to join multicast group: ") + strerror(errno)); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Execution will continue only with FS discovery support"); + return -1; + } + LOG("Successfully joined multicast group"); + return outgoing_socket; +} + +#endif // CAPIO_MULTICAST_UTILS_HPP \ No newline at end of file diff --git a/capio-server/src/communication-service/data-plane/mtcl_backend.cpp b/capio-server/src/communication-service/data-plane/mtcl_backend.cpp new file mode 100644 index 000000000..001b97783 --- /dev/null +++ b/capio-server/src/communication-service/data-plane/mtcl_backend.cpp @@ -0,0 +1,299 @@ +#include +#include +#include +#include +#include +#include +#include + +extern CapioFileManager *file_manager; +extern CapioStorageService *storage_service; +int MTCLBackend::read_next_request(char *req, char *args) { + + START_LOG(gettid(), "call(req=%s)", req); + int code = -1; + auto [ptr, ec] = std::from_chars(req, req + 4, code); + if (ec == std::errc()) { + strcpy(args, ptr + 1); + } else { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + "Received invalid code: " + std::to_string(code)); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + "Offending request: " + std::string(ptr) + " / " + req); + ERR_EXIT("Invalid request %d:%s", code, ptr); + } + return code; +} + +/** + * This thread will handle connections towards a single target. + * Algorithm works in this way. In the beginning, the role of the node that starts to send is + * chosen as the smaller lexicographically between the two hostnames involved in the + * communication. Then two phases of sending and receiving up to max_net_op are performed. + * The two nodes switch phases after a final synchronization with the special request + * HAVE_FINISH_SEND_REQUEST. + * When the sender sends this request, either because it has reached the max_net_op or because + * there are no more messages to be sent, the two nodes switch roles. This continues until the + * remote handle pointer is valid + */ +void MTCLBackend::serverConnectionHandler(MTCL::HandleUser HandlerPointer, + const std::string &remote_hostname, MessageQueue *queue, + const int sleep_time, const bool *terminate) { + + char ownHostname[HOST_NAME_MAX]; + gethostname(ownHostname, HOST_NAME_MAX); + bool my_turn_to_send = ownHostname > remote_hostname; + + char request_has_finished_to_send[CAPIO_REQ_MAX_SIZE]{0}; + sprintf(request_has_finished_to_send, "%03d", HAVE_FINISH_SEND_REQUEST); + + START_LOG(gettid(), "call(remote_hostname=%s)", remote_hostname.c_str()); + + LOG("Will begin execution with %s phase", my_turn_to_send ? "sending" : "receiving"); + + while (HandlerPointer.isValid()) { + // execute up to N operation of send &/or receive, to avoid starvation + + if (my_turn_to_send) { + constexpr int max_net_op = 10; + LOG("Send PHASE"); + for (int i = 0; i < max_net_op; i++) { + // Send of request + if (const auto request_opt = queue->try_get_request(); request_opt.has_value()) { + const auto &request = request_opt.value(); + LOG("Request to be sent = %s", request.c_str()); + HandlerPointer.send(request.c_str(), request.length()); + + // Retrieve size of response + capio_off64_t response_size; + HandlerPointer.receive(&response_size, sizeof(response_size)); + LOG("Response will have size %ld", response_size); + char *response_buffer = new char[response_size]; + HandlerPointer.receive(response_buffer, response_size); + LOG("Received response"); + // push response back to the source + queue->push_response(response_buffer, response_size, request); + LOG("Pushed response back!"); + } + } + LOG("Completed SEND PHASE"); + // Send message I have finished + HandlerPointer.send(request_has_finished_to_send, sizeof(request_has_finished_to_send)); + + } else { + + bool continue_receive_phase = true; + size_t receive_size = 0; + LOG("Receive PHASE"); + while (continue_receive_phase) { + // Receive phase + HandlerPointer.probe(receive_size, false); + if (receive_size > 0) { + LOG("A request is incoming"); + + char incoming_request[CAPIO_REQ_MAX_SIZE], request_args[CAPIO_REQ_MAX_SIZE]; + HandlerPointer.receive(incoming_request, CAPIO_REQ_MAX_SIZE); + LOG("Received request = %s", incoming_request); + int requestCode = read_next_request(incoming_request, request_args); + LOG("Request code is %d", requestCode); + switch (requestCode) { + case HAVE_FINISH_SEND_REQUEST: { + // Finished sending data. Set continue_receive_phase to + // false to go to next phase + LOG("Other has finished sending phase. Switching me from receive to send"); + continue_receive_phase = false; + break; + } + + case FETCH_FROM_REMOTE: { + LOG("Received request for data from remote server"); + // Scan request fetch from remote + char filepath[PATH_MAX]; + capio_off64_t offset, count; + sscanf(request_args, "%s %llu %llu", filepath, &offset, &count); + LOG("filepath=%s, offset=%ld, count=%ld", filepath, offset, count); + const auto buffer = new char[count]; + auto read_size = + storage_service->readFromFileToBuffer(filepath, offset, buffer, count); + HandlerPointer.send(&read_size, sizeof(read_size)); + HandlerPointer.send(buffer, read_size); + delete[] buffer; + break; + } + + default: + break; + } + } + } + } + + // terminate phase + if (*terminate) { + LOG("[TERM PHASE] Closing connection"); + HandlerPointer.close(); + LOG("[TERM PHASE] Terminating thread server_connection_handler"); + return; + } + + my_turn_to_send = !my_turn_to_send; + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time)); + } +} + +void MTCLBackend::incomingConnectionListener( + const bool *continue_execution, int sleep_time, + std::unordered_map *open_connections, std::mutex *guard, + std::vector *_connection_threads, bool *terminate) { + + char ownHostname[HOST_NAME_MAX] = {0}; + gethostname(ownHostname, HOST_NAME_MAX); + + START_LOG(gettid(), "call(sleep_time=%d, hostname=%s)", sleep_time, ownHostname); + + while (*continue_execution) { + auto UserManager = MTCL::Manager::getNext(std::chrono::microseconds(sleep_time)); + + if (!UserManager.isValid()) { + continue; + } + LOG("Handle user is valid"); + char connected_hostname[HOST_NAME_MAX] = {0}; + UserManager.receive(connected_hostname, HOST_NAME_MAX); + + server_println(CAPIO_SERVER_CLI_LOG_SERVER, + std::string("Accepted connection with ") + connected_hostname); + + LOG("Received connection hostname: %s", connected_hostname); + + auto *queue = new MessageQueue(); + { + const std::lock_guard lock(*guard); + open_connections->insert({connected_hostname, queue}); + } + _connection_threads->push_back(new std::thread(serverConnectionHandler, + std::move(UserManager), connected_hostname, + queue, sleep_time, terminate)); + } +} + +void MTCLBackend::connect_to(std::string hostname_port) { + START_LOG(gettid(), "call( hostname_port=%s)", hostname_port.c_str()); + std::string remoteHost = hostname_port.substr(0, hostname_port.find_last_of(':')); + const std::string remoteToken = usedProtocol + ":" + hostname_port; + + if (remoteToken == selfToken || // skip on 0.0.0.0 + remoteToken == usedProtocol + ":" + ownHostname + ":" + ownPort // skip on my real IP + ) { + LOG("Skipping to connect to self"); + return; + } + + if (open_connections.contains(remoteHost)) { + LOG("Remote host %s is already connected", remoteHost.c_str()); + return; + } + + LOG("Trying to connect on remote: %s", remoteToken.c_str()); + if (auto UserManager = MTCL::Manager::connect(remoteToken); UserManager.isValid()) { + server_println(CAPIO_SERVER_CLI_LOG_SERVER, + std::string("Opened connection with ") + remoteToken); + LOG("Opened connection with: %s", remoteToken.c_str()); + UserManager.send(ownHostname, HOST_NAME_MAX); + + auto *queue = new MessageQueue(); + { + const std::lock_guard lg(*_guard); + open_connections.insert({remoteHost, queue}); + } + connection_threads.push_back(new std::thread(serverConnectionHandler, + std::move(UserManager), remoteHost, queue, + thread_sleep_times, terminate)); + } else { + server_println(CAPIO_SERVER_CLI_LOG_SERVER_WARNING, "Warning: tried to connect to " + + std::string(remoteHost) + + " but connection is not valid"); + } +} + +MTCLBackend::MTCLBackend(const std::string &proto, const std::string &port, int sleep_time) + : selfToken(proto + ":0.0.0.0:" + port), ownPort(port), usedProtocol(proto), + thread_sleep_times(sleep_time) { + START_LOG(gettid(), "INFO: instance of CapioCommunicationService"); + + terminate = new bool; + *terminate = false; + + _guard = new std::mutex(); + + gethostname(ownHostname, HOST_NAME_MAX); + LOG("My hostname is %s. Starting to listen on connection %s", ownHostname, selfToken.c_str()); + + std::string hostname_id("server-"); + hostname_id += ownHostname; + MTCL::Manager::init(hostname_id); + + *continue_execution = true; + + MTCL::Manager::listen(selfToken); + + th = new std::thread(incomingConnectionListener, std::ref(continue_execution), sleep_time, + &open_connections, _guard, &connection_threads, terminate); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "MTCL_backend initialization completed."); +} + +MTCLBackend::~MTCLBackend() { + START_LOG(gettid(), "call()"); + *terminate = true; + *continue_execution = false; + + for (const auto thread : connection_threads) { + thread->join(); + } + LOG("Terminated connection threads"); + + pthread_cancel(th->native_handle()); + th->join(); + delete th; + delete continue_execution; + delete terminate; + + LOG("Handler closed."); + + MTCL::Manager::finalize(); + LOG("Finalizing MTCL backend"); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "MTCL_backend cleanup completed."); +} + +std::vector MTCLBackend::get_open_connections() { + std::vector keys; + keys.reserve(open_connections.size()); // avoid reallocations + + for (const auto &pair : open_connections) { + keys.push_back(pair.first); + } + + return keys; +} + +std::tuple MTCLBackend::fetchFromRemoteHost(const std::string &hostname, + const std::filesystem::path &filepath, + capio_off64_t offset, + capio_off64_t count) { + + START_LOG(gettid(), "call(hostname=%s, path=%s, offset=%ld, count=%ld)", hostname.c_str(), + filepath.c_str(), offset, count); + + char REQUEST[CAPIO_REQ_MAX_SIZE]; + + sprintf(REQUEST, "%04d %s %llu %llu", FETCH_FROM_REMOTE, filepath.c_str(), offset, count); + LOG("Sending request %s. Fetching queue to hostname %s", REQUEST, hostname.c_str()); + auto queues = open_connections.at(hostname); + LOG("obtained access to queue"); + + queues->push_request(REQUEST); + LOG("Request pushed to output queue"); + auto [buff_size, response_buffer] = queues->get_response(); + LOG("Obtained response. Buffer size of response is %ld", buff_size); + return std::make_tuple(buff_size, response_buffer); +} diff --git a/capio-server/src/file-manager/file_manager.cpp b/capio-server/src/file-manager/file_manager.cpp new file mode 100644 index 000000000..994990de9 --- /dev/null +++ b/capio-server/src/file-manager/file_manager.cpp @@ -0,0 +1,385 @@ +#ifndef FILE_MANAGER_HPP +#define FILE_MANAGER_HPP +#include "include/file-manager/file_manager.hpp" +#include "capio/env.hpp" +#include +#include +#include +#include +#include + +extern capiocl::engine::Engine *capio_cl_engine; +extern ClientManager *client_manager; +extern CapioFileManager *file_manager; +extern CapioStorageService *storage_service; + +std::string CapioFileManager::getMetadataPath(const std::string &path) { + START_LOG(gettid(), "call(path=%s)", path.c_str()); + + const std::filesystem::path &metadata_path = get_capio_metadata_path(); + std::filesystem::path input_path(path); + + auto metadata_it = metadata_path.begin(); + auto input_it = input_path.begin(); + + std::filesystem::path relative_path; + while (metadata_it != metadata_path.end() && input_it != input_path.end() && + *metadata_it == *input_it) { + ++metadata_it; + ++input_it; + } + + for (; input_it != input_path.end(); ++input_it) { + relative_path /= *input_it; + } + + relative_path += ".capio"; + + std::filesystem::path token_full_path = metadata_path / relative_path; + + LOG("Computed token path is: %s", token_full_path.c_str()); + return token_full_path; +} + +std::string CapioFileManager::getAndCreateMetadataPath(const std::string &path) { + START_LOG(gettid(), "call(path=%s)", path.c_str()); + static std::unordered_map metadata_paths; + if (metadata_paths.find(path) == metadata_paths.end()) { + std::filesystem::path result = getMetadataPath(path); + + metadata_paths.emplace(path, result); + LOG("Creating metadata directory (%s)", result.parent_path().c_str()); + std::filesystem::create_directories(result.parent_path()); + LOG("Created capio metadata parent path (if no file existed). returning metadata token " + "file"); + } + LOG("token_path=%s", metadata_paths[path].c_str()); + return metadata_paths[path]; +} + +uintmax_t CapioFileManager::get_file_size_if_exists(const std::filesystem::path &path) { + return std::filesystem::exists(path) ? std::filesystem::file_size(path) : 0; +} + +void CapioFileManager::addThreadAwaitingCreation(const std::string &path, pid_t tid) { + START_LOG(gettid(), "call(path=%s, tid=%ld)", path.c_str(), tid); + const std::lock_guard lg(creation_mutex); + thread_awaiting_file_creation[path].push_back(tid); +} + +void CapioFileManager::_unlockThreadAwaitingCreation(const std::string &path, + const std::vector &pids) { + START_LOG(gettid(), "call(path=%s)", path.c_str()); + for (const auto tid : pids) { + client_manager->reply_to_client(tid, 1); + /* + * Here we need to create a new remote file, as it might be that the file is not + * produced by this node but by another remote one + */ + storage_service->createRemoteFile(path, {}); + } +} + +void CapioFileManager::addThreadAwaitingData(const std::string &path, int tid, + size_t expected_size) { + START_LOG(gettid(), "call(path=%s, tid=%ld, expected_size=%ld)", path.c_str(), tid, + expected_size); + + // check if file needs to be handled by the storage service instead + if (capio_cl_engine->isStoredInMemory(path)) { + LOG("File is stored in memory. delegating storage_service to await for data"); + storage_service->addThreadWaitingForData(tid, path, 0, expected_size); + return; + } + + const std::lock_guard lg(data_mutex); + thread_awaiting_data[path].emplace(tid, expected_size); +} + +void CapioFileManager::_unlockThreadAwaitingData( + const std::string &path, std::unordered_map &pids_awaiting) { + START_LOG(gettid(), "call(path=%s)", path.c_str()); + + for (auto item = pids_awaiting.begin(); item != pids_awaiting.end();) { + LOG("Handling thread"); + + /** + * if the file is a directory, allow to continue by returning ULLONG_MAX. This behaviour + * should be triggered only if the file is a directory and the rule specified on it is + * Fire No Update + */ + const bool is_directory = std::filesystem::is_directory(path); + const uintmax_t filesize = is_directory ? ULLONG_MAX : get_file_size_if_exists(path); + /* + * Check for file size only if it is directory, otherwise, + * return the max allowed size, to allow the process to continue. + * This is caused by the fact that std::filesystem::file_size is + * implementation defined when invoked on directories + */ + + if (capio_cl_engine->isProducer(path, client_manager->get_app_name(item->first))) { + LOG("Thread %ld can be unlocked as thread is producer", item->first); + // if producer, return the file as committed to allow to execute operations without + // being interrupted + // todo: this might be an issue later. not sure yet + client_manager->reply_to_client(item->first, ULLONG_MAX); + // remove thread from map + LOG("Removing thread %ld from threads awaiting on data", item->first); + item = pids_awaiting.erase(item); + } else if (capio_cl_engine->isFirable(path) && filesize >= item->second) { + /** + * if is Fire No Update and there is enough data + */ + LOG("Thread %ld can be unlocked as mode is FNU AND there is enough data to serve " + "(%llu bytes available. Requested %llu)", + item->first, filesize, item->second); + client_manager->reply_to_client(item->first, filesize); + // remove thread from map + LOG("Removing thread %ld from threads awaiting on data", item->first); + item = pids_awaiting.erase(item); + } else if (isCommitted(path)) { + LOG("Thread %ld can be unlocked as file is committed", item->first); + client_manager->reply_to_client(item->first, ULLONG_MAX); + // remove thread from map + LOG("Removing thread %ld from threads awaiting on data", item->first); + item = pids_awaiting.erase(item); + } else { + // DEFAULT: no condition to unlock has occurred, hence wait... + LOG("Waiting threads cannot yet be unlocked"); + ++item; + } + } + + LOG("Completed loops over threads vector for file!"); +} + +void CapioFileManager::increaseCloseCount(const std::filesystem::path &path) { + START_LOG(gettid(), "call(path=%s)", path.c_str()); + auto metadata_path = getAndCreateMetadataPath(path); + auto lock = new DistributedSemaphore(metadata_path + ".lock", 300); + long long close_count = 0; + LOG("Gained mutual exclusive access to token file %s", (metadata_path + ".lock").c_str()); + + std::fstream f(metadata_path, std::ios::in | std::ios::out); + LOG("Opened CAPIO metadata file %s", metadata_path.c_str()); + f >> close_count; + LOG("Close count is %llu. Increasing by one", close_count); + close_count++; + f.close(); + + auto out = fopen(metadata_path.c_str(), "w"); + fprintf(out, " %llu \n", close_count); + fclose(out); + + LOG("Updated close count to %llu", close_count); + + delete lock; +} + +void CapioFileManager::setCommitted(const std::filesystem::path &path) { + START_LOG(gettid(), "call(path=%s)", path.c_str()); + auto metadata_path = getAndCreateMetadataPath(path); + LOG("Creating token %s", metadata_path.c_str()); + auto fd = open(metadata_path.c_str(), O_CREAT | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, 0664); + LOG("Token fd = %d", fd); + close(fd); +} + +void CapioFileManager::setCommitted(const pid_t tid) { + START_LOG(gettid(), "call(tid=%d)", tid); + auto files = client_manager->get_produced_files(tid); + for (const auto &file : *files) { + LOG("Committing file %s", file.c_str()); + CapioFileManager::setCommitted(file); + } +} + +bool CapioFileManager::isCommitted(const std::filesystem::path &path) { + START_LOG(gettid(), "call(path=%s)", path.c_str()); + /** + * Hash map to store committed files to avoid recomputing the commit state of a given file + * Files inside here are inserted only when they are committed + */ + static std::unordered_map committed_files; + + if (committed_files.find(path) != committed_files.end()) { + LOG("Committed: TRUE"); + return true; + } + + if (std::filesystem::is_directory(path)) { + // is directory + // check for n_files inside a directory + LOG("Path is a directory"); + auto file_count = capio_cl_engine->getDirectoryFileCount(path); + LOG("Expected file count is %ld", file_count); + long count = 0; + for (auto const &file : std::filesystem::directory_iterator{path}) { + if (file.path().extension() != ".capio") { + ++count; + } + } + + LOG("Final result: %s", count >= file_count ? "COMMITTED" : "NOT COMMITTED"); + + if (count >= file_count) { + committed_files[path] = true; + LOG("Committed: TRUE"); + return true; + } + LOG("Committed: FALSE"); + return false; + } + + // if file + LOG("Path is a file"); + std::string metadata_computed_path = getAndCreateMetadataPath(path); + LOG("Computed metadata file path is %s", metadata_computed_path.c_str()); + + std::string commit_rule = capio_cl_engine->getCommitRule(path); + + if (commit_rule == CAPIO_FILE_COMMITTED_ON_FILE) { + LOG("Commit rule is on_file. Checking for file dependencies"); + bool commit_computed = true; + for (const auto &file : capio_cl_engine->getCommitOnFileDependencies(path)) { + commit_computed = commit_computed && isCommitted(file); + } + + LOG("Commit result for file %s is: %s", path.c_str(), + commit_computed ? "committed" : "not committed"); + if (commit_computed) { + committed_files[path] = true; + } + LOG("Committed: %s", commit_computed ? "TRUE" : "FALSE"); + return commit_computed; + } + + if (commit_rule == CAPIO_FILE_COMMITTED_ON_CLOSE) { + LOG("Commit rule is ON_CLOSE"); + + if (!std::filesystem::exists(metadata_computed_path)) { + LOG("Commit file (%s) does not yet exists.", metadata_computed_path.c_str()); + LOG("Committed: FALSE"); + return false; + } + + int commit_count = capio_cl_engine->getCommitCloseCount(path); + LOG("Expected close count is: %d", commit_count); + if (commit_count <= 0) { + LOG("File needs to be closed exactly once and token exists. returning"); + LOG("Committed: TRUE"); + return true; + } + + long actual_commit_count = 0; + LOG("Commit file exists. retrieving commit count"); + std::ifstream in(metadata_computed_path); + if (in.is_open()) { + LOG("Opened file"); + in >> actual_commit_count; + } + LOG("Obtained actual commit count: %l", actual_commit_count); + LOG("File %s committed", actual_commit_count >= commit_count ? "IS" : "IS NOT"); + + if (actual_commit_count >= commit_count) { + committed_files[path] = true; + return true; + LOG("Committed: TRUE"); + } + LOG("Committed: FALSE"); + return false; + } + + LOG("Commit rule is ON_TERMINATION. File exists? %s", + std::filesystem::exists(metadata_computed_path) ? "TRUE" : "FALSE"); + + if (std::filesystem::exists(metadata_computed_path)) { + committed_files[path] = true; + LOG("Committed: TRUE"); + return true; + } + LOG("Committed: FALSE"); + return false; +} + +void CapioFileManager::checkFilesAwaitingCreation() { + // NOTE: do not put inside here log code as it will generate a lot of useless log + const std::lock_guard lg(creation_mutex); + std::vector path_to_delete; + + for (auto element : thread_awaiting_file_creation) { + if (std::filesystem::exists(element.first)) { + START_LOG(gettid(), "\n\ncall()"); + LOG("File %s exists. Unlocking thread awaiting for creation", element.first.c_str()); + CapioFileManager::_unlockThreadAwaitingCreation(element.first, element.second); + LOG("Completed handling."); + path_to_delete.push_back(element.first); + } + } + + for (auto path : path_to_delete) { + thread_awaiting_file_creation.erase(path); + } +} + +void CapioFileManager::checkFileAwaitingData() { + // NOTE: do not put inside here log code as it will generate a lot of useless log + const std::lock_guard lg(data_mutex); + for (auto iter = thread_awaiting_data.begin(); iter != thread_awaiting_data.end();) { + START_LOG(gettid(), "\n\ncall()"); + // no need to check if file exists as this method is called only by read_handler + // and as such, the file already exists + // actual update, end eventual removal from map is handled by the + // CapioFileManager class and not by the FileSystemMonitor class + _unlockThreadAwaitingData(iter->first, iter->second); + + // cleanup of map while iterating over it + if (iter->second.empty()) { + LOG("There are no threads waiting for path %s. cleaning up map", iter->first.c_str()); + iter = thread_awaiting_data.erase(iter); + } else { + LOG("There are threads waiting for path %s. SKIPPING CLEANUP", iter->first.c_str()); + ++iter; + } + LOG("Completed handling."); + } +} + +void CapioFileManager::checkDirectoriesNFiles() const { + /* + * WARN: this function directly access the _location internal structure in read only mode to + * avoid race conditions. Since we do not update locations, get the pointer only at the + * beginning and then use it. + */ + + for (const auto loc = capio_cl_engine->getPaths(); const std::string &path : loc) { + if (capio_cl_engine->isFile(path) || capio_cl_engine->isExcluded(path)) { + /* + * In this case we are trying to check for a file, or an excluded path. + * skip this check and go to the next path. + */ + continue; + } + const auto n_files = capio_cl_engine->getDirectoryFileCount(path); + if (n_files > 0) { + START_LOG(gettid(), "call()"); + LOG("Directory %s needs %ld files before being committed", path.c_str(), n_files); + // There must be n_files inside the directory to commit the file + long count = 0; + if (std::filesystem::exists(path)) { + auto iterator = std::filesystem::directory_iterator(path); + for ([[maybe_unused]] const auto &entry : iterator) { + ++count; + } + } + + LOG("Directory %s has %ld files inside", path.c_str(), count); + if (count >= n_files) { + LOG("Committing directory"); + this->setCommitted(path); + } + } + } +} + +#endif // FILE_MANAGER_HPP \ No newline at end of file diff --git a/capio-server/src/file-manager/fs_monitor.cpp b/capio-server/src/file-manager/fs_monitor.cpp new file mode 100644 index 000000000..5f2621c71 --- /dev/null +++ b/capio-server/src/file-manager/fs_monitor.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include + +extern CapioFileManager *file_manager; +extern FileSystemMonitor *fs_monitor; + +FileSystemMonitor::FileSystemMonitor() { + START_LOG(gettid(), "call()"); + *continue_execution = true; + th = new std::thread(_main, std::ref(continue_execution)); + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "CapioFileSystemMonitor initialization completed."); +} + +void FileSystemMonitor::print_message_error(const std::string &func, + const std::exception &exception) { + START_LOG(gettid(), "call()"); + std::cout << std::endl + << "~~~~~~~~~~~~~~[\033[31mFileSystemMonitor: FATAL " + "EXCEPTION\033[0m]~~~~~~~~~~~~~~" + << std::endl + << "| Exception thrown while handling method: " << func << " : " << std::endl + << "| TID of offending thread: " << gettid() << std::endl + << "| PID of offending thread: " << getpid() << std::endl + << "| PPID of offending thread: " << getppid() << std::endl + << "| " << std::endl + << "| `" << typeid(exception).name() << ": " << exception.what() << std::endl + << "|" << std::endl + << "~~~~~~~~~~~~~~[\033[31mFileSystemMonitor: FATAL " + "EXCEPTION\033[0m]~~~~~~~~~~~~~~" + << std::endl + << std::endl; +} + +void FileSystemMonitor::_main(const bool *continue_execution) { + START_LOG(gettid(), "INFO: instance of FileSystemMonitor"); + + timespec sleep{}; + sleep.tv_nsec = 300; // sleep 0.3 seconds + while (*continue_execution) { + try { + file_manager->checkFilesAwaitingCreation(); + } catch (const std::exception &exception) { + print_message_error("file_manager->checkFilesAwaitingCreation()", exception); + } + + try { + file_manager->checkFileAwaitingData(); + } catch (const std::exception &exception) { + print_message_error("file_manager->checkFileAwaitingData()", exception); + } + + try { + file_manager->checkDirectoriesNFiles(); + } catch (const std::exception &exception) { + print_message_error("file_manager->checkDirectoriesNFiles()", exception); + } + nanosleep(&sleep, nullptr); + } +} + +FileSystemMonitor::~FileSystemMonitor() { + START_LOG(gettid(), "call()"); + *continue_execution = false; + try { + th->join(); + } catch (const std::system_error &exception) { + print_message_error("~FileSystemMonitor()::th->joing()", exception); + } + + delete th; + delete continue_execution; + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "CapioFileSystemMonitor cleanup completed."); +} diff --git a/capio-server/src/storage-service/capio_memory_file.cpp b/capio-server/src/storage-service/capio_memory_file.cpp new file mode 100644 index 000000000..b6ec0fea1 --- /dev/null +++ b/capio-server/src/storage-service/capio_memory_file.cpp @@ -0,0 +1,155 @@ + +#include "include/storage-service/capio_file.hpp" +#include + +auto CapioMemoryFile::compute_offsets(const std::size_t offset, std::size_t length) { + START_LOG(gettid(), "call(offset=%llu, length=%llu)", offset, length); + // Compute the offset of the memoryBlocks component. + const auto map_offset = offset / _pageSizeBytes; + + // Compute the first write offset relative to the first block of memory + const auto mem_block_offset = offset % _pageSizeBytes; + + // compute the first write size. if the write operation is bigger than the size of the page + // in bytes, then we need to perform the first write operation with size equals to the + // distance between the write offset and the end of the page. otherwise it is possible to + // use the given length. The returned offset starts from mem_block_offset + const auto first_write_size = + length > _pageSizeBytes - mem_block_offset ? _pageSizeBytes - mem_block_offset : length; + + LOG("Computed offsets. map_offset=%llu, mem_block_offset=%llu, first_write_size=%llu", + map_offset, mem_block_offset, first_write_size); + return std::tuple(map_offset, mem_block_offset, first_write_size); +} + +std::vector &CapioMemoryFile::get_block(u_int64_t id) { + std::vector &block = memoryBlocks[id]; + block.resize(_pageSizeBytes); // reserve 4MB of space + return block; +} + +CapioMemoryFile::CapioMemoryFile(const std::string &filePath) : CapioFile(filePath) { + cross_page_buffer_view = new char[_pageSizeBytes]; +} + +CapioMemoryFile::~CapioMemoryFile() { delete[] cross_page_buffer_view; } + +std::size_t CapioMemoryFile::writeData(const char *buffer, const std::size_t file_offset, + std::size_t buffer_length) { + const auto &[map_offset, write_offset, first_write_size] = + compute_offsets(file_offset, buffer_length); + + // Execute first write which could be a smaller size + auto &block = memoryBlocks[map_offset]; + block.resize(_pageSizeBytes); // reserve 4MB of space + + std::copy(buffer, buffer + first_write_size, block.begin() + write_offset); + + // update remaining bytes to write + buffer_length -= first_write_size; + size_t map_count = 1; // start from map following the one obtained from the first write + + // Variable to store the read offset of the input buffer + auto buffer_offset = first_write_size; + + while (buffer_length > 0) { + auto &block = memoryBlocks[map_offset + map_count]; + block.resize(_pageSizeBytes); // reserve 4MB of space + + // Compute the actual size of the current write + const auto write_size = buffer_length > _pageSizeBytes ? _pageSizeBytes : buffer_length; + + std::copy(buffer + buffer_offset, buffer + buffer_offset + write_size, block.data()); + + buffer_offset += write_size; + map_count++; + buffer_length -= _pageSizeBytes; + } + + totalSize = std::max(totalSize, buffer_offset); + return totalSize; +} + +std::size_t CapioMemoryFile::readData(char *buffer, std::size_t file_offset, + std::size_t buffer_size) { + std::size_t bytesRead = 0; + + const auto &[map_offset, mem_block_offset_begin, target_buffer_size] = + compute_offsets(file_offset, buffer_size); + + // Traverse the memory blocks to read the requested data starting from the first block of + // date + for (auto it = memoryBlocks.lower_bound(map_offset); it != memoryBlocks.end(); ++it) { + auto &[blockOffset, block] = *it; + + if (blockOffset * _pageSizeBytes >= file_offset + buffer_size) { + break; // Past the requested range + } + + // Copy the data to the buffer + std::size_t copyLength = target_buffer_size - mem_block_offset_begin; + std::copy(block.begin() + mem_block_offset_begin, + block.begin() + mem_block_offset_begin + target_buffer_size, buffer + bytesRead); + + bytesRead += copyLength; + } + + return bytesRead; +} + +void CapioMemoryFile::readFromQueue(SPSCQueue &queue, std::size_t offset, std::size_t length) { + const auto &[map_offset, write_offset, first_write_size] = compute_offsets(offset, length); + + auto remaining_bytes = length; + + auto &block = memoryBlocks[map_offset]; + block.resize(_pageSizeBytes); // reserve 4MB of space + + queue.read(block.data() + write_offset, first_write_size); + // update remaining bytes to write + remaining_bytes -= first_write_size; + size_t map_count = 1; // start from map following the one obtained from the first write + + // Variable to store the read offset of the input buffer + auto buffer_offset = first_write_size; + + while (remaining_bytes > 0) { + auto &next_block = memoryBlocks[map_offset + map_count]; + next_block.resize(_pageSizeBytes); // reserve 4MB of space + // Compute the actual size of the current write + const auto write_size = length > _pageSizeBytes ? _pageSizeBytes : length; + + queue.read(next_block.data(), write_size); + buffer_offset += write_size; + map_count++; + remaining_bytes -= _pageSizeBytes; + } + + totalSize = std::max(totalSize, offset + length); +} + +std::size_t CapioMemoryFile::writeToQueue(SPSCQueue &queue, std::size_t offset, + std::size_t length) const { + START_LOG(gettid(), "call(offset=%llu, length=%llu)", offset, length); + std::size_t bytesRead = 0; + + while (bytesRead < length) { + const auto [map_offset, mem_block_offset_begin, buffer_view_size] = + compute_offsets(offset, length - bytesRead); + + if (const auto it = memoryBlocks.lower_bound(map_offset); it != memoryBlocks.end()) { + auto &[blockOffset, block] = *it; + + if (blockOffset >= offset + length) { + return bytesRead; // Past the requested range + } + + // Copy the data to the buffer + queue.write(block.data() + mem_block_offset_begin, buffer_view_size); + + bytesRead += buffer_view_size; + offset += buffer_view_size; + } + } + return bytesRead; +} diff --git a/capio-server/src/storage-service/capio_remote_file.cpp b/capio-server/src/storage-service/capio_remote_file.cpp new file mode 100644 index 000000000..05cbc1b0d --- /dev/null +++ b/capio-server/src/storage-service/capio_remote_file.cpp @@ -0,0 +1,39 @@ + +#include "include/communication-service/data-plane/backend_interface.hpp" +#include "include/storage-service/capio_storage_service.hpp" + +#include + +CapioRemoteFile::CapioRemoteFile(const std::string &filePath, const std::string &home_node) + : CapioFile(filePath, home_node) {} + +CapioRemoteFile::~CapioRemoteFile() {} + +std::size_t CapioRemoteFile::writeData(const char *buffer, const std::size_t file_offset, + std::size_t buffer_length) { + throw std::runtime_error("Not implemented: writeData"); +} + +std::size_t CapioRemoteFile::readData(char *buffer, std::size_t file_offset, + std::size_t buffer_size) { + throw std::runtime_error("Not implemented: readData"); +} + +void CapioRemoteFile::readFromQueue(SPSCQueue &queue, std::size_t offset, std::size_t length) { + throw std::runtime_error("Not implemented: readFromQueue"); +} + +std::size_t CapioRemoteFile::writeToQueue(SPSCQueue &queue, std::size_t offset, + std::size_t length) const { + + START_LOG(gettid(), "call(offset=%ld,count=%ld,path=%s)", offset, length, fileName.c_str()); + + auto [buffer_size, buffer] = + capio_backend->fetchFromRemoteHost(this->homeNode, this->fileName, offset, length); + + queue.write(buffer, buffer_size); + + delete[] buffer; + + return buffer_size; +} diff --git a/capio-server/src/storage-service/capio_storage_service.cpp b/capio-server/src/storage-service/capio_storage_service.cpp new file mode 100644 index 000000000..eb78fb451 --- /dev/null +++ b/capio-server/src/storage-service/capio_storage_service.cpp @@ -0,0 +1,184 @@ + + +#include +#include +#include +#include +#include +#include +extern capiocl::engine::Engine *capio_cl_engine; + +auto CapioStorageService::getFile(const std::string &file_name) const { + START_LOG(gettid(), "getFile(file_name=%s)", file_name.c_str()); + if (_stored_files->find(file_name) == _stored_files->end()) { + LOG("File not found. Creating file!"); + DBG(gettid(), [&]() { + for (auto f : *_stored_files) { + LOG("Found path in storage service %s", f.first.c_str()); + } + }); + + createMemoryFile(file_name); + } + auto file = _stored_files->at(file_name); + LOG("Returning %s instance with path %s", + file->is_remote() ? "CapioRemotFile" : "CapioMemoryFile", file->getFileName().c_str()); + return _stored_files->at(file_name); +} + +CapioStorageService::CapioStorageService() { + START_LOG(gettid(), "call()"); + _stored_files = new std::unordered_map; + _client_to_server_queue = new std::unordered_map; + _server_to_client_queue = new std::unordered_map; + _threads_waiting_for_memory_data = + new std::unordered_map>>; + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "CapioStorageService initialization completed."); +} + +CapioStorageService::~CapioStorageService() { + // TODO: dump files to FS + delete _stored_files; + delete _client_to_server_queue; + delete _server_to_client_queue; + delete _threads_waiting_for_memory_data; + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "CapioStorageService cleanup completed."); +} + +void CapioStorageService::createMemoryFile(const std::string &file_name) const { + _stored_files->emplace(file_name, new CapioMemoryFile(file_name)); +} + +void CapioStorageService::createRemoteFile(const std::string &file_name, + const std::string &home_node) const { + /* + * First we check that the file associate does not yet exists, as it might be produced + * by another app running under the same server instance. if it is not found, we create + * the file + */ + START_LOG(gettid(), "call(file_name=%s, home_node=%s)", file_name.c_str(), home_node.c_str()); + if (!_stored_files->contains(file_name)) { + LOG("File not found. Creating a new remote file"); + _stored_files->emplace(file_name, new CapioRemoteFile(file_name, home_node)); + } + LOG("Created remote file at path %s with home_node %s", + _stored_files->at(file_name)->getFileName().c_str(), + _stored_files->at(file_name)->getHomeNode().c_str()); +} + +void CapioStorageService::deleteFile(const std::string &file_name) const { + _stored_files->erase(file_name); +} + +void CapioStorageService::notifyEvent(const std::string &event_name, + const std::filesystem::path &filename) const { + // TODO: implement this +} + +void CapioStorageService::addThreadWaitingForData(pid_t tid, const std::string &path, + capio_off64_t offset, capio_off64_t size) const { + START_LOG(gettid(), "call(tid=%d, path=%s, offset=%lld, size=%lld)", tid, path.c_str(), offset, + size); + if (_threads_waiting_for_memory_data->find(path) == _threads_waiting_for_memory_data->end()) { + _threads_waiting_for_memory_data->insert({path, {}}); + } + + _threads_waiting_for_memory_data->at(path).emplace_back(std::make_tuple(offset, size, tid)); +} + +void CapioStorageService::check_and_unlock_thread_awaiting_data(const std::string &path) { + auto threads = _threads_waiting_for_memory_data->at(path); + + auto file_size = sizeOf(path); + + for (auto &[offset, size, thread_id] : threads) { + if (file_size >= offset + size) { + reply_to_client(thread_id, path, offset, size); + } + } +} + +size_t CapioStorageService::sizeOf(const std::string &path) const { + START_LOG(gettid(), "call(file=%s)", path.c_str()); + return getFile(path)->getSize(); +} + +void CapioStorageService::register_client(const std::string &app_name, const pid_t pid) const { + START_LOG(gettid(), "call(app_name=%s)", app_name.c_str()); + _client_to_server_queue->emplace(pid, new SPSCQueue("queue-" + std::to_string(pid) + +".cts", + get_cache_lines(), get_cache_line_size(), + capio_global_configuration->workflow_name, + false)); + _server_to_client_queue->emplace(pid, new SPSCQueue("queue-" + std::to_string(pid) + +".stc", + get_cache_lines(), get_cache_line_size(), + capio_global_configuration->workflow_name, + false)); + LOG("Created communication queues"); +} + +size_t CapioStorageService::reply_to_client(pid_t pid, const std::string &file, + capio_off64_t offset, capio_off64_t size) const { + START_LOG(gettid(), "call(pid=%llu, file=%s, offset=%llu, size=%llu)", pid, file.c_str(), + offset, size); + + return getFile(file)->writeToQueue(*_server_to_client_queue->at(pid), offset, size); +} + +void CapioStorageService::reply_to_client_raw(pid_t pid, const char *data, + const capio_off64_t len) const { + _server_to_client_queue->at(pid)->write(data, len); +} + +void CapioStorageService::recive_from_client(pid_t tid, const std::string &file, + capio_off64_t offset, off64_t size) const { + START_LOG(gettid(), "call(tid=%d, file=%s, offset=%lld, size=%lld)", tid, file.c_str(), offset, + size); + const auto f = getFile(file); + const auto queue = _client_to_server_queue->at(tid); + f->readFromQueue(*queue, offset, size); +} + +void CapioStorageService::remove_client(const pid_t pid) const { + _client_to_server_queue->erase(pid); + _server_to_client_queue->erase(pid); +} + +size_t CapioStorageService::sendFilesToStoreInMemory(const long pid) const { + START_LOG(gettid(), "call(pid=%d)", pid); + + if (capio_global_configuration->StoreOnlyInMemory) { + LOG("All files should be handled in memory. sending * wildcard"); + char f[PATH_MAX + 1]{0}; + f[0] = '*'; + _server_to_client_queue->at(pid)->write(f, PATH_MAX); + LOG("Return value=%llu", 1); + return 1; + } + + auto files_to_store_in_mem = capio_cl_engine->getFileToStoreInMemory(); + for (const auto &file : files_to_store_in_mem) { + LOG("Sending file %s", file.c_str()); + char f[PATH_MAX + 1]{0}; + memcpy(f, file.c_str(), file.size()); + _server_to_client_queue->at(pid)->write(f, PATH_MAX); + } + + LOG("Return value=%llu", files_to_store_in_mem.size()); + return files_to_store_in_mem.size(); +} + +void CapioStorageService::storeData(const std::filesystem::path &path, const capio_off64_t offset, + const capio_off64_t buff_size, const char *buffer) const { + const auto file = getFile(path); + + file->writeData(buffer, offset, buff_size); +} + +size_t CapioStorageService::readFromFileToBuffer(const std::filesystem::path &filepath, + capio_off64_t offset, char *buffer, + capio_off64_t count) const { + const auto file = this->getFile(filepath); + + return file->readData(buffer, offset, count); +} diff --git a/capio-server/src/utils/command_line_parser.cpp b/capio-server/src/utils/command_line_parser.cpp new file mode 100644 index 000000000..0eba0ad1b --- /dev/null +++ b/capio-server/src/utils/command_line_parser.cpp @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +std::string parseCLI(int argc, char **argv, char *resolve_prefix) { + Logger *log; + + args::ArgumentParser parser(CAPIO_SERVER_ARG_PARSER_PRE, CAPIO_SERVER_ARG_PARSER_EPILOGUE); + parser.LongSeparator(" "); + parser.LongPrefix("--"); + parser.ShortPrefix("-"); + + args::Group arguments(parser, "Arguments"); + args::HelpFlag help(arguments, "help", "Display this help menu", {'h', "help"}); + args::ValueFlag logfile_src(arguments, "filename", + CAPIO_SERVER_ARG_PARSER_LOGILE_OPT_HELP, {'l', "log"}); + args::ValueFlag logfile_folder( + arguments, "filename", CAPIO_SERVER_ARG_PARSER_LOGILE_DIR_OPT_HELP, {'d', "log-dir"}); + args::ValueFlag config(arguments, "filename", + CAPIO_SERVER_ARG_PARSER_CONFIG_OPT_HELP, {'c', "config"}); + args::Flag noConfigFile(arguments, "no-config", + CAPIO_SERVER_ARG_PARSER_CONFIG_NO_CONF_FILE_HELP, {"no-config"}); + + args::ValueFlag backend( + arguments, "backend", CAPIO_SERVER_ARG_PARSER_BACKEND_OPT_HELP, {'b', "backend"}); + + args::ValueFlag backend_port(arguments, "port", + CAPIO_SERVER_ARG_PARSER_BACKEND_PORT_OPT_HELP, {'p', "port"}); + + args::Flag continueOnErrorFlag(arguments, "continue-on-error", + CAPIO_SERVER_ARG_PARSER_MEM_STORAGE_ONLY_HELP, + {"continue-on-error"}); + + args::Flag memStorageOnly(arguments, "mem-storage-only", + CAPIO_SERVER_ARG_PARSER_CONFIG_NCONTINUE_ON_ERROR_HELP, {"mem-only"}); + + args::ValueFlag capio_cl_resolve_path( + arguments, "capio-cl-relative-to", CAPIO_SERVER_ARG_PARSER_CONFIG_RESOLVE_RELATIVE_TO_HELP, + {"resolve-capiocl-to"}); + + args::ValueFlag controlPlaneBackend( + arguments, "backend", CAPIO_SERVER_ARG_PARSER_CONFIG_CONTROL_PLANE_BACKEND, + {"control-backend"}); + + try { + parser.ParseCLI(argc, argv); + } catch (args::Help &) { + std::cout << CAPIO_SERVER_ARG_PARSER_PRE_COMMAND << parser; + exit(EXIT_SUCCESS); + } catch (args::ParseError &e) { + START_LOG(gettid(), "call()"); + std::cerr << e.what() << std::endl; + std::cerr << parser; + ERR_EXIT("%s", e.what()); + } catch (args::ValidationError &e) { + START_LOG(gettid(), "call()"); + std::cerr << e.what() << std::endl; + std::cerr << parser; + ERR_EXIT("%s", e.what()); + } + + if (continueOnErrorFlag) { +#ifdef CAPIO_LOG + continue_on_error = true; + std::cout << CAPIO_LOG_SERVER_CLI_CONT_ON_ERR_WARNING << std::endl; +#else + + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "--continue-on-error flag given, but logger is not compiled into CAPIO. " + "Flag is ignored."); +#endif + } + + if (memStorageOnly) { + capio_global_configuration->StoreOnlyInMemory = true; + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, + "All files will be stored in memory whenever possible."); + } + + if (logfile_folder) { +#ifdef CAPIO_LOG + log_master_dir_name = args::get(logfile_folder); +#else + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Capio logfile folder, but logging capabilities not compiled into capio!"); +#endif + } + + if (logfile_src) { +#ifdef CAPIO_LOG + // log file was given + std::string token = args::get(logfile_src); + if (token.find(".log") != std::string::npos) { + token.erase(token.length() - 4); // delete .log if for some reason + // is given as parameter + } + logfile_prefix = token; +#else + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Capio logfile provided, but logging capabilities not compiled into capio!"); +#endif + } +#ifdef CAPIO_LOG + auto logname = open_server_logfile(); + log = new Logger(__func__, __FILE__, __LINE__, gettid(), "Created new log file"); + server_println(CAPIO_SERVER_CLI_LOG_SERVER, "started logging to logfile " + logname.string()); +#endif + + if (config) { + std::string token = args::get(config); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "parsing config file: " + token); + // TODO: pass config file path + } else if (noConfigFile) { + capio_global_configuration->workflow_name = std::string_view(get_capio_workflow_name()); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, "skipping config file parsing."); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "Obtained from environment variable current workflow name: " + + capio_global_configuration->workflow_name); + + } else { + START_LOG(gettid(), "call()"); + server_println( + CAPIO_LOG_SERVER_CLI_LEVEL_ERROR, + "Error: no config file provided. To skip config file use --no-config option!"); + ERR_EXIT("no config file provided, and --no-config not provided"); + } + +#ifdef CAPIO_LOG + CAPIO_LOG_LEVEL = get_capio_log_level(); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, + "LOG_LEVEL set to: " + std::to_string(CAPIO_LOG_LEVEL)); + std::cout << CAPIO_LOG_SERVER_CLI_LOGGING_ENABLED_WARNING; + log->log("LOG_LEVEL set to: %d", CAPIO_LOG_LEVEL); + delete log; +#else + if (std::getenv("CAPIO_LOG_LEVEL") != nullptr) { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + CAPIO_LOG_SERVER_CLI_LOGGING_NOT_AVAILABLE); + } +#endif + + // Port used for communication backend + int port = DEFAULT_CAPIO_BACKEND_PORT; + std::string control_backend_name = "multicast"; + if (backend_port) { + port = args::get(backend_port); + } + + if (controlPlaneBackend) { + control_backend_name = std::string(args::get(controlPlaneBackend)); + } + + std::string backend_name = "none"; + if (backend) { + std::string tmp = args::get(backend); + std::transform(tmp.begin(), tmp.end(), tmp.begin(), ::toupper); + backend_name = tmp; + } + + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "Selected backend is: " + backend_name); + capio_communication_service = + new CapioCommunicationService(backend_name, port, control_backend_name); + + if (capio_cl_resolve_path) { + auto path = args::get(capio_cl_resolve_path); + memcpy(resolve_prefix, path.c_str(), PATH_MAX); + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_INFO, "CAPIO-CL relative file prefix: " + path); + } else { + server_println(CAPIO_LOG_SERVER_CLI_LEVEL_WARNING, + "No CAPIO-CL resolve file prefix provided"); + } + + if (config) { + return args::get(config); + } + return ""; +} \ No newline at end of file diff --git a/capio-server/src/utils/distributed_semaphore.cpp b/capio-server/src/utils/distributed_semaphore.cpp new file mode 100644 index 000000000..f971c7164 --- /dev/null +++ b/capio-server/src/utils/distributed_semaphore.cpp @@ -0,0 +1,44 @@ +#include +#include + +void DistributedSemaphore::lock() { + START_LOG(gettid(), "call(locking=%s)", name.c_str()); + if (!locked) { + while (fp == -1) { + nanosleep(&sleep, nullptr); + fp = open(name.c_str(), O_EXCL | O_CREAT | O_WRONLY, 0777); + } + LOG("Locked %s", name.c_str()); + if (write(fp, capio_global_configuration->node_name, + strlen(capio_global_configuration->node_name)) == -1) { + ERR_EXIT("Unable to insert lock holder %s on lock file %s", + capio_global_configuration->node_name, name.c_str()); + } + } + LOG("Completed spinlock on lock file %s", name.c_str()); + locked = true; +} + +void DistributedSemaphore::unlock() const { + START_LOG(gettid(), "call(unlocking=%s)", name.c_str()); + if (locked) { + close(fp); + unlink(name.c_str()); + } + LOG("Unlocked %s", name.c_str()); +} + +DistributedSemaphore::DistributedSemaphore(std::string locking, int sleep_time) + : name(locking), locked(false), fp(-1) { + START_LOG(gettid(), "call(locking=%s, sleep_time=%ld)", name.c_str(), sleep_time); + sleep.tv_nsec = sleep_time; + + this->lock(); +} + +DistributedSemaphore::~DistributedSemaphore() { + START_LOG(gettid(), "call()"); + if (locked) { + this->unlock(); + } +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/capio-tests/CMakeLists.txt similarity index 54% rename from tests/CMakeLists.txt rename to capio-tests/CMakeLists.txt index fccf68a2f..ff7613edd 100644 --- a/tests/CMakeLists.txt +++ b/capio-tests/CMakeLists.txt @@ -6,7 +6,17 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.14.0 ) -FetchContent_MakeAvailable(GoogleTest) +FetchContent_Declare( + mtcl + GIT_REPOSITORY https://github.com/ParaGroup/MTCL + GIT_TAG fbd8aa924b916c6578963af315fdb5f10c3969ad +) +FetchContent_Declare( + capio_cl + GIT_REPOSITORY https://github.com/High-Performance-IO/CAPIO-CL.git + GIT_TAG main +) +FetchContent_MakeAvailable(GoogleTest mtcl capio_cl) ##################################### # CMake module imports @@ -20,4 +30,6 @@ include(GoogleTest) add_subdirectory(unit/posix) add_subdirectory(unit/server) add_subdirectory(unit/syscall) -add_subdirectory(integration) +add_subdirectory(unit/MemoryFiles) +add_subdirectory(multinode/integration) +add_subdirectory(multinode/backend) diff --git a/capio-tests/multinode/backend/CMakeLists.txt b/capio-tests/multinode/backend/CMakeLists.txt new file mode 100644 index 000000000..906696522 --- /dev/null +++ b/capio-tests/multinode/backend/CMakeLists.txt @@ -0,0 +1,76 @@ +##################################### +# Target information +##################################### +set(TARGET_NAME capio_backend_unit_tests) + +set(TARGET_SOURCES + src/main.cpp +) + +set(TARGET_INCLUDE_FOLDER "${CMAKE_SOURCE_DIR}/capio-server/") + +##################################### +# Target definition +##################################### +add_executable(${TARGET_NAME} ${TARGET_SOURCES}) + +##################################### +# Include files and directories +##################################### +target_include_directories(${TARGET_NAME} PRIVATE + ${TARGET_INCLUDE_FOLDER} + ${mtcl_SOURCE_DIR}/include + ${httplib_SOURCE_DIR} + "${CMAKE_SOURCE_DIR}/capio-server/" + ${capio_cl_SOURCE_DIR} + ${capio_cl_SOURCE_DIR}/include +) + +file(GLOB_RECURSE CAPIO_SERVER_SOURCES + "${CMAKE_SOURCE_DIR}/capio-server/*.cpp" +) + + +file(GLOB_RECURSE CAPIO_SERVER_HEADERS "${TARGET_INCLUDE_FOLDER}/*.hpp") + +list(FILTER CAPIO_SERVER_SOURCES EXCLUDE REGEX + ".*/json_parser\\.cpp$" +) + +list(FILTER CAPIO_SERVER_SOURCES EXCLUDE REGEX + ".*/capio_server\\.cpp$" +) + +list(FILTER CAPIO_SERVER_SOURCES EXCLUDE REGEX + ".*/command_line_parser\\.cpp$" +) + + +target_sources(${TARGET_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp + ${CAPIO_SERVER_SOURCES} + "${CAPIO_COMMON_HEADERS}" + "${CAPIO_SERVER_HEADERS}" +) + + +##################################### +# Link libraries +##################################### +target_link_libraries(${TARGET_NAME} PRIVATE GTest::gtest_main rt libcapio_cl) + +##################################### +# Configure tests +##################################### +gtest_discover_tests(${TARGET_NAME} + WORKING_DIRECTORY ../../.. +) + + +##################################### +# Install rules +##################################### +install(TARGETS ${TARGET_NAME} + LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} +) + diff --git a/capio-tests/multinode/backend/docker-compose.yml b/capio-tests/multinode/backend/docker-compose.yml new file mode 100644 index 000000000..7a4cfc8a4 --- /dev/null +++ b/capio-tests/multinode/backend/docker-compose.yml @@ -0,0 +1,43 @@ +services: + node1: + image: alphaunito/capio:latest + tty: true + working_dir: /shared + volumes: + - shared_data:/shared + networks: + capio_net: + aliases: + - node1 + environment: + - CAPIO_LOG_LEVEL=-1 + - APP_TYPE=writer + - CAPIO_DIR=. + command: | + bash -c " capio_server -b tcp --mem-only --no-config & \ + LD_PRELOAD=libcapio_posix.so capio_backend_unit_tests --gtest_break_on_failure --gtest_print_time=1" + + node2: + image: alphaunito/capio:latest + tty: true + working_dir: /shared + volumes: + - shared_data:/shared + networks: + capio_net: + aliases: + - node2 + environment: + - CAPIO_LOG_LEVEL=-1 + - APP_TYPE=reader + - CAPIO_DIR=. + command: | + sleep 5 && bash -c " capio_server -b tcp --mem-only --no-config & \ + LD_PRELOAD=libcapio_posix.so capio_backend_unit_tests --gtest_break_on_failure --gtest_print_time=1" + +volumes: + shared_data: + +networks: + capio_net: + driver: bridge \ No newline at end of file diff --git a/capio-tests/multinode/backend/src/MTCL.hpp b/capio-tests/multinode/backend/src/MTCL.hpp new file mode 100644 index 000000000..5bfcf27e7 --- /dev/null +++ b/capio-tests/multinode/backend/src/MTCL.hpp @@ -0,0 +1,64 @@ +#ifndef TEST_CAPIOCOMMUNICATIONSERVICE_HPP +#define TEST_CAPIOCOMMUNICATIONSERVICE_HPP + +#include +#include +#include +#include +#include +#include + +const char *filename = "data.bin"; +const int chunkSize = 1024; +const int totalSize = 2048; + +inline int writer() { + + char buffer[chunkSize]; + for (int i = 0; i < chunkSize; i++) { + buffer[i] = i % 26 + 'A'; + } + + FILE *fp = fopen(filename, "wb"); + EXPECT_NE(fp, nullptr); + + for (int i = 0; i < totalSize / chunkSize; i++) { + EXPECT_EQ(fwrite(buffer, 1, chunkSize, fp), chunkSize); + } + + fclose(fp); + printf("Wrote %d bytes to %s\n", totalSize, filename); + return 0; +} + +inline int reader() { + + char buffer[chunkSize]; + size_t totalRead = 0; + + FILE *fp = fopen(filename, "rb"); + EXPECT_NE(fp, nullptr); + + // Read in 1024-byte chunks until EOF + size_t bytesRead; + while ((bytesRead = fread(buffer, 1, chunkSize, fp)) > 0) { + totalRead += bytesRead; + } + + EXPECT_EQ(totalRead, totalSize); + + fclose(fp); + printf("Total read: %zu bytes\n", totalRead); + return 0; +} + +TEST(CapioCommServiceTest, TestCapioMemoryNetworkBackend) { + + if (const auto program = std::getenv("APP_TYPE"); std::string(program) == "writer") { + EXPECT_EQ(writer(), 0); + } else { + EXPECT_EQ(reader(), 0); + } +} + +#endif // TEST_CAPIOCOMMUNICATIONSERVICE_HPP diff --git a/capio-tests/multinode/backend/src/main.cpp b/capio-tests/multinode/backend/src/main.cpp new file mode 100644 index 000000000..3d83a01a4 --- /dev/null +++ b/capio-tests/multinode/backend/src/main.cpp @@ -0,0 +1,19 @@ + +#include "MTCL.hpp" +#include +#include +#include "capiocl/engine.h" +#include +#include + +capiocl::engine::Engine *capio_cl_engine; + +CapioGlobalConfiguration *capio_global_configuration; + +int main(int argc, char **argv) { + capio_cl_engine = new capiocl::engine::Engine(); + capio_global_configuration = new CapioGlobalConfiguration(); + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/capio-tests/multinode/integration/CMakeLists.txt b/capio-tests/multinode/integration/CMakeLists.txt new file mode 100644 index 000000000..8b81562e2 --- /dev/null +++ b/capio-tests/multinode/integration/CMakeLists.txt @@ -0,0 +1,53 @@ +##################################### +# Target information +##################################### +set(TARGET_NAME_MAP capio_integration_test_map) +set(TARGET_NAME_MERGE capio_integration_test_merge) +set(TARGET_NAME_SPLIT capio_integration_test_split) + +##################################### +# Target definition +##################################### +add_executable(${TARGET_NAME_MAP} src/mapreduce.cpp) +add_executable(${TARGET_NAME_MERGE} src/merge.cpp) +add_executable(${TARGET_NAME_SPLIT} src/split.cpp) + +##################################### +# Link libraries +##################################### +target_link_libraries(${TARGET_NAME_MAP} PRIVATE GTest::gtest) +target_link_libraries(${TARGET_NAME_MERGE} PRIVATE GTest::gtest) +target_link_libraries(${TARGET_NAME_SPLIT} PRIVATE GTest::gtest) + +##################################### +# Configure tests +##################################### +gtest_discover_tests(${TARGET_NAME_MAP} + WORKING_DIRECTORY ../../.. + PROPERTIES ENVIRONMENT LD_PRELOAD=${PROJECT_BINARY_DIR}/src/posix/libcapio_posix.so +) + +gtest_discover_tests(${TARGET_NAME_MERGE} + WORKING_DIRECTORY ../../.. + PROPERTIES ENVIRONMENT LD_PRELOAD=${PROJECT_BINARY_DIR}/src/posix/libcapio_posix.so +) + +gtest_discover_tests(${TARGET_NAME_SPLIT} + WORKING_DIRECTORY ../../.. + PROPERTIES ENVIRONMENT LD_PRELOAD=${PROJECT_BINARY_DIR}/src/posix/libcapio_posix.so +) + +##################################### +# Install rules +##################################### +install(TARGETS ${TARGET_NAME_MAP} + LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install(TARGETS ${TARGET_NAME_MERGE} + LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install(TARGETS ${TARGET_NAME_SPLIT} + LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} +) \ No newline at end of file diff --git a/capio-tests/multinode/integration/docker-compose.yml b/capio-tests/multinode/integration/docker-compose.yml new file mode 100644 index 000000000..12a0b086e --- /dev/null +++ b/capio-tests/multinode/integration/docker-compose.yml @@ -0,0 +1,52 @@ +services: + split: + image: alphaunito/capio:latest + tty: true + working_dir: /shared + environment: + - CAPIO_DIR=/shared + - CAPIO_LOG_LEVEL=-1 + volumes: + - shared_data:/shared + networks: + capio_net: + aliases: + - split + command: capio_integration_test_split --gtest_break_on_failure --gtest_print_time=1 + + map: + image: alphaunito/capio:latest + tty: true + working_dir: /shared + environment: + - CAPIO_DIR=/shared + - CAPIO_LOG_LEVEL=-1 + volumes: + - shared_data:/shared + networks: + capio_net: + aliases: + - map + command: capio_integration_test_map --gtest_break_on_failure --gtest_print_time=1 + + merge: + image: alphaunito/capio:latest + tty: true + working_dir: /shared + environment: + - CAPIO_DIR=/shared + - CAPIO_LOG_LEVEL=-1 + volumes: + - shared_data:/shared + networks: + capio_net: + aliases: + - merge + command: capio_integration_test_merge --gtest_break_on_failure --gtest_print_time=1 + +volumes: + shared_data: + +networks: + capio_net: + driver: bridge \ No newline at end of file diff --git a/capio-tests/multinode/integration/src/common.hpp b/capio-tests/multinode/integration/src/common.hpp new file mode 100644 index 000000000..5b3f4bc36 --- /dev/null +++ b/capio-tests/multinode/integration/src/common.hpp @@ -0,0 +1,283 @@ +#ifndef COMMON_HPP +#define COMMON_HPP +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// just a bunch of random phrases +static const char *phrases[] = { + "This is short phrase!", + "I never knew what hardship looked like until it started raining bowling balls.", + "The clock within this blog and the clock on my laptop are 1 hour different from each other.", + "The glacier came alive as the climbers hiked closer.", + "Mom didn't understand why no one else wanted a hot tub full of jello.", + "The reservoir water level continued to lower while we enjoyed our long shower.", + "I was very proud of my nickname throughout high school but today- I couldn’t be any different " + "to what my nickname was.", + "In Pisa there is the leaning tower!", + "Dolores wouldn't have eaten the meal if she had known what it actually was.", + "He enjoys practicing his ballet in the bathroom.", + "It isn't true that my mattress is made of cotton candy.", + "The hand sanitizer was actually clear glue.", + "Can I generate meaningful random sentences?", + "Sometimes a random word just isn't enough, and that is where the random sentence generator " + "comes into play. By inputting the desired number, you can make a list of as many random " + "sentences as you want or need. Producing random sentences can be helpful in a number of " + "different ways.", + "Bye!", + "The memory we used to share is no longer coherent.", + "The tour bus was packed with teenage girls heading toward their next adventure.", + "The small white buoys marked the location of hundreds of crab pots.", + "The wake behind the boat told of the past while the open sea for told life in the unknown " + "future.", + "Going from child, to childish, to childlike is only a matter of time.", + "The beauty of the African sunset disguised the danger lurking nearby.", + "Little Red Riding Hood decided to wear orange today.", + "I honestly find her about as intimidating as a basket of kittens." + "Hello world!"}; + +static const int maxphraselen = 1024; +static const int maxfilename = 32; +static const int maxnumfiles = 10000; +static const int REALLOC_BATCH = 10485760; // 10MB +static const int REDUCE_CHUNK = 10240; // 10K +static const int IO_BUFFER = 1048576; // 1MB +static char fmtout[] = "%s/outfile_%05d.dat"; +[[maybe_unused]] static char fmtin[] = + "%s/infile_%05d.dat"; // 5 is the number of digits of maxnumfiles + +// reading file data into memory +static inline std::vector readdata(FILE *fp) { + std::vector data; + data.reserve(REALLOC_BATCH); + + char *line = nullptr; + size_t len = 0; + ssize_t r; + + while (errno = 0, (r = getline(&line, &len, fp)) > 0) { + if (data.capacity() < data.size() + static_cast(r)) { + // grow in batches + size_t newCap = data.capacity(); + while (newCap < data.size() + static_cast(r)) { + newCap += REALLOC_BATCH; + } + data.reserve(newCap); + } + + // append raw bytes + data.insert(data.end(), line, line + r); + } + + if (errno != 0) { + perror("getline"); + data.clear(); + } + + free(line); // getline() allocates, must free + return data; +} + +static inline double diffmsec(const struct timeval a, const struct timeval b) { + long sec = (a.tv_sec - b.tv_sec); + long usec = (a.tv_usec - b.tv_usec); + + if (usec < 0) { + --sec; + usec += 1000000; + } + return ((double) (sec * 1000) + ((double) usec) / 1000.0); +} + +[[maybe_unused]] static int writedata(const std::vector &data, float percent, + const char *destdir, ssize_t dstart, ssize_t dfiles) { + int error = 0; + std::vector fps(dfiles, nullptr); + + char filepath[2 * PATH_MAX]{0}; + + // Open (truncate) all files + for (int j = 0, i = dstart; i < (dstart + dfiles); ++i, ++j) { + sprintf(filepath, fmtout, destdir, i); + fps[j] = fopen(filepath, "w"); + if (!fps[j]) { + perror("fopen"); + std::cerr << "cannot create (open) the file " << filepath << "\n"; + error = -1; + } + } + + if (!error) { + size_t nbytes = static_cast(data.size() * percent); + size_t cnt = 0; + const char *dataptr = data.data(); + + while (nbytes > 0) { + size_t chunk = (nbytes > REDUCE_CHUNK) ? REDUCE_CHUNK : nbytes; + if (fwrite(dataptr, 1, chunk, fps[cnt]) != chunk) { + perror("fwrite"); + error = -1; + break; + } + cnt = (cnt + 1) % dfiles; + nbytes -= chunk; + } + } + + // Close all files + for (auto f : fps) { + if (f) { + fclose(f); + } + } + + return error; +} + +[[maybe_unused]] static char *getrandomphrase(char *buffer, size_t len) { + static int phrases_entry = sizeof(phrases) / sizeof(phrases[0]); + + bzero(buffer, len); + + int r; + switch ((r = rand()) % 4) { + case 0: { + strncat(buffer, phrases[r % phrases_entry], len - 1); + break; + } + case 1: { + strncat(buffer, phrases[r % phrases_entry], len - 1); + ssize_t l = len - strlen(buffer) - 1; + if (l > 0) { + strncat(buffer, phrases[rand() % phrases_entry], l); + } + break; + } + case 2: { + strncat(buffer, phrases[r % phrases_entry], len - 1); + ssize_t l = len - strlen(buffer) - 1; + if (l > 0) { + strncat(buffer, phrases[rand() % phrases_entry], l); + } + l = len - strlen(buffer) - 1; + if (l > 0) { + strncat(buffer, phrases[rand() % phrases_entry], l); + } + break; + } + case 3: { + strncat(buffer, phrases[r % phrases_entry], len - 1); + break; + } + } + buffer[strlen(buffer)] = '\n'; + + return buffer; +} + +char **build_args() { + char **args = (char **) malloc(3 * sizeof(uintptr_t)); + + char const *command = std::getenv("CAPIO_SERVER_PATH"); + if (command == nullptr) { + command = "capio_server"; + } + + args[0] = strdup(command); + args[1] = strdup("--no-config"); + args[2] = (char *) nullptr; + + return args; +} + +char **build_env(char **envp) { + std::vector vars; + for (int i = 0; envp[i] != nullptr; i++) { + const std::string_view var(envp[i]); + const std::string_view key = var.substr(0, var.find('=')); + if (key != "LD_PRELOAD") { + vars.emplace_back(i); + } + } + + char **cleaned_env = (char **) malloc((vars.size() + 2) * sizeof(uintptr_t)); + for (long unsigned int i = 0; i < vars.size(); i++) { + cleaned_env[i] = strdup(envp[i]); + } + cleaned_env[vars.size()] = strdup("LD_PRELOAD="); + cleaned_env[vars.size() + 1] = (char *) nullptr; + + return cleaned_env; +} + +class CapioServerEnvironment : public testing::Environment { + private: + char **args; + char **envp; + int server_pid; + + public: + explicit CapioServerEnvironment(char **envp) : args(build_args()), envp(build_env(envp)) { + server_pid = -1; + }; + + ~CapioServerEnvironment() override { + for (int i = 0; args[i] != nullptr; i++) { + free(args[i]); + } + free(args); + for (int i = 0; envp[i] != nullptr; i++) { + free(envp[i]); + } + free(envp); + }; + + void SetUp() override { + if (server_pid < 0) { + ASSERT_NE(std::getenv("CAPIO_DIR"), nullptr); + ASSERT_GE(server_pid = fork(), 0); + if (server_pid == 0) { + execvpe(args[0], args, envp); + _exit(127); + } else { + sleep(5); + } + } + } + + void TearDown() override { + if (server_pid > 0) { + kill(server_pid, SIGTERM); + waitpid(server_pid, nullptr, 0); + server_pid = -1; + } + } +}; + +class CapioEnvironmentHandler : public testing::EmptyTestEventListener { + + CapioServerEnvironment *_env; + + // Called before a test starts. + void OnTestStart(const testing::TestInfo &test_info) override { _env->SetUp(); } + + // Called after a test ends. + void OnTestEnd(const testing::TestInfo &test_info) override { _env->TearDown(); } + + public: + explicit CapioEnvironmentHandler(char **envp) + : testing::EmptyTestEventListener(), _env(new CapioServerEnvironment(envp)) {} +}; + +#endif // COMMON_HPP diff --git a/capio-tests/multinode/integration/src/mapreduce.cpp b/capio-tests/multinode/integration/src/mapreduce.cpp new file mode 100644 index 000000000..2f7ad2063 --- /dev/null +++ b/capio-tests/multinode/integration/src/mapreduce.cpp @@ -0,0 +1,77 @@ +#include "common.hpp" + +int mapReduceFunction(const char *sourcedirname, ssize_t sstart, ssize_t sfiles, + const char *destdirname, ssize_t dstart, ssize_t dfiles, float percent, + int *return_value) { + struct timeval before{}, after{}; + struct stat statbuf{}; + + gettimeofday(&before, nullptr); + + EXPECT_NE(stat(sourcedirname, &statbuf), -1); + EXPECT_TRUE(S_ISDIR(statbuf.st_mode)); + EXPECT_GE(sstart, 0); + EXPECT_GT(sfiles, 0); + + EXPECT_NE(stat(destdirname, &statbuf), -1); + EXPECT_TRUE(S_ISDIR(statbuf.st_mode)); + EXPECT_GE(dstart, 0); + EXPECT_GT(dfiles, 0); + + EXPECT_GT(percent, 0); + EXPECT_LE(percent, 1); + + std::vector merged; + + char filepath[2 * PATH_MAX]{0}; + // Concatenate all files into `merged` + for (int i = sstart; i < (sstart + sfiles); ++i) { + sprintf(filepath, fmtin, sourcedirname, i); + + FILE *fp = fopen(filepath, "r"); + EXPECT_TRUE(fp != nullptr); + + auto chunk = readdata(fp); + EXPECT_FALSE(chunk.empty()); + + merged.insert(merged.end(), chunk.begin(), chunk.end()); + + fclose(fp); + } + + // Call writedata with the concatenated buffer + int r = writedata(merged, percent, destdirname, dstart, dfiles); + + *return_value = r; + + gettimeofday(&after, nullptr); + double elapsed_time = diffmsec(after, before); + fprintf(stdout, "MAPREDUCE: elapsed time (ms) : %g\n", elapsed_time); + + return 0; +} + +TEST(integrationTests, RunMapReducerTest) { + int ret1 = -1, ret2 = -1; + std::thread mapReducer1(mapReduceFunction, std::getenv("CAPIO_DIR"), 0, 5, + std::getenv("CAPIO_DIR"), 0, 5, 0.3, &ret1); + std::thread mapReducer2(mapReduceFunction, std::getenv("CAPIO_DIR"), 1, 5, + std::getenv("CAPIO_DIR"), 1, 5, 0.3, &ret2); + + mapReducer1.join(); + mapReducer2.join(); + + EXPECT_EQ(ret1, 0); + EXPECT_EQ(ret2, 0); +} + +int main(int argc, char **argv, char **envp) { + testing::InitGoogleTest(&argc, argv); + + // Destroy CAPIO SERVER and recreate it between each test execution + // to provide a clean environment for each test + testing::TestEventListeners &listeners = testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new CapioEnvironmentHandler(envp)); + + return RUN_ALL_TESTS(); +} diff --git a/capio-tests/multinode/integration/src/merge.cpp b/capio-tests/multinode/integration/src/merge.cpp new file mode 100644 index 000000000..3a7477988 --- /dev/null +++ b/capio-tests/multinode/integration/src/merge.cpp @@ -0,0 +1,65 @@ +#include "common.hpp" + +#include + +int mergeFunction(ssize_t nfiles, const char *sourcedir, const char *destdir) { + timeval before{}, after{}; + struct stat statbuf{}; + + gettimeofday(&before, nullptr); + + EXPECT_GT(nfiles, 0); + + EXPECT_NE(stat(sourcedir, &statbuf), -1); + EXPECT_TRUE(S_ISDIR(statbuf.st_mode)); + + EXPECT_NE(stat(destdir, &statbuf), -1); + EXPECT_TRUE(S_ISDIR(statbuf.st_mode)); + + std::vector merged; // final result buffer + + char filepath[2 * PATH_MAX]{0}; + for (int i = 0; i < nfiles; ++i) { + sprintf(filepath, fmtout, sourcedir, i); + FILE *fp = fopen(filepath, "r"); + EXPECT_TRUE(fp != nullptr); + + auto chunk = readdata(fp); + EXPECT_FALSE(chunk.empty()); + + // append this file's data to merged + merged.insert(merged.end(), chunk.begin(), chunk.end()); + + fclose(fp); + } + + char resultpath[2 * PATH_MAX]{0}; + sprintf(resultpath, "%s/result.dat", destdir); + FILE *fp = fopen(resultpath, "w"); + EXPECT_TRUE(fp); + + EXPECT_EQ(fwrite(merged.data(), 1, merged.size(), fp), merged.size()); + + fclose(fp); + + gettimeofday(&after, nullptr); + double elapsed_time = diffmsec(after, before); + fprintf(stdout, "MERGE: elapsed time (ms) : %g\n", elapsed_time); + + return 0; +} + +TEST(integrationTests, RunMergeProcess) { + EXPECT_EQ(mergeFunction(2, std::getenv("CAPIO_DIR"), std::getenv("CAPIO_DIR")), 0); +} + +int main(int argc, char **argv, char **envp) { + testing::InitGoogleTest(&argc, argv); + + // Destroy CAPIO SERVER and recreate it between each test execution + // to provide a clean environment for each test + testing::TestEventListeners &listeners = testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new CapioEnvironmentHandler(envp)); + + return RUN_ALL_TESTS(); +} diff --git a/capio-tests/multinode/integration/src/split.cpp b/capio-tests/multinode/integration/src/split.cpp new file mode 100644 index 000000000..a3d9adb96 --- /dev/null +++ b/capio-tests/multinode/integration/src/split.cpp @@ -0,0 +1,82 @@ +#include "common.hpp" + +int splitFunction(ssize_t nlines, ssize_t nfiles, char *dirname) { + struct timeval before, after; + gettimeofday(&before, NULL); + EXPECT_GT(nlines, 0); + EXPECT_GT(nfiles, 0); + + // sanity check + EXPECT_LE(nfiles, maxnumfiles); + + struct stat statbuf; + EXPECT_NE(stat(dirname, &statbuf), -1); + EXPECT_TRUE(S_ISDIR(statbuf.st_mode)); // not a directory + + FILE **fp = (FILE **) calloc(sizeof(FILE *), nfiles); + EXPECT_TRUE(fp); + + char **buffer = (char **) calloc(IO_BUFFER, nfiles); + EXPECT_TRUE(buffer); + + int error = 0; + char filepath[2 * PATH_MAX]{0}; + // opening (truncating) all files + for (int i = 0; i < nfiles; ++i) { + sprintf(filepath, fmtin, dirname, i); + fp[i] = fopen(filepath, "w"); + + EXPECT_TRUE(fp[i]); + EXPECT_EQ(setvbuf(fp[i], buffer[i], _IOFBF, IO_BUFFER), 0); + } + if (!error) { + char *buffer = (char *) calloc(maxphraselen, 1); + if (!buffer) { + perror("malloc"); + error = -1; + } + if (!error) { + size_t cnt = 0; + for (ssize_t i = 0; i < nlines; ++i) { + char *line = getrandomphrase(buffer, maxphraselen); + size_t n = strlen(line); + if (fwrite(line, 1, n, fp[cnt]) != n) { + perror("fwrite"); + error = -1; + break; + } + cnt = (cnt + 1) % nfiles; // generiting one line for each file in a rr fashion + } + } + } + + // closing all files + for (int i = 0; i < nfiles; ++i) { + if (fp[i]) { + fclose(fp[i]); + } + } + free(fp); + + gettimeofday(&after, NULL); + double elapsed_time = diffmsec(after, before); + fprintf(stdout, "SPLIT elapsed time (ms) : %g\n", elapsed_time); + + return error; +} + +TEST(integrationTests, RunSplitProcess) { + + EXPECT_EQ(splitFunction(10, 10, std::getenv("CAPIO_DIR")), 0); +} + +int main(int argc, char **argv, char **envp) { + testing::InitGoogleTest(&argc, argv); + + // Destroy CAPIO SERVER and recreate it between each test execution + // to provide a clean environment for each test + testing::TestEventListeners &listeners = testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new CapioEnvironmentHandler(envp)); + + return RUN_ALL_TESTS(); +} diff --git a/capio-tests/unit/MemoryFiles/CMakeLists.txt b/capio-tests/unit/MemoryFiles/CMakeLists.txt new file mode 100644 index 000000000..e2802bd7e --- /dev/null +++ b/capio-tests/unit/MemoryFiles/CMakeLists.txt @@ -0,0 +1,70 @@ +##################################### +# Target information +##################################### +set(TARGET_NAME capio_memory_file_unit_tests) + +file(GLOB_RECURSE CAPIO_SERVER_SOURCES ${CMAKE_SOURCE_DIR}/capio-server/ "*.cpp") + +set(TARGET_INCLUDE_FOLDER + "${CMAKE_SOURCE_DIR}/capio-server/" + "${CMAKE_SOURCE_DIR}/capio-posix") + +set(TARGET_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp + ${CAPIO_SERVER_SOURCES} +) + +##################################### +# Target definition +##################################### +add_executable(${TARGET_NAME} ${TARGET_SOURCES}) + +##################################### +# External projects +##################################### +set(SYSCALL_INTERCEPT_BINARY_DIR "${PROJECT_BINARY_DIR}/capio-posix/syscall_intercept") +set(SYSCALL_INTERCEPT_INCLUDE_FOLDER + "${SYSCALL_INTERCEPT_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR}") +set(SYSCALL_INTERCEPT_LIB_FOLDER + "${SYSCALL_INTERCEPT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +##################################### +# Definitions +##################################### +target_compile_definitions(${TARGET_NAME} PRIVATE __CAPIO_POSIX) + +##################################### +# Include files and directories +##################################### +file(GLOB_RECURSE CAPIO_POSIX_HEADERS "${TARGET_INCLUDE_FOLDER}/*.hpp") +file(GLOB_RECURSE SYSCALL_INTERCEPT_HEADERS "${SYSCALL_INTERCEPT_INCLUDE_FOLDER}/*.h") +target_sources(${TARGET_NAME} PRIVATE + "${CAPIO_COMMON_HEADERS}" + "${CAPIO_POSIX_HEADERS}" + "${GTEST_CONFIG_HEADERS}" + "${SYSCALL_INTERCEPT_HEADERS}" +) +target_include_directories(${TARGET_NAME} PRIVATE + "${TARGET_INCLUDE_FOLDER}" + "${SYSCALL_INTERCEPT_INCLUDE_FOLDER}" +) + +##################################### +# Link libraries +##################################### +target_link_directories(${TARGET_NAME} PRIVATE ${SYSCALL_INTERCEPT_LIB_FOLDER}) +target_link_libraries(${TARGET_NAME} PRIVATE rt syscall_intercept GTest::gtest_main) + +##################################### +# Configure tests +##################################### +gtest_discover_tests(${TARGET_NAME} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} +) + +##################################### +# Install rules +##################################### +install(TARGETS ${TARGET_NAME} + LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} +) \ No newline at end of file diff --git a/capio-tests/unit/MemoryFiles/src/main.cpp b/capio-tests/unit/MemoryFiles/src/main.cpp new file mode 100644 index 000000000..77b800299 --- /dev/null +++ b/capio-tests/unit/MemoryFiles/src/main.cpp @@ -0,0 +1,55 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +const std::string filename = "hello.txt"; +constexpr size_t textSize = 32 * 1024 * 1024; // 32 MBB + +inline std::string generateLongText() { + std::string pattern = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n"; + std::string longText; + while (longText.size() + pattern.size() <= textSize) { + longText += pattern; + } + // Pad the remaining bytes + if (longText.size() < textSize) { + longText.append(textSize - longText.size(), 'X'); + } + return longText; +} + +TEST(CapioMemoryFileTest, TestReadAndWrite32MBFile) { + std::string longText = generateLongText(); + + std::ofstream outFile(filename, std::ios::out | std::ios::binary); + EXPECT_TRUE(outFile.is_open()); + outFile.write(longText.c_str(), longText.size()); + outFile.close(); + + std::string fileContent(textSize, ' '); + std::ifstream inFile(filename, std::ios::in | std::ios::binary); + + EXPECT_TRUE(inFile.is_open()); + EXPECT_TRUE(inFile.read(fileContent.data(), fileContent.size())); + + inFile.close(); + + EXPECT_EQ(fileContent.size(), longText.size()); + + for (size_t i = 0; i < longText.size(); ++i) { + EXPECT_EQ(fileContent[i], longText[i]); + } +} + +int main(int argc, char **argv, char **envp) { + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/tests/unit/posix/CMakeLists.txt b/capio-tests/unit/posix/CMakeLists.txt similarity index 89% rename from tests/unit/posix/CMakeLists.txt rename to capio-tests/unit/posix/CMakeLists.txt index 4bb8bf0a0..57db916bd 100644 --- a/tests/unit/posix/CMakeLists.txt +++ b/capio-tests/unit/posix/CMakeLists.txt @@ -2,7 +2,8 @@ # Target information ##################################### set(TARGET_NAME capio_posix_unit_tests) -set(TARGET_INCLUDE_FOLDER "${PROJECT_SOURCE_DIR}/src/posix") +set(TARGET_INCLUDE_FOLDER "${CMAKE_SOURCE_DIR}/capio-posix") +list(FILTER TARGET_INCLUDE_FOLDER EXCLUDE REGEX ".*/libcapio_posix\\.cpp$") set(TARGET_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/realpath.cpp ) @@ -15,7 +16,7 @@ add_executable(${TARGET_NAME} ${TARGET_SOURCES}) ##################################### # External projects ##################################### -set(SYSCALL_INTERCEPT_BINARY_DIR "${PROJECT_BINARY_DIR}/src/posix/syscall_intercept") +set(SYSCALL_INTERCEPT_BINARY_DIR "${PROJECT_BINARY_DIR}/capio-posix/syscall_intercept") set(SYSCALL_INTERCEPT_INCLUDE_FOLDER "${SYSCALL_INTERCEPT_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR}") set(SYSCALL_INTERCEPT_LIB_FOLDER diff --git a/tests/unit/posix/src/realpath.cpp b/capio-tests/unit/posix/src/realpath.cpp similarity index 99% rename from tests/unit/posix/src/realpath.cpp rename to capio-tests/unit/posix/src/realpath.cpp index cfa38e8ae..e338f2212 100644 --- a/tests/unit/posix/src/realpath.cpp +++ b/capio-tests/unit/posix/src/realpath.cpp @@ -1,8 +1,6 @@ -#include - -#include - #include +#include +#include #include "utils/filesystem.hpp" diff --git a/tests/unit/server/CMakeLists.txt b/capio-tests/unit/server/CMakeLists.txt similarity index 64% rename from tests/unit/server/CMakeLists.txt rename to capio-tests/unit/server/CMakeLists.txt index 38931c6f3..93c8c1263 100644 --- a/tests/unit/server/CMakeLists.txt +++ b/capio-tests/unit/server/CMakeLists.txt @@ -2,10 +2,7 @@ # Target information ##################################### set(TARGET_NAME capio_server_unit_tests) -set(TARGET_INCLUDE_FOLDER "${PROJECT_SOURCE_DIR}/src/server") -set(TARGET_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/src/capio_file.cpp -) + ##################################### # Target definition @@ -15,17 +12,33 @@ add_executable(${TARGET_NAME} ${TARGET_SOURCES}) ##################################### # Include files and directories ##################################### +file(GLOB_RECURSE CAPIO_SERVER_SOURCES + "${CMAKE_SOURCE_DIR}/capio-server/src/storage-service/*.cpp" + "${CMAKE_SOURCE_DIR}/capio-server/src/capio-cl-engine/capio_cl_engine.cpp" + "${CMAKE_SOURCE_DIR}/capio-server/src/client-manager/client_manager.cpp" +) + +set(TARGET_INCLUDE_FOLDER "${CMAKE_SOURCE_DIR}/capio-server/") file(GLOB_RECURSE CAPIO_SERVER_HEADERS "${TARGET_INCLUDE_FOLDER}/*.hpp") + + target_sources(${TARGET_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp + ${CAPIO_SERVER_SOURCES} "${CAPIO_COMMON_HEADERS}" "${CAPIO_SERVER_HEADERS}" + +) +target_include_directories(${TARGET_NAME} PRIVATE + ${TARGET_INCLUDE_FOLDER} + ${capio_cl_SOURCE_DIR} + ${capio_cl_SOURCE_DIR}/include ) -target_include_directories(${TARGET_NAME} PRIVATE ${TARGET_INCLUDE_FOLDER}) ##################################### # Link libraries ##################################### -target_link_libraries(${TARGET_NAME} PRIVATE GTest::gtest_main rt) +target_link_libraries(${TARGET_NAME} PRIVATE GTest::gtest_main rt libcapio_cl) ##################################### # Configure tests @@ -34,9 +47,11 @@ gtest_discover_tests(${TARGET_NAME} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} ) + ##################################### # Install rules ##################################### install(TARGETS ${TARGET_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} -) \ No newline at end of file +) + diff --git a/capio-tests/unit/server/src/CapioCacheSPSCQueueTests.hpp b/capio-tests/unit/server/src/CapioCacheSPSCQueueTests.hpp new file mode 100644 index 000000000..f65f8f385 --- /dev/null +++ b/capio-tests/unit/server/src/CapioCacheSPSCQueueTests.hpp @@ -0,0 +1,426 @@ +#ifndef CAPIOCACHESPSCQUEUETESTS_HPP +#define CAPIOCACHESPSCQUEUETESTS_HPP + +#include + +#include "../capio-server/include/utils/configuration.hpp" + +#include "../capio-posix/utils/env.hpp" +#include "../capio-posix/utils/filesystem.hpp" +#include "../capio-posix/utils/types.hpp" +#include "capio/response_queue.hpp" + +#include + +inline SPSCQueue *cts_queue, *stc_queue; +inline std::unordered_map *bufs_response; +inline CircularBuffer *buf_requests; + +#include "../capio-posix/utils/cache.hpp" +#include "SourceText.hpp" + +auto checkStringEquality = [](const std::string &a, const std::string &b) { + size_t minLen = std::min(a.size(), b.size()); + for (size_t i = 0; i < minLen; ++i) { + if (a[i] != b[i]) { + std::cout << "Difference at offset " << i << ": '" << a[i] << "' vs '" << b[i] << "'\n"; + } + } + if (a.size() != b.size()) { + std::cout << "Strings have different lengths. input: " << a.size() + << ". check: " << b.size() << std::endl; + } +}; + +WriteRequestCacheMEM *writeCache; +ReadRequestCacheMEM *readCache; + +int test_fd = -1; +std::string test_file_name = "test.dat"; + +void init_server_data_structures() { + writeCache = new WriteRequestCacheMEM(); + readCache = new ReadRequestCacheMEM(); + bufs_response = new std::unordered_map(); + files = new CPFiles_t(); + capio_files_descriptors = new CPFileDescriptors_t(); + cts_queue = new SPSCQueue("queue-tests.cts", get_cache_lines(), get_cache_line_size()); + stc_queue = new SPSCQueue("queue-tests.stc", get_cache_lines(), get_cache_line_size()); + buf_requests = + new CircularBuffer(SHM_COMM_CHAN_NAME, CAPIO_REQ_BUFF_CNT, CAPIO_REQ_MAX_SIZE); + + auto tid = gettid(); + bufs_response->insert( + std::make_pair(tid, new ResponseQueue(SHM_COMM_CHAN_NAME_RESP + std::to_string(tid)))); +} + +void delete_server_data_structures() { + delete cts_queue; + delete stc_queue; + delete writeCache; + delete readCache; + delete files; + delete capio_files_descriptors; + delete buf_requests; + for (auto itm : *bufs_response) { + delete itm.second; + } + delete bufs_response; +} + +class WriteMemReqWrapper : public WriteRequestCacheMEM { + public: + void request(const off64_t count, const long tid, const char *path, + const capio_off64_t offset) const { + this->write_request(count, tid, path, offset); + } +}; + +TEST(CapioCacheSPSCQueue, TestWriteCacheWithSpscQueueWrite) { + + unsigned long long_test_length = strlen(SOURCE_TEST_TEXT) + 1; + auto tmp_buf = new std::unique_ptr(new char[long_test_length]); + + init_server_data_structures(); + + capio_files_descriptors->emplace(test_fd, test_file_name); + files->insert({test_fd, {std::make_shared(0), 0, 0, false}}); + + writeCache->write(test_fd, SOURCE_TEST_TEXT, long_test_length); + writeCache->flush(); + + std::thread t1([long_test_length, tmp_buf] { + cts_queue->read(tmp_buf->get(), long_test_length); + + // In the event a flush is triggered with a write of size 0, read again + if (strlen(tmp_buf->get()) == 0) { + cts_queue->read(tmp_buf->get(), long_test_length); + } + + if (strncmp(tmp_buf->get(), SOURCE_TEST_TEXT, long_test_length) != 0) { + checkStringEquality(std::string(tmp_buf->get()), std::string(SOURCE_TEST_TEXT)); + } + + EXPECT_EQ(strncmp(tmp_buf->get(), SOURCE_TEST_TEXT, long_test_length), 0); + }); + + t1.join(); + + delete tmp_buf; + delete_server_data_structures(); +} + +TEST(CapioCacheSPSCQueue, TestWriteCacheSPSCQueueAndCapioFile) { + unsigned long long_test_length = strlen(SOURCE_TEST_TEXT) + 1; + auto readBufSize = 1024; + + init_server_data_structures(); + + capio_files_descriptors->emplace(test_fd, test_file_name); + files->insert({test_fd, {std::make_shared(0), 0, 0, false}}); + + writeCache->write(test_fd, SOURCE_TEST_TEXT, long_test_length); + writeCache->flush(); + + std::thread t2([long_test_length, readBufSize] { + CapioMemoryFile *testFile = new CapioMemoryFile(test_file_name); + testFile->readFromQueue(*cts_queue, 0, long_test_length); + char *readBuf = new char[readBufSize]{}; + for (unsigned long offset = 0; offset < long_test_length; offset += readBufSize) { + testFile->readData(readBuf, offset, readBufSize); + EXPECT_EQ(strncmp(readBuf, SOURCE_TEST_TEXT + offset, readBufSize), 0); + } + + delete[] readBuf; + delete testFile; + }); + + t2.join(); + + delete_server_data_structures(); +} + +TEST(CapioCacheSPSCQueue, TestWriteCacheSPSCQueueAndCapioFileWithRequest) { + unsigned long long_test_length = strlen(SOURCE_TEST_TEXT) + 1; + auto readBufSize = 1024; + + init_server_data_structures(); + + capio_files_descriptors->emplace(test_fd, test_file_name); + files->insert({test_fd, {std::make_shared(0), 0, 0, false}}); + + std::thread server_thread([readBufSize] { + char *req = new char[CAPIO_REQ_MAX_SIZE]; + int code; + long int tid; + char path[PATH_MAX]; + off64_t write_size; + capio_off64_t offset; + + buf_requests->read(req, CAPIO_REQ_MAX_SIZE); + + auto [ptr, ec] = std::from_chars(req, req + 4, code); + EXPECT_EQ(ec, std::errc()); + strcpy(req, ptr + 1); + EXPECT_EQ(code, CAPIO_REQUEST_WRITE_MEM); + sscanf(req, "%ld %s %llu %ld", &tid, path, &offset, &write_size); + EXPECT_EQ(strcmp(path, test_file_name.c_str()), 0); + + CapioMemoryFile *testFile = new CapioMemoryFile(path); + testFile->readFromQueue(*cts_queue, 0, write_size); + + char *readBuf = new char[readBufSize]{}; + for (auto offset = 0; offset < write_size; offset += readBufSize) { + testFile->readData(readBuf, offset, readBufSize); + EXPECT_EQ(strncmp(readBuf, SOURCE_TEST_TEXT + offset, readBufSize), 0); + } + + delete[] readBuf; + delete[] req; + delete testFile; + }); + + WriteMemReqWrapper wrapper; + + wrapper.request(long_test_length, gettid(), test_file_name.c_str(), 0); + + writeCache->write(test_fd, SOURCE_TEST_TEXT, long_test_length); + writeCache->flush(); + + server_thread.join(); + + delete_server_data_structures(); +} + +TEST(CapioCacheSPSCQueue, TestWriteCacheSPSCQueueAndCapioFileWithRequestAndSeek) { + unsigned long long_test_length = strlen(SOURCE_TEST_TEXT) + 1; + auto readBufSize = 1024; + + init_server_data_structures(); + + capio_files_descriptors->emplace(test_fd, test_file_name); + files->insert({test_fd, {std::make_shared(0), 0, 0, false}}); + + std::thread t3([readBufSize, long_test_length] { + char *req = new char[CAPIO_REQ_MAX_SIZE]; + int code; + long int tid; + char path[PATH_MAX]; + off64_t write_size; + capio_off64_t offset, total_read_size = 0; + CapioMemoryFile *testFile = nullptr; + + while (total_read_size < 2 * long_test_length) { + buf_requests->read(req, CAPIO_REQ_MAX_SIZE); + + auto [ptr, ec] = std::from_chars(req, req + 4, code); + EXPECT_EQ(ec, std::errc()); + strcpy(req, ptr + 1); + EXPECT_EQ(code, CAPIO_REQUEST_WRITE_MEM); + sscanf(req, "%ld %s %llu %ld", &tid, path, &offset, &write_size); + EXPECT_EQ(strcmp(path, test_file_name.c_str()), 0); + + if (testFile == nullptr) { + testFile = new CapioMemoryFile(path); + } + + testFile->readFromQueue(*cts_queue, 0, write_size); + + char *readBuf = new char[readBufSize]{}; + for (auto offset = 0; offset < write_size; offset += readBufSize) { + testFile->readData(readBuf, offset, readBufSize); + EXPECT_EQ(strncmp(readBuf, SOURCE_TEST_TEXT + offset, readBufSize), 0); + } + + delete[] readBuf; + total_read_size += write_size; + } + EXPECT_EQ(total_read_size, 2 * long_test_length); + delete[] req; + delete testFile; + }); + + // WriteMemReqWrapper wrapper; + + writeCache->write(test_fd, SOURCE_TEST_TEXT, long_test_length); + + // simulate a SEEK on a file + set_capio_fd_offset(test_fd, 2 * long_test_length); + + // perform to write ad a different offset + writeCache->write(test_fd, SOURCE_TEST_TEXT, long_test_length); + + // simulate Close on file + writeCache->flush(); + + t3.join(); + + delete_server_data_structures(); +} + +TEST(CapioCacheSPSCQueue, TestReadCacheWithSpscQueueRead) { + + capio_off64_t long_test_length = strlen(SOURCE_TEST_TEXT) + 1; + auto tmp_buf = new std::unique_ptr(new char[long_test_length]); + auto response_tid = gettid(); + + init_server_data_structures(); + + capio_files_descriptors->emplace(test_fd, test_file_name); + files->insert({test_fd, {std::make_shared(0), 0, 0, false}}); + + std::thread server_thread([long_test_length, response_tid] { + int iteration = 0; + + capio_off64_t total_data_sent = 0; + while (total_data_sent < long_test_length) { + + char req[CAPIO_REQ_MAX_SIZE]; + int code; + long int tid; + capio_off64_t read_size, client_cache_line_size, read_begin_offset; + buf_requests->read(req, CAPIO_REQ_MAX_SIZE); + + auto [ptr, ec] = std::from_chars(req, req + 4, code); + EXPECT_EQ(ec, std::errc()); + strcpy(req, ptr + 1); + EXPECT_EQ(code, CAPIO_REQUEST_READ_MEM); + sscanf(req, "%ld %llu %llu %llu", &tid, &read_begin_offset, &read_size, + &client_cache_line_size); + + auto size_to_send = + read_size < client_cache_line_size ? read_size : client_cache_line_size; + + bufs_response->at(response_tid)->write(size_to_send); + stc_queue->write(SOURCE_TEST_TEXT + read_begin_offset, size_to_send); + total_data_sent += size_to_send; + iteration++; + } + }); + + readCache->read(test_fd, tmp_buf->get(), long_test_length); + auto result = strncmp(SOURCE_TEST_TEXT, tmp_buf->get(), long_test_length); + EXPECT_EQ(result, 0); + server_thread.join(); + delete_server_data_structures(); +} + +TEST(CapioCacheSPSCQueue, TestReadCacheWithSpscQueueReadWithCapioFile) { + + capio_off64_t long_test_length = strlen(SOURCE_TEST_TEXT) + 1; + auto tmp_buf = new std::unique_ptr(new char[long_test_length]); + auto response_tid = gettid(); + + init_server_data_structures(); + + capio_files_descriptors->emplace(test_fd, test_file_name); + files->insert({test_fd, {std::make_shared(0), 0, 0, false}}); + + std::thread server_thread([long_test_length, response_tid] { + int iteration = 0; + CapioMemoryFile testFile(test_file_name); + + testFile.writeData(SOURCE_TEST_TEXT, 0, long_test_length); + + capio_off64_t total_data_sent = 0; + while (total_data_sent < long_test_length) { + + char req[CAPIO_REQ_MAX_SIZE]; + int code; + long int tid; + capio_off64_t read_size, client_cache_line_size, read_begin_offset; + buf_requests->read(req, CAPIO_REQ_MAX_SIZE); + + auto [ptr, ec] = std::from_chars(req, req + 4, code); + EXPECT_EQ(ec, std::errc()); + strcpy(req, ptr + 1); + EXPECT_EQ(code, CAPIO_REQUEST_READ_MEM); + sscanf(req, "%ld %llu %llu %llu", &tid, &read_begin_offset, &read_size, + &client_cache_line_size); + + auto size_to_send = + read_size < client_cache_line_size ? read_size : client_cache_line_size; + + bufs_response->at(response_tid)->write(size_to_send); + testFile.writeToQueue(*stc_queue, read_begin_offset, size_to_send); + total_data_sent += size_to_send; + iteration++; + } + }); + + readCache->read(test_fd, tmp_buf->get(), long_test_length); + auto result = strncmp(SOURCE_TEST_TEXT, tmp_buf->get(), long_test_length); + EXPECT_EQ(result, 0); + server_thread.join(); + delete_server_data_structures(); +} + +TEST(CapioCacheSPSCQueue, TestReadCacheWithSpscQueueReadWithCapioFileAndSeek) { + + capio_off64_t long_test_length = strlen(SOURCE_TEST_TEXT) + 1; + auto tmp_buf = new std::unique_ptr(new char[long_test_length]); + auto response_tid = gettid(); + + init_server_data_structures(); + + capio_files_descriptors->emplace(test_fd, test_file_name); + files->insert({test_fd, {std::make_shared(0), 0, 0, false}}); + + std::thread server_thread([long_test_length, response_tid] { + START_LOG(gettid(), "call(server_instance)"); + int iteration = 0; + CapioMemoryFile testFile(test_file_name); + + testFile.writeData(SOURCE_TEST_TEXT, 0, long_test_length); + LOG("Wrote data to server test file"); + + capio_off64_t total_data_sent = 0; + while (total_data_sent < 2000) { + + char req[CAPIO_REQ_MAX_SIZE]{0}; + char file[1024]; + int code, use_cache; + long int tid; + capio_off64_t read_size, client_cache_line_size, read_begin_offset; + + buf_requests->read(req, CAPIO_REQ_MAX_SIZE); + LOG("Received request: %s", req); + sscanf(req, "%d %ld %llu %llu %llu %d %s", &code, &tid, &read_begin_offset, &read_size, + &client_cache_line_size, &use_cache, file); + EXPECT_EQ(code, CAPIO_REQUEST_READ_MEM); + LOG("code: %d", code); + LOG("tid: %ld", tid); + LOG("read_begin_offset: %lld", read_begin_offset); + LOG("read_size: %lld", read_size); + LOG("client_cache_line_size: %lld", client_cache_line_size); + LOG("use_cache: %lld", use_cache); + LOG("file: %s", file); + auto size_to_send = + read_size < client_cache_line_size ? read_size : client_cache_line_size; + LOG("Sending %ld data", size_to_send); + bufs_response->at(response_tid)->write(size_to_send); + testFile.writeToQueue(*stc_queue, read_begin_offset, size_to_send); + total_data_sent += size_to_send; + iteration++; + } + }); + + auto tmp_buf_ptr = tmp_buf->get(); + + readCache->read(test_fd, tmp_buf_ptr, 1000); + EXPECT_EQ(strncmp(SOURCE_TEST_TEXT, tmp_buf->get(), 1000), 0); + + // emulate seek + auto new_offset = get_capio_fd_offset(test_fd) + 1000; + set_capio_fd_offset(test_fd, new_offset); + + tmp_buf_ptr += new_offset; + readCache->read(test_fd, tmp_buf_ptr, 1000); + // checkStringEquality(SOURCE_TEST_TEXT + new_offset, tmp_buf->get() + new_offset); + EXPECT_EQ(strncmp(SOURCE_TEST_TEXT + new_offset, tmp_buf->get() + new_offset, 1000), 0); + + server_thread.join(); + delete_server_data_structures(); +} + +#endif // CAPIOCACHESPSCQUEUETESTS_HPP \ No newline at end of file diff --git a/capio-tests/unit/server/src/CapioFileTests.hpp b/capio-tests/unit/server/src/CapioFileTests.hpp new file mode 100644 index 000000000..72ebb7ce2 --- /dev/null +++ b/capio-tests/unit/server/src/CapioFileTests.hpp @@ -0,0 +1,150 @@ +#ifndef CAPIOFILETESTS_HPP +#define CAPIOFILETESTS_HPP +#include + +constexpr size_t FILE_SIZE = 8 * 1024 * 1024; + +TEST(CapioMemoryFileTest, TestSimpleWriteAndReadDifferentOffsets) { + CapioMemoryFile file("test.txt"); + const std::string string1 = "Hello world string 1"; + const std::string string2 = "Hello world string 2"; + const std::string string3 = "Cantami diva del pelide Achille l'ira funesta che infiniti..."; + + char input_buffer1[100]{}; + file.writeData(string1.data(), 0, string1.size()); + file.readData(input_buffer1, 0, string1.size()); + EXPECT_TRUE(input_buffer1 == string1); + + char input_buffer2[100]{}; + file.writeData(string2.data(), FILE_SIZE, string2.size()); + file.readData(input_buffer2, FILE_SIZE, string2.size()); + EXPECT_TRUE(input_buffer2 == string2); + + char input_buffer3[100]{}; + file.writeData(string3.data(), 2 * FILE_SIZE, string3.size()); + file.readData(input_buffer3, 2 * FILE_SIZE, string3.size()); + EXPECT_TRUE(input_buffer3 == string3); +} + +TEST(CapioMemoryFileTest, TestHugeFileOnMultiplePages) { + CapioMemoryFile file1("test.txt"); + capio_off64_t LONG_TEXT_SIZE = 1024 * 1024 * 16; + auto LONG_TEXT = new char[LONG_TEXT_SIZE]{}; // 16 MB string + for (capio_off64_t i = 0; i < LONG_TEXT_SIZE; i++) { + LONG_TEXT[i] = 'A' + (random() % 26); + } + + auto input_buffer4 = new char[LONG_TEXT_SIZE + 1]{}; + bool write_count_match = file1.writeData(LONG_TEXT, 0, LONG_TEXT_SIZE) == LONG_TEXT_SIZE; + EXPECT_TRUE(write_count_match); + + file1.readData(input_buffer4, 0, LONG_TEXT_SIZE); + + bool ok = true; + for (capio_off64_t i = 0; i < LONG_TEXT_SIZE && ok; i++) { + ok &= (LONG_TEXT[i] == input_buffer4[i]); + if (!ok) { + std::cout << "Check failed at byte " << i << " out of " << LONG_TEXT_SIZE << std::endl; + } + } + EXPECT_TRUE(ok); + + delete[] LONG_TEXT; + delete[] input_buffer4; +} + +TEST(CapioMemoryFileTest, TestWriteAndRead) { + CapioMemoryFile file("test.txt"); + + // 8 MB buffer + const auto buffer = new std::vector(); + buffer->reserve(FILE_SIZE); + for (size_t i = 0; i < buffer->size(); i++) { + buffer->push_back(static_cast('a' + i % 26)); + } + + file.writeData(buffer->data(), 0, buffer->size()); + + auto buffer_read = new char[FILE_SIZE + 1]{}; + file.readData(buffer_read, 0, FILE_SIZE); + + bool ok = true; + for (capio_off64_t i = 0; i < buffer->size() && ok; i++) { + ok &= (buffer->data()[i] == buffer_read[i]) && (buffer->data()[i] != '\0'); + if (!ok) { + std::cout << "Check failed at byte " << i << " out of " << buffer->size() << std::endl; + } + } + EXPECT_TRUE(ok); + + delete buffer; + delete[] buffer_read; +} + +TEST(CapioMemoryFileTest, TestWriteAndReadDifferentPageStartOffset) { + CapioMemoryFile file("test.txt"); + + // 8 MB buffer + const auto buffer = new std::vector(); + buffer->reserve(FILE_SIZE); + for (size_t i = 0; i < buffer->size(); i++) { + buffer->push_back(static_cast('a' + i % 26)); + } + + file.writeData(buffer->data(), FILE_SIZE / 2, buffer->size()); + + auto buffer_read = new char[FILE_SIZE + 1]; + file.readData(buffer_read, FILE_SIZE / 2, FILE_SIZE); + + bool ok = true; + for (capio_off64_t i = 0; i < buffer->size() && ok; i++) { + ok &= (buffer->data()[i] == buffer_read[i]) && (buffer->data()[i] != '\0'); + if (!ok) { + std::cout << "Check failed at byte " << i << " out of " << buffer->size() << std::endl; + } + } + EXPECT_TRUE(ok); + + delete buffer; + delete[] buffer_read; +} + +TEST(CapioMemoryFileTest, TestThreadsSpscqueueAndCapioMemFile) { + + SPSCQueue *communication_queue = new SPSCQueue("test.queue", CAPIO_CACHE_LINES_DEFAULT, + CAPIO_CACHE_LINE_SIZE_DEFAULT, "demo", true); + CapioMemoryFile file_source("source.txt"), file_destination("destination.txt"); + + // 8 MB buffer + auto buffer = new char[10 * FILE_SIZE]; + for (size_t i = 0; i < 10 * FILE_SIZE; i++) { + buffer[i] = 'a' + i % 26; + } + + file_source.writeData(buffer, FILE_SIZE / 2, 10 * FILE_SIZE); + + std::thread writer([communication_queue, &file_source]() { + file_source.writeToQueue(*communication_queue, FILE_SIZE / 2, 10 * FILE_SIZE); + }); + + file_destination.readFromQueue(*communication_queue, FILE_SIZE / 2, 10 * FILE_SIZE); + writer.join(); + + auto buffer_read = new char[10 * FILE_SIZE]; + file_destination.readData(buffer_read, FILE_SIZE / 2, 10 * FILE_SIZE); + + bool ok = true; + for (capio_off64_t i = 0; i < 10 * FILE_SIZE && ok; i++) { + ok &= (buffer[i] == buffer_read[i]) && (buffer_read[i] != '\0'); + if (!ok) { + std::cout << "Check failed at byte " << i << " out of " << 10 * FILE_SIZE << std::endl; + } + } + EXPECT_TRUE(ok); + + delete[] buffer; + delete[] buffer_read; + delete communication_queue; +} + +#endif // CAPIOFILETESTS_HPP diff --git a/capio-tests/unit/server/src/SourceText.hpp b/capio-tests/unit/server/src/SourceText.hpp new file mode 100644 index 000000000..56905ef51 --- /dev/null +++ b/capio-tests/unit/server/src/SourceText.hpp @@ -0,0 +1,76 @@ +#ifndef SOURCETEXT_HPP +#define SOURCETEXT_HPP + +constexpr char SOURCE_TEST_TEXT[] = + "Cantami, o Diva, del PelΓ¬de Achille l'ira funesta che infiniti addusse lutti agli Achei, " + "molte anzi tempo all'Orco generose travolse alme d'eroi, e di cani e d'augelli orrido pasto " + "lor salme abbandonΓ² (cosΓ¬ di Giove l'alto consiglio s'adempΓ¬a), da quando primamente " + "disgiunse aspra contesa il re de' prodi Atride e il divo Achille. E qual de' numi inimicolli? " + "Il figlio di Latona e di Giove. Irato al Sire destΓ² quel Dio nel campo un feral morbo, e la " + "gente perΓ¬a: colpa d'Atride che fece a Crise sacerdote oltraggio. Degli Achivi era Crise alle " + "veloci prore venuto a riscattar la figlia con molto prezzo. In man le bende avea, e l'aureo " + "scettro dell'arciero Apollo: e agli Achei tutti supplicando, e in prima ai due supremi " + "condottieri Atridi: O Atridi, ei disse, o coturnati Achei, gl'immortali del cielo abitatori " + "concedanvi espugnar la PrΓ―ameia cittade, e salvi al patrio suol tornarvi. Deh mi sciogliete " + "la diletta figlia, ricevetene il prezzo, e il saettante figlio di Giove rispettate. - Al " + "prego tutti acclamΓ’r: doversi il sacerdote riverire, e accettar le ricche offerte. Ma la " + "proposta al cor d'AgamennΓ³ne non talentando, in guise aspre il superbo accommiatollo, e " + "minaccioso aggiunse: Vecchio, non far che presso a queste navi ned or nΓ© poscia piΓΉ ti colga " + "io mai; chΓ© forse nulla ti varrΓ  lo scettro nΓ© l'infula del Dio. Franca non fia costei, se " + "lungi dalla patria, in Argo, nella nostra magion pria non la sfiori vecchiezza, all'opra " + "delle spole intenta, e a parte assunta del regal mio letto. Or va, nΓ© m'irritar, se salvo ir " + "brami. Impaurissi il vecchio, ed al comando obbedΓ¬. Taciturno incamminossi del risonante mar " + "lungo la riva; e in disparte venuto, al santo Apollo di Latona figliuol, fe' questo prego: " + "Dio dall'arco d'argento, o tu che Crisa proteggi e l'alma Cilla, e sei di TΓ¨nedo possente " + "imperador, SmintΓ¨o, deh m'odi. Se di serti devoti unqua il leggiadro tuo delubro adornai, se " + "di giovenchi e di caprette io t'arsi i fianchi opimi, questo voto m'adempi; il pianto mio " + "paghino i Greci per le tue saette. SΓ¬ disse orando. L'udΓ¬ Febo, e scese dalle cime d'Olimpo " + "in gran disdegno coll'arco su le spalle, e la faretra tutta chiusa. Mettean le frecce orrendo " + "su gli omeri all'irato un tintinnΓ¬o al mutar de' gran passi; ed ei simΓ¬le a fosca notte giΓΉ " + "venΓ¬a. Piantossi delle navi al cospetto: indi uno strale liberΓ² dalla corda, ed un ronzΓ¬o " + "terribile mandΓ² l'arco d'argento. Prima i giumenti e i presti veltri assalse, poi le schiere " + "a ferir prese, vibrando le mortifere punte; onde per tutto degli esanimi corpi ardean le " + "pire. Nove giorni volΓ’r pel campo acheo le divine quadrella. A parlamento nel decimo chiamΓ² " + "le turbe Achille; chΓ© gli pose nel cor questo consiglio Giuno la diva dalle bianche braccia, " + "de' moribondi Achei fatta pietosa. Come fur giunti e in un raccolti, in mezzo levossi Achille " + "piΓ¨-veloce, e disse: Atride, or sΓ¬ cred'io volta daremo nuovamente errabondi al patrio lido, " + "se pur morte fuggir ne fia concesso; chΓ© guerra e peste ad un medesmo tempo ne struggono. Ma " + "via; qualche indovino interroghiamo, o sacerdote, o pure interprete di sogni (chΓ© da Giove " + "anche il sogno procede), onde ne dica perchΓ© tanta con noi d'Apollo Γ¨ l'ira: se di preci o di " + "vittime neglette il Dio n'incolpa, e se d'agnelli e scelte capre accettando l'odoroso fumo, " + "il crudel morbo allontanar gli piaccia. CosΓ¬ detto, s'assise. In piedi allora di Testore il " + "figliuol Calcante alzossi, de' veggenti il piΓΉ saggio, a cui le cose eran conte che fur, sono " + "e saranno; e per quella, che dono era d'Apollo, profetica virtΓΉ, de' Greci a Troia avea " + "scorte le navi. Ei dunque in mezzo pien di senno parlΓ² queste parole: Amor di Giove, generoso " + "Achille, vuoi tu che dell'arcier sovrano Apollo ti riveli lo sdegno? Io t'obbedisco. Ma del " + "braccio l'aita e della voce a me tu pria, signor, prometti e giura: perchΓ© tal che qui grande " + "ha su gli Argivi tutti possanza, e a cui l'Acheo s'inchina, n'andrΓ , per mio pensar, molto " + "sdegnoso. Quando il potente col minor s'adira, reprime ei sΓ¬ del suo rancor la vampa per " + "alcun tempo, ma nel cor la cova, finchΓ© prorompa alla vendetta. Or dinne se salvo mi farai. - " + "Parla securo, rispose Achille, e del tuo cor l'arcano, qual ch'ei si sia, di' franco. Per " + "Apollo che pregato da te ti squarcia il velo de' fati, e aperto tu li mostri a noi, per " + "questo Apollo a Giove caro io giuro: nessun, finch'io m'avrΓ² spirto e pupilla, con empia mano " + "innanzi a queste navi oserΓ  vΓ―olar la tua persona, nessuno degli Achei; no, s'anco parli " + "d'AgamennΓ³n che sΓ© medesmo or vanta dell'esercito tutto il piΓΉ possente. Allor fe' core il " + "buon profeta, e disse: nΓ© d'obblΓ―ati sacrifici il Dio nΓ© di voti si duol, ma dell'oltraggio " + "che al sacerdote fe' poc'anzi Atride, che francargli la figlia ed accettarne il riscatto " + "negΓ². La colpa Γ¨ questa onde cotante ne diΓ¨ strette, ed altre l'arcier divino ne darΓ ; nΓ© " + "pria ritrarrΓ  dal castigo la man grave, che si rimandi la fatal donzella non redenta nΓ© " + "compra al padre amato, e si spedisca un'ecatombe a Crisa. CosΓ¬ forse avverrΓ  che il Dio si " + "plachi. Tacque, e s'assise. Allor l'Atride eroe il re supremo AgamennΓ³n levossi corruccioso. " + "Offuscavagli la grande ira il cor gonfio, e come bragia rossi fiammeggiavano gli occhi. E " + "tale ei prima squadrΓ² torvo Calcante, indi proruppe: Profeta di sciagure, unqua un accento " + "non uscΓ¬ di tua bocca a me gradito. Al maligno tuo cor sempre fu dolce predir disastri, e " + "d'onor vote e nude son l'opre tue del par che le parole. E fra gli Argivi profetando or " + "cianci che delle frecce sue Febo gl'impiaga, sol perch'io ricusai della fanciulla CrisΓ«ide il " + "riscatto. Ed io bramava certo tenerla in signoria, tal sendo che a Clitennestra pur, da me " + "condutta vergine sposa, io la prepongo, a cui di persona costei punto non cede, nΓ© di care " + "sembianze, nΓ© d'ingegno ne' bei lavori di Minerva istrutto. Ma libera sia pur, se questo Γ¨ il " + "meglio; chΓ© la salvezza io cerco, e non la morte del popol mio. Ma voi mi preparate tosto il " + "compenso, chΓ© de' Greci io solo restarmi senza guiderdon non deggio; ed ingiusto ciΓ² fΓ΄ra, or " + "che una tanta preda, il vedete, dalle man mi fugge. O d'avarizia al par che di grandezza " + "famoso Atride, gli rispose Achille, qual premio ti daranno, e per che modo i magnanimi Achei? " + "Che molta in serbo vi sia ricchezza non partita, ignoro: delle vinte cittΓ  tutte divise ne " + "fur le spoglie, nΓ© diritto or torna a nuove parti congregarle in una."; + +#endif // SOURCETEXT_HPP diff --git a/capio-tests/unit/server/src/main.cpp b/capio-tests/unit/server/src/main.cpp new file mode 100644 index 000000000..36bdf52c1 --- /dev/null +++ b/capio-tests/unit/server/src/main.cpp @@ -0,0 +1,27 @@ +#include +#include +#include +#include "capiocl/engine.h" +#include +#include +#define syscall_no_intercept syscall + +CapioGlobalConfiguration* capio_global_configuration; + +std::string workflow_name = CAPIO_DEFAULT_WORKFLOW_NAME; + +std::string node_name; + +capiocl::engine::Engine *capio_cl_engine; + +#include "CapioCacheSPSCQueueTests.hpp" +#include "CapioFileTests.hpp" + + +int main(int argc, char **argv) { + capio_global_configuration = new CapioGlobalConfiguration(); + capio_cl_engine = new capiocl::engine::Engine(); + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/tests/unit/syscall/CMakeLists.txt b/capio-tests/unit/syscall/CMakeLists.txt similarity index 100% rename from tests/unit/syscall/CMakeLists.txt rename to capio-tests/unit/syscall/CMakeLists.txt diff --git a/tests/unit/syscall/src/chdir.cpp b/capio-tests/unit/syscall/src/chdir.cpp similarity index 100% rename from tests/unit/syscall/src/chdir.cpp rename to capio-tests/unit/syscall/src/chdir.cpp diff --git a/tests/unit/syscall/src/clone.cpp b/capio-tests/unit/syscall/src/clone.cpp similarity index 99% rename from tests/unit/syscall/src/clone.cpp rename to capio-tests/unit/syscall/src/clone.cpp index 0f43d937e..5506cdead 100644 --- a/tests/unit/syscall/src/clone.cpp +++ b/capio-tests/unit/syscall/src/clone.cpp @@ -83,4 +83,4 @@ TEST(SystemCallTest, TestThreadCloneProducerConsumerWithStat) { EXPECT_NE(fclose(fp), EOF); EXPECT_NE(unlink(PATHNAME), -1); t1.join(); -} \ No newline at end of file +} diff --git a/tests/unit/syscall/src/directory.cpp b/capio-tests/unit/syscall/src/directory.cpp similarity index 100% rename from tests/unit/syscall/src/directory.cpp rename to capio-tests/unit/syscall/src/directory.cpp diff --git a/tests/unit/syscall/src/dirent.cpp b/capio-tests/unit/syscall/src/dirent.cpp similarity index 97% rename from tests/unit/syscall/src/dirent.cpp rename to capio-tests/unit/syscall/src/dirent.cpp index e8104e528..58e71be80 100644 --- a/tests/unit/syscall/src/dirent.cpp +++ b/capio-tests/unit/syscall/src/dirent.cpp @@ -71,7 +71,7 @@ TEST(SystemCallTest, TestDirentsOnCapioDir) { break; } - for (size_t bpos = 0, i = 0; bpos < nread && i < 10; i++) { + for (long int bpos = 0, i = 0; bpos < nread && i < 10; i++) { auto d = (struct linux_dirent64 *) (buf + bpos); EXPECT_NE(std::find(expectedNames.begin(), expectedNames.end(), d->d_name), diff --git a/tests/unit/syscall/src/dup.cpp b/capio-tests/unit/syscall/src/dup.cpp similarity index 100% rename from tests/unit/syscall/src/dup.cpp rename to capio-tests/unit/syscall/src/dup.cpp diff --git a/tests/unit/syscall/src/fcntl.cpp b/capio-tests/unit/syscall/src/fcntl.cpp similarity index 100% rename from tests/unit/syscall/src/fcntl.cpp rename to capio-tests/unit/syscall/src/fcntl.cpp diff --git a/tests/unit/syscall/src/file.cpp b/capio-tests/unit/syscall/src/file.cpp similarity index 100% rename from tests/unit/syscall/src/file.cpp rename to capio-tests/unit/syscall/src/file.cpp diff --git a/capio-tests/unit/syscall/src/main.cpp b/capio-tests/unit/syscall/src/main.cpp new file mode 100644 index 000000000..f1710743f --- /dev/null +++ b/capio-tests/unit/syscall/src/main.cpp @@ -0,0 +1,10 @@ +#include "capio/constants.hpp" +#include + +std::string workflow_name = CAPIO_DEFAULT_WORKFLOW_NAME; + +int main(int argc, char **argv, char **envp) { + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/tests/unit/syscall/src/rename.cpp b/capio-tests/unit/syscall/src/rename.cpp similarity index 100% rename from tests/unit/syscall/src/rename.cpp rename to capio-tests/unit/syscall/src/rename.cpp diff --git a/tests/unit/syscall/src/stat.cpp b/capio-tests/unit/syscall/src/stat.cpp similarity index 95% rename from tests/unit/syscall/src/stat.cpp rename to capio-tests/unit/syscall/src/stat.cpp index de2198f2d..e643ee53d 100644 --- a/tests/unit/syscall/src/stat.cpp +++ b/capio-tests/unit/syscall/src/stat.cpp @@ -23,7 +23,7 @@ TEST(SystemCallTest, TestStatOnFile) { EXPECT_EQ(access(PATHNAME, F_OK), 0); EXPECT_EQ(write(fd, BUFFER, strlen(BUFFER)), strlen(BUFFER)); EXPECT_NE(close(fd), -1); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(stat(PATHNAME, &statbuf), 0); check_statbuf(statbuf, strlen(BUFFER) * sizeof(char)); EXPECT_NE(unlink(PATHNAME), -1); @@ -34,7 +34,7 @@ TEST(SystemCallTest, TestStatOnDirectory) { constexpr const char *PATHNAME = "test"; EXPECT_NE(mkdir(PATHNAME, S_IRWXU), -1); EXPECT_EQ(access(PATHNAME, F_OK), 0); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(stat(PATHNAME, &statbuf), 0); check_statbuf(statbuf, 4096); EXPECT_NE(rmdir(PATHNAME), -1); @@ -42,7 +42,7 @@ TEST(SystemCallTest, TestStatOnDirectory) { } TEST(SystemCallTest, TestStatOnNonexistentFile) { - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(stat("test", &statbuf), -1); EXPECT_EQ(errno, ENOENT); } @@ -55,7 +55,7 @@ TEST(SystemCallTest, TestFstatOnFile) { EXPECT_NE(fd, -1); EXPECT_EQ(access(PATHNAME, F_OK), 0); EXPECT_EQ(write(fd, BUFFER, strlen(BUFFER)), strlen(BUFFER)); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstat(fd, &statbuf), 0); check_statbuf(statbuf, strlen(BUFFER) * sizeof(char)); EXPECT_NE(close(fd), -1); @@ -70,7 +70,7 @@ TEST(SystemCallTest, TestFstatOnDirectory) { int flags = O_RDONLY | O_DIRECTORY; int fd = open(PATHNAME, flags, S_IRUSR | S_IWUSR); EXPECT_NE(fd, -1); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstat(fd, &statbuf), 0); check_statbuf(statbuf, 4096); EXPECT_NE(close(fd), -1); @@ -79,7 +79,7 @@ TEST(SystemCallTest, TestFstatOnDirectory) { } TEST(SystemCallTest, TestFstatOnInvalidFd) { - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstat(-1, &statbuf), -1); EXPECT_EQ(errno, EBADF); } @@ -93,7 +93,7 @@ TEST(SystemCallTest, TestFstatatOnFileWithAtFdcwd) { EXPECT_EQ(faccessat(AT_FDCWD, PATHNAME, F_OK, 0), 0); EXPECT_EQ(write(fd, BUFFER, strlen(BUFFER)), strlen(BUFFER)); EXPECT_NE(close(fd), -1); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(AT_FDCWD, PATHNAME, &statbuf, 0), 0); check_statbuf(statbuf, strlen(BUFFER) * sizeof(char)); EXPECT_NE(unlinkat(AT_FDCWD, PATHNAME, 0), -1); @@ -104,7 +104,7 @@ TEST(SystemCallTest, TestFstatatOnDirectoryWithAtFdcwd) { constexpr const char *PATHNAME = "test"; EXPECT_NE(mkdirat(AT_FDCWD, PATHNAME, S_IRWXU), -1); EXPECT_EQ(faccessat(AT_FDCWD, PATHNAME, F_OK, 0), 0); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(AT_FDCWD, PATHNAME, &statbuf, 0), 0); check_statbuf(statbuf, 4096); EXPECT_NE(unlinkat(AT_FDCWD, PATHNAME, AT_REMOVEDIR), -1); @@ -122,7 +122,7 @@ TEST(SystemCallTest, TestFstatatOnFileInDifferentDirectoryWithAbsolutePath) { EXPECT_EQ(faccessat(0, PATHNAME, F_OK, 0), 0); EXPECT_EQ(write(fd, BUFFER, strlen(BUFFER)), strlen(BUFFER)); EXPECT_NE(close(fd), -1); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(0, PATHNAME, &statbuf, 0), 0); check_statbuf(statbuf, strlen(BUFFER) * sizeof(char)); EXPECT_NE(unlinkat(0, PATHNAME, 0), -1); @@ -134,7 +134,7 @@ TEST(SystemCallTest, TestFstatatOnDirectoryInDifferentDirectoryWithAbsolutePath) const char *PATHNAME = path_fs.c_str(); EXPECT_NE(mkdirat(0, PATHNAME, S_IRWXU), -1); EXPECT_EQ(faccessat(0, PATHNAME, F_OK, 0), 0); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(0, PATHNAME, &statbuf, 0), 0); check_statbuf(statbuf, 4096); EXPECT_NE(unlinkat(0, PATHNAME, AT_REMOVEDIR), -1); @@ -152,7 +152,7 @@ TEST(SystemCallTest, TestFstatatOnFileInDifferentDirectoryWithDirfd) { EXPECT_EQ(faccessat(dirfd, PATHNAME, F_OK, 0), 0); EXPECT_EQ(write(fd, BUFFER, strlen(BUFFER)), strlen(BUFFER)); EXPECT_NE(close(fd), -1); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(dirfd, PATHNAME, &statbuf, 0), 0); check_statbuf(statbuf, strlen(BUFFER) * sizeof(char)); EXPECT_NE(unlinkat(dirfd, PATHNAME, 0), -1); @@ -167,7 +167,7 @@ TEST(SystemCallTest, TestFstatatOnDirectoryInDifferentDirectoryWithDirfd) { EXPECT_NE(dirfd, -1); EXPECT_NE(mkdirat(dirfd, PATHNAME, S_IRWXU), -1); EXPECT_EQ(faccessat(dirfd, PATHNAME, F_OK, 0), 0); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(dirfd, PATHNAME, &statbuf, 0), 0); check_statbuf(statbuf, 4096); EXPECT_NE(unlinkat(dirfd, PATHNAME, AT_REMOVEDIR), -1); @@ -176,7 +176,7 @@ TEST(SystemCallTest, TestFstatatOnDirectoryInDifferentDirectoryWithDirfd) { } TEST(SystemCallTest, TwstFstatatWithAtEmptyPathAndAtFdcwd) { - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(AT_FDCWD, "", &statbuf, AT_EMPTY_PATH), 0); check_statbuf(statbuf, 4096); } @@ -188,7 +188,7 @@ TEST(SystemCallTest, TestFstatatOnFileWithAtEmptyPathAndDirfd) { int dirfd = openat(AT_FDCWD, PATHNAME, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR); EXPECT_EQ(faccessat(AT_FDCWD, PATHNAME, F_OK, 0), 0); EXPECT_EQ(write(dirfd, BUFFER, strlen(BUFFER)), strlen(BUFFER)); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(dirfd, "", &statbuf, AT_EMPTY_PATH), 0); check_statbuf(statbuf, strlen(BUFFER) * sizeof(char)); EXPECT_NE(close(dirfd), -1); @@ -202,7 +202,7 @@ TEST(SystemCallTest, TestFstatatOnDirectoryWIthAtEmptyPathAndDirfd) { EXPECT_EQ(faccessat(AT_FDCWD, PATHNAME, F_OK, 0), 0); int dirfd = openat(AT_FDCWD, PATHNAME, O_RDONLY | O_DIRECTORY); EXPECT_NE(dirfd, -1); - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(dirfd, "", &statbuf, AT_EMPTY_PATH), 0); check_statbuf(statbuf, 4096); EXPECT_NE(close(dirfd), -1); @@ -211,20 +211,20 @@ TEST(SystemCallTest, TestFstatatOnDirectoryWIthAtEmptyPathAndDirfd) { } TEST(SystemCallTest, TestFstatatOnNonexistentFile) { - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(AT_FDCWD, "test", &statbuf, 0), -1); EXPECT_EQ(errno, ENOENT); } TEST(SystemCallTest, TestFstatatOnRelativePathWithInvalidDirfd) { constexpr const char *PATHNAME = "test"; - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(-1, PATHNAME, &statbuf, 0), -1); EXPECT_EQ(errno, EBADF); } TEST(SystemCallTest, TestFstatatWithEmptyPathAndNoAtEmptyPath) { - struct stat statbuf {}; + struct stat statbuf{}; EXPECT_EQ(fstatat(AT_FDCWD, "", &statbuf, 0), -1); EXPECT_EQ(errno, ENOENT); } diff --git a/tests/unit/syscall/src/statx.cpp b/capio-tests/unit/syscall/src/statx.cpp similarity index 94% rename from tests/unit/syscall/src/statx.cpp rename to capio-tests/unit/syscall/src/statx.cpp index 9191c73d5..adff1ef38 100644 --- a/tests/unit/syscall/src/statx.cpp +++ b/capio-tests/unit/syscall/src/statx.cpp @@ -22,7 +22,7 @@ TEST(SystemCallTest, TestStatxOnFileWithAtFdcwd) { EXPECT_EQ(faccessat(AT_FDCWD, PATHNAME, F_OK, 0), 0); EXPECT_EQ(write(fd, BUFFER, strlen(BUFFER)), strlen(BUFFER)); EXPECT_NE(close(fd), -1); - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(AT_FDCWD, PATHNAME, 0, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), 0); check_statxbuf(statxbuf, strlen(BUFFER) * sizeof(char)); EXPECT_NE(unlinkat(AT_FDCWD, PATHNAME, 0), -1); @@ -33,7 +33,7 @@ TEST(SystemCallTest, TestStatxOnDirectoryWithAtFdcwd) { constexpr const char *PATHNAME = "test"; EXPECT_NE(mkdirat(AT_FDCWD, PATHNAME, S_IRWXU), -1); EXPECT_EQ(faccessat(AT_FDCWD, PATHNAME, F_OK, 0), 0); - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(AT_FDCWD, PATHNAME, 0, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), 0); check_statxbuf(statxbuf, 4096); EXPECT_NE(unlinkat(AT_FDCWD, PATHNAME, AT_REMOVEDIR), -1); @@ -51,7 +51,7 @@ TEST(SystemCallTest, TestStatxOnFileInDifferentDirectoryWithAbsolutePath) { EXPECT_EQ(faccessat(0, PATHNAME, F_OK, 0), 0); EXPECT_EQ(write(fd, BUFFER, strlen(BUFFER)), strlen(BUFFER)); EXPECT_NE(close(fd), -1); - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(AT_FDCWD, PATHNAME, 0, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), 0); check_statxbuf(statxbuf, strlen(BUFFER) * sizeof(char)); EXPECT_NE(unlinkat(0, PATHNAME, 0), -1); @@ -63,7 +63,7 @@ TEST(SystemCallTest, TestStatxOnDirectoryInDifferentDirectoryWithAbsoluePath) { const char *PATHNAME = path_fs.c_str(); EXPECT_NE(mkdirat(0, PATHNAME, S_IRWXU), -1); EXPECT_EQ(faccessat(0, PATHNAME, F_OK, 0), 0); - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(AT_FDCWD, PATHNAME, 0, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), 0); check_statxbuf(statxbuf, 4096); EXPECT_NE(unlinkat(0, PATHNAME, AT_REMOVEDIR), -1); @@ -81,7 +81,7 @@ TEST(SystemCallTest, TestStatxOnFileInDifferentDirectoryWithDirfd) { EXPECT_EQ(faccessat(dirfd, PATHNAME, F_OK, 0), 0); EXPECT_EQ(write(fd, BUFFER, strlen(BUFFER)), strlen(BUFFER)); EXPECT_NE(close(fd), -1); - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(dirfd, PATHNAME, 0, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), 0); check_statxbuf(statxbuf, strlen(BUFFER) * sizeof(char)); EXPECT_NE(unlinkat(dirfd, PATHNAME, 0), -1); @@ -96,7 +96,7 @@ TEST(SystemCallTest, TestStatxOnDirectoryInDifferentDirectoryWithDirfd) { EXPECT_NE(dirfd, -1); EXPECT_NE(mkdirat(dirfd, PATHNAME, S_IRWXU), -1); EXPECT_EQ(faccessat(dirfd, PATHNAME, F_OK, 0), 0); - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(dirfd, PATHNAME, 0, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), 0); check_statxbuf(statxbuf, 4096); EXPECT_NE(unlinkat(dirfd, PATHNAME, AT_REMOVEDIR), -1); @@ -105,7 +105,7 @@ TEST(SystemCallTest, TestStatxOnDirectoryInDifferentDirectoryWithDirfd) { } TEST(SystemCallTest, TestStatxWithAtEmptyPathAndAtFdcwd) { - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(AT_FDCWD, "", AT_EMPTY_PATH, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), 0); check_statxbuf(statxbuf, 4096); } @@ -117,7 +117,7 @@ TEST(SystemCallTest, TestStatxOnFileWithAtEmptyPathAndDirfd) { int dirfd = openat(AT_FDCWD, PATHNAME, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR); EXPECT_EQ(faccessat(AT_FDCWD, PATHNAME, F_OK, 0), 0); EXPECT_EQ(write(dirfd, BUFFER, strlen(BUFFER)), strlen(BUFFER)); - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(dirfd, "", AT_EMPTY_PATH, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), 0); check_statxbuf(statxbuf, strlen(BUFFER) * sizeof(char)); EXPECT_NE(close(dirfd), -1); @@ -131,7 +131,7 @@ TEST(SystemCallTest, TestStatxOnDirectoryWithAtEmptyPathAndDirfd) { EXPECT_EQ(faccessat(AT_FDCWD, PATHNAME, F_OK, 0), 0); int dirfd = openat(AT_FDCWD, PATHNAME, O_RDONLY | O_DIRECTORY); EXPECT_NE(dirfd, -1); - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(dirfd, "", AT_EMPTY_PATH, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), 0); check_statxbuf(statxbuf, 4096); EXPECT_NE(close(dirfd), -1); @@ -140,27 +140,27 @@ TEST(SystemCallTest, TestStatxOnDirectoryWithAtEmptyPathAndDirfd) { } TEST(SystemCallTest, TestStatxOnNonexistentFile) { - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(AT_FDCWD, "test", 0, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), -1); EXPECT_EQ(errno, ENOENT); } TEST(SystemCallTest, TestStatxOnRelativePathWithInvalidDirfd) { constexpr const char *PATHNAME = "test"; - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(-1, PATHNAME, 0, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), -1); EXPECT_EQ(errno, EBADF); } TEST(SystemCallTest, TestStatxWithStatxReservedSet) { constexpr const char *PATHNAME = "test"; - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(-1, PATHNAME, 0, STATX__RESERVED, &statxbuf), -1); EXPECT_EQ(errno, EINVAL); } TEST(SystemCallTest, TestStatxWithEmptyPathAndNoEmptyPath) { - struct statx statxbuf {}; + struct statx statxbuf{}; EXPECT_EQ(statx(AT_FDCWD, "", 0, STATX_BASIC_STATS | STATX_BTIME, &statxbuf), -1); EXPECT_EQ(errno, ENOENT); } \ No newline at end of file diff --git a/tests/unit/syscall/src/write.cpp b/capio-tests/unit/syscall/src/write.cpp similarity index 100% rename from tests/unit/syscall/src/write.cpp rename to capio-tests/unit/syscall/src/write.cpp diff --git a/doxy/Doxyfile b/doxy/Doxyfile new file mode 100644 index 000000000..f8e4df32d --- /dev/null +++ b/doxy/Doxyfile @@ -0,0 +1,2885 @@ +# Doxyfile 1.12.0 + +# 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 (\" \"). +# +# Note: +# +# Use Doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use Doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# 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 +# https://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 = "CAPIO" + +# 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 = + +# 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 = "Cross Application Programmable IO - Software documentation" + +# 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. + +PROJECT_LOGO = logo.png + +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + +# 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 = + +# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 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. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = YES + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# 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, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, 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 = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# 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 = YES + +# 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 = NO + +# If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by Doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# 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 + +# By default Python docstrings are displayed as preformatted text and Doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# Doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as Doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# 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:^^" +# 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:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +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 = YES + +# 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 + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = 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, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, 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). 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. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +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 https://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 = YES + +# 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: 6. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 6 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# 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 makes 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: +# https://www.riverbankcomputing.com/software) 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 = YES + +# 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 + +# The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use +# during processing. When set to 0 Doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# 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 = YES + +# 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 = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# 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 = YES + +# 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 = YES + +# 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 this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# 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 +# will also hide undocumented C++ concepts if enabled. 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 +# 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 + +# With the correct setting of option CASE_SENSE_NAMES Doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and macOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# 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_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# 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 = YES + +# 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 = YES + +# 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 = YES + +# 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 = + +# 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. See also section "Changing the +# layout of pages" for information. +# +# 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 https://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 = + +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + +#--------------------------------------------------------------------------- +# 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 = YES + +# 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 = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete +# function parameter documentation. If set to NO, Doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = 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 parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about +# undocumented enumeration values. If set to NO, Doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the Doxygen process Doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# 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) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of Doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# 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). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +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 = ../src ../README.md logo.png + +# 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: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). +# See also: INPUT_ENCODING for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# 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. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cxxm \ + *.cpp \ + *.cppm \ + *.ccm \ + *.c++ \ + *.c++m \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.ixx \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# 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 = YES + +# 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 = + +# 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 = */tests/* \ + *build* + +# 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, +# ANamespace::AClass, ANamespace::*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 Doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# 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 = ../README.md + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# 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 = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# multi-line macros, enums or list initialized variables 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 = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity 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 https://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 configuration 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 + +#--------------------------------------------------------------------------- +# 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 IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# 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 = html + +# 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). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = theme/doxygen-awesome.css + +# 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 tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generates light mode output, DARK always +# generates dark mode output, AUTO_LIGHT automatically sets the mode according +# to the user preference, uses light mode if no preference is set (the default), +# AUTO_DARK automatically sets the mode according to the user preference, uses +# dark mode if no preference is set and TOGGLE allows a user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# 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 color-wheel, see +# https://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 gray-scales 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_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = 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 = NO + +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + +# 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: +# https://developer.apple.com/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 https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.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 determines the URL 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. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# 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 = org.doxygen.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 = org.doxygen.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 = Publisher + +# 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 +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# 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 main .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 = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# 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: +# https://doc.qt.io/archives/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: +# https://doc.qt.io/archives/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: +# https://doc.qt.io/archives/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: +# https://doc.qt.io/archives/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: +# https://doc.qt.io/archives/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 (absolute path +# including file name) 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 (see "Fine-tuning the output"). 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 + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = 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 + +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = NO + +# 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 + +# If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# 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 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://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 + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) 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 https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# 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/v2.7-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 +# , /