uutils/coreutils · error · io::Error
invalid link path
Error message
invalid link path
What it means
replace_link creates/updates a hard link by linking a temporary name inside the destination's parent directory and renaming it onto the destination. The destination path must have a final component; if dest ends in '..' or is empty/'/', file_name() returns None and the function fails with 'invalid link path'.
Solutions
- Ensure the destination path includes a real file name component
- Validate the destination with Path::file_name().is_some() before calling
- If linking into a directory, append the desired file name explicitly
Example fix
// before
replace_link(src, Path::new("/some/dir/"))
// after
replace_link(src, &Path::new("/some/dir").join("linkname")) Defensive patterns
Strategy: validation
Validate before calling
fn valid_link_target(dest: &Path) -> bool {
!dest.as_os_str().is_empty() && dest.file_name().is_some()
} Type guard
fn has_file_name(p: &Path) -> Option<&std::ffi::OsStr> { p.file_name() } Try / catch
match replace_link(src, dest) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => eprintln!("destination needs a file name: {dest:?}"),
other => other?,
} Prevention
- Always end destination paths with an explicit file name
- Reject '.'/'..'/empty paths from user input before linking
- Join directory targets with a default name instead of passing them raw
When it happens
Trigger: Calling replace_link (via `link` or copy hard-link handling) with a destination such as ".", "..", "/", "dir/" or an empty path, so dest.file_name() is None.
Common situations: Scripts interpolating variables that end up empty; user passing a directory instead of a filename; paths built by joining with trailing separators then canonicalized awkwardly.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- invalid destination path
- no unique temporary name available in the destination…
- path component exists but is not a directory
- mv-error-dest-appeared
- mv-error-inter-device-move-failed
AI-assisted analysis of uutils/coreutils@92995feca2 (2026-09-20).
Data as JSON: /api/errors/d53c99376006d0c0.
Report an issue: GitHub.
Appendix: source
Thrown at src/uucore/src/lib/features/fs.rs:1129
// GNU's template is `CuXXXXXX`: a 2-char prefix plus 6 random chars
// from a 62-char alphabet. The ~3% modulo bias per slot is irrelevant
// for an 8-char unguessability budget.
const ALPHABET: &[u8; 62] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
match link_at(target, CWD, dest.as_os_str(), symbolic) {
Err(e) if e.kind() == ErrorKind::AlreadyExists => {}
res => return res,
}
let parent = dest
.parent()
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or_else(|| Path::new("."));
let basename = dest
.file_name()
.ok_or_else(|| Error::new(ErrorKind::InvalidInput, "invalid link path"))?;
let dir = openat(
CWD,
parent,
OFlags::DIRECTORY | OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW,
Mode::empty(),
)?;
let mut urandom = fs::File::open("/dev/urandom")?;
for _ in 0..32 {
let mut name = *b"Cu------";
let mut raw = [0u8; 6];
urandom.read_exact(&mut raw)?;
for (slot, byte) in name[2..].iter_mut().zip(raw) {
*slot = ALPHABET[byte as usize % ALPHABET.len()];
}
let tmp = OsStr::from_bytes(&name);
match link_at(target, &dir, tmp, symbolic) {View on GitHub (pinned to 92995feca2)