parcopy
Parallel, atomic, and safe file/directory copying for Rust.
A production-grade library for copying files and directories with safety guarantees that go beyond the standard library.
Features
- Parallel copying - Uses rayon for concurrent file operations
- Atomic writes - Uses temp file + rename pattern to ensure no partial files
- TOCTOU safe - Uses
persist_noclobberto prevent race conditions - Incremental copy - Only copy files newer than destination (
UpdateNewer) - Reflink support - Instant copy-on-write on btrfs/XFS/APFS
- Timestamp preserving - Copies file modification and access times
- Permission preserving - Copies file and directory permissions
- Symlink aware - Correctly handles symlinks without following them
- Symlink loop detection - Prevents infinite recursion from circular symlinks
- Security hardened - Detects and optionally blocks escaping symlinks
- Graceful cancellation - Cooperative cancellation with Ctrl+C support
Why parcopy?
| Feature | std::fs |
fs_extra |
parcopy |
|---|---|---|---|
| Parallel | ❌ | ❌ | ✅ |
| Atomic writes | ❌ | ❌ | ✅ |
| TOCTOU safe | ❌ | ❌ | ✅ |
| Incremental copy | ❌ | ❌ | ✅ |
| Reflink/CoW | ❌ | ❌ | ✅ |
| Timestamp preservation | ❌ | ❌ | ✅ |
| Progress callbacks | ❌ | ✅ | ✅ |
Installation
[]
= "0.2"
Optional Features
[]
= { = "0.2", = ["progress", "reflink"] }
| Feature | Description |
|---|---|
progress |
Progress bar support with indicatif |
reflink |
Copy-on-write support for btrfs/XFS/APFS |
tracing |
Structured logging with tracing crate |
serde |
Serialize/Deserialize for CopyOptions |
full |
Enable all optional features |
Quick Start
Builder API (Recommended)
The easiest way to use parcopy is with the CopyBuilder:
use CopyBuilder;
// Simple copy with smart defaults
let stats = new.run?;
println!;
Incremental Backup
Only copy files that have changed:
use CopyBuilder;
let stats = new
.update_newer
.run?;
println!;
High-Performance Copy
Optimize for NFS or network filesystems:
use CopyBuilder;
let stats = new
.parallel // More threads for NFS
.overwrite // Replace existing files
.no_fsync // Skip fsync for speed
.run?;
Security-Hardened Copy
Copy untrusted directories safely:
use CopyBuilder;
let stats = new
.block_escaping_symlinks // Block symlinks with "../"
.max_depth // Limit directory depth
.run?;
Cancellable Copy
Support graceful cancellation from signal handlers:
use CopyBuilder;
use Arc;
use AtomicBool;
let cancel = new;
// Clone for signal handler
let cancel_clone = cancel.clone;
// In your signal handler: cancel_clone.store(true, Ordering::Relaxed);
let result = new
.cancel_token
.run;
match result
Function API
For more control, use the function API with CopyOptions:
use ;
use Path;
let options = default
.with_parallel
.with_on_conflict
.with_max_depth
.without_fsync;
let stats = copy_dir?;
Configuration Options
| Option | Default | Description |
|---|---|---|
parallel |
16 | Number of concurrent copy operations |
on_conflict |
Skip |
How to handle existing files |
fsync |
true |
Sync data to disk after each file |
preserve_permissions |
true |
Copy file permissions |
preserve_timestamps |
true |
Copy file timestamps |
max_depth |
None |
Maximum directory depth |
block_escaping_symlinks |
false |
Block symlinks with .. |
cancel_token |
None |
Cancellation token for graceful stop |
Conflict Strategies
| Strategy | Description |
|---|---|
OnConflict::Skip |
Skip files that already exist (default) |
OnConflict::Overwrite |
Replace existing files |
OnConflict::UpdateNewer |
Only copy if source is newer |
OnConflict::Error |
Return error if file exists |
Copy Statistics
All copy operations return CopyStats:
use CopyBuilder;
let stats = new.run?;
println!;
println!;
println!;
println!;
println!;
println!;
Safety Guarantees
Atomic Writes
Files are written to a temporary file in the destination directory, then renamed atomically:
- No partial files - Interrupted copies leave no garbage
- All-or-nothing - Other processes see complete files or nothing
- Power failure safe - With
fsync: true, data survives crashes
TOCTOU Protection
Uses renameat2(RENAME_NOREPLACE) on Linux to atomically fail if the destination was created between our existence check and the rename.
Symlink Safety
- Symlinks are never followed during directory traversal
- Symlink loops are detected and reported
- Escaping symlinks (
../) are warned or blocked
Performance Notes
NFS Optimization
This crate is optimized for NFS and network filesystems where many small files cause metadata storms. By parallelizing operations, multiple NFS RPCs can be in-flight simultaneously.
// For slow NFS, reduce parallelism to avoid overwhelming the server
let stats = new
.parallel
.run?;
Local SSD
For local SSDs, parallelism helps less but doesn't hurt:
// Default parallelism (16) works well for local storage too
let stats = new.run?;
Large Files
For large files, the reflink feature provides instant copy-on-write on supported filesystems (btrfs, XFS, APFS):
[]
= { = "0.2", = ["reflink"] }
CLI Tool
A CLI tool pcp is available in the cli directory:
# Install
# Usage
Canonical CLI Behavior
RFC-0001 defines a canonical CLI behavior model for profiles, plan/execute modes, and output contracts:
- Canonical behavior model:
docs/reference/cli-behavior.md - Migration notes:
docs/migrations/2026-02-rfc-0001-cli.md
Graceful Cancellation
Press Ctrl+C during a copy operation:
- First press: Graceful cancel — finishes in-flight files, reports progress
- Second press: Hard abort — immediate exit
Re-run the same command to resume (existing files are skipped by default).
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.