uutils/coreutils · error · io::Error

mv-error-dest-appeared (path)

Error message

mv-error-dest-appeared (path)

What it means

`create_dir_fail_closed` is a security-hardening wrapper: after mv removes a destination, `create_dir_all` would silently accept a symlink planted at that path (TOCTOU attack redirecting the move). It uses plain `create_dir` and, if the path already exists, raises this error naming the path rather than following it.

Solutions

  1. Ensure no other process writes into the destination directory during the move
  2. Move into a fresh, private destination (e.g. under a 0700 directory)
  3. Re-run the mv after the concurrent writer finished
  4. If you see this in a shared /tmp-like location, treat it as a security signal and audit for symlink attacks

Example fix

// before
mv src /shared/dest    # /shared/dest touched concurrently
// after
mkdir /tmp/mv-$$ && chmod 700 /tmp/mv-$$ && mv src /tmp/mv-$$/dest
Defensive patterns

Strategy: retry

Validate before calling

# ensure destination tree is quiescent
fuser -s "$dest" 2>/dev/null && { echo 'dest in use' >&2; exit 1; }

Type guard

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

Try / catch

match mv_result {
    Err(e) if e.to_string().contains("destination appeared") => {
        eprintln!("concurrent modification at destination; retry after quiescing writers");
    }
    r => r?,
}

Prevention

When it happens

Trigger: During `mv src dest_dir` with directory-copy fallback (`copy_dir_contents`), something (another process or attacker) creates a file/symlink at a subdirectory path of the destination between removal and creation.

Common situations: Concurrent processes writing into the same destination tree, hostile multi-user tmp directories (symlink attacks), or racy build scripts re-creating paths mid-move.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of uutils/coreutils@92995feca2 (2026-09-20). Data as JSON: /api/errors/1180747e25d5b8b9. Report an issue: GitHub.

Appendix: source

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

    }

    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 92995feca2)