The standalone filter_rule_parse() already rejected the rsync xattr-name
'x' modifier, but the list parser used by --filter/-f silently dropped the
flag for merge/dir-merge rules (and relied on a second parse for plain
rules). Reject it explicitly in filter_list_parse_append_depth() with the
same diagnostic, so '-x', 'merge,x' and 'dir-merge,x' all fail cleanly.
Also reject the unimplemented rsync merge modifiers 'e', 'n' and 'w'
instead of folding them into the pattern, which previously produced
misleading errors such as "could not read merge file 'n file'". Only a
token made up solely of modifier characters is treated as a modifier run,
so glued patterns ('-newfile', '-e2e') and mixed tokens ("H,!secret")
keep their historical parsing.
Adds tests/test_filter.c with focused rejection and supported-syntax
cases.
295 lines
10 KiB
CMake
295 lines
10 KiB
CMake
cmake_minimum_required(VERSION 3.22)
|
|
|
|
project(FastFileTransfer VERSION 2.28.0)
|
|
|
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
set(CMAKE_C_STANDARD 11)
|
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
|
|
|
add_compile_options(-Wall -g -O3)
|
|
|
|
# --- Sanitizer option ---
|
|
set(SANITIZER "none" CACHE STRING "Sanitizer to enable (address, thread, undefined, none)")
|
|
set_property(CACHE SANITIZER PROPERTY STRINGS address thread undefined 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(SANITIZER STREQUAL "undefined")
|
|
add_compile_options(-fsanitize=undefined -fno-omit-frame-pointer -g)
|
|
add_link_options(-fsanitize=undefined)
|
|
elseif(NOT SANITIZER STREQUAL "none")
|
|
message(FATAL_ERROR "Unknown sanitizer: ${SANITIZER}. Supported values: address, thread, undefined, none")
|
|
endif()
|
|
|
|
# --- Strict warnings option ---
|
|
option(STRICT_WARNINGS "Enable strict warnings (Wextra, Wpedantic, Werror)" OFF)
|
|
if(STRICT_WARNINGS)
|
|
add_compile_options(-Wextra -Wpedantic -Werror)
|
|
endif()
|
|
|
|
# --- Coverage option ---
|
|
option(ENABLE_COVERAGE "Enable gcov coverage" OFF)
|
|
if(ENABLE_COVERAGE)
|
|
add_compile_options(--coverage -fprofile-arcs -ftest-coverage -O0 -g)
|
|
add_link_options(--coverage)
|
|
endif()
|
|
|
|
# --- Build hardening option ---
|
|
# Production hardening is applied to the shipping server/client binaries only,
|
|
# and only when no sanitizer or coverage instrumentation is active: sanitizers
|
|
# carry their own instrumentation, and _FORTIFY_SOURCE requires an optimising
|
|
# build (never the -O0 used for coverage).
|
|
option(ENABLE_HARDENING "Enable compiler/linker hardening for production targets" ON)
|
|
set(HARDENING_ACTIVE OFF)
|
|
if(ENABLE_HARDENING AND SANITIZER STREQUAL "none" AND NOT ENABLE_COVERAGE)
|
|
set(HARDENING_ACTIVE ON)
|
|
endif()
|
|
|
|
include(FetchContent)
|
|
FetchContent_Declare(
|
|
xxhash
|
|
GIT_REPOSITORY https://github.com/Cyan4973/xxHash
|
|
# v0.8.3 is a lightweight tag pointing at this exact commit (no ^{} peel
|
|
# entry); pin the commit SHA instead of the mutable tag.
|
|
GIT_TAG e626a72bc2321cd320e953a0ccf1584cad60f363 # v0.8.3
|
|
SOURCE_SUBDIR cmake_unofficial
|
|
)
|
|
FetchContent_MakeAvailable(xxhash)
|
|
|
|
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
|
find_package(Threads REQUIRED)
|
|
|
|
find_library(ZSTD_LIBRARY zstd)
|
|
if(NOT ZSTD_LIBRARY)
|
|
message(FATAL_ERROR "zstd library not found. Ensure it is in your nix-shell!")
|
|
endif()
|
|
|
|
find_library(ZLIB_LIBRARY z)
|
|
if(NOT ZLIB_LIBRARY)
|
|
message(FATAL_ERROR "zlib library not found. Ensure zlib1g-dev / nix zlib is available!")
|
|
endif()
|
|
|
|
find_library(LZ4_LIBRARY lz4)
|
|
if(NOT LZ4_LIBRARY)
|
|
message(FATAL_ERROR "lz4 library not found. Ensure liblz4-dev / nix lz4 is available!")
|
|
endif()
|
|
|
|
find_package(OpenSSL REQUIRED)
|
|
|
|
# --- Explicit source lists ---
|
|
# The shared library is self-contained: it must never depend on the client or
|
|
# server modules. In particular, the receiver pipeline (receive_thread /
|
|
# write_thread) lives under src/server, not here, so the client executable can
|
|
# link the shared library without pulling in any server code.
|
|
set(SHARED_SRCS
|
|
src/shared/array_list.c
|
|
src/shared/batch.c
|
|
src/shared/charset.c
|
|
src/shared/checksum.c
|
|
src/shared/chmod.c
|
|
src/shared/chunk.c
|
|
src/shared/compression.c
|
|
src/shared/config.c
|
|
src/shared/credentials.c
|
|
src/shared/daemon_conf.c
|
|
src/shared/daemon_limits.c
|
|
src/shared/data.c
|
|
src/shared/delay_updates.c
|
|
src/shared/delete_plan.c
|
|
src/shared/delta.c
|
|
src/shared/file.c
|
|
src/shared/file_list.c
|
|
src/shared/file_receive.c
|
|
src/shared/file_send.c
|
|
src/shared/file_store.c
|
|
src/shared/filter.c
|
|
src/shared/format.c
|
|
src/shared/hardlink.c
|
|
src/shared/identity.c
|
|
src/shared/log.c
|
|
src/shared/metadata.c
|
|
src/shared/motd.c
|
|
src/shared/multiprocessing.c
|
|
src/shared/protocol.c
|
|
src/shared/queue.c
|
|
src/shared/stop_condition.c
|
|
src/shared/transport_ssh.c
|
|
src/shared/transport_tcp.c
|
|
src/shared/transport_tls.c
|
|
src/shared/utils.c
|
|
src/shared/xattr.c
|
|
)
|
|
|
|
# Server implementation (no main): the receiver read/write pipeline plus the
|
|
# CLI parser. The server executable adds its own main (server.c).
|
|
set(SERVER_CORE_SRCS
|
|
src/server/receiver.c
|
|
src/server/receiver_pipeline.c
|
|
src/server/server_cli.c
|
|
)
|
|
set(SERVER_MAIN_SRCS src/server/server.c)
|
|
|
|
# Client implementation (no main): everything except the CLI entry point.
|
|
set(CLIENT_CORE_SRCS
|
|
src/client/change_list.c
|
|
src/client/client_send.c
|
|
src/client/client_validation.c
|
|
src/client/scanner.c
|
|
src/client/usage.c
|
|
)
|
|
set(CLIENT_MAIN_SRCS src/client/client_cli.c)
|
|
|
|
# --- Library targets ---
|
|
add_library(fastsync_shared STATIC ${SHARED_SRCS})
|
|
target_include_directories(fastsync_shared PUBLIC src/shared)
|
|
target_link_libraries(fastsync_shared PUBLIC Threads::Threads ${ZSTD_LIBRARY} ${ZLIB_LIBRARY}
|
|
${LZ4_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash)
|
|
|
|
add_library(fastsync_client_core STATIC ${CLIENT_CORE_SRCS})
|
|
target_include_directories(fastsync_client_core PUBLIC src/client)
|
|
target_link_libraries(fastsync_client_core PUBLIC fastsync_shared)
|
|
|
|
add_library(fastsync_server_core STATIC ${SERVER_CORE_SRCS})
|
|
target_include_directories(fastsync_server_core PUBLIC src/server)
|
|
target_link_libraries(fastsync_server_core PUBLIC fastsync_shared)
|
|
|
|
# --- Main executables ---
|
|
# The client links only the shared library and its own core; it deliberately
|
|
# does NOT get src/server on its include path nor compile receiver.c.
|
|
add_executable(server ${SERVER_MAIN_SRCS})
|
|
target_link_libraries(server PRIVATE fastsync_server_core)
|
|
|
|
add_executable(client ${CLIENT_MAIN_SRCS})
|
|
target_link_libraries(client PRIVATE fastsync_client_core)
|
|
|
|
# --- Production hardening ---
|
|
# Each compile flag is probed so a compiler/architecture that lacks it still
|
|
# configures cleanly. _FORTIFY_SOURCE is guarded separately because it only
|
|
# works in an optimising build. xxHash is a static archive built by
|
|
# FetchContent, so it must be position-independent for the -pie link; the same
|
|
# applies to the first-party static libraries linked into the -pie binaries.
|
|
if(HARDENING_ACTIVE)
|
|
set_target_properties(xxhash fastsync_shared fastsync_server_core fastsync_client_core
|
|
PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
|
include(CheckCCompilerFlag)
|
|
foreach(flag -fstack-protector-strong -fstack-clash-protection -fPIE)
|
|
string(MAKE_C_IDENTIFIER "HARDEN_${flag}" _harden_var)
|
|
check_c_compiler_flag("${flag}" ${_harden_var})
|
|
endforeach()
|
|
check_c_compiler_flag("-D_FORTIFY_SOURCE=2" HARDEN_FORTIFY_SOURCE)
|
|
foreach(target fastsync_shared fastsync_server_core fastsync_client_core server client)
|
|
foreach(flag -fstack-protector-strong -fstack-clash-protection -fPIE)
|
|
string(MAKE_C_IDENTIFIER "HARDEN_${flag}" _harden_var)
|
|
if(${_harden_var})
|
|
target_compile_options(${target} PRIVATE ${flag})
|
|
endif()
|
|
endforeach()
|
|
if(HARDEN_FORTIFY_SOURCE)
|
|
target_compile_options(${target} PRIVATE -D_FORTIFY_SOURCE=2)
|
|
endif()
|
|
endforeach()
|
|
foreach(target server client)
|
|
target_link_options(${target} PRIVATE -pie -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack)
|
|
endforeach()
|
|
endif()
|
|
|
|
# --- Testing ---
|
|
enable_testing()
|
|
|
|
# --- Unit tests ---
|
|
# The monolithic test binary exercises both client and server code, so it is
|
|
# the one place that legitimately sees both include directories and links both
|
|
# core libraries. client_cli.c is compiled here directly (with the test build
|
|
# define) rather than linked from fastsync_client_core so its test-only shims
|
|
# and the absence of main() are preserved.
|
|
set(TEST_SRCS
|
|
tests/runner.c
|
|
tests/test_array_list.c
|
|
tests/test_batch.c
|
|
tests/test_change_list.c
|
|
tests/test_checksum.c
|
|
tests/test_chunk.c
|
|
tests/test_client_cli.c
|
|
tests/test_compression.c
|
|
tests/test_config.c
|
|
tests/test_credentials.c
|
|
tests/test_daemon_conf.c
|
|
tests/test_daemon_limits.c
|
|
tests/test_data.c
|
|
tests/test_delay_updates.c
|
|
tests/test_delete_plan.c
|
|
tests/test_delta.c
|
|
tests/test_file.c
|
|
tests/test_file_list.c
|
|
tests/test_file_sendfile.c
|
|
tests/test_filter.c
|
|
tests/test_format.c
|
|
tests/test_fuzz_smoke.c
|
|
tests/test_glob.c
|
|
tests/test_hardlink.c
|
|
tests/test_iconv.c
|
|
tests/test_log.c
|
|
tests/test_metadata.c
|
|
tests/test_motd.c
|
|
tests/test_multiprocessing.c
|
|
tests/test_property.c
|
|
tests/test_protocol.c
|
|
tests/test_protocol_error.c
|
|
tests/test_queue.c
|
|
tests/test_receiver_timeout.c
|
|
tests/test_robustness.c
|
|
tests/test_scanner.c
|
|
tests/test_server.c
|
|
tests/test_server_cli.c
|
|
tests/test_shared_utils.c
|
|
tests/test_stop.c
|
|
tests/test_stress.c
|
|
tests/test_transport_ssh.c
|
|
tests/test_transport_tcp.c
|
|
tests/test_transport_tls.c
|
|
tests/test_xattr.c
|
|
)
|
|
|
|
add_executable(tests ${TEST_SRCS} src/client/client_cli.c)
|
|
target_include_directories(tests PRIVATE tests)
|
|
target_compile_definitions(tests PRIVATE FASTSYNC_TEST_BUILD)
|
|
target_link_libraries(tests PRIVATE fastsync_server_core fastsync_client_core)
|
|
add_test(NAME unit_all COMMAND tests)
|
|
|
|
# --- Fuzz targets (requires clang) ---
|
|
option(ENABLE_FUZZ "Build fuzz targets (requires clang)" OFF)
|
|
if(ENABLE_FUZZ)
|
|
if(NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
|
|
message(FATAL_ERROR "ENABLE_FUZZ requires Clang (compiler is ${CMAKE_C_COMPILER_ID})")
|
|
endif()
|
|
set(FUZZ_SRCS
|
|
tests/fuzz/fuzz_chunk_deserialize.c
|
|
tests/fuzz/fuzz_compress_decompress.c
|
|
tests/fuzz/fuzz_config_receive.c
|
|
tests/fuzz/fuzz_delta_deserialize.c
|
|
tests/fuzz/fuzz_delta_signature_deserialize.c
|
|
tests/fuzz/fuzz_glob_match.c
|
|
tests/fuzz/fuzz_identity_parse.c
|
|
tests/fuzz/fuzz_manifest.c
|
|
tests/fuzz/fuzz_metadata_from_buf.c
|
|
tests/fuzz/fuzz_protocol_framing.c
|
|
tests/fuzz/fuzz_xattr_block.c
|
|
)
|
|
# Compile the sources under test directly so libFuzzer's coverage
|
|
# instrumentation sees them (static libraries would be uninstrumented).
|
|
set(FUZZ_CORE_SRCS ${SHARED_SRCS} src/server/receiver.c src/server/receiver_pipeline.c)
|
|
foreach(FUZZ_SRC ${FUZZ_SRCS})
|
|
get_filename_component(FUZZ_NAME ${FUZZ_SRC} NAME_WE)
|
|
add_executable(${FUZZ_NAME} ${FUZZ_SRC} ${FUZZ_CORE_SRCS})
|
|
target_include_directories(${FUZZ_NAME} PRIVATE tests src/shared src/server)
|
|
target_compile_options(${FUZZ_NAME} PRIVATE -fsanitize=fuzzer,address,undefined -fno-omit-frame-pointer)
|
|
target_link_options(${FUZZ_NAME} PRIVATE -fsanitize=fuzzer,address,undefined)
|
|
target_link_libraries(${FUZZ_NAME} PRIVATE Threads::Threads ${ZSTD_LIBRARY} ${ZLIB_LIBRARY}
|
|
${LZ4_LIBRARY} OpenSSL::SSL OpenSSL::Crypto xxhash)
|
|
endforeach()
|
|
endif()
|