{"record":{"id":"264730f1af3a04e9","repo":"can1357/oh-my-pi","slug":"permission-denied","errorCode":null,"errorMessage":"Permission denied","messagePattern":"Permission denied","errorType":"error_code","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/pi-builtins/src/mv.rs","lineNumber":1424,"sourceCode":"\t// instead of copying\n\t#[cfg(unix)]\n\t{\n\t\tif let (Some(tracker), Some(scanner)) = (hardlink_tracker, hardlink_scanner) {\n\t\t\tuse hardlink::HardlinkOptions;\n\t\t\tlet hardlink_options = HardlinkOptions::default();\n\t\t\tif let Some(existing_target) = tracker.check_hardlink(host, from, to, scanner, &hardlink_options)\n\t\t\t{\n\t\t\t\t// Create a hardlink to the first moved file instead of copying\n\t\t\t\tfs::hard_link(host.resolve(&existing_target), &to_fs)?;\n\t\t\t\tfs::remove_file(host.resolve(from))?;\n\t\t\t\treturn Ok(());\n\t\t\t}\n\t\t}\n\t}\n\n\t// Regular file copy\n\tfs::copy(host.resolve(from), &to_fs)\n\t\t.map_err(|err| io::Error::new(err.kind(), \"Permission denied\"))?;\n\n\t// Copy xattrs, ignoring ENOTSUP errors (filesystem doesn't support xattrs)\n\t#[cfg(all(unix, not(any(target_os = \"macos\", target_os = \"redox\"))))]\n\t{\n\t\tlet _ = copy_xattrs_if_supported(host, from, to);\n\t}\n\n\tfs::remove_file(host.resolve(from))\n\t\t.map_err(|err| io::Error::new(err.kind(), \"Permission denied\"))?;\n\tOk(())\n}\n\n/// Copy xattrs from source to destination, ignoring ENOTSUP/EOPNOTSUPP errors.\n/// These errors indicate the filesystem doesn't support extended attributes,\n/// which is acceptable when moving files across filesystems.\n#[cfg(all(unix, not(any(target_os = \"macos\", target_os = \"redox\"))))]\nfn copy_xattrs_if_supported(host: &Host, from: &Path, to: &Path) -> io::Result<()> {\n\tmatch fsxattr::copy_xattrs(host.resolve(from), host.resolve(to)) {","sourceCodeStart":1406,"sourceCodeEnd":1442,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-builtins/src/mv.rs#L1406-L1442","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: moving a root-owned file as an unprivileged user fails\nmv /var/log/audit.log /tmp/audit.log\n\n// after: ensure access before moving\nsudo chmod a+r /var/log/audit.log  # or run the move as the file owner\nmv /var/log/audit.log /tmp/audit.log","handlingStrategy":"try-catch","validationCode":"use std::os::unix::fs::PermissionsExt;\nfn can_move_file(src: &Path, dst_dir: &Path) -> bool {\n    let src_ok = fs::metadata(src).map(|m| m.permissions().mode() & 0o400 != 0).unwrap_or(false);\n    let dir_ok = fs::metadata(dst_dir).map(|m| m.is_dir() && m.permissions().mode() & 0o200 != 0).unwrap_or(false);\n    src_ok && dir_ok\n}","typeGuard":"fn is_permission_denied(err: &io::Error) -> bool {\n    err.kind() == io::ErrorKind::PermissionDenied\n}","tryCatchPattern":"match mv_result {\n    Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {\n        eprintln!(\"move failed: check source readability and destination-directory writability: {e}\");\n        // surface to user / retry with elevated rights\n    }\n    Err(e) => return Err(e),\n    Ok(()) => {}\n}","preventionTips":["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."],"tags":["filesystem","permissions","io","rust"],"backgroundTag":"permission-denied","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}