Skip to content

migrate_blocking aborts the entire walk on any WalkDir entry error, leaving move-mode migration partial #409

Description

@forkwright

Finding

In migrate_blocking, every entry yielded by the WalkDir iterator is converted to a fatal HostError and propagated with ?. A single unreadable directory, broken symlink, or permission-denied error therefore aborts the whole function. In move mode the files already processed have been relocated to their canonical destination while the remainder stay at the source, leaving the library split with no record of what moved.

Evidence

crates/archon/src/migrate.rs:78-88 — the per-entry error is made fatal:

for entry in WalkDir::new(source).follow_links(false).into_iter() {
    let entry: DirEntry = entry.map_err(|e| {
        let path = e.path().unwrap_or(source).to_path_buf();
        HostError::MigrateIo {
            operation: format!("walk {}", path.display()),
            source: e.into_io_error().unwrap_or_else(|| {
                std::io::Error::other("walkdir error without underlying IO error")
            }),
            location: snafu::location!(),
        }
    })?;

The trailing ? returns out of the loop, so control never reaches the report.errors accounting for an enumeration error.

Why this matters

Move-mode migration is irreversible mid-stream. After an abort the operator holds two partial libraries — some media at the destination, the rest at the source — with no manifest of what moved and no error count (the MigrationReport.errors counter is never incremented for walk errors, since that path is unreachable). On a sovereign device, where a hostile or degraded filesystem (a planted unreadable node, a permission trap) is part of the threat model, a single such node can wedge the entire media library into a silent, inconsistent state; re-running the migration reprocesses survivors but cannot undo the already-moved files if their canonical path changed.

Desired correction

Do not make a WalkDir enumeration error fatal. On an entry error, push a message into report.messages, increment report.errors, and continue to the next entry. Reserve the fatal ? return path for I/O failures on actual file operations (copy/rename), not directory enumeration.

Done when: a migration over a source tree containing an unreadable entry completes with a non-zero report.errors count instead of returning Err, and the migration_skips_non_media_files test is augmented to assert that unreadable entries are counted as errors (not propagated or panicked).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions