# Atomic Cross-Parent Rename Plan
This document tracks the implementation plan and current status for converging
Rust's cross-parent rename behavior on littlefs-style atomic rename semantics.
## C Reference Map
The reference implementation is in `littlefs_c/lfs.c`:
- `lfs_rename_` resolves the old entry and the destination slot, rejects type
mismatches, and handles same-pair special cases.
- `lfs_fs_prepmove` stores a pending move in global state as a delete tag plus
the old metadata pair.
- The destination directory commit uses `LFS_FROM_MOVE` to copy the source
struct and attrs from the old pair/id into the new id.
- If source and destination are different pairs, a second commit deletes the old
id and clears the pending move.
- `lfs_fs_demove` runs during consistency repair. If power failed after the
destination commit but before the source delete, it follows the global move
state and deletes the old id.
- `lfs_fs_preporphans` and `lfs_fs_deorphan` handle directory replacement and
orphaned directory pairs. Full-orphan cleanup only matters after power loss;
half-orphan cleanup also interacts with metadata relocation.
The key compatibility point is that a committed destination entry must carry
enough global move state for a later mount to finish deleting the old source.
Rust currently does not parse, fold, commit, or repair that global state.
## Rust Implementation Steps
1. Parse global state entries.
- Add `LFS_TYPE_MOVESTATE` and `LFS_FROM_MOVE` support to metadata parsing.
- Fold global state from root and directory commits into a mounted
`GlobalState` model.
- Add read-only tests with C-generated images that contain pending move
state if fixtures can be created deterministically.
2. Encode global state commits.
- Add commit-entry builders for MOVESTATE and FROM_MOVE.
- Teach compacted pair reconstruction to preserve or clear relevant global
state entries.
- Unit-test tag encoding and parser round trips.
3. Implement same-pair rename through the littlefs shape.
- Keep current final behavior but move the code toward `DELETE old +
CREATE new + FROM_MOVE/copy` semantics.
- Preserve attrs and CTZ/dir structs exactly.
- Existing same-directory tests should continue to pass.
4. Implement cross-parent file rename atomically.
- Before the destination commit, set pending move to `(old_pair, old_id)`.
- Commit destination entry with `FROM_MOVE`.
- Commit source delete and clear pending move.
- Power-loss tests must show every snapshot mounts as either old name only
or new name only after Rust's mount-time repair runs.
- C must read the final image and match Rust manifest.
5. Implement cross-parent directory rename and replacement cases.
- Match C's visible behavior for moving a directory into its own subtree
when the destination parent already exists: the operation succeeds and the
subtree disappears from the root-visible tree.
- When replacing an existing empty directory, mark orphan state before the
destination commit and clear it after old directory pair cleanup.
- Add power-loss tests for directory move, empty-directory replacement, and
non-empty replacement rejection.
6. Mount-time repair.
- Add a consistency step for mutable mount that runs demove/deorphan before
new writes, matching littlefs' `lfs_fs_forceconsistency`.
- Read-only mount should expose a conservative final view or return a clear
unsupported/corrupt error until repair is implemented; do not silently
mis-fold pending moves.
## Test Matrix
Each implementation slice needs both Rust snapshot tests and C oracle checks:
| File move across parents | Snapshots old-or-new after repair; final C read/list/manifest. |
| Directory move across parents | Subtree remains traversable; old path missing; final C manifest. |
| Replace existing file | Implemented for same-pair and cross-pair file targets. Tests check source attrs move, destination-only attrs disappear, pending move repair finishes cross-parent replacement, and final images are read by C. |
| Replace empty directory | Implemented for supported flows: source subtree replaces an empty destination, pending cross-parent move snapshots repair on mutable mount, and orphaned destination directory pairs are dropped from the threaded metadata list. Remaining work is rarer relocation/orphan interleavings and exact C error parity for every replacement edge. |
| Reject non-empty directory replacement | Implemented with a C-visible old-state test. |
| Move into own subtree | Implemented for the C-observed missing-destination case where the destination parent already exists. Rust now matches C's final visible manifest: the old subtree disappears from root, unrelated entries remain, snapshots are old-or-removed, and final bytes are checked by C. Existing-destination variants still need broader parity coverage. |
| Move involving split tails | Destination and source pair lookup works across hardtail chains. |
| Move while relocation triggers | Move state is not lost when metadata pair relocation happens. |
## Current Boundary
File rename now follows the littlefs move-state shape for cross-pair moves and
replacement targets; directory rename has the same replacement behavior for
empty targets. Mutable mount repairs pending move state by deleting the old
source id. Rust recognizes orphan global state, folds MOVESTATE using C's
latest-entry-per-pair rule, writes orphan counts for supported rmdir and
empty-directory replacement paths, patches the threaded-list with `dir_drop`,
and repairs full-orphan and half-orphan snapshots through mutable mount.
Remaining gaps are richer C-generated pending-state fixtures, every directory
replacement edge case, existing-destination rename-into-descendant variants, and
rarer interleavings where pending moves/orphans combine with nested split-chain
relocation or lower-level bad-block retries.