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

AOS managed path must be a real directory

Error message

AOS managed path must be a real directory: {}

What it means

create_private_dir calls fs::create_dir_all and then verifies with symlink_metadata that the resulting path is a genuine directory and not a symlink; otherwise it throws this io::Error (InvalidInput). The library enforces this because AOS-managed directories hold private state and a symlinked path could redirect writes outside the managed layout (a symlink-attack/tofu concern).

Solutions

  1. Remove the symlink or non-directory at the reported path and let the library create a real directory: rm <path> (for a symlink) or mv it away, then retry.
  2. If the symlink was for relocation, instead set the AOS home env var (e.g. AOS_HOME) to the real directory location rather than symlinking inside the managed tree.
  3. Migrate any data from the old location into the newly created real directory.
  4. Re-run the operation and confirm with 'ls -la' that the path is 'd' (directory), not 'l' (link) or '-' (file).

Example fix

# before: symlinked managed directory
~/.aos -> /mnt/data/aos
# after: real directory via env relocation
rm ~/.aos
export AOS_HOME=/mnt/data/aos   # library creates a real dir here
Defensive patterns

Strategy: validation

Validate before calling

use std::fs;
let md = fs::symlink_metadata(&path)?;
if md.file_type().is_symlink() || !md.is_dir() {
    return Err(format!("{} must be a real directory (not a symlink or file); remove/move it and let AOS recreate it", path.display()));
}

Try / catch

match result {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput && e.to_string().contains("must be a real directory") => {
        eprintln!("{} is a symlink or file; replace it with a real directory or relocate via AOS_HOME", path.display());
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling ensure_unicity_ce_manifest or ensure_layout when the AOS-managed path (e.g. the manifest or layout directory, typically under AOS_HOME) either already exists as a symlink or is a regular file/special file instead of a directory.

Common situations: A user symlinked the state directory (e.g. ~/.aos -> /mnt/data/aos) to relocate storage to another disk; a previous file was left where the directory is expected; a migration or restore tool recreated paths incorrectly.

Related errors


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

Appendix: source

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

            if metadata.permissions().mode() & 0o111 == 0 {
                return Err(io::Error::new(
                    io::ErrorKind::PermissionDenied,
                    format!(
                        "bundled {label} executable is not executable at {}",
                        binary.display()
                    ),
                ));
            }
        }
        Ok(())
    }
}

fn create_private_dir(path: &Path) -> io::Result<()> {
    fs::create_dir_all(path)?;
    let metadata = fs::symlink_metadata(path)?;
    if metadata.file_type().is_symlink() || !metadata.is_dir() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            format!(
                "AOS managed path must be a real directory: {}",
                path.display()
            ),
        ));
    }
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        fs::set_permissions(path, fs::Permissions::from_mode(0o700))?;
    }
    Ok(())
}

fn validate_path_entry(path: &Path, variable: &str) -> io::Result<()> {
    std::env::join_paths(std::iter::once(path))
        .map(drop)

View on GitHub (pinned to f6f22024fb)