unicity-aos/aos-ce · error · io::Error

bundled product runtime path must have a final component

Error message

bundled product runtime path must have a final component

What it means

In checked_target_path's NotFound branch, after obtaining the parent, the code calls file_name() to recover the final path component; if the path ends in ".." or is a bare root, file_name() returns None and this InvalidInput error is thrown. It prevents canonicalizing a target whose final component cannot be reattached.

Solutions

  1. Pass a target path whose final component is the runtime directory name (e.g. /opt/product/runtime, not /opt/product/..)
  2. Strip trailing slashes and resolve any ".." components before calling migrate_runtime
  3. Assert path.file_name().is_some() on the constructed target before invoking the migration

Example fix

// before
let target = Path::new("/opt/product/..");
migrate_runtime(target, ...)?;

// after
let target = Path::new("/opt/product/runtime");
migrate_runtime(target, ...)?;
Defensive patterns

Strategy: validation

Validate before calling

fn target_final_component_ok(target: &std::path::Path) -> bool {
    target.file_name().is_some()
}

Type guard

fn final_component(p: &std::path::Path) -> Option<std::ffi::OsString> {
    p.file_name().map(|n| n.to_os_string())
}

Try / catch

match migrate_runtime(&target, ...) {
    Ok(()) => {},
    Err(e) if e.to_string().contains("must have a final component") =>
        eprintln!("target must end with the runtime directory name, not '..' or '/'"),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling migrate_runtime with a bundled product runtime target that has a parent but no final component, such as "/opt/product/..", "/", or a path ending in a trailing traversal component.

Common situations: Building the target by string concatenation that leaves a trailing slash-dot segment; normalizing away the runtime directory name before calling the API.

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 unicity-aos/aos-ce@f6f22024fb (2026-09-13). Data as JSON: /api/errors/c3f923e17e476767. Report an issue: GitHub.

Appendix: source

Thrown at crates/unicity-aos-bootstrap/src/migration.rs:548

}

fn checked_target_path(path: &Path) -> io::Result<PathBuf> {
    match fs::symlink_metadata(path) {
        Ok(metadata) => {
            if metadata.file_type().is_symlink() || !metadata.is_dir() {
                return invalid("bundled product runtime must be a real directory");
            }
            path.canonicalize()
        }
        Err(error) if error.kind() == io::ErrorKind::NotFound => {
            let parent = path.parent().ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "bundled product runtime path must have a parent",
                )
            })?;
            let name = path.file_name().ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "bundled product runtime path must have a final component",
                )
            })?;
            Ok(parent.canonicalize()?.join(name))
        }
        Err(error) => Err(error),
    }
}

pub(crate) fn imported_legacy_distros(home: &AosHome) -> io::Result<Vec<LegacyDistro>> {
    let receipt = read_receipt(&home.migration_receipt())?;
    Ok(receipt.legacy_distros)
}

fn legacy_distros(runtime_home: &Path) -> io::Result<Vec<LegacyDistro>> {
    let homes = runtime_home.join("home");
    if !homes.is_dir() {

View on GitHub (pinned to f6f22024fb)