can1357/oh-my-pi · error · io::Error
Permission denied
Error message
Permission denied
What it means
In pi-builtins' `mv` implementation, when a direct `rename` fails (typically cross-device), `rename_file_fallback` falls back to copy-then-delete. This error is raised when `fs::copy(host.resolve(from), &to_fs)` fails at mv.rs:1424. Note the wrapper hardcodes the message 'Permission denied' while preserving the OS error kind, so the underlying cause may be any copy failure (EACCES, ENOENT, ENOSPC, EISDIR), not only an actual permission denial.
Source
Thrown at crates/pi-builtins/src/mv.rs:1424
// instead of copying
#[cfg(unix)]
{
if let (Some(tracker), Some(scanner)) = (hardlink_tracker, hardlink_scanner) {
use hardlink::HardlinkOptions;
let hardlink_options = HardlinkOptions::default();
if let Some(existing_target) = tracker.check_hardlink(host, from, to, scanner, &hardlink_options)
{
// Create a hardlink to the first moved file instead of copying
fs::hard_link(host.resolve(&existing_target), &to_fs)?;
fs::remove_file(host.resolve(from))?;
return Ok(());
}
}
}
// Regular file copy
fs::copy(host.resolve(from), &to_fs)
.map_err(|err| io::Error::new(err.kind(), "Permission denied"))?;
// Copy xattrs, ignoring ENOTSUP errors (filesystem doesn't support xattrs)
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
{
let _ = copy_xattrs_if_supported(host, from, to);
}
fs::remove_file(host.resolve(from))
.map_err(|err| io::Error::new(err.kind(), "Permission denied"))?;
Ok(())
}
/// Copy xattrs from source to destination, ignoring ENOTSUP/EOPNOTSUPP errors.
/// These errors indicate the filesystem doesn't support extended attributes,
/// which is acceptable when moving files across filesystems.
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
fn copy_xattrs_if_supported(host: &Host, from: &Path, to: &Path) -> io::Result<()> {
match fsxattr::copy_xattrs(host.resolve(from), host.resolve(to)) {View on GitHub (pinned to 9690622007)
Solutions
- Check read permission on the source file and write permission on the destination directory (ls -l, chmod/chown as needed).
- Verify the destination directory exists and is on a writable filesystem (not mounted read-only).
- Check available disk space with df; free space or pick another destination.
- Inspect the OS error kind carried by the io::Error to distinguish real EACCES from ENOENT/ENOSPC, since the message text is hardcoded.
- If permissions cannot be fixed, copy the file with elevated rights or have the file's owner perform the move.
Example fix
// before: moving a root-owned file as an unprivileged user fails mv /var/log/audit.log /tmp/audit.log // after: ensure access before moving sudo chmod a+r /var/log/audit.log # or run the move as the file owner mv /var/log/audit.log /tmp/audit.log
Defensive patterns
Strategy: try-catch
Validate before calling
use std::os::unix::fs::PermissionsExt;
fn can_move_file(src: &Path, dst_dir: &Path) -> bool {
let src_ok = fs::metadata(src).map(|m| m.permissions().mode() & 0o400 != 0).unwrap_or(false);
let dir_ok = fs::metadata(dst_dir).map(|m| m.is_dir() && m.permissions().mode() & 0o200 != 0).unwrap_or(false);
src_ok && dir_ok
} Type guard
fn is_permission_denied(err: &io::Error) -> bool {
err.kind() == io::ErrorKind::PermissionDenied
} Try / catch
match mv_result {
Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {
eprintln!("move failed: check source readability and destination-directory writability: {e}");
// surface to user / retry with elevated rights
}
Err(e) => return Err(e),
Ok(()) => {}
} Prevention
- Verify source read permission and destination-directory write permission before moving across filesystems.
- Check available disk space on the destination volume for large files.
- Never assume the message text is accurate: branch on io::ErrorKind, since 'Permission denied' is hardcoded.
- Avoid moving files owned by other users in sticky-bit or root-owned directories from unprivileged code.
When it happens
Trigger: Calling the mv/move builtin on a regular file that requires the copy fallback (source and destination on different filesystems) where fs::copy fails: unreadable source file, unwritable or non-existent destination directory, insufficient disk space, or the source vanished between the rename attempt and the copy.
Common situations: Moving files across mount points or from tmpfs to disk with restrictive permissions; moving a file owned by another user without read permission; destination directory lacking write permission; moving within a container where the target volume is read-only; disk full during large-file moves.
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
- {}: {error}
- Too many levels of symbolic links
- failed to create a unique fc temporary file
- 2
- dates before 1970 are unsupported
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/264730f1af3a04e9.
Report an issue: GitHub.