uutils/coreutils · error · io::Error

mv-error-dest-appeared

Error message

mv-error-dest-appeared

What it means

create_dir_fail_closed uses plain fs::create_dir (not create_dir_all) when rebuilding the destination directory tree during mv's copy fallback. If the destination path suddenly already exists, it maps AlreadyExists to this localized 'destination appeared' error including the quoted path. This is a deliberate fail-closed TOCTOU defense: create_dir_all would silently follow a symlink planted at `path` after the caller removed the destination, letting the move escape the destination tree.

Source

Thrown at src/uu/mv/src/mv.rs:1219

    }

    result?;

    // Remove the source directory after successful copy
    fs::remove_dir_all(from)?;

    Ok(())
}

/// Copy directory recursively, optionally preserving hardlinks
/// Create `path`, refusing to reuse anything already there.
///
/// `create_dir_all` would accept a symlink planted at `path` after the caller
/// removed the destination, redirecting the move out of the destination tree.
fn create_dir_fail_closed(path: &Path) -> io::Result<()> {
    fs::create_dir(path).map_err(|e| {
        if e.kind() == io::ErrorKind::AlreadyExists {
            io::Error::new(
                io::ErrorKind::AlreadyExists,
                translate!("mv-error-dest-appeared", "path" => path.quote()),
            )
        } else {
            e
        }
    })
}

fn get_dir_size(path: &Path) -> io::Result<u64> {
    let metadata = path.symlink_metadata()?;

    if !metadata.is_dir() {
        return Ok(metadata.len());
    }

    let mut size = 0;
    for entry in fs::read_dir(path)? {

View on GitHub (pinned to 85295bbf78)

Solutions

  1. Re-run the mv once the concurrent writer is finished; the race is transient.
  2. Ensure only one process moves into the same destination (use a lock file or flock).
  3. Check `path` for an unexpected file/symlink and remove it, then retry.
  4. If a race attack is suspected, audit the destination directory permissions before retrying.

Example fix

// before
mv src dst & mv src2 dst &        // two movers race on dst/
// after
flock /tmp/mv-dst.lock mv src dst && flock /tmp/mv-dst.lock mv src2 dst
Defensive patterns

Strategy: retry

Validate before calling

if path.symlink_metadata().is_ok() {
    eprintln!("destination already occupied: {}", path.display());
}
// also ensure no concurrent movers:
// flock lockfile around mv invocation

Type guard

fn dest_is_clean_symlink(path: &Path) -> bool {
    path.symlink_metadata().map(|m| m.file_type().is_symlink()).unwrap_or(false)
}

Try / catch

match mv_result {
    Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
        // destination appeared mid-move: serialize with a lock and retry once
    }
    r => r?,
}

Prevention

When it happens

Trigger: A concurrent process creates a file or symlink at exactly the directory path mv is about to create, between mv's remove of the old destination and its create_dir call; produces AlreadyExists from fs::create_dir which is translated to this error.

Common situations: Another mv/cp/rsync instance running in parallel on the same destination; a watcher or build system recreating the directory; an attacker planting a symlink at the destination path; NFS or network shares with stale caching.

Related errors


AI-assisted analysis of uutils/coreutils@85295bbf78 (2026-08-31). Data as JSON: /api/errors/65f34e8225eb1eda. Report an issue: GitHub.