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

  1. Check read permission on the source file and write permission on the destination directory (ls -l, chmod/chown as needed).
  2. Verify the destination directory exists and is on a writable filesystem (not mounted read-only).
  3. Check available disk space with df; free space or pick another destination.
  4. Inspect the OS error kind carried by the io::Error to distinguish real EACCES from ENOENT/ENOSPC, since the message text is hardcoded.
  5. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/264730f1af3a04e9. Report an issue: GitHub.