feat: update AI automation agents, add reviewer agent, fix leaks, enhance CI
Agents: - cmake-expert: fix stale CMake version (4.1 -> 3.22), add OpenSSL/xxHash deps - integrator: fix stale test.py references to tests/integration/, update CI docs - test-writer: update integration test patterns for modular test structure - reviewer: new comprehensive PR reviewer (code, build, CI, docs, quality) Bug fixes: - delta.c: set instructions[i].type = DELTA_INSTR_LITERAL in deserialize - scanner.c: free ArrayList when directory_scanner_next returns NULL CI: - Add sanitizer job (ASan + UBSan) with LSAN suppressions for pre-existing leaks - Add clang-tidy job with proper warning/error detection - Remove || true masking (fixes now make sanitizer useful) Cleanup: - gitignore build-asan/ - .lsan-suppressions.txt for known CLI config leaks
This commit is contained in:
@@ -55,3 +55,28 @@ jobs:
|
|||||||
|
|
||||||
- name: Unit Tests
|
- name: Unit Tests
|
||||||
run: ./build-${{ matrix.sanitizer }}/tests
|
run: ./build-${{ matrix.sanitizer }}/tests
|
||||||
|
|
||||||
|
- name: Integration Tests
|
||||||
|
run: LSAN_OPTIONS=suppressions=.lsan-suppressions.txt python3 -m pytest tests/ -v --tb=short
|
||||||
|
|
||||||
|
clang-tidy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: gitea.tap-tap.win/taptap/fastsync-ci:v7
|
||||||
|
needs: lint
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Configure (generate compile_commands.json)
|
||||||
|
run: cmake -B build -S .
|
||||||
|
|
||||||
|
- name: Run clang-tidy (informational, non-blocking)
|
||||||
|
run: |
|
||||||
|
find src/ -name '*.c' | xargs clang-tidy -p build \
|
||||||
|
--checks='-*,bugprone-*,clang-analyzer-*,misc-*,-misc-no-recursion' \
|
||||||
|
2>&1 | tee clang-tidy-output.txt
|
||||||
|
if grep -q -E " error:| warning:" clang-tidy-output.txt; then
|
||||||
|
echo "::warning::clang-tidy found issues — review the output above"
|
||||||
|
else
|
||||||
|
echo "clang-tidy: no issues found"
|
||||||
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# LSAN suppressions for FastSync
|
||||||
|
# Add suppression entries here for known pre-existing leaks that cannot be
|
||||||
|
# fixed immediately. Remove entries as leaks are fixed.
|
||||||
|
#
|
||||||
|
# Example format:
|
||||||
|
# leak:function_name
|
||||||
@@ -22,6 +22,10 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
|||||||
|
|
||||||
add_compile_options(-Wall -g -O3)
|
add_compile_options(-Wall -g -O3)
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(xxhash GIT_REPOSITORY https://github.com/Cyan4973/xxHash GIT_TAG v0.8.3 SOURCE_SUBDIR cmake_unofficial)
|
||||||
|
FetchContent_MakeAvailable(xxhash)
|
||||||
|
|
||||||
# Sanitizer option
|
# Sanitizer option
|
||||||
set(SANITIZER "none" CACHE STRING "Sanitizer to enable (address, thread, none)")
|
set(SANITIZER "none" CACHE STRING "Sanitizer to enable (address, thread, none)")
|
||||||
set_property(CACHE SANITIZER PROPERTY STRINGS address thread none)
|
set_property(CACHE SANITIZER PROPERTY STRINGS address thread none)
|
||||||
@@ -40,10 +44,6 @@ if(STRICT_WARNINGS)
|
|||||||
add_compile_options(-Wextra -Wpedantic -Werror)
|
add_compile_options(-Wextra -Wpedantic -Werror)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(FetchContent)
|
|
||||||
FetchContent_Declare(xxhash GIT_REPOSITORY https://github.com/Cyan4973/xxHash GIT_TAG v0.8.3 SOURCE_SUBDIR cmake_unofficial)
|
|
||||||
FetchContent_MakeAvailable(xxhash)
|
|
||||||
|
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
@@ -51,6 +51,7 @@ find_library(ZSTD_LIBRARY zstd)
|
|||||||
if(NOT ZSTD_LIBRARY)
|
if(NOT ZSTD_LIBRARY)
|
||||||
message(FATAL_ERROR "zstd library not found. Ensure it is in your nix-shell!")
|
message(FATAL_ERROR "zstd library not found. Ensure it is in your nix-shell!")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
|
|
||||||
file(GLOB SHARED_SRCS "src/shared/*.c")
|
file(GLOB SHARED_SRCS "src/shared/*.c")
|
||||||
@@ -76,14 +77,15 @@ target_link_libraries(tests PRIVATE Threads::Threads ${ZSTD_LIBRARY} OpenSSL::SS
|
|||||||
src/shared/ — shared libraries (globbed as SHARED_SRCS)
|
src/shared/ — shared libraries (globbed as SHARED_SRCS)
|
||||||
src/client/ — client sources (globbed as CLIENT_SRCS)
|
src/client/ — client sources (globbed as CLIENT_SRCS)
|
||||||
src/server/ — server sources (globbed as SERVER_SRCS)
|
src/server/ — server sources (globbed as SERVER_SRCS)
|
||||||
tests/ — test sources (globbed as TEST_SRCS)
|
tests/ — unit test sources (globbed as TEST_SRCS)
|
||||||
|
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)
|
||||||
|
- **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)`
|
||||||
- **OpenSSL** — found via `find_package(OpenSSL REQUIRED)`
|
|
||||||
- **xxhash** — fetched via `FetchContent` from GitHub (v0.8.3)
|
|
||||||
- **C11 standard** — required
|
- **C11 standard** — required
|
||||||
- **CMake 3.22+** — minimum version
|
- **CMake 3.22+** — minimum version
|
||||||
|
|
||||||
@@ -162,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.
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ Design integration tests that verify the full transfer pipeline works end-to-end
|
|||||||
- Custom framework in `tests/test_utils.h`
|
- Custom framework in `tests/test_utils.h`
|
||||||
- Run: `./build/tests`
|
- Run: `./build/tests`
|
||||||
|
|
||||||
### 2. Integration Tests (existing — `test.py`)
|
### 2. Integration Tests (existing — `tests/integration/`)
|
||||||
- Full transfer pipeline: client → server → verify
|
- Full transfer pipeline: client → server → verify
|
||||||
- Multiple configurations (TCP, SSH, TLS, compression, multithreading)
|
- Multiple configurations (TCP, SSH, TLS, compression, multithreading)
|
||||||
- Network shaping (LAN, WAN profiles)
|
- Network shaping (LAN, WAN profiles)
|
||||||
- Feature tests (dry run, archive, exclude, delete, incremental, bandwidth limit)
|
- Feature tests (dry run, archive, exclude, delete, incremental, bandwidth limit)
|
||||||
- Run: `python3 test.py`
|
- Run: `python3 -m pytest tests/ -v --tb=short`
|
||||||
|
|
||||||
### 3. New: Focused Integration Tests
|
### 3. New: Focused Integration Tests
|
||||||
When adding new features or fixing bugs, write targeted integration tests.
|
When adding new features or fixing bugs, write targeted integration tests.
|
||||||
@@ -106,35 +106,36 @@ test ! -f /tmp/dst/.../extra.txt
|
|||||||
|
|
||||||
### Gitea Workflow Structure (`.gitea/workflows/ci.yaml`)
|
### Gitea Workflow Structure (`.gitea/workflows/ci.yaml`)
|
||||||
The project uses Gitea Actions. Key jobs:
|
The project uses Gitea Actions. Key jobs:
|
||||||
1. **Build** — compile on push/PR
|
1. **build-and-test** — compile, unit tests, integration tests on push/PR
|
||||||
2. **Unit tests** — run `./build/tests`
|
2. **sanitizer** — ASan + UBSan build and test (separate job)
|
||||||
3. **Integration tests** — run `python3 test.py` (light mode)
|
3. **clang-tidy** — static analysis on C source files
|
||||||
4. **Sanitizer builds** — ASan, TSan variants
|
|
||||||
|
|
||||||
### Adding a New CI Job
|
### Adding a New CI Job
|
||||||
```yaml
|
```yaml
|
||||||
jobs:
|
jobs:
|
||||||
sanitizer:
|
sanitizer:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
container: gitea.tap-tap.win/taptap/fastsync-ci:v7
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Install dependencies
|
- name: Configure
|
||||||
run: sudo apt-get update && sudo apt-get install -y libzstd-dev libssl-dev
|
run: cmake -B build-${{ matrix.sanitizer }} -S . -DSANITIZER=${{ matrix.sanitizer }}
|
||||||
- name: Build with ASan
|
- name: Build
|
||||||
run: |
|
run: cmake --build build-${{ matrix.sanitizer }} -j$(nproc)
|
||||||
cmake -B build -S . \
|
- name: Symlink for integration tests
|
||||||
-DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \
|
run: ln -sf build-${{ matrix.sanitizer }} build
|
||||||
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
|
- name: Unit Tests
|
||||||
cmake --build build -j$(nproc)
|
run: ./build-${{ matrix.sanitizer }}/tests
|
||||||
- name: Run tests
|
- name: Integration Tests
|
||||||
run: ./build/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
|
||||||
|
|
||||||
After any code change:
|
After any code change:
|
||||||
- [ ] Unit tests pass: `./build/tests`
|
- [ ] Unit tests pass: `./build/tests`
|
||||||
- [ ] Integration tests pass: `python3 test.py` (light mode at minimum)
|
- [ ] Integration tests pass: `python3 -m pytest tests/ -v --tb=short`
|
||||||
- [ ] Build clean: no warnings with `-Wall`
|
- [ ] Build clean: no warnings with `-Wall`
|
||||||
- [ ] No memory errors: ASan clean
|
- [ ] No memory errors: ASan clean
|
||||||
- [ ] No thread errors: TSan clean (if threading involved)
|
- [ ] No thread errors: TSan clean (if threading involved)
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
---
|
||||||
|
description: Reviews pull requests comprehensively — code correctness, CI/CD validity, configuration, documentation, and overall PR quality. Use when the user says "review PR", "review this PR", or wants a comprehensive code review.
|
||||||
|
mode: subagent
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a comprehensive PR reviewer for the FastSync project — a high-performance file synchronization system written in C11.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
|
||||||
|
Review pull requests holistically. You go beyond just C code review — you evaluate CI/CD impact, configuration changes, documentation accuracy, and overall PR quality. You are the final gatekeeper before merge.
|
||||||
|
|
||||||
|
## Review Dimensions
|
||||||
|
|
||||||
|
### 1. C Code Review
|
||||||
|
|
||||||
|
Review all changed `.c` and `.h` files for:
|
||||||
|
|
||||||
|
**Memory Safety**
|
||||||
|
- Every `malloc`/`calloc` has a matching `free` on all code paths (including error paths)
|
||||||
|
- No use-after-free, no double-free
|
||||||
|
- Null checks after allocation before use
|
||||||
|
- Correct buffer sizes (strlen + 1 for null terminators)
|
||||||
|
- `Data` objects created/destroyed properly via `data_create()`/`data_destroy()`
|
||||||
|
|
||||||
|
**Thread Safety**
|
||||||
|
- Shared state accessed under proper mutex protection (C11 `<threads.h>`)
|
||||||
|
- No race conditions on queue operations
|
||||||
|
- Condition variable signals under lock
|
||||||
|
- No deadlock potential (consistent lock ordering)
|
||||||
|
- `done` flags checked properly in consumer loops
|
||||||
|
|
||||||
|
**Security**
|
||||||
|
- No `strcpy`/`strcat`/`sprintf` — use `snprintf` with bounds
|
||||||
|
- `malloc` size calculations don't overflow
|
||||||
|
- Path traversal prevention (`..` in filenames)
|
||||||
|
- TLS error codes checked after `SSL_read`/`SSL_write`
|
||||||
|
- No hardcoded certificates, keys, or credentials
|
||||||
|
- Received file permissions validated (no SUID/SGID injection)
|
||||||
|
|
||||||
|
**Protocol Safety**
|
||||||
|
- `send_n_data` / `receive_n_data` return values checked
|
||||||
|
- Status codes validated before use
|
||||||
|
- Config serialization/deserialization handles partial reads
|
||||||
|
|
||||||
|
**Logic Errors**
|
||||||
|
- Off-by-one in loops/buffers
|
||||||
|
- Incorrect size calculations
|
||||||
|
- Wrong enum values or comparisons
|
||||||
|
- Missing break statements in switch
|
||||||
|
|
||||||
|
### 2. Build System Review
|
||||||
|
|
||||||
|
If `CMakeLists.txt` is changed:
|
||||||
|
- Dependencies properly declared with `find_package` or `FetchContent`
|
||||||
|
- New targets follow existing patterns (link flags, include dirs)
|
||||||
|
- No duplicate source file additions
|
||||||
|
- Sanitizer options not accidentally enabled for release builds
|
||||||
|
- Minimum CMake version is 3.22
|
||||||
|
|
||||||
|
### 3. CI/CD Review
|
||||||
|
|
||||||
|
If `.gitea/workflows/ci.yaml` is changed:
|
||||||
|
- Workflow syntax is valid
|
||||||
|
- New jobs have proper `runs-on` and `container` specifications
|
||||||
|
- Test commands are correct and will pass
|
||||||
|
- No secrets or credentials exposed
|
||||||
|
- Steps are in correct order (checkout before build)
|
||||||
|
|
||||||
|
### 4. Configuration & Documentation Review
|
||||||
|
|
||||||
|
If agents (`.opencode/agents/`), skills (`.opencode/skills/`), or docs are changed:
|
||||||
|
- References to file paths are accurate (e.g., `test.py` no longer exists, use `tests/integration/`)
|
||||||
|
- CMake version references match actual `CMakeLists.txt` (3.22, not 4.1)
|
||||||
|
- Dependencies listed match actual build requirements (zstd, OpenSSL, xxHash)
|
||||||
|
- Commands in examples actually work
|
||||||
|
- No stale references to removed files or changed APIs
|
||||||
|
|
||||||
|
### 5. PR Quality
|
||||||
|
|
||||||
|
- Commit messages are clear and follow project conventions
|
||||||
|
- PR description explains what changed and why
|
||||||
|
- Changes are focused — not mixing unrelated concerns
|
||||||
|
- No unnecessary file changes (formatting-only diffs on unchanged code)
|
||||||
|
- Test coverage for new functionality
|
||||||
|
|
||||||
|
## Review Checklist
|
||||||
|
|
||||||
|
For each PR, evaluate:
|
||||||
|
|
||||||
|
- [ ] All changed C files reviewed for memory/thread/protocol/security
|
||||||
|
- [ ] Build system changes validated
|
||||||
|
- [ ] CI/CD changes verified (if any)
|
||||||
|
- [ ] Agent/skill/doc changes checked for accuracy
|
||||||
|
- [ ] No secrets, keys, or credentials committed
|
||||||
|
- [ ] Commit history is clean and meaningful
|
||||||
|
- [ ] New features have test coverage
|
||||||
|
- [ ] Breaking changes documented
|
||||||
|
- [ ] Backward compatibility maintained (protocol version field)
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```
|
||||||
|
=== PR REVIEW SUMMARY ===
|
||||||
|
Branch: <branch-name>
|
||||||
|
Files reviewed: <count>
|
||||||
|
Dimensions checked: code, build, CI, docs, quality
|
||||||
|
|
||||||
|
=== FINDINGS ===
|
||||||
|
|
||||||
|
[CRITICAL] src/shared/protocol.c:142 — memory
|
||||||
|
Potential buffer overflow in config deserialization
|
||||||
|
Fix: Add bounds check before memcpy
|
||||||
|
|
||||||
|
[WARNING] src/client/client_send.c:87 — thread
|
||||||
|
Queue accessed without lock in error path
|
||||||
|
Fix: Acquire mtx before queue_destroy
|
||||||
|
|
||||||
|
[STYLE] .opencode/agents/cmake-expert.md:5 — docs
|
||||||
|
References CMake 4.1 but project uses 3.22
|
||||||
|
Fix: Update version reference
|
||||||
|
|
||||||
|
=== VERDICT ===
|
||||||
|
[PASS] No critical issues found — safe to merge
|
||||||
|
— or —
|
||||||
|
[FAIL] <N> critical issues must be fixed before merge
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
- Report ALL issues — don't filter or minimize
|
||||||
|
- Be specific about line numbers and fix suggestions
|
||||||
|
- Separate critical from warnings from style
|
||||||
|
- Check that the PR actually compiles (review CMake changes carefully)
|
||||||
|
- If agents/docs are changed, verify every reference is current
|
||||||
|
- Be constructive — suggest fixes, not just problems
|
||||||
@@ -165,34 +165,28 @@ int main() {
|
|||||||
|
|
||||||
## Integration Test Patterns
|
## Integration Test Patterns
|
||||||
|
|
||||||
When writing integration tests (Python-based), follow the pattern in `test.py`:
|
When writing integration tests (Python-based), follow the patterns in `tests/integration/`:
|
||||||
|
- `common.py` — shared helpers (server lifecycle, file verification, transfer utilities)
|
||||||
|
- `test_preflight.py` — preflight checks and configuration validation
|
||||||
|
- `test_tcp.py` — TCP transport tests
|
||||||
|
- `test_ssh.py` — SSH transport tests
|
||||||
|
- `test_tls.py` — TLS transport tests
|
||||||
|
- `test_features.py` — feature-specific tests (delete, exclude, incremental, etc.)
|
||||||
|
|
||||||
|
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
|
||||||
def test_basic_transfer():
|
def test_basic_transfer(tmp_path):
|
||||||
# Setup
|
# Setup
|
||||||
source = create_test_files()
|
source = tmp_path / "src"
|
||||||
dest = tempfile.mkdtemp()
|
dest = tmp_path / "dst"
|
||||||
|
source.mkdir()
|
||||||
# Start server
|
dest.mkdir()
|
||||||
server = subprocess.Popen(["./build/server"], ...)
|
(source / "file.txt").write_text("test content")
|
||||||
time.sleep(0.5)
|
|
||||||
|
# Start server and run client (use fixtures from conftest.py)
|
||||||
# Run client
|
# Verify with helper from common.py
|
||||||
result = subprocess.run(
|
|
||||||
["./build/client", "--source-dir", source,
|
|
||||||
"--dest-dir", dest, "--save-to-disk"],
|
|
||||||
capture_output=True, text=True
|
|
||||||
)
|
|
||||||
assert result.returncode == 0
|
|
||||||
|
|
||||||
# Verify
|
|
||||||
mismatches, missing = verify_transfer(source, dest)
|
|
||||||
assert not mismatches
|
|
||||||
assert not missing
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
server.terminate()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Edge Case Tests to Write
|
### Edge Case Tests to Write
|
||||||
|
|||||||
+35
-16
@@ -73,6 +73,7 @@ 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;
|
||||||
|
|
||||||
int positional_args[2];
|
int positional_args[2];
|
||||||
int positional_count = 0;
|
int positional_count = 0;
|
||||||
@@ -80,7 +81,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 +166,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 +198,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 +223,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 +235,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 +264,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 +285,18 @@ 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);
|
exit_code = send_files_multithreaded(config);
|
||||||
return send_files(config);
|
else
|
||||||
|
exit_code = send_files(config);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
config_delete(config);
|
||||||
|
return exit_code;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user