Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c055960b8 | ||
|
|
405e5b3b00 |
No files matched your search
@@ -102,7 +102,6 @@ partial, alternate, and planned behavior.
|
|||||||
| `--max-size <n>` | Skip files larger than n bytes |
|
| `--max-size <n>` | Skip files larger than n bytes |
|
||||||
| `--min-size <n>` | Skip files smaller than n bytes |
|
| `--min-size <n>` | Skip files smaller than n bytes |
|
||||||
| `--incremental` | Skip files unchanged since last transfer (size + mtime). Auto-enables `--preserve`. Incompatible with `-s`. |
|
| `--incremental` | Skip files unchanged since last transfer (size + mtime). Auto-enables `--preserve`. Incompatible with `-s`. |
|
||||||
| `--existing` | Skip files not already present at the destination; update existing files normally. |
|
|
||||||
| `--bwlimit <KB/s>` | Bandwidth limit in kilobytes per second |
|
| `--bwlimit <KB/s>` | Bandwidth limit in kilobytes per second |
|
||||||
| `--chunk-size <n>` | Chunk size in bytes (default: 10485760) |
|
| `--chunk-size <n>` | Chunk size in bytes (default: 10485760) |
|
||||||
| `--timeout <sec>` | I/O timeout in seconds (default: 30) |
|
| `--timeout <sec>` | I/O timeout in seconds (default: 30) |
|
||||||
@@ -466,7 +465,7 @@ defaults to the current directory. |
|
|||||||
|
|
||||||
## Protocol and Security
|
## Protocol and Security
|
||||||
|
|
||||||
FastSync protocol version `2.3.0` is shared by the client and server. The
|
FastSync protocol version `2.2.0` is shared by the client and server. The
|
||||||
current protocol is sender-driven and includes configuration negotiation,
|
current protocol is sender-driven and includes configuration negotiation,
|
||||||
incremental checks, checksums, manifests, keep-alives, abort handling, and
|
incremental checks, checksums, manifests, keep-alives, abort handling, and
|
||||||
FastSync-native delta messages. Client and server versions must currently
|
FastSync-native delta messages. Client and server versions must currently
|
||||||
|
|||||||
+27
-14
@@ -6,11 +6,11 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
|
|
||||||
| Status | Count | Description |
|
| Status | Count | Description |
|
||||||
|--------|-------|-------------|
|
|--------|-------|-------------|
|
||||||
| ✅ Implemented | 35 | Feature works end-to-end |
|
| ✅ Implemented | 34 | Feature works end-to-end |
|
||||||
| 🔀 Alt Arg | 3 | Functionality exists but under different flag/semantics |
|
| 🔀 Alt Arg | 3 | Functionality exists but under different flag/semantics |
|
||||||
| ⚠️ Partial | 3 | Flag parsed/stored but behavior incomplete |
|
| ⚠️ Partial | 3 | Flag parsed/stored but behavior incomplete |
|
||||||
| ❌ Not Implemented | 97 | Flag not recognized or no behavior |
|
| ❌ Not Implemented | 110 | Flag not recognized or no behavior |
|
||||||
| **Total** | **138** | |
|
| **Total** | **150** | |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `--exclude=PATTERN` | Exclude files matching pattern | ✅ Implemented | Glob matching in scanner |
|
| `--exclude=PATTERN` | Exclude files matching pattern | ✅ Implemented | Glob matching in scanner |
|
||||||
| `--include=PATTERN` | Include files matching pattern | ✅ Implemented | Glob matching in scanner |
|
| `--include=PATTERN` | Include files matching pattern | ✅ Implemented | Glob matching in scanner |
|
||||||
| `-C`, `--cvs-exclude` | Auto-ignore CVS files | ❌ Not Implemented | Removed because it had no effect |
|
| `-C`, `--cvs-exclude` | Auto-ignore CVS files | ❌ Not Implemented | Removed because it had no effect |
|
||||||
|
| `--no-OPTION` | Turn off an implied option | ❌ Not Implemented | Rsync supports this for options such as `--no-D`; FastSync does not |
|
||||||
|
|
||||||
## 2. Modifying Output
|
## 2. Modifying Output
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `--out-format=FORMAT` | Custom output format | ❌ Not Implemented | Removed because it had no effect |
|
| `--out-format=FORMAT` | Custom output format | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `--log-file=FILE` | Log to file | ✅ Implemented | `log_file` config field |
|
| `--log-file=FILE` | Log to file | ✅ Implemented | `log_file` config field |
|
||||||
| `--log-file-format=FMT` | Log format | ❌ Not Implemented | |
|
| `--log-file-format=FMT` | Log format | ❌ Not Implemented | |
|
||||||
| `--8-bit-output` | Leave high-bit chars unescaped | ❌ Not Implemented | |
|
| `-8`, `--8-bit-output` | Leave high-bit chars unescaped | ❌ Not Implemented | |
|
||||||
| `--list-only` | List files instead of copying | ❌ Not Implemented | Removed because it had no effect |
|
| `--list-only` | List files instead of copying | ❌ Not Implemented | Removed because it had no effect |
|
||||||
|
|
||||||
## 3. File Selection
|
## 3. File Selection
|
||||||
@@ -52,7 +53,8 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
|------|-------------------|-----------------|-------|
|
|------|-------------------|-----------------|-------|
|
||||||
| `--exclude-from=FILE` | Read exclude patterns from file | ✅ Implemented | Reads patterns from file |
|
| `--exclude-from=FILE` | Read exclude patterns from file | ✅ Implemented | Reads patterns from file |
|
||||||
| `--include-from=FILE` | Read include patterns from file | ✅ Implemented | Reads patterns from file |
|
| `--include-from=FILE` | Read include patterns from file | ✅ Implemented | Reads patterns from file |
|
||||||
| `--filter=RULE` | Add file-filtering rule | ❌ Not Implemented | Removed because it had no effect |
|
| `-f`, `--filter=RULE` | Add a file-filtering rule | ❌ Not Implemented | `-f` is FastSync's sendfile flag |
|
||||||
|
| `-F` | Add the default `.rsync-filter` rules | ❌ Not Implemented | |
|
||||||
| `--files-from=FILE` | Read source file list from file | ❌ Not Implemented | Removed because it had no effect |
|
| `--files-from=FILE` | Read source file list from file | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `-0`, `--from0` | Delimit *-from files with NULs | ❌ Not Implemented | |
|
| `-0`, `--from0` | Delimit *-from files with NULs | ❌ Not Implemented | |
|
||||||
| `--max-size=SIZE` | Skip files larger than SIZE | ✅ Implemented | `max_size` in scanner |
|
| `--max-size=SIZE` | Skip files larger than SIZE | ✅ Implemented | `max_size` in scanner |
|
||||||
@@ -60,9 +62,10 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `-I`, `--ignore-times` | Don't skip files matching size+time | ❌ Not Implemented | |
|
| `-I`, `--ignore-times` | Don't skip files matching size+time | ❌ Not Implemented | |
|
||||||
| `--size-only` | Skip based on size only | ❌ Not Implemented | |
|
| `--size-only` | Skip based on size only | ❌ Not Implemented | |
|
||||||
| `-@`, `--modify-window=NUM` | Mod-time comparison accuracy | ❌ Not Implemented | |
|
| `-@`, `--modify-window=NUM` | Mod-time comparison accuracy | ❌ Not Implemented | |
|
||||||
| `--existing` | Skip creating new files on receiver | ✅ Implemented | Existing destination files continue through normal update handling |
|
| `--existing` | Skip creating new files on receiver | ❌ Not Implemented | |
|
||||||
| `--ignore-existing` | Skip updating existing files | ❌ Not Implemented | |
|
| `--ignore-existing` | Skip updating existing files | ❌ Not Implemented | |
|
||||||
| `--remove-source-files` | Sender removes synced files | ❌ Not Implemented | |
|
| `--remove-source-files` | Sender removes synced files | ❌ Not Implemented | |
|
||||||
|
| `-x`, `--one-file-system` | Do not cross filesystem boundaries | ❌ Not Implemented | |
|
||||||
|
|
||||||
## 4. Directory Options
|
## 4. Directory Options
|
||||||
|
|
||||||
@@ -72,6 +75,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `-R`, `--relative` | Use relative path names | ❌ Not Implemented | Removed because it had no effect |
|
| `-R`, `--relative` | Use relative path names | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `--no-implied-dirs` | Don't send implied dirs with -R | ❌ Not Implemented | |
|
| `--no-implied-dirs` | Don't send implied dirs with -R | ❌ Not Implemented | |
|
||||||
| `-d`, `--dirs` | Transfer dirs without recursing | ❌ Not Implemented | |
|
| `-d`, `--dirs` | Transfer dirs without recursing | ❌ Not Implemented | |
|
||||||
|
| `--old-dirs`, `--old-d` | Transfer directories without recursing | ❌ Not Implemented | Compatibility aliases for `--dirs` |
|
||||||
| `--mkpath` | Create missing path components | ❌ Not Implemented | |
|
| `--mkpath` | Create missing path components | ❌ Not Implemented | |
|
||||||
|
|
||||||
## 5. Transfer Modifications
|
## 5. Transfer Modifications
|
||||||
@@ -83,7 +87,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `--append` | Append data to shorter files | ❌ Not Implemented | Removed because it had no effect |
|
| `--append` | Append data to shorter files | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `--append-verify` | Append with old-data checksum | ❌ Not Implemented | Removed because it had no effect |
|
| `--append-verify` | Append with old-data checksum | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `-W`, `--whole-file` | Copy whole file (no delta) | ❌ Not Implemented | |
|
| `-W`, `--whole-file` | Copy whole file (no delta) | ❌ Not Implemented | |
|
||||||
| `--block-size=SIZE` | Force checksum block-size | ⚠️ Partial | Parsed as `--delta-block`; controls delta transfer block size |
|
| `-B`, `--block-size=SIZE` | Force checksum block-size | ⚠️ Partial | Parsed as `--delta-block`; controls delta transfer block size |
|
||||||
|
|
||||||
## 6. Destination Handling
|
## 6. Destination Handling
|
||||||
|
|
||||||
@@ -94,12 +98,14 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `--backup-dir=DIR` | Backup directory hierarchy | ✅ Implemented | `backup_dir` config field |
|
| `--backup-dir=DIR` | Backup directory hierarchy | ✅ Implemented | `backup_dir` config field |
|
||||||
| `--suffix=SUFFIX` | Backup suffix (default ~) | ✅ Implemented | `suffix` config field |
|
| `--suffix=SUFFIX` | Backup suffix (default ~) | ✅ Implemented | `suffix` config field |
|
||||||
| `--delay-updates` | Put updated files in place at end | ❌ Not Implemented | |
|
| `--delay-updates` | Put updated files in place at end | ❌ Not Implemented | |
|
||||||
|
| `-T`, `--temp-dir=DIR` | Create temporary files in DIR | ❌ Not Implemented | `-T` is FastSync's timeout alias |
|
||||||
|
|
||||||
## 7. Deletion
|
## 7. Deletion
|
||||||
|
|
||||||
| Flag | Rsync Description | FastSync Status | Notes |
|
| Flag | Rsync Description | FastSync Status | Notes |
|
||||||
|------|-------------------|-----------------|-------|
|
|------|-------------------|-----------------|-------|
|
||||||
| `--delete` | Delete extraneous files from dest | ✅ Implemented | `use_delete` config field |
|
| `--delete` | Delete extraneous files from dest | ✅ Implemented | `use_delete` config field |
|
||||||
|
| `--del` | Delete during transfer | ❌ Not Implemented | Alias for `--delete-during` |
|
||||||
| `--delete-before` | Delete before transfer | ❌ Not Implemented | Removed because it had no effect |
|
| `--delete-before` | Delete before transfer | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `--delete-during` | Delete during transfer | ❌ Not Implemented | |
|
| `--delete-during` | Delete during transfer | ❌ Not Implemented | |
|
||||||
| `--delete-delay` | Find deletions during, delete after | ❌ Not Implemented | |
|
| `--delete-delay` | Find deletions during, delete after | ❌ Not Implemented | |
|
||||||
@@ -108,7 +114,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `--max-delete=NUM` | Max files to delete | ❌ Not Implemented | Removed because it had no effect |
|
| `--max-delete=NUM` | Max files to delete | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `--ignore-errors` | Delete even with I/O errors | ❌ Not Implemented | |
|
| `--ignore-errors` | Delete even with I/O errors | ❌ Not Implemented | |
|
||||||
| `--force` | Force deletion of non-empty dirs | ❌ Not Implemented | |
|
| `--force` | Force deletion of non-empty dirs | ❌ Not Implemented | |
|
||||||
| `--prune-empty-dirs` | Prune empty dir chains | ❌ Not Implemented | Removed because it had no effect |
|
| `-m`, `--prune-empty-dirs` | Prune empty dir chains | ❌ Not Implemented | `-m` enables FastSync multithreading instead |
|
||||||
|
|
||||||
## 8. Metadata Preservation
|
## 8. Metadata Preservation
|
||||||
|
|
||||||
@@ -135,6 +141,12 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `-J`, `--omit-link-times` | Omit symlinks from --times | ❌ Not Implemented | |
|
| `-J`, `--omit-link-times` | Omit symlinks from --times | ❌ Not Implemented | |
|
||||||
| `--super` | Receiver attempts super-user activities | ❌ Not Implemented | |
|
| `--super` | Receiver attempts super-user activities | ❌ Not Implemented | |
|
||||||
| `--fake-super` | Store/recover privileged attrs via xattrs | ❌ Not Implemented | |
|
| `--fake-super` | Store/recover privileged attrs via xattrs | ❌ Not Implemented | |
|
||||||
|
| `--open-noatime` | Avoid changing access time when opening files | ❌ Not Implemented | |
|
||||||
|
| `--numeric-ids` | Do not map uid/gid by name | ❌ Not Implemented | |
|
||||||
|
| `--usermap=STRING` | Map usernames | ❌ Not Implemented | |
|
||||||
|
| `--groupmap=STRING` | Map group names | ❌ Not Implemented | |
|
||||||
|
| `--chown=USER:GROUP` | Map owner and group | ❌ Not Implemented | |
|
||||||
|
| `--copy-as=USER[:GROUP]` | Perform the copy as another user/group | ❌ Not Implemented | |
|
||||||
|
|
||||||
## 9. Symlink Handling
|
## 9. Symlink Handling
|
||||||
|
|
||||||
@@ -160,19 +172,19 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| Flag | Rsync Description | FastSync Status | Notes |
|
| Flag | Rsync Description | FastSync Status | Notes |
|
||||||
|------|-------------------|-----------------|-------|
|
|------|-------------------|-----------------|-------|
|
||||||
| `--checksum` | Skip based on checksum | ✅ Implemented | With `--incremental`, compares xxHash64 content checksums; `-c` remains compression |
|
| `--checksum` | Skip based on checksum | ✅ Implemented | With `--incremental`, compares xxHash64 content checksums; `-c` remains compression |
|
||||||
| `--checksum-choice=STR` | Choose checksum algorithm | ❌ Not Implemented | xxHash used internally |
|
| `--checksum-choice=STR` (`--cc`) | Choose checksum algorithm | ❌ Not Implemented | xxHash used internally |
|
||||||
| `--compare-dest=DIR` | Compare dest files relative to DIR | ❌ Not Implemented | Removed because it had no effect |
|
| `--compare-dest=DIR` | Compare dest files relative to DIR | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `--copy-dest=DIR` | Include copies of unchanged files | ❌ Not Implemented | Removed because it had no effect |
|
| `--copy-dest=DIR` | Include copies of unchanged files | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `--link-dest=DIR` | Hardlink to files when unchanged | ❌ Not Implemented | Removed because it had no effect |
|
| `--link-dest=DIR` | Hardlink to files when unchanged | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `--fuzzy`, `--no-fuzzy` | Find similar file for basis | ❌ Not Implemented | |
|
| `-y`, `--fuzzy`, `--no-fuzzy` | Find similar file for basis | ❌ Not Implemented | `--no-fuzzy` has no short alias |
|
||||||
|
|
||||||
## 12. Compression
|
## 12. Compression
|
||||||
|
|
||||||
| Flag | Rsync Description | FastSync Status | Notes |
|
| Flag | Rsync Description | FastSync Status | Notes |
|
||||||
|------|-------------------|-----------------|-------|
|
|------|-------------------|-----------------|-------|
|
||||||
| `-z`, `--compress` | Compress file data | 🔀 Alt Arg | Always uses zstd (rsync supports multiple algorithms) |
|
| `-z`, `--compress` | Compress file data | 🔀 Alt Arg | Always uses zstd (rsync supports multiple algorithms) |
|
||||||
| `--compress-choice=STR` | Choose compression algorithm | ❌ Not Implemented | Removed because it had no effect; FastSync always uses zstd |
|
| `--compress-choice=STR` (`--zc`) | Choose compression algorithm | ❌ Not Implemented | Removed because it had no effect; FastSync always uses zstd |
|
||||||
| `--compress-level=NUM` | Set compression level | ✅ Implemented | 1-22, default 5 |
|
| `--compress-level=NUM` (`--zl`) | Set compression level | ✅ Implemented | 1-22, default 5 |
|
||||||
| `--compress-threads=NUM` | Set compression threads | ❌ Not Implemented | |
|
| `--compress-threads=NUM` | Set compression threads | ❌ Not Implemented | |
|
||||||
| `--skip-compress=LIST` | Skip compress for suffixes | ❌ Not Implemented | Internal skip for hardcoded types; not user-configurable |
|
| `--skip-compress=LIST` | Skip compress for suffixes | ❌ Not Implemented | Internal skip for hardcoded types; not user-configurable |
|
||||||
|
|
||||||
@@ -189,6 +201,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `--address=ADDRESS` | Bind address for outgoing socket | ❌ Not Implemented | Removed because it had no effect |
|
| `--address=ADDRESS` | Bind address for outgoing socket | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `-4`, `--ipv4` | Prefer IPv4 | ❌ Not Implemented | Removed because it had no effect |
|
| `-4`, `--ipv4` | Prefer IPv4 | ❌ Not Implemented | Removed because it had no effect |
|
||||||
| `-6`, `--ipv6` | Prefer IPv6 | ❌ Not Implemented | Removed because it had no effect |
|
| `-6`, `--ipv6` | Prefer IPv6 | ❌ Not Implemented | Removed because it had no effect |
|
||||||
|
| `--remote-option=OPT`, `-M` | Send an option only to the remote side | ❌ Not Implemented | `-M` is FastSync's metadata-preservation flag |
|
||||||
|
|
||||||
## 14. Daemon Mode
|
## 14. Daemon Mode
|
||||||
|
|
||||||
@@ -214,6 +227,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `--old-args` | Disable modern arg protection | ❌ Not Implemented | |
|
| `--old-args` | Disable modern arg protection | ❌ Not Implemented | |
|
||||||
| `--ignore-missing-args` | Ignore missing source args | ❌ Not Implemented | |
|
| `--ignore-missing-args` | Ignore missing source args | ❌ Not Implemented | |
|
||||||
| `--delete-missing-args` | Delete missing source args | ❌ Not Implemented | |
|
| `--delete-missing-args` | Delete missing source args | ❌ Not Implemented | |
|
||||||
|
| `--max-alloc=SIZE` | Limit a single memory allocation | ❌ Not Implemented | |
|
||||||
|
|
||||||
## 16. Batch Operations
|
## 16. Batch Operations
|
||||||
|
|
||||||
@@ -234,7 +248,6 @@ This document maps rsync's full feature set to FastSync's current implementation
|
|||||||
| `--iconv=CONVERT_SPEC` | Charset conversion | ❌ Not Implemented | |
|
| `--iconv=CONVERT_SPEC` | Charset conversion | ❌ Not Implemented | |
|
||||||
| `--checksum-seed=NUM` | Set checksum seed | ❌ Not Implemented | |
|
| `--checksum-seed=NUM` | Set checksum seed | ❌ Not Implemented | |
|
||||||
| `-s`, `--secluded-args` | Use protocol to send args | ❌ Not Implemented | |
|
| `-s`, `--secluded-args` | Use protocol to send args | ❌ Not Implemented | |
|
||||||
| `--no-OPTION` | Turn off implied option | ❌ Not Implemented | |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -247,7 +260,7 @@ Ranked by user demand, implementation complexity, and interoperability impact:
|
|||||||
| 1 | `--whole-file` / `-W` | Low | High — users expect opt-out of delta |
|
| 1 | `--whole-file` / `-W` | Low | High — users expect opt-out of delta |
|
||||||
| 2 | `--ignore-times` / `-I` | Low | Medium — useful for forcing re-transfer |
|
| 2 | `--ignore-times` / `-I` | Low | Medium — useful for forcing re-transfer |
|
||||||
| 3 | `--size-only` | Low | Medium — common migration scenario |
|
| 3 | `--size-only` | Low | Medium — common migration scenario |
|
||||||
| 4 | `--ignore-existing` | Low | Medium — common sync patterns |
|
| 4 | `--existing` / `--ignore-existing` | Low | Medium — common sync patterns |
|
||||||
| 5 | `--remove-source-files` | Low | High — common for moves/backup |
|
| 5 | `--remove-source-files` | Low | High — common for moves/backup |
|
||||||
| 6 | `--delete-during` | Medium | High — performance improvement |
|
| 6 | `--delete-during` | Medium | High — performance improvement |
|
||||||
| 7 | `--delay-updates` | Medium | High — atomic updates |
|
| 7 | `--delay-updates` | Medium | High — atomic updates |
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ static const OptionEntry OPTION_TABLE[] = {
|
|||||||
{"--sparse", "-S", OPT_FLAG, offsetof(Config, preserve_sparse)},
|
{"--sparse", "-S", OPT_FLAG, offsetof(Config, preserve_sparse)},
|
||||||
{"--inplace", NULL, OPT_FLAG, offsetof(Config, inplace)},
|
{"--inplace", NULL, OPT_FLAG, offsetof(Config, inplace)},
|
||||||
{"--checksum", NULL, OPT_FLAG, offsetof(Config, checksum)},
|
{"--checksum", NULL, OPT_FLAG, offsetof(Config, checksum)},
|
||||||
{"--existing", NULL, OPT_FLAG, offsetof(Config, existing)},
|
|
||||||
|
|
||||||
{"--source-dir", NULL, OPT_STRING, offsetof(Config, send_directory)},
|
{"--source-dir", NULL, OPT_STRING, offsetof(Config, send_directory)},
|
||||||
{"--dest-dir", NULL, OPT_STRING, offsetof(Config, receive_root_directory)},
|
{"--dest-dir", NULL, OPT_STRING, offsetof(Config, receive_root_directory)},
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ void print_usage(void) {
|
|||||||
printf(" --max-size <n> Skip files larger than n bytes\n");
|
printf(" --max-size <n> Skip files larger than n bytes\n");
|
||||||
printf(" --min-size <n> Skip files smaller than n bytes\n");
|
printf(" --min-size <n> Skip files smaller than n bytes\n");
|
||||||
printf(" --incremental Skip files unchanged since last transfer\n");
|
printf(" --incremental Skip files unchanged since last transfer\n");
|
||||||
printf(" --existing Skip files not already present at destination\n");
|
|
||||||
printf(" --delta Delta transfer for changed files (requires --incremental)\n");
|
printf(" --delta Delta transfer for changed files (requires --incremental)\n");
|
||||||
printf(" --delta-block <n> Delta block size in bytes (default: %d)\n",
|
printf(" --delta-block <n> Delta block size in bytes (default: %d)\n",
|
||||||
DELTA_BLOCK_SIZE_DEFAULT);
|
DELTA_BLOCK_SIZE_DEFAULT);
|
||||||
|
|||||||
+11
-14
@@ -68,7 +68,6 @@ static void config_set_defaults(Config* config) {
|
|||||||
config->debug_level = 0;
|
config->debug_level = 0;
|
||||||
config->list_only = false;
|
config->list_only = false;
|
||||||
config->human_readable = false;
|
config->human_readable = false;
|
||||||
config->existing = false;
|
|
||||||
config->update = false;
|
config->update = false;
|
||||||
config->inplace = false;
|
config->inplace = false;
|
||||||
config->append = false;
|
config->append = false;
|
||||||
@@ -124,13 +123,12 @@ static bool validate_received_config(const Config* config) {
|
|||||||
valid_wire_bool(config->copy_unsafe_links) &&
|
valid_wire_bool(config->copy_unsafe_links) &&
|
||||||
valid_wire_bool(config->preserve_hard_links) && valid_wire_bool(config->preserve_acls) &&
|
valid_wire_bool(config->preserve_hard_links) && valid_wire_bool(config->preserve_acls) &&
|
||||||
valid_wire_bool(config->preserve_xattrs) && valid_wire_bool(config->preserve_devices) &&
|
valid_wire_bool(config->preserve_xattrs) && valid_wire_bool(config->preserve_devices) &&
|
||||||
valid_wire_bool(config->preserve_sparse) && valid_wire_bool(config->existing) &&
|
valid_wire_bool(config->preserve_sparse) && valid_wire_bool(config->update) &&
|
||||||
valid_wire_bool(config->update) && valid_wire_bool(config->inplace) &&
|
valid_wire_bool(config->inplace) && valid_wire_bool(config->append) &&
|
||||||
valid_wire_bool(config->append) && valid_wire_bool(config->append_verify) &&
|
valid_wire_bool(config->append_verify) && valid_wire_bool(config->delete_excluded) &&
|
||||||
valid_wire_bool(config->delete_excluded) && valid_wire_bool(config->delete_after) &&
|
valid_wire_bool(config->delete_after) && valid_wire_bool(config->relative) &&
|
||||||
valid_wire_bool(config->relative) && valid_wire_bool(config->prune_empty_dirs) &&
|
valid_wire_bool(config->prune_empty_dirs) && valid_wire_bool(config->partial) &&
|
||||||
valid_wire_bool(config->partial) && valid_wire_bool(config->delete_before) &&
|
valid_wire_bool(config->delete_before) && valid_wire_bool(config->checksum) &&
|
||||||
valid_wire_bool(config->checksum) &&
|
|
||||||
(!config->use_compression ||
|
(!config->use_compression ||
|
||||||
(config->compression_level >= 1 && config->compression_level <= 22)) &&
|
(config->compression_level >= 1 && config->compression_level <= 22)) &&
|
||||||
config->chunk_size > 0 && config->chunk_size <= MAX_CHUNK_SIZE &&
|
config->chunk_size > 0 && config->chunk_size <= MAX_CHUNK_SIZE &&
|
||||||
@@ -245,11 +243,10 @@ static bool send_file_options(int fd, const Config* c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool send_selection_options(int fd, const Config* c) {
|
static bool send_selection_options(int fd, const Config* c) {
|
||||||
return send_int(fd, c->existing) && send_int(fd, c->update) && send_int(fd, c->inplace) &&
|
return send_int(fd, c->update) && send_int(fd, c->inplace) && send_int(fd, c->append) &&
|
||||||
send_int(fd, c->append) && send_int(fd, c->append_verify) &&
|
send_int(fd, c->append_verify) && send_int(fd, c->delete_excluded) &&
|
||||||
send_int(fd, c->delete_excluded) && send_int(fd, c->delete_after) &&
|
send_int(fd, c->delete_after) && send_n_data(fd, &c->max_delete, sizeof(c->max_delete)) &&
|
||||||
send_n_data(fd, &c->max_delete, sizeof(c->max_delete)) && send_int(fd, c->relative) &&
|
send_int(fd, c->relative) && send_int(fd, c->prune_empty_dirs);
|
||||||
send_int(fd, c->prune_empty_dirs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool send_resume_options(int fd, const Config* c) {
|
static bool send_resume_options(int fd, const Config* c) {
|
||||||
@@ -307,7 +304,7 @@ static bool receive_file_options(int fd, Config* c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool receive_selection_options(int fd, Config* c) {
|
static bool receive_selection_options(int fd, Config* c) {
|
||||||
bool* flags[] = {&c->existing, &c->update, &c->inplace, &c->append,
|
bool* flags[] = {&c->update, &c->inplace, &c->append,
|
||||||
&c->append_verify, &c->delete_excluded, &c->delete_after};
|
&c->append_verify, &c->delete_excluded, &c->delete_after};
|
||||||
for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
|
for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
|
||||||
if (!receive_wire_bool(fd, flags[i]))
|
if (!receive_wire_bool(fd, flags[i]))
|
||||||
|
|||||||
+1
-2
@@ -76,7 +76,6 @@ typedef struct Config {
|
|||||||
bool human_readable;
|
bool human_readable;
|
||||||
|
|
||||||
// Issue #127: Transfer modes
|
// Issue #127: Transfer modes
|
||||||
bool existing;
|
|
||||||
bool update;
|
bool update;
|
||||||
bool inplace;
|
bool inplace;
|
||||||
bool append;
|
bool append;
|
||||||
@@ -129,7 +128,7 @@ typedef struct Config {
|
|||||||
char* compress_choice;
|
char* compress_choice;
|
||||||
} Config;
|
} Config;
|
||||||
|
|
||||||
#define PROTOCOL_VERSION "2.3.0"
|
#define PROTOCOL_VERSION "2.2.0"
|
||||||
#define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024)
|
#define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024)
|
||||||
|
|
||||||
Config* config_create(void);
|
Config* config_create(void);
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
|
|||||||
const char* backup_dir = (config && config->backup_dir) ? config->backup_dir : NULL;
|
const char* backup_dir = (config && config->backup_dir) ? config->backup_dir : NULL;
|
||||||
const char* partial_dir = (config && config->partial_dir) ? config->partial_dir : NULL;
|
const char* partial_dir = (config && config->partial_dir) ? config->partial_dir : NULL;
|
||||||
char *confined_backup = NULL, *confined_partial = NULL, *disk_path = NULL;
|
char *confined_backup = NULL, *confined_partial = NULL, *disk_path = NULL;
|
||||||
char* destination_path = NULL;
|
|
||||||
char *backup_path = NULL, *parent_copy = NULL;
|
char *backup_path = NULL, *parent_copy = NULL;
|
||||||
|
|
||||||
if (!file || !file->path || !file->data || (file->data->size != 0 && !file->data->data) ||
|
if (!file || !file->path || !file->data || (file->data->size != 0 && !file->data->data) ||
|
||||||
@@ -55,27 +54,13 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
|
|||||||
|
|
||||||
const char* actual_root =
|
const char* actual_root =
|
||||||
(partial_dir && config && config->partial) ? confined_partial : root_directory;
|
(partial_dir && config && config->partial) ? confined_partial : root_directory;
|
||||||
destination_path = path_cat(root_directory, file->path);
|
|
||||||
disk_path = path_cat(actual_root, file->path);
|
disk_path = path_cat(actual_root, file->path);
|
||||||
if (destination_path == NULL || disk_path == NULL) {
|
if (disk_path == NULL) {
|
||||||
free(confined_backup);
|
free(confined_backup);
|
||||||
free(confined_partial);
|
free(confined_partial);
|
||||||
free(destination_path);
|
|
||||||
free(disk_path);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --existing checks the final destination, not a temporary partial path. */
|
|
||||||
if (config && config->existing && !file_path_exists_secure(destination_path)) {
|
|
||||||
free(confined_backup);
|
|
||||||
free(confined_partial);
|
|
||||||
free(destination_path);
|
|
||||||
free(disk_path);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
free(destination_path);
|
|
||||||
destination_path = NULL;
|
|
||||||
|
|
||||||
/* --update is receiver-side policy: never replace a newer destination. */
|
/* --update is receiver-side policy: never replace a newer destination. */
|
||||||
if (config && config->update) {
|
if (config && config->update) {
|
||||||
struct stat destination_stat;
|
struct stat destination_stat;
|
||||||
@@ -83,7 +68,6 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
|
|||||||
destination_stat.st_mtime > file->metadata->mtime_sec) {
|
destination_stat.st_mtime > file->metadata->mtime_sec) {
|
||||||
free(confined_backup);
|
free(confined_backup);
|
||||||
free(confined_partial);
|
free(confined_partial);
|
||||||
free(destination_path);
|
|
||||||
free(disk_path);
|
free(disk_path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -125,7 +109,6 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
|
|||||||
free(backup_path);
|
free(backup_path);
|
||||||
free(confined_backup);
|
free(confined_backup);
|
||||||
free(confined_partial);
|
free(confined_partial);
|
||||||
free(destination_path);
|
|
||||||
free(disk_path);
|
free(disk_path);
|
||||||
return ok;
|
return ok;
|
||||||
|
|
||||||
@@ -134,7 +117,6 @@ fail:
|
|||||||
free(backup_path);
|
free(backup_path);
|
||||||
free(confined_backup);
|
free(confined_backup);
|
||||||
free(confined_partial);
|
free(confined_partial);
|
||||||
free(destination_path);
|
|
||||||
free(disk_path);
|
free(disk_path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,34 +214,6 @@ class TestIncremental:
|
|||||||
assert f.read() == b"hello world\n"
|
assert f.read() == b"hello world\n"
|
||||||
|
|
||||||
|
|
||||||
class TestExisting:
|
|
||||||
def test_existing_updates_existing_and_skips_new(self, shared_server):
|
|
||||||
clean_dir(DEST_DIR)
|
|
||||||
result, _ = run_client(SOURCE_DIR, DEST_DIR, flags=["-M"], port=shared_server.port)
|
|
||||||
assert result.returncode == 0, f"Initial sync failed: {(result.stderr or result.stdout)[:200]}"
|
|
||||||
|
|
||||||
received = get_dest_received_dir(DEST_DIR, SOURCE_DIR)
|
|
||||||
source_file = os.path.join(SOURCE_DIR, "small.txt")
|
|
||||||
new_source_file = os.path.join(SOURCE_DIR, "new-existing-test.txt")
|
|
||||||
with open(source_file, "wb") as f:
|
|
||||||
f.write(b"updated existing content\n")
|
|
||||||
with open(new_source_file, "wb") as f:
|
|
||||||
f.write(b"this file must not be created\n")
|
|
||||||
|
|
||||||
try:
|
|
||||||
result, _ = run_client(SOURCE_DIR, DEST_DIR,
|
|
||||||
flags=["-M", "--existing"], port=shared_server.port)
|
|
||||||
assert result.returncode == 0, f"--existing sync failed: {(result.stderr or result.stdout)[:200]}"
|
|
||||||
|
|
||||||
with open(os.path.join(received, "small.txt"), "rb") as f:
|
|
||||||
assert f.read() == b"updated existing content\n"
|
|
||||||
assert not os.path.exists(os.path.join(received, "new-existing-test.txt"))
|
|
||||||
finally:
|
|
||||||
os.unlink(new_source_file)
|
|
||||||
with open(source_file, "wb") as f:
|
|
||||||
f.write(b"hello world\n")
|
|
||||||
|
|
||||||
|
|
||||||
class TestDelete:
|
class TestDelete:
|
||||||
def test_delete_removes_extra_files(self, shared_server):
|
def test_delete_removes_extra_files(self, shared_server):
|
||||||
clean_dir(DEST_DIR)
|
clean_dir(DEST_DIR)
|
||||||
|
|||||||
@@ -339,17 +339,6 @@ static void test_parse_args_archive() {
|
|||||||
config_delete(cfg);
|
config_delete(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_parse_args_existing() {
|
|
||||||
Config* cfg = config_create();
|
|
||||||
char* argv[] = {"fastsync", "--existing", "/src", "/dst"};
|
|
||||||
int positional_args[2];
|
|
||||||
int positional_count = 0;
|
|
||||||
|
|
||||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), 0);
|
|
||||||
EXPECT_TRUE(cfg->existing);
|
|
||||||
config_delete(cfg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_client_cli() {
|
void test_client_cli() {
|
||||||
test_validate_config_required_paths();
|
test_validate_config_required_paths();
|
||||||
test_validate_config_incompatible_options();
|
test_validate_config_incompatible_options();
|
||||||
@@ -371,5 +360,4 @@ void test_client_cli() {
|
|||||||
test_parse_args_unknown_option();
|
test_parse_args_unknown_option();
|
||||||
test_parse_args_rejects_unimplemented_options();
|
test_parse_args_rejects_unimplemented_options();
|
||||||
test_parse_args_archive();
|
test_parse_args_archive();
|
||||||
test_parse_args_existing();
|
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,6 @@ static void test_config_send_receive() {
|
|||||||
send_cfg->use_metadata = true;
|
send_cfg->use_metadata = true;
|
||||||
send_cfg->compression_level = 5;
|
send_cfg->compression_level = 5;
|
||||||
send_cfg->chunk_size = 1024;
|
send_cfg->chunk_size = 1024;
|
||||||
send_cfg->existing = true;
|
|
||||||
|
|
||||||
/* Use socketpair for bidirectional communication */
|
/* Use socketpair for bidirectional communication */
|
||||||
int p[2];
|
int p[2];
|
||||||
@@ -161,8 +160,6 @@ static void test_config_send_receive() {
|
|||||||
ok = false;
|
ok = false;
|
||||||
if (recv_cfg->chunk_size != 1024)
|
if (recv_cfg->chunk_size != 1024)
|
||||||
ok = false;
|
ok = false;
|
||||||
if (!recv_cfg->existing)
|
|
||||||
ok = false;
|
|
||||||
}
|
}
|
||||||
config_delete(recv_cfg);
|
config_delete(recv_cfg);
|
||||||
close(p[0]);
|
close(p[0]);
|
||||||
|
|||||||
@@ -88,50 +88,6 @@ static void test_file_save_to_disk() {
|
|||||||
rmdir("test_save_tmp");
|
rmdir("test_save_tmp");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_file_save_to_disk_existing() {
|
|
||||||
const char* root = "test_existing_tmp";
|
|
||||||
const char* existing_path = "test_existing_tmp/existing.txt";
|
|
||||||
const char* missing_path = "test_existing_tmp/missing.txt";
|
|
||||||
EXPECT_TRUE(file_write_to_disk(existing_path, "old", 3, false, false));
|
|
||||||
|
|
||||||
Config* cfg = config_create();
|
|
||||||
EXPECT_NOT_NULL(cfg);
|
|
||||||
cfg->existing = true;
|
|
||||||
|
|
||||||
File* existing = file_create("existing.txt");
|
|
||||||
EXPECT_NOT_NULL(existing);
|
|
||||||
existing->data->data = malloc(3);
|
|
||||||
EXPECT_NOT_NULL(existing->data->data);
|
|
||||||
memcpy(existing->data->data, "new", 3);
|
|
||||||
existing->data->size = 3;
|
|
||||||
EXPECT_TRUE(file_save_to_disk(root, existing, cfg));
|
|
||||||
file_destroy(existing);
|
|
||||||
|
|
||||||
File* missing = file_create("missing.txt");
|
|
||||||
EXPECT_NOT_NULL(missing);
|
|
||||||
missing->data->data = malloc(7);
|
|
||||||
EXPECT_NOT_NULL(missing->data->data);
|
|
||||||
memcpy(missing->data->data, "skipped", 7);
|
|
||||||
missing->data->size = 7;
|
|
||||||
EXPECT_TRUE(file_save_to_disk(root, missing, cfg));
|
|
||||||
file_destroy(missing);
|
|
||||||
|
|
||||||
FILE* fp = fopen(existing_path, "rb");
|
|
||||||
char content[4] = {0};
|
|
||||||
EXPECT_NOT_NULL(fp);
|
|
||||||
// cppcheck-suppress knownConditionTrueFalse
|
|
||||||
if (fp) {
|
|
||||||
EXPECT_EQ_INT((int)fread(content, 1, 3, fp), 3);
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
EXPECT_EQ_STR(content, "new");
|
|
||||||
EXPECT_EQ_INT(access(missing_path, F_OK), -1);
|
|
||||||
|
|
||||||
config_delete(cfg);
|
|
||||||
unlink(existing_path);
|
|
||||||
rmdir(root);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_file_write_to_disk_basic() {
|
static void test_file_write_to_disk_basic() {
|
||||||
const char* content = "Basic file_write_to_disk test";
|
const char* content = "Basic file_write_to_disk test";
|
||||||
EXPECT_TRUE(file_write_to_disk("test_file_write_to_disk_basic.txt", content, strlen(content),
|
EXPECT_TRUE(file_write_to_disk("test_file_write_to_disk_basic.txt", content, strlen(content),
|
||||||
@@ -502,7 +458,6 @@ void test_file() {
|
|||||||
test_file_load_data();
|
test_file_load_data();
|
||||||
test_file_load_data_missing_file();
|
test_file_load_data_missing_file();
|
||||||
test_file_save_to_disk();
|
test_file_save_to_disk();
|
||||||
test_file_save_to_disk_existing();
|
|
||||||
test_file_write_to_disk_basic();
|
test_file_write_to_disk_basic();
|
||||||
test_file_write_to_disk_creates_dirs();
|
test_file_write_to_disk_creates_dirs();
|
||||||
test_file_write_to_disk_does_not_follow_symlink();
|
test_file_write_to_disk_does_not_follow_symlink();
|
||||||
|
|||||||
Reference in new issue
Block a user