A modern, GNU-compatible file copy utility rewritten in Rust.
Zero-copy kernel I/O • Parallel directory traversal • Sparse file detection • Reflink support
cargo build --release
|
Uses |
Raw |
|
|
Instant copy-on-write cloning on Btrfs, XFS via |
|
Mode, ownership, timestamps (nanosecond), xattr, ACL, and hard links with correct ordering to prevent permission races. |
Same-file detection via inode, symlink loop protection, TOCTOU-safe operations, setuid handling, path traversal prevention. |
Averaged over 3 runs × 5 iterations — Linux 6.18 — release profile (opt-level=3, LTO)
| Benchmark | GNU cp | Ours | Speedup | |
|---|---|---|---|---|
Many small files 1000 × 1 KB |
20.0 ms |
12.9 ms |
1.6x |
|
Recursive archive 500 × 4 KB, -a |
12.9 ms |
8.1 ms |
1.6x |
|
Mixed sizes 50×1K + 30×100K + 15×1M + 5×10M |
53.3 ms |
48.7 ms |
1.1x |
|
Large file 100 MB single file |
79.2 ms |
77.1 ms |
1.0x |
|
Deep tree 5 lvl × 4 dirs × 10 files |
213 ms |
204 ms |
1.0x |
|
Hardlink-heavy 50 files × 20 links |
10.5 ms |
10.8 ms |
1.0x |
|
Symlink-heavy 100 files + 400 symlinks |
7.6 ms |
7.8 ms |
1.0x |
|
Sparse file 100 MB, 50 MB hole |
1.4 ms |
2.2 ms |
0.7x* |
* Sparse scan overhead from
SEEK_HOLE/SEEK_DATAprobing on small files.
Parallel threshold sweep
| Files in directory | Time |
|---|---|
| 32 | 2.8 ms |
| 64 | 3.3 ms |
| 128 | 3.4 ms |
| 256 | 5.5 ms |
Parallel I/O kicks in at 64 entries (PARALLEL_THRESHOLD in src/dir.rs).
Startup overhead
~1.8 ms for a 1-byte file copy (50 runs average).
┌─────────────────────────────────────────────────────────┐
│ Copy Pipeline │
├───────────┬──────────────┬──────────────┬──────────────┤
│ │ │ │ │
│ CLI │ Target │ Directory │ Metadata │
│ Parse │ Resolution │ Walk │ Sync │
│ │ │ │ │
│ Clap │ same-file │ fast path: │ xattr │
│ derives │ self-copy │ openat │ chown │
│ flags │ path checks │ readdir │ chmod │
│ into │ │ mkdirat │ utimensat │
│ Options │ │ │ ACL │
│ │ │ slow path: │ │
│ │ │ walkdir │ │
└───────────┴──────────────┴──────┬───────┴──────────────┘
│
┌───────────▼───────────┐
│ Copy Engine │
│ │
│ FICLONE (reflink) │
│ ↓ fail │
│ copy_file_range │
│ ↓ fail │
│ sendfile │
│ ↓ fail │
│ read / write │
└───────────────────────┘
# Basic copy
cp source.txt dest.txt
# Recursive copy preserving everything
cp -a my_project/ backup/
# Copy with progress bar
cp --progress large_file.iso /mnt/usb/
# CoW reflink (instant on Btrfs/XFS)
cp --reflink=auto vm_disk.qcow2 snapshot.qcow2
# Sparse-aware copy
cp --sparse=always database.img /backup/
# Debug mode — shows which copy method was used
cp --debug file.dat /dst/All CLI flags
| Flag | Description |
|---|---|
-a, --archive |
Same as -dR --preserve=all |
-R, -r, --recursive |
Copy directories recursively |
-p |
Preserve mode, ownership, timestamps |
-f, --force |
Remove destination before copy if needed |
-n, --no-clobber |
Do not overwrite existing files |
-u, --update |
Copy only when source is newer |
-v, --verbose |
Explain what is being done |
-l, --link |
Hard link files instead of copying |
-s, --symbolic-link |
Create symlinks instead of copying |
-L, --dereference |
Always follow symlinks in source |
-P, --no-dereference |
Never follow symlinks in source |
--preserve=ATTR |
Preserve: mode, ownership, timestamps, links, xattr, all |
--no-preserve=ATTR |
Don't preserve specified attributes |
--sparse=WHEN |
Sparse file creation: auto, always, never |
--reflink=WHEN |
CoW cloning: auto, always, never |
--backup[=CONTROL] |
Backup: numbered, existing, simple, none |
-S, --suffix |
Override backup suffix (default: ~) |
-x, --one-file-system |
Stay on the same filesystem |
-t, --target-directory |
Copy all sources into directory |
-T, --no-target-directory |
Treat destination as normal file |
--parents |
Replicate source path structure under dest |
--attributes-only |
Copy metadata only, no file data |
--remove-destination |
Remove each destination before copy |
--debug |
Show copy method used (implies -v) |
--progress |
Show progress bar during copy |
168 tests + 19 benchmarks — all passing
| Suite | Tests | Suite | Tests | |
|---|---|---|---|---|
security |
38 | unit_metadata |
9 | |
unit_copy |
24 | unit_sparse |
9 | |
unit_options |
18 | unit_parallel |
8 | |
unit_util |
18 | unit_engine |
9 | |
integration |
12 | unit_backup |
11 | |
unit_dir |
12 | benchmarks |
19 |
# Run all tests
cargo test --release
# Run benchmarks with output
cargo test --release bench_ -- --nocapturesrc/
├── main.rs ··········· Entry point, CLI dispatch 125 lines
├── cli.rs ············ Clap-derived CLI definitions 201 lines
├── options.rs ········ CopyOptions resolution from flags 236 lines
├── dir.rs ············ Recursive directory copy (fast + slow) 1030 lines
├── copy.rs ··········· Single file copy logic 335 lines
├── engine.rs ········· Copy engine (reflink/cfr/sendfile/rw) 199 lines
├── metadata.rs ······· Permission, xattr, ACL, timestamps 225 lines
├── sparse.rs ········· Sparse file hole detection + copy 192 lines
├── error.rs ·········· Error types (thiserror) 145 lines
├── util.rs ··········· Path utilities, target resolution 120 lines
├── backup.rs ········· Backup file creation 77 lines
└── progress.rs ······· Progress bar (indicatif) 24 lines
tests/
├── common/mod.rs ····· Shared test harness (Env fixture) 194 lines
└── 12 test files ····· 168 tests + 19 benchmarks 3560 lines
html/
└── index.html ········ Project showcase page 1475 lines
# Build from source
git clone https://github.com/yrbane/cp.git
cd cp
cargo build --release
# Binary is at target/release/cpContributions are welcome! Feel free to open an issue or submit a pull request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/my-feature) - Run the tests (
cargo test --release) - Commit your changes
- Push and open a pull request
This project is released into the public domain under the Unlicense. You are free to copy, modify, publish, use, compile, sell, or distribute this software for any purpose, commercial or non-commercial, and by any means.
Built with Rust — Unlicense