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

product manifest path must be a regular file

Error message

product manifest path must be a regular file

What it means

ensure_unicity_ce_manifest materializes the bundled manifest and then checks the product manifest path with symlink_metadata. If the path is a symlink or not a regular file, it returns InvalidInput with "product manifest path must be a regular file". This prevents an attacker or misconfiguration from pointing the product manifest at a symlinked or special file outside the managed root.

Solutions

  1. Delete the symlink/special file at the manifest path so ensure_unicity_ce_manifest can materialize the bundled manifest itself.
  2. Reinstall or restore the runtime bundle so the product path is a real regular file.
  3. Verify with `ls -la` that the path is not a symlink (the 'l' in permissions) and is a regular file.
  4. Remove any scripts or config management that symlink files inside the managed root.

Example fix

// before (shell)
ln -sf /etc/shared/product.toml $ROOT/product.toml
// after
rm -f $ROOT/product.toml  # let ensure_unicity_ce_manifest materialize it
rm -f $ROOT/product.toml && ensure_unicity_ce_manifest()?
Defensive patterns

Strategy: validation

Validate before calling

fn manifest_path_is_clean(path: &Path) -> Result<(), String> {
    match std::fs::symlink_metadata(path) {
        Ok(m) if m.file_type().is_symlink() => Err(format!("{:?} is a symlink; remove it", path)),
        Ok(m) if !m.is_file() => Err(format!("{:?} is not a regular file; remove it", path)),
        _ => Ok(()),
    }
}

Type guard

fn is_clean_regular_file(path: &Path) -> bool {
    std::fs::symlink_metadata(path)
        .map(|m| m.is_file() && !m.file_type().is_symlink())
        .unwrap_or(false)
}

Try / catch

if let Err(e) = runtime.ensure_unicity_ce_manifest() {
    if e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains("product manifest") {
        eprintln!("manifest path is a symlink/special file; remove it and rerun");
    } else { return Err(e.into()); }
}

Prevention

When it happens

Trigger: Calling ensure_unicity_ce_manifest (directly or via foreground_daemon_command / ensure_runtime_available) when the manifest path exists as a symlink, directory, device node, or other non-regular file.

Common situations: An admin replaced the manifest with a symlink to a shared config to 'centralize' it; a packaging script linked the file; leftover state from a previous layout migration; tampering or a broken install.

Related errors


AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13). Data as JSON: /api/errors/2bc674c77024b675. Report an issue: GitHub.

Appendix: source

Thrown at crates/unicity-aos-bootstrap/src/lib.rs:197

        };
        validate_capsule_dir(&path, &capsule_assets_from_manifest()?)
    }

    /// Materialize the Unicity CE manifest embedded in this product binary.
    ///
    /// The product CLI hands this local path to the neutral runtime, so first-run
    /// provisioning uses the manifest shipped with the installed AOS release rather
    /// than following a mutable repository branch.
    ///
    /// # Errors
    /// Returns an error when the product manifest cannot be written atomically.
    pub fn ensure_unicity_ce_manifest(&self) -> io::Result<PathBuf> {
        let path = self.unicity_ce_manifest_path();
        let capsule_dir = self.capsule_dir()?;
        let manifest = materialize_manifest(&capsule_dir)?;
        match fs::symlink_metadata(&path) {
            Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "product manifest path must be a regular file",
                ));
            }
            Ok(_) if fs::read(&path)?.as_slice() == manifest.as_bytes() => return Ok(path),
            Ok(_) => {}
            Err(error) if error.kind() == io::ErrorKind::NotFound => {}
            Err(error) => return Err(error),
        }
        self.ensure_layout()?;
        create_private_dir(&self.root.join("distributions"))?;
        let parent = path.parent().expect("manifest path has a parent");
        create_private_dir(parent)?;
        let temporary = path.with_extension("toml.tmp");
        if let Ok(metadata) = fs::symlink_metadata(&temporary) {
            if metadata.file_type().is_symlink() || !metadata.is_file() {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,

View on GitHub (pinned to f6f22024fb)