uutils/coreutils · error · io::Error

mv-error-permission-denied

Error message

mv-error-permission-denied

What it means

In mv's rename fallback path (rename_file_fallback), the source file cannot be opened with open_source (no-follow) because the caller lacks read permission on the file. The code replaces the underlying io::Error's message with the localized 'mv-error-permission-denied' message while preserving the io::ErrorKind, so users see a generic permission-denied message for the copy-based move.

Solutions

  1. Check the source file permissions with ls -l and add read permission for the invoking user (chmod u+r, or use sudo).
  2. Verify you own the file or are root; permission checks are against the effective uid/gid.
  3. If the file only lacks rename permission in the parent dir, confirm the fallback is actually needed (same-filesystem mv does a plain rename and does not need read access).
  4. Check for ACLs/SELinux/AppArmor restrictions beyond the classic mode bits.

Example fix

// before (shell): mv -x /mnt/usb/secret /home/user/  -> permission denied (copy fallback, unreadable 0600 root file)
sudo mv /mnt/usb/secret /home/user/
// after: move succeeds, or first fix perms
chmod u+r /mnt/usb/secret && mv /mnt/usb/secret /home/user/
Defensive patterns

Strategy: validation

Validate before calling

// Check readability before mv cross-device
const path = '/mnt/usb/secret';
const stats = fs.statSync(path);
const readable = (stats.mode & parseInt('444', 8)) !== 0 || process.getuid() === 0;
if (!readable) throw new Error(`cannot read ${path}: permission denied`);

Type guard

function isReadable(p) {
  try { fs.accessSync(p, fs.constants.R_OK); return true; }
  catch { return false; }
}

Try / catch

try {
  spawnSync('mv', [src, dst]);
} catch (e) {
  if (e.message.includes('permission denied')) {
    console.error(`mv failed: check read access on ${src} or use sudo`);
  }
}

Prevention

When it happens

Trigger: mv falls back to copy+delete when rename() fails (e.g. cross-device move) and open_source(from, nofollow=true) fails because the source file's mode denies read permission to the current user (e.g. mode 0o200 or 0o000 file, or a file owned by another user).

Common situations: Moving a write-only or unreadable file across filesystems; scripts running as a non-root user trying to move root-owned 0600 files; moving files on a mounted USB/network drive (cross-device) where the source is unreadable.

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@92995feca2 (2026-09-20). Data as JSON: /api/errors/79605d521358c27c. Report an issue: GitHub.

Appendix: source

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

            {
                // 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(any(
            target_os = "freebsd",
            target_os = "hurd",
            target_os = "linux",
            target_os = "android",
            target_os = "netbsd"
        ))]
        {

View on GitHub (pinned to 92995feca2)