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
- Check the source file permissions with ls -l and add read permission for the invoking user (chmod u+r, or use sudo).
- Verify you own the file or are root; permission checks are against the effective uid/gid.
- 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).
- 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
- Check ls -l and ACLs on source files before cross-device moves
- Run as root or the file owner when moving protected files
- Prefer same-filesystem renames to avoid the copy fallback
- Audit SELinux/AppArmor denials when mode bits look correct
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
- mv-error-dest-appeared
- mv-error-inter-device-move-failed
- mv-error-inter-device-move-failed (from, to, err)
- Too many levels of symbolic links
- could not allocate a unique temp name in destination…
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)