uutils/coreutils · error · io::Error

mv-error-permission-denied

Error message

mv-error-permission-denied

What it means

rename_file_fallback's Unix path opens the source with open_source(..., nofollow=true) from uucore::safe_copy so the read cannot be swapped to another file mid-move. Any failure opening the source is replaced by this localized permission-denied error (the original err.kind() is kept but the message is generic). It means mv could not obtain a safe handle to the file it is about to copy across devices.

Source

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

            {
                // Create a hardlink to the first moved file instead of copying
                fs::hard_link(&existing_target, to)?;
                fs::remove_file(from)?;
                return Ok(());
            }
        }
    }

    // Open src/dst with O_NOFOLLOW and keep the fds alive across copy,
    // chown, xattr, and chmod so a concurrent path-swap can't redirect any
    // step to a different inode.
    #[cfg(unix)]
    {
        use std::fs::Permissions;
        use std::os::unix::fs::{MetadataExt, PermissionsExt};
        use uucore::safe_copy::{create_dest_restrictive, open_source};
        let src_file = open_source(from, /* nofollow */ true)
            .map_err(|err| io::Error::new(err.kind(), translate!("mv-error-permission-denied")))?;
        let src_mode = src_file
            .metadata()
            .map_err(|err| io::Error::new(err.kind(), translate!("mv-error-permission-denied")))?
            .mode()
            & 0o7777;
        let mut dst_file = create_dest_restrictive(to, /* nofollow */ true)
            .map_err(|err| io::Error::new(err.kind(), translate!("mv-error-permission-denied")))?;
        uucore::buf_copy::copy_fast(&mut &src_file, &mut dst_file)
            .map_err(|err| io::Error::new(err.kind(), translate!("mv-error-permission-denied")))?;

        #[cfg(not(any(target_vendor = "apple", target_os = "redox")))]
        {
            let _ = fsxattr::copy_xattrs_fd_ignore_unsupported(&src_file, &dst_file);
        }

        // chown before chmod: chown(2) clears setuid/setgid for non-root,
        // so the final mode must be applied last to preserve those bits.
        //

View on GitHub (pinned to 85295bbf78)

Solutions

  1. Fix source access: chmod/chown the file or get read permission on every parent directory (needs +x on dirs).
  2. Re-run as a user with rights, e.g. `sudo mv`, if policy allows.
  3. Re-check the source still exists (`ls -l`) — it may have been deleted concurrently.
  4. If the source is unreadable but you only need it relocated on the same device, avoid the copy fallback (rename within one filesystem needs no read access).

Example fix

// before
mv /secure/id_rsa /mnt/usb/   // mode 0600, other user
// after
sudo mv /secure/id_rsa /mnt/usb/ && sudo chown $(id -u) /mnt/usb/id_rsa
Defensive patterns

Strategy: validation

Validate before calling

fn can_read_source(p: &Path) -> bool {
    std::fs::File::open(p).is_ok() // or check mode bits on unix
}
if !can_read_source(from) { eprintln!("no read access to {}", from.display()); }

Type guard

fn readable(p: &Path) -> bool {
    use std::os::unix::fs::PermissionsExt;
    std::fs::metadata(p).map(|m| m.mode() & 0o444 != 0).unwrap_or(false)
}

Try / catch

match mv_result {
    Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
        // advise chmod/chown or sudo; same-device rename still possible
    }
    r => r?,
}

Prevention

When it happens

Trigger: `mv` falls back to copy (cross-device rename) and open_source on the source fails: missing read permission on the file, missing search (x) permission on a parent directory, or the file vanished between stat and open (ENOENT).

Common situations: Moving files owned by another user with mode 0600; traversing a directory without execute permission; moving from a directory being concurrently cleaned; AIDE/cleanup daemons deleting files mid-move.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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