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
- Re-run the mv once the concurrent writer is finished; the race is transient.
- Ensure only one process moves into the same destination (use a lock file or flock).
- Check `path` for an unexpected file/symlink and remove it, then retry.
- 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
- Serialize moves into shared destinations with flock/lock files
- Keep destination directories writable only by the moving process
- Audit destination dirs for unexpected symlinks before scripted moves
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
- mv-error-dangling-symlink
- mv-error-inter-device-move-failed
- mv-error-permission-denied
- Is a directory
- Too many levels of symbolic links
AI-assisted analysis of uutils/coreutils@85295bbf78 (2026-08-31).
Data as JSON: /api/errors/65f34e8225eb1eda.
Report an issue: GitHub.