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
- Delete the symlink/special file at the manifest path so ensure_unicity_ce_manifest can materialize the bundled manifest itself.
- Reinstall or restore the runtime bundle so the product path is a real regular file.
- Verify with `ls -la` that the path is not a symlink (the 'l' in permissions) and is a regular file.
- 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
- Never symlink files inside the managed runtime root, even for convenience.
- After aborted installs or migrations, check for symlinks/special files with `find <root> -type l`.
- Restrict write access to the runtime root with private directory permissions so it cannot be tampered with.
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
- temporary product manifest path must be a regular file
- AOS capsule directory must be a real directory
- AOS managed path must be a real directory
- AOS capsule directory contains a non-regular entry
- Unicity AOS health service must bind to 127.0.0.1
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)