uutils/coreutils · error · std::io::Error

mv-error-backup-might-destroy-source

Error message

mv-error-backup-might-destroy-source

What it means

Before moving, `mv` checks whether creating the requested backup would overwrite the source file itself (same name after applying the backup suffix); if so it refuses with this NotFound-kind error, since the backup step would destroy the file being moved.

Solutions

  1. Use a destination name that does not collide with source+suffix
  2. Disable backup for this call (drop -b/--backup) so the guard doesn't apply
  3. Choose a different --suffix so backup name != source path
  4. Check spelling: with backup mode you probably meant `mv file.bak file`, not the reverse

Example fix

// before
mv --suffix=.bak notes.txt notes.txt.bak
// after
mv --suffix=.bak notes.txt.bak notes.txt   # restore direction, or drop -b when moving onto the suffixed name
Defensive patterns

Strategy: validation

Validate before calling

# refuse when dest == source + backup suffix
suffix=.bak; [ "$dest" = "$src$suffix" ] && [ -n "$backup" ] && { echo "backup would destroy source" >&2; exit 1; }

Type guard

fn backup_safe(src: &str, dest: &str, suffix: &str, backup: bool) -> bool {
    !backup || dest != format!("{src}{suffix}")
}

Try / catch

match mv_result {
    Err(e) if e.to_string().contains("might destroy source") => {
        eprintln!("destination collides with backup of source; pick another name");
    }
    r => r?,
}

Prevention

When it happens

Trigger: `mv --backup` (or -b) where the target path differs from source only by the backup suffix, e.g. `mv --suffix=.bak file file.bak` styles, or moving `a` onto `a~` when suffix is `~` and a backup would be made of `a~` onto itself.

Common situations: Scripts that construct destination names by appending the backup suffix to the source, typos where source and target are nearly identical, or restoring files into their backup names with backup mode enabled.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

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

    }
}

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