Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c686b0655 | |||
| c7f34eaf9d | |||
| bd17f50f9b | |||
| a4b35e136b | |||
| 02ceb6828b | |||
| 0f9e689e39 | |||
| a7bb6454a4 |
@@ -73,11 +73,13 @@ jobs:
|
|||||||
- name: Configure (generate compile_commands.json)
|
- name: Configure (generate compile_commands.json)
|
||||||
run: cmake -B build -S .
|
run: cmake -B build -S .
|
||||||
|
|
||||||
- name: Run clang-tidy
|
- name: Run clang-tidy (informational, non-blocking)
|
||||||
run: |
|
run: |
|
||||||
find src/ -name '*.c' | xargs clang-tidy -p build \
|
find src/ -name '*.c' | xargs clang-tidy -p build \
|
||||||
--checks='-*,bugprone-*,clang-analyzer-*,misc-*,-misc-no-recursion' \
|
--checks='-*,bugprone-*,clang-analyzer-*,misc-*,-misc-no-recursion' \
|
||||||
2>&1 | tee clang-tidy-output.txt
|
2>&1 | tee clang-tidy-output.txt
|
||||||
if grep -q -E " error:| warning:" clang-tidy-output.txt; then
|
if grep -q -E " error:| warning:" clang-tidy-output.txt; then
|
||||||
echo "clang-tidy found issues — review the output above"
|
echo "::warning::clang-tidy found issues — review the output above"
|
||||||
|
else
|
||||||
|
echo "clang-tidy: no issues found"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# LSAN suppressions for FastSync
|
# LSAN suppressions for FastSync
|
||||||
# Pre-existing leaks — not introduced by this PR.
|
# Add suppression entries here for known pre-existing leaks that cannot be
|
||||||
# Remove these as each leak is fixed.
|
# fixed immediately. Remove entries as leaks are fixed.
|
||||||
|
#
|
||||||
# Config object never freed at CLI exit (main allocates, OS reclaims)
|
# Example format:
|
||||||
leak:config_create
|
# leak:function_name
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ You are a system architect for the FastSync project — a high-performance file
|
|||||||
|
|
||||||
Make high-level design decisions. Evaluate trade-offs, plan module interactions, design data flow, and ensure architectural coherence across the codebase.
|
Make high-level design decisions. Evaluate trade-offs, plan module interactions, design data flow, and ensure architectural coherence across the codebase.
|
||||||
|
|
||||||
|
> **Environment rule:** dependency installation must always use the project's custom Docker image (repo-root `Dockerfile`, same as CI) — never ad-hoc host package installs. See `AGENTS.md`.
|
||||||
|
|
||||||
## Project Architecture
|
## Project Architecture
|
||||||
|
|
||||||
### Module Map
|
### Module Map
|
||||||
|
|||||||
@@ -21,18 +21,29 @@ set(CMAKE_C_STANDARD 11)
|
|||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
add_compile_options(-Wall -g -O3)
|
add_compile_options(-Wall -g -O3)
|
||||||
# add_compile_options(-Wall -g -O1 -fsanitize=address)
|
|
||||||
# add_link_options(-fsanitize=address)
|
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(xxhash GIT_REPOSITORY https://github.com/Cyan4973/xxHash GIT_TAG v0.8.3 SOURCE_SUBDIR cmake_unofficial)
|
||||||
xxhash
|
|
||||||
GIT_REPOSITORY https://github.com/Cyan4973/xxHash
|
|
||||||
GIT_TAG v0.8.3
|
|
||||||
SOURCE_SUBDIR cmake_unofficial
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(xxhash)
|
FetchContent_MakeAvailable(xxhash)
|
||||||
|
|
||||||
|
# Sanitizer option
|
||||||
|
set(SANITIZER "none" CACHE STRING "Sanitizer to enable (address, thread, none)")
|
||||||
|
set_property(CACHE SANITIZER PROPERTY STRINGS address thread none)
|
||||||
|
if(SANITIZER STREQUAL "address")
|
||||||
|
add_compile_options(-fsanitize=address -fno-omit-frame-pointer -g)
|
||||||
|
add_link_options(-fsanitize=address)
|
||||||
|
elseif(SANITIZER STREQUAL "thread")
|
||||||
|
add_compile_options(-fsanitize=thread -fno-omit-frame-pointer -g)
|
||||||
|
add_link_options(-fsanitize=thread)
|
||||||
|
elseif(NOT SANITIZER STREQUAL "none")
|
||||||
|
message(FATAL_ERROR "Unknown sanitizer: ${SANITIZER}. Supported values: address, thread, none")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
option(STRICT_WARNINGS "Enable strict warnings" OFF)
|
||||||
|
if(STRICT_WARNINGS)
|
||||||
|
add_compile_options(-Wextra -Wpedantic -Werror)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
@@ -43,13 +54,11 @@ endif()
|
|||||||
|
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
|
|
||||||
# Source file collection
|
|
||||||
file(GLOB SHARED_SRCS "src/shared/*.c")
|
file(GLOB SHARED_SRCS "src/shared/*.c")
|
||||||
file(GLOB SERVER_SRCS "src/server/*.c")
|
file(GLOB SERVER_SRCS "src/server/*.c")
|
||||||
file(GLOB CLIENT_SRCS "src/client/*.c")
|
file(GLOB CLIENT_SRCS "src/client/*.c")
|
||||||
file(GLOB TEST_SRCS "tests/*.c")
|
file(GLOB TEST_SRCS "tests/*.c")
|
||||||
|
|
||||||
# Targets
|
|
||||||
add_executable(server ${SERVER_SRCS} ${SHARED_SRCS})
|
add_executable(server ${SERVER_SRCS} ${SHARED_SRCS})
|
||||||
target_include_directories(server PRIVATE src/shared src/server src/client)
|
target_include_directories(server PRIVATE src/shared src/server src/client)
|
||||||
target_link_libraries(server PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash)
|
target_link_libraries(server PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash)
|
||||||
@@ -75,7 +84,7 @@ tests/integration/ — Python pytest integration tests
|
|||||||
### Dependencies
|
### Dependencies
|
||||||
- **zstd** — found via `find_library(ZSTD_LIBRARY zstd)`
|
- **zstd** — found via `find_library(ZSTD_LIBRARY zstd)`
|
||||||
- **OpenSSL** — found via `find_package(OpenSSL REQUIRED)` (TLS 1.2+ transport)
|
- **OpenSSL** — found via `find_package(OpenSSL REQUIRED)` (TLS 1.2+ transport)
|
||||||
- **xxHash** — fetched via `FetchContent` from GitHub (delta transfer hashing)
|
- **xxHash** — fetched via `FetchContent` from GitHub (delta transfer hashing, v0.8.3)
|
||||||
- **pthreads** — found via `find_package(Threads REQUIRED)`
|
- **pthreads** — found via `find_package(Threads REQUIRED)`
|
||||||
- **C11 standard** — required
|
- **C11 standard** — required
|
||||||
- **CMake 3.22+** — minimum version
|
- **CMake 3.22+** — minimum version
|
||||||
@@ -83,10 +92,11 @@ tests/integration/ — Python pytest integration tests
|
|||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
- Use `file(GLOB ...)` for source collection (existing pattern).
|
- Use `file(GLOB ...)` for source collection (existing pattern).
|
||||||
- All targets link `Threads::Threads` and `${ZSTD_LIBRARY}`.
|
- All targets link `Threads::Threads`, `${ZSTD_LIBRARY}`, `OpenSSL::SSL`, `OpenSSL::Crypto`, and `xxhash`.
|
||||||
- Include directories: `src/shared`, `src/server`, `src/client`, `tests` (for test target).
|
- Include directories: `src/shared`, `src/server`, `src/client`, `tests` (for test target).
|
||||||
- Sanitizer support is commented out but present (`-fsanitize=address`).
|
- Sanitizer support: pass `-DSANITIZER=address` or `-DSANITIZER=thread` to cmake (live option in CMakeLists.txt).
|
||||||
- Build with `cmake -B build -S . && cmake --build build -j$(nproc)`.
|
- Build with `cmake -B build -S . && cmake --build build -j$(nproc)`.
|
||||||
|
- Install dependencies only via the project's custom Docker image (repo-root `Dockerfile`, same image CI uses) — never via host package installs; see `AGENTS.md`.
|
||||||
|
|
||||||
## When Making Changes
|
## When Making Changes
|
||||||
|
|
||||||
@@ -95,28 +105,21 @@ tests/integration/ — Python pytest integration tests
|
|||||||
3. Add new dependencies with `find_package` or `find_library`.
|
3. Add new dependencies with `find_package` or `find_library`.
|
||||||
4. When adding a new executable target, follow the pattern of existing targets.
|
4. When adding a new executable target, follow the pattern of existing targets.
|
||||||
5. When adding a new library (static/shared), use `add_library` and follow the project's naming.
|
5. When adding a new library (static/shared), use `add_library` and follow the project's naming.
|
||||||
6. For sanitizer builds, use the commented-out `-fsanitize=address` lines as reference.
|
6. For sanitizer builds, pass `-DSANITIZER=address` or `-DSANITIZER=thread` to cmake (matching CI's matrix strategy).
|
||||||
7. Always verify the build compiles after changes.
|
7. Always verify the build compiles after changes.
|
||||||
|
|
||||||
## Sanitizer Configurations
|
## Sanitizer Configurations
|
||||||
|
|
||||||
### AddressSanitizer (memory errors)
|
Use the project's built-in `-DSANITIZER=` option (matching the CI matrix):
|
||||||
```bash
|
```bash
|
||||||
cmake -B build -S . \
|
cmake -B build -S . -DSANITIZER=address # AddressSanitizer (memory errors)
|
||||||
-DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \
|
cmake --build build -j$(nproc)
|
||||||
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
|
|
||||||
|
cmake -B build -S . -DSANITIZER=thread # ThreadSanitizer (race conditions)
|
||||||
cmake --build build -j$(nproc)
|
cmake --build build -j$(nproc)
|
||||||
```
|
```
|
||||||
|
|
||||||
### ThreadSanitizer (race conditions)
|
For UndefinedBehaviorSanitizer (no `-DSANITIZER=undefined` option in CMakeLists.txt yet), use the manual flag approach:
|
||||||
```bash
|
|
||||||
cmake -B build -S . \
|
|
||||||
-DCMAKE_C_FLAGS="-fsanitize=thread -g" \
|
|
||||||
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread"
|
|
||||||
cmake --build build -j$(nproc)
|
|
||||||
```
|
|
||||||
|
|
||||||
### UndefinedBehaviorSanitizer
|
|
||||||
```bash
|
```bash
|
||||||
cmake -B build -S . \
|
cmake -B build -S . \
|
||||||
-DCMAKE_C_FLAGS="-fsanitize=undefined -fno-omit-frame-pointer -g" \
|
-DCMAKE_C_FLAGS="-fsanitize=undefined -fno-omit-frame-pointer -g" \
|
||||||
@@ -124,14 +127,6 @@ cmake -B build -S . \
|
|||||||
cmake --build build -j$(nproc)
|
cmake --build build -j$(nproc)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Combined Sanitizers
|
|
||||||
```bash
|
|
||||||
cmake -B build -S . \
|
|
||||||
-DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -g" \
|
|
||||||
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined"
|
|
||||||
cmake --build build -j$(nproc)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using ccache (faster rebuilds)
|
### Using ccache (faster rebuilds)
|
||||||
```bash
|
```bash
|
||||||
cmake -B build -S . -DCMAKE_C_COMPILER_LAUNCHER=ccache
|
cmake -B build -S . -DCMAKE_C_COMPILER_LAUNCHER=ccache
|
||||||
@@ -169,31 +164,19 @@ cmake --build build -j$(nproc)
|
|||||||
./build/tests
|
./build/tests
|
||||||
```
|
```
|
||||||
|
|
||||||
## When Adding Sanitizer Support to CMakeLists.txt
|
## Sanitizer Integration
|
||||||
|
|
||||||
Use CMake options for cleaner integration:
|
The project uses a single `SANITIZER` cache variable in `CMakeLists.txt`:
|
||||||
```cmake
|
```cmake
|
||||||
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
|
set(SANITIZER "none" CACHE STRING "Sanitizer to enable (address, thread, none)")
|
||||||
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)
|
set_property(CACHE SANITIZER PROPERTY STRINGS address thread none)
|
||||||
option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer" OFF)
|
|
||||||
|
|
||||||
if(ENABLE_ASAN)
|
|
||||||
add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
|
|
||||||
add_link_options(-fsanitize=address)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(ENABLE_TSAN)
|
|
||||||
add_compile_options(-fsanitize=thread)
|
|
||||||
add_link_options(-fsanitize=thread)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(ENABLE_UBSAN)
|
|
||||||
add_compile_options(-fsanitize=undefined)
|
|
||||||
add_link_options(-fsanitize=undefined)
|
|
||||||
endif()
|
|
||||||
```
|
```
|
||||||
|
Supported values: `address`, `thread`, `none`. Unknown values trigger `FATAL_ERROR`.
|
||||||
|
|
||||||
Then build with:
|
Build with:
|
||||||
```bash
|
```bash
|
||||||
cmake -B build -S . -DENABLE_ASAN=ON
|
cmake -B build -S . -DSANITIZER=address
|
||||||
|
cmake --build build -j$(nproc)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To add support for a new sanitizer (e.g., UBSan), add an `elseif(SANITIZER STREQUAL "undefined")` block following the existing `address`/`thread` pattern.
|
||||||
|
|||||||
@@ -115,18 +115,21 @@ The project uses Gitea Actions. Key jobs:
|
|||||||
jobs:
|
jobs:
|
||||||
sanitizer:
|
sanitizer:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: gitea.tap-tap.win/taptap/fastsync-ci:v6
|
container: gitea.tap-tap.win/taptap/fastsync-ci:v7
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Build with ASan + UBSan
|
- name: Configure
|
||||||
run: |
|
run: cmake -B build-${{ matrix.sanitizer }} -S . -DSANITIZER=${{ matrix.sanitizer }}
|
||||||
cmake -B build -S . \
|
- name: Build
|
||||||
-DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -g" \
|
run: cmake --build build-${{ matrix.sanitizer }} -j$(nproc)
|
||||||
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined"
|
- name: Symlink for integration tests
|
||||||
cmake --build build -j$(nproc)
|
run: ln -sf build-${{ matrix.sanitizer }} build
|
||||||
- name: Run tests
|
- name: Unit Tests
|
||||||
run: ./build/tests
|
run: ./build-${{ matrix.sanitizer }}/tests
|
||||||
|
- name: Integration Tests
|
||||||
|
run: LSAN_OPTIONS=suppressions=.lsan-suppressions.txt python3 -m pytest tests/ -v --tb=short
|
||||||
```
|
```
|
||||||
|
The symlink step is required because `tests/conftest.py` expects `./build` to exist.
|
||||||
|
|
||||||
## Verification Checklist
|
## Verification Checklist
|
||||||
|
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ When writing integration tests (Python-based), follow the patterns in `tests/int
|
|||||||
- `test_tls.py` — TLS transport tests
|
- `test_tls.py` — TLS transport tests
|
||||||
- `test_features.py` — feature-specific tests (delete, exclude, incremental, etc.)
|
- `test_features.py` — feature-specific tests (delete, exclude, incremental, etc.)
|
||||||
|
|
||||||
Use `conftest.py` fixtures for server setup/teardown.
|
Use `tests/conftest.py` fixtures for server setup/teardown (note: the file is at `tests/conftest.py`, not `tests/integration/conftest.py`).
|
||||||
|
|
||||||
### Minimal Integration Test
|
### Minimal Integration Test
|
||||||
```python
|
```python
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
FastSync is a high-performance file synchronization system written in C11. It supports TCP and SSH transports, TLS encryption (OpenSSL), streaming zstd compression, multithreaded transfers, and incremental sync. The build uses CMake; CI runs on Gitea Actions (`.gitea/workflows/ci.yaml`).
|
||||||
|
|
||||||
|
## Dependency installation
|
||||||
|
|
||||||
|
**Rule: always install dependencies using the project's custom Docker image — never via ad-hoc system package installs on the host** (no `apt-get install` / `pip install` on the host machine).
|
||||||
|
|
||||||
|
The image is built from the repo-root `Dockerfile` and is the same image CI uses: `gitea.tap-tap.win/taptap/fastsync-ci:v7`. It contains the full toolchain: gcc/g++, CMake, libzstd-dev, libssl-dev, make, git, cppcheck, clang-format, python3 + pytest, openssh-client, and Node.js.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use the prebuilt CI image directly (faster, guaranteed CI parity)
|
||||||
|
docker pull gitea.tap-tap.win/taptap/fastsync-ci:v7
|
||||||
|
docker tag gitea.tap-tap.win/taptap/fastsync-ci:v7 fastsync-ci:local
|
||||||
|
|
||||||
|
# Or build the image from the repo-root Dockerfile
|
||||||
|
# (Note: the prebuilt :v7 image reflects the previous Dockerfile state;
|
||||||
|
# rebuild from source to pick up any newly added packages like lcov/valgrind.)
|
||||||
|
docker build -t fastsync-ci:local .
|
||||||
|
|
||||||
|
# Build, run unit tests, and run integration tests inside the container
|
||||||
|
docker run --rm -v "$PWD:/workspace" -w /workspace fastsync-ci:local \
|
||||||
|
sh -c 'cmake -B build -S . && cmake --build build -j$(nproc) && ./build/tests && python3 -m pytest tests/'
|
||||||
|
|
||||||
|
# Avoid root-owned build/ artifacts by matching your host UID/GID
|
||||||
|
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/workspace" \
|
||||||
|
-w /workspace fastsync-ci:local \
|
||||||
|
sh -c 'cmake -B build -S . && cmake --build build -j$(nproc) && ./build/tests && python3 -m pytest tests/'
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** The first `cmake configure` (`cmake -B build -S .`) fetches xxHash from GitHub via `FetchContent` — network access is required. Subsequent reconfigures reuse the cached source.
|
||||||
|
|
||||||
|
If a dependency is missing from the image, add it to the `Dockerfile` (and rebuild) rather than installing it on the host.
|
||||||
|
|
||||||
|
## CI Conventions
|
||||||
|
|
||||||
|
When configuring for CI parity, use:
|
||||||
|
```bash
|
||||||
|
cmake -B build -S . -DSTRICT_WARNINGS=ON # -Wextra -Wpedantic -Werror
|
||||||
|
cmake -B build -S . -DSANITIZER=address # AddressSanitizer (ASan)
|
||||||
|
cmake -B build -S . -DSANITIZER=thread # ThreadSanitizer (TSan)
|
||||||
|
```
|
||||||
|
|
||||||
|
The CI workflow (`.gitea/workflows/ci.yaml`) runs lint (clang-format, cppcheck), build + test (unit + integration), and sanitizer (currently only `address`) jobs sequentially.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -B build -S . && cmake --build build -j$(nproc)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build/tests # unit tests
|
||||||
|
python3 -m pytest tests/ # integration tests
|
||||||
|
```
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
FROM ubuntu:24.04
|
FROM ubuntu:24.04
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
gcc g++ make libc6-dev cmake libzstd-dev libssl-dev git ca-certificates curl cppcheck clang-format \
|
gcc g++ make libc6-dev cmake libzstd-dev libssl-dev git ca-certificates curl cppcheck clang-format \
|
||||||
python3 python3-pip python3-venv openssl openssh-client && \
|
python3 python3-pip python3-venv openssl openssh-client lcov valgrind && \
|
||||||
pip3 install --break-system-packages pytest && \
|
pip3 install --break-system-packages pytest && \
|
||||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
||||||
apt-get install -y --no-install-recommends nodejs && \
|
apt-get install -y --no-install-recommends nodejs && \
|
||||||
|
|||||||
+40
-17
@@ -73,6 +73,8 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
Config* config = config_create(str_dup(PROTOCOL_VERSION), NULL, NULL, save_to_disk, false, false,
|
Config* config = config_create(str_dup(PROTOCOL_VERSION), NULL, NULL, save_to_disk, false, false,
|
||||||
false, false, 5, false, 0);
|
false, false, 5, false, 0);
|
||||||
|
int exit_code = 0;
|
||||||
|
bool config_owned_by_pipeline = false;
|
||||||
|
|
||||||
int positional_args[2];
|
int positional_args[2];
|
||||||
int positional_count = 0;
|
int positional_count = 0;
|
||||||
@@ -80,7 +82,7 @@ int main(int argc, char* argv[]) {
|
|||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "--help") == 0) {
|
if (strcmp(argv[i], "--help") == 0) {
|
||||||
print_usage();
|
print_usage();
|
||||||
return 0;
|
goto cleanup;
|
||||||
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--archive") == 0) {
|
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--archive") == 0) {
|
||||||
config->use_compression = true;
|
config->use_compression = true;
|
||||||
config->use_multithreading = true;
|
config->use_multithreading = true;
|
||||||
@@ -165,11 +167,13 @@ int main(int argc, char* argv[]) {
|
|||||||
unsigned long long kbps = strtoull(argv[++i], &end, 10);
|
unsigned long long kbps = strtoull(argv[++i], &end, 10);
|
||||||
if (errno != 0 || *end != '\0' || kbps == 0) {
|
if (errno != 0 || *end != '\0' || kbps == 0) {
|
||||||
fprintf(stderr, "Error: --bwlimit must be a positive integer\n");
|
fprintf(stderr, "Error: --bwlimit must be a positive integer\n");
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
if (kbps > ULLONG_MAX / 1024) {
|
if (kbps > ULLONG_MAX / 1024) {
|
||||||
fprintf(stderr, "Error: --bwlimit value too large\n");
|
fprintf(stderr, "Error: --bwlimit value too large\n");
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
io_set_bwlimit(kbps * 1024);
|
io_set_bwlimit(kbps * 1024);
|
||||||
log_message(LOG_LEVEL_INFO, "Set bandwidth limit to %llu KB/s", kbps);
|
log_message(LOG_LEVEL_INFO, "Set bandwidth limit to %llu KB/s", kbps);
|
||||||
@@ -195,14 +199,16 @@ int main(int argc, char* argv[]) {
|
|||||||
} else if (argv[i][0] == '-') {
|
} else if (argv[i][0] == '-') {
|
||||||
fprintf(stderr, "Unknown option: %s\n", argv[i]);
|
fprintf(stderr, "Unknown option: %s\n", argv[i]);
|
||||||
print_usage();
|
print_usage();
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
} else {
|
} else {
|
||||||
if (positional_count < 2)
|
if (positional_count < 2)
|
||||||
positional_args[positional_count++] = i;
|
positional_args[positional_count++] = i;
|
||||||
else {
|
else {
|
||||||
fprintf(stderr, "Unexpected argument: %s\n", argv[i]);
|
fprintf(stderr, "Unexpected argument: %s\n", argv[i]);
|
||||||
print_usage();
|
print_usage();
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,7 +224,8 @@ int main(int argc, char* argv[]) {
|
|||||||
} else if (positional_count == 1) {
|
} else if (positional_count == 1) {
|
||||||
fprintf(stderr, "Error: missing destination argument\n");
|
fprintf(stderr, "Error: missing destination argument\n");
|
||||||
print_usage();
|
print_usage();
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
} else {
|
} else {
|
||||||
if (!config->send_directory && env_source)
|
if (!config->send_directory && env_source)
|
||||||
config->send_directory = str_dup((char*)env_source);
|
config->send_directory = str_dup((char*)env_source);
|
||||||
@@ -229,22 +236,26 @@ int main(int argc, char* argv[]) {
|
|||||||
if (!config->send_directory || !config->receive_root_directory) {
|
if (!config->send_directory || !config->receive_root_directory) {
|
||||||
fprintf(stderr, "Error: source and destination directories are required\n");
|
fprintf(stderr, "Error: source and destination directories are required\n");
|
||||||
print_usage();
|
print_usage();
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
if (config->use_sendfile && (config->use_chunk_serialization || config->use_compression)) {
|
if (config->use_sendfile && (config->use_chunk_serialization || config->use_compression)) {
|
||||||
fprintf(stderr, "Error: -f/--sendfile cannot be combined with -c (compression) or -s (chunk "
|
fprintf(stderr, "Error: -f/--sendfile cannot be combined with -c (compression) or -s (chunk "
|
||||||
"serialization)\n");
|
"serialization)\n");
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->transport == TRANSPORT_SSH && config->use_sendfile) {
|
if (config->transport == TRANSPORT_SSH && config->use_sendfile) {
|
||||||
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
|
fprintf(stderr, "Error: -f/--sendfile is not supported with SSH transport\n");
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->use_incremental && config->use_chunk_serialization) {
|
if (config->use_incremental && config->use_chunk_serialization) {
|
||||||
fprintf(stderr, "Error: --incremental is not supported with -s (chunk serialization)\n");
|
fprintf(stderr, "Error: --incremental is not supported with -s (chunk serialization)\n");
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->use_incremental && !config->use_metadata) {
|
if (config->use_incremental && !config->use_metadata) {
|
||||||
@@ -254,15 +265,18 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
if (config->use_delta && !config->use_incremental) {
|
if (config->use_delta && !config->use_incremental) {
|
||||||
fprintf(stderr, "Error: --delta requires --incremental\n");
|
fprintf(stderr, "Error: --delta requires --incremental\n");
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
if (config->use_delta && config->use_chunk_serialization) {
|
if (config->use_delta && config->use_chunk_serialization) {
|
||||||
fprintf(stderr, "Error: --delta cannot be combined with -s (chunk serialization)\n");
|
fprintf(stderr, "Error: --delta cannot be combined with -s (chunk serialization)\n");
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
if (config->use_delta && config->use_sendfile) {
|
if (config->use_delta && config->use_sendfile) {
|
||||||
fprintf(stderr, "Error: --delta cannot be combined with -f (sendfile)\n");
|
fprintf(stderr, "Error: --delta cannot be combined with -f (sendfile)\n");
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
if (config->use_delta && !config->use_metadata) {
|
if (config->use_delta && !config->use_metadata) {
|
||||||
log_message(LOG_LEVEL_INFO, "Enabling metadata preservation for --delta");
|
log_message(LOG_LEVEL_INFO, "Enabling metadata preservation for --delta");
|
||||||
@@ -272,12 +286,21 @@ int main(int argc, char* argv[]) {
|
|||||||
if (config->use_tls) {
|
if (config->use_tls) {
|
||||||
if (!config->tls_cert || !config->tls_key) {
|
if (!config->tls_cert || !config->tls_key) {
|
||||||
fprintf(stderr, "Error: --tls requires --cert and --key\n");
|
fprintf(stderr, "Error: --tls requires --cert and --key\n");
|
||||||
return 1;
|
exit_code = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
tls_global_init();
|
tls_global_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->use_multithreading)
|
if (config->use_multithreading) {
|
||||||
return send_files_multithreaded(config);
|
config_owned_by_pipeline = true;
|
||||||
return send_files(config);
|
exit_code = send_files_multithreaded(config);
|
||||||
|
} else {
|
||||||
|
exit_code = send_files(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (!config_owned_by_pipeline)
|
||||||
|
config_delete(config);
|
||||||
|
return exit_code;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user