{"record":{"id":"1180747e25d5b8b9","repo":"uutils/coreutils","slug":"mv-error-dest-appeared-path","errorCode":null,"errorMessage":"mv-error-dest-appeared (path)","messagePattern":"mv-error-dest-appeared \\(path\\)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"src/uu/mv/src/mv.rs","lineNumber":1184,"sourceCode":"    }\n\n    result?;\n\n    // Remove the source directory after successful copy\n    fs::remove_dir_all(from)?;\n\n    Ok(())\n}\n\n/// Copy directory recursively, optionally preserving hardlinks\n/// Create `path`, refusing to reuse anything already there.\n///\n/// `create_dir_all` would accept a symlink planted at `path` after the caller\n/// removed the destination, redirecting the move out of the destination tree.\nfn create_dir_fail_closed(path: &Path) -> io::Result<()> {\n    fs::create_dir(path).map_err(|e| {\n        if e.kind() == io::ErrorKind::AlreadyExists {\n            io::Error::new(\n                io::ErrorKind::AlreadyExists,\n                translate!(\"mv-error-dest-appeared\", \"path\" => path.quote()),\n            )\n        } else {\n            e\n        }\n    })\n}\n\nfn get_dir_size(path: &Path) -> io::Result<u64> {\n    let metadata = path.symlink_metadata()?;\n\n    if !metadata.is_dir() {\n        return Ok(metadata.len());\n    }\n\n    let mut size = 0;\n    for entry in fs::read_dir(path)? {","sourceCodeStart":1166,"sourceCodeEnd":1202,"githubUrl":"https://github.com/uutils/coreutils/blob/92995feca29e324a7c559ede91db81c32d9f5d59/src/uu/mv/src/mv.rs#L1166-L1202","documentation":"`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.","triggerScenarios":"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.","commonSituations":"Concurrent processes writing into the same destination tree, hostile multi-user tmp directories (symlink attacks), or racy build scripts re-creating paths mid-move.","solutions":["Ensure no other process writes into the destination directory during the move","Move into a fresh, private destination (e.g. under a 0700 directory)","Re-run the mv after the concurrent writer finished","If you see this in a shared /tmp-like location, treat it as a security signal and audit for symlink attacks"],"exampleFix":"// before\nmv src /shared/dest    # /shared/dest touched concurrently\n// after\nmkdir /tmp/mv-$$ && chmod 700 /tmp/mv-$$ && mv src /tmp/mv-$$/dest","handlingStrategy":"retry","validationCode":"# ensure destination tree is quiescent\nfuser -s \"$dest\" 2>/dev/null && { echo 'dest in use' >&2; exit 1; }","typeGuard":"fn is_symlink(p: &Path) -> bool { p.symlink_metadata().map(|m| m.file_type().is_symlink()).unwrap_or(false) }","tryCatchPattern":"match mv_result {\n    Err(e) if e.to_string().contains(\"destination appeared\") => {\n        eprintln!(\"concurrent modification at destination; retry after quiescing writers\");\n    }\n    r => r?,\n}","preventionTips":["Never share a destination directory between concurrent mv/cp processes","Move into private, freshly created directories","In multi-user tmp dirs, use mktemp -d with 0700 perms","Treat this error as a possible symlink attack, not a benign race"],"tags":["mv","race-condition","security","symlink"],"backgroundTag":"file-already-exists","analyzedSha":"92995feca29e324a7c559ede91db81c32d9f5d59","analyzedAt":"2026-09-20T12:39:35.476Z","contentChangedAt":"2026-09-20T12:39:35.476Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}