uutils/coreutils · error · io::Error

mv-error-inter-device-move-failed

Error message

mv-error-inter-device-move-failed

What it means

In rename_file_fallback, when the destination is a symlink, mv first removes it before copying the real file. If that remove fails, the error is re-wrapped with the localized inter-device move message including from, to and the underlying OS error, preserving the original io::ErrorKind. It indicates the cross-device rename fallback could not even clear the existing destination symlink.

Source

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

        }
        // Preserve ownership (uid/gid) from the source
        let _ = preserve_ownership(from, to);
    }

    Ok(())
}

fn rename_file_fallback(
    from: &Path,
    to: &Path,
    #[cfg(unix)] hardlink_tracker: Option<&mut HardlinkTracker>,
    #[cfg(unix)] hardlink_scanner: Option<&HardlinkGroupScanner>,
) -> io::Result<()> {
    // Remove existing target file if it exists
    if to.is_symlink() {
        fs::remove_file(to).map_err(|err| {
            let inter_device_msg = translate!("mv-error-inter-device-move-failed", "from" => from.quote(), "to" => to.quote(), "err" => err);
            io::Error::new(err.kind(), inter_device_msg)
        })?;
    } else if to.exists() {
        // For non-symlinks, just remove the file without special error handling
        fs::remove_file(to)?;
    }

    // Check if this file is part of a hardlink group and if so, create a hardlink instead of copying
    #[cfg(unix)]
    {
        if let (Some(tracker), Some(scanner)) = (hardlink_tracker, hardlink_scanner) {
            use crate::hardlink::HardlinkOptions;
            let hardlink_options = HardlinkOptions::default();
            if let Some(existing_target) =
                tracker.check_hardlink(from, to, scanner, &hardlink_options)
            {
                // Create a hardlink to the first moved file instead of copying
                fs::hard_link(&existing_target, to)?;
                fs::remove_file(from)?;

View on GitHub (pinned to 85295bbf78)

Solutions

  1. Read the wrapped `err` for the real cause (EPERM/EACCES/EROFS) and fix permissions or remount read-write.
  2. Remove the destination symlink manually, then re-run mv.
  3. Check for immutable flags (`lsattr`, `chattr -i`) or MAC policy denials in audit logs.
  4. Verify you own the destination directory (sticky bit requires ownership of the link).

Example fix

// before
mv bigfile /mnt/ro/link   // dest on read-only mount
// after
sudo mount -o remount,rw /mnt && mv bigfile /mnt/link
Defensive patterns

Strategy: try-catch

Validate before calling

let meta = dest.symlink_metadata()?;
if meta.file_type().is_symlink() {
    // test unlinkability up front
    if std::fs::remove_file(dest).is_err() {
        eprintln!("cannot remove existing dest symlink; fix perms first");
    }
}

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) => eprintln!("mv fallback failed removing dest symlink: {e} (kind={:?})", e.kind()),
    Ok(_) => {}
}

Prevention

When it happens

Trigger: `mv file dest_symlink` where rename fails (EXDEV/cross-device), the fallback tries fs::remove_file(dest_symlink), and the unlink fails — e.g. destination is on a read-only or protected mount, sticky-bit directory not owned by the user, or the link disappeared/was replaced concurrently.

Common situations: Moving onto a symlink in a /tmp-like sticky directory owned by another user; destination filesystem mounted read-only; immutable attribute (chattr +i) on the destination; SELinux/AppArmor denials on unlink.

Related errors


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