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

  1. Ensure the destination path includes a real file name component
  2. Validate the destination with Path::file_name().is_some() before calling
  3. 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

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


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)