Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 0.2.1 — 2026-10-21

- Enforce no-overwrite at commit time on Linux/Android with RENAME_NOREPLACE, and otherwise safe no-replace hard-link publication.
- Use the original private staging file handle for all copy modes; change file permissions only after publication.
- Sync data before overwriting, published metadata for overwrites/moves and destination directories on Unix overwrites.
- Retry interrupted reads and allocate large progress buffers on the heap.
- Regression tests: late destinations, dangling symlinks, eight simultaneous publishers.
- Sync the published directory after resumed overwrites even without move; regression test checks intact source and matching destination.
- Tests on Termux and Fedora vm01 include forced interruption and verified restart; GitHub CI covers Linux, macOS and Windows.

## 0.2.0 — 2026-10-21

- Resume staging remains owner-private (0600) until the rename publishes it; final permissions are set on the published inode to keep interruptions resumable.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mch"
version = "0.2.0"
version = "0.2.1"
edition = "2024"
license = "AGPL-3.0-or-later"
description = "Mighty Copy with Hash (mch) - copy/move with post-copy verification (default: BLAKE3)."
Expand All @@ -9,7 +9,7 @@ readme = "README.md"

[package.metadata.mch-release]
# Explicit review decision; automatic version bump alone must not publish resume.
approved_unix_resume = "v1-single-regular-file"
approved_unix_resume = "v1-single-regular-file-reviewed-0.2.1-hardening"

[dependencies]
anyhow = "1"
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**MCH** is a Rust command-line tool for copying or moving local files and directory trees with optional post-copy integrity verification. Its default hash is **BLAKE3**.

Current version: **0.2.0**. Licensed under **GNU AGPL-3.0-or-later**.
Current version: **0.2.1**. Licensed under **GNU AGPL-3.0-or-later**.

## Features

Expand All @@ -14,7 +14,8 @@ Current version: **0.2.0**. Licensed under **GNU AGPL-3.0-or-later**.
- Preserve Unix symbolic links as links, including dangling links; links are **not followed** when traversing a directory.
- Preview all changes with `--dry-run`.
- Refuse to overwrite existing files by default; use `--overwrite` explicitly.
- Stage regular files in the destination directory and rename them into place only after successful verification.
- Enforce a safe, atomic no-replace publication when overwrite is disabled; concurrent writers cannot silently replace a late destination.
- Stage regular files privately in the destination directory and publish only after successful verification, keeping the staging file handle open throughout copying.
- With `--move`, remove only **planned source files and subsequently empty planned directories** after successful copy/verification and destination synchronization. A new file in the source tree causes a refusal rather than recursive deletion.
- Reject copying a directory into itself, duplicate destination names, destination parent symlinks, and special files (e.g. FIFOs).

Expand Down Expand Up @@ -104,7 +105,7 @@ mch --resume --progress always large.iso /backup/large.iso
**Important: MCH is not a transactional backup system.**

- Verification normally reads the operating-system **page cache** after writing; equal hashes do not prove that media/controller data has been durably written or will survive a power loss.
- During `--move` MCH synchronizes staged regular files before publishing and, **on Unix**, synchronizes affected destination directory entries before removing planned source files. Filesystems, controllers and power failures may still behave differently, and MCH is not a transactional backup. On Windows, destination directory persistence is not guaranteed.
- During explicit overwrites and moves, MCH synchronizes staged regular file contents and on Unix also synchronizes affected destination directory entries. Actual power-loss durability still depends on the filesystem and hardware.
- MCH checks the planned paths before copying and performs verification before committing each file. It does not take a filesystem snapshot. If sources change during copying, verification may fail; sources must be kept stable for reliable results.
- An error partway through an operation may leave some **completed** destination files or newly created directories behind. New directories can temporarily retain default permissions/timestamps until finalization succeeds. Sources are retained if copying or verification fails. It is safe to inspect the destination and retry, using `--overwrite` if appropriate.
- For a successful `--move`, original sources are deleted after verification. Deleting originals can itself fail, in which case both copies may remain. Do not assume an all-or-nothing transaction across sources.
Expand Down
10 changes: 9 additions & 1 deletion docs/SPECIFICATION.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Technical specification — MCH 0.2.0
# Technical specification — MCH 0.2.1

Status: implementation contract, not a formal safety proof.

Expand Down Expand Up @@ -46,6 +46,14 @@ Preflight errors must leave originals untouched. Failures in the copy phase reta
- A kill/power interruption can leave incomplete staging and metadata. A mismatched or missing metadata pair is a deliberate hard error, not silently reset. The source/destination filesystem must provide safe regular-file and rename semantics.
- This does **not** defend against hostile races or a same-inode/same-size/same-mtime content mutation, cannot guarantee directory durability on power loss, and does not resume directory trees, multiple files or Windows copies.

## v0.2.1 security patch

- The preflight destination check is not the final publication decision. When overwriting is disabled, publication must be atomic no-replace: Linux/Android use renameat2(RENAME_NOREPLACE) and a fail-closed hard-link fallback where supported; other platforms use a no-replace hard link or safely refuse publication.
- Normal regular-file copying retains an open private staging file handle instead of reopening the path with fs::copy. Unix staging stays private (0600) until publication; source permissions are then applied via the open handle.
- With explicit overwrite or move, staged bytes and published metadata are synchronized. On Unix, explicit overwrites synchronize destination directories, including resume overwrite without move.
- Hash and progress reads retry Interrupted; the 1 MiB progress buffer resides on the heap.
- These changes do not defend against all hostile parent-directory races or guarantee power-loss durability on all filesystems.

## Limitations and security boundaries

- No filesystem snapshots: sources should remain unchanged while MCH runs.
Expand Down
Loading
Loading