uutils/coreutils · error · io::Error

mv-error-backup-might-destroy-source

Error message

mv-error-backup-might-destroy-source

What it means

mv refuses to perform the move when creating the requested backup would overwrite (destroy) the source itself. backup_would_destroy_source detects that the backup target name — destination plus backup suffix — resolves to the source file, so proceeding would clobber the input. It returns NotFound with this message quoting both target and source.

Source

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

    }
}

fn parse_paths(files: &[OsString], opts: &Options) -> Vec<PathBuf> {
    let paths = files.iter().map(Path::new);

    if opts.strip_slashes {
        paths
            .map(|p| p.components().as_path().to_owned())
            .collect::<Vec<PathBuf>>()
    } else {
        paths.map(ToOwned::to_owned).collect::<Vec<PathBuf>>()
    }
}

fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> {
    // `mv` never follows a symlink source, so the guard must not either.
    if backup_would_destroy_source(source, target, &opts.suffix, opts.backup, false) {
        return Err(io::Error::new(
            io::ErrorKind::NotFound,
            translate!("mv-error-backup-might-destroy-source", "target" => target.quote(), "source" => source.quote()),
        )
        .into());
    }
    let Ok(source_metadata) = source.symlink_metadata() else {
        return Err(if path_ends_with_terminator(source) {
            MvError::CannotStatNotADirectory(source.quote().to_string()).into()
        } else {
            MvError::NoSuchFile(source.quote().to_string()).into()
        });
    };

    // `symlink_metadata` does not follow symlinks, so this is equivalent to
    // `source.is_dir() && !source.is_symlink()` without the extra `stat` calls.
    let source_is_dir = source_metadata.is_dir();
    let target_is_dir = match target.symlink_metadata() {
        Ok(metadata) if metadata.is_symlink() => fs::canonicalize(target).is_ok_and(|p| p.is_dir()),

View on GitHub (pinned to 85295bbf78)

Solutions

  1. Change the backup suffix (--suffix=SFX or VERSION_CONTROL) so the backup name does not equal the source path
  2. Check whether source and target are the same file (same inode) with `ls -i` and fix the script's path construction
  3. Drop --backup/-b if no backup is actually needed
  4. Quote/normalize paths in scripts to avoid aliasing through symlinks or hardlinks

Example fix

// before
mv --suffix=.bak notes.txt notes.txt.bak   # backup name == ...
// after
mv --suffix=.old notes.txt notes.txt.bak
Defensive patterns

Strategy: validation

Validate before calling

# ensure backup name does not collide with the source
if [ "$target$suffix" = "$source" ] || [ -e "$target$suffix" ] && [ "$target$suffix" -ef "$source" ]; then
  echo "backup would destroy source"; exit 1
fi

Try / catch

null

Prevention

When it happens

Trigger: mv --backup (or -b with a suffix/VAR) where source and dest are the same file via different paths or the backup name equals the source path, e.g., `mv --suffix=.orig a a.orig` styles of invocations or hardlink/symlink aliasing between source and backup target.

Common situations: Scripts that build backup suffixes from variables which collide with the source name; moving a file onto a path whose backup suffix name points back at the same inode (hardlink); typos where source and target differ only by the backup suffix.

Related errors


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