{"record":{"id":"e9f86ba44b06695a","repo":"astrid-runtime/astrid","slug":"legacy-source-is-not-a-regular-file","errorCode":null,"errorMessage":"legacy source is not a regular file: {}","messagePattern":"legacy source is not a regular file: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs","lineNumber":662,"sourceCode":"            format!(\"legacy source contains a special entry: {}\", path.display()),\n        ));\n    }\n    SourceIdentity::present(\n        SourceDigest::from_blake3(hasher.finalize()),\n        identity.entries,\n        identity.bytes,\n    )\n    .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))\n}\n\n/// Validate one regular-file source using the same no-follow, private,\n/// same-device, and mount checks as recursive snapshots.  Distro lock files\n/// keep their component-owned digest format, so they use this structural\n/// check instead of the generic tree hash.\npub(super) fn validate_source_path(path: &Path) -> io::Result<()> {\n    let metadata = fs::symlink_metadata(path)?;\n    if metadata.file_type().is_symlink() || !metadata.is_file() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"legacy source is not a regular file: {}\", path.display()),\n        ));\n    }\n    astrid_core::platform_fs::verify_no_redirects(path)?;\n    validate_private_entry(path, &metadata)?;\n    if active_mountpoint(path)? {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\"legacy source is an active mount: {}\", path.display()),\n        ));\n    }\n    if let Some(parent) = path.parent()\n        && let Ok(parent_metadata) = fs::symlink_metadata(parent)\n        && device_id(&parent_metadata) != device_id(&metadata)\n    {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,","sourceCodeStart":644,"sourceCodeEnd":680,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs#L644-L680","documentation":"validate_source_path checks a legacy migration source path before it is used. It takes symlink_metadata (which does not follow symlinks) and requires the entry to be a plain regular file; a symlink or any non-file (directory, socket, fifo, device node) fails this check with InvalidData. The library does this so migration digests bind to real file bytes, not to redirectable or non-regular entries.","triggerScenarios":"Calling validate_source_path (or the distro lock-file validation flow that uses it) with a path that is a symlink, a directory, or another special file type.","commonSituations":"Passing a convenience symlink like ~/.config/foo instead of the real file; pointing at a directory when a lock file path was expected; a special file (fifo/socket) created at the expected path; a distro that replaced a regular lock file with a symlink.","solutions":["Resolve the path to its final target and pass the real regular file (e.g. fs::canonicalize, then re-check it is a file).","ls -la the path and confirm it is a regular file, not a symlink or directory.","If the target is a directory, use the directory snapshot path (snapshot_owner_controlled_path / snapshot_path_with_access) instead of the file validator.","Recreate or restore the regular file at that path if a symlink or special file was placed there unintentionally."],"exampleFix":"// before\nlet src = Path::new(\"/etc/astrid.lock\"); // actually a symlink to /usr/share/astrid/astrid.lock\nvalidate_source_path(src)?;\n\n// after\nlet src = std::fs::canonicalize(\"/etc/astrid.lock\")?;\nlet md = std::fs::metadata(&src)?;\nassert!(md.is_file());\nvalidate_source_path(&src)?;","handlingStrategy":"validation","validationCode":"fn is_regular_nonsymlink(path: &std::path::Path) -> std::io::Result<bool> {\n    let md = std::fs::symlink_metadata(path)?;\n    Ok(!md.file_type().is_symlink() && md.is_file())\n}\nif !is_regular_nonsymlink(&src)? {\n    return Err(format!(\"{} must be a regular file, not a symlink/dir\", src.display()));\n}","typeGuard":"fn is_plain_file(path: &std::path::Path) -> bool {\n    std::fs::symlink_metadata(path)\n        .map(|md| !md.file_type().is_symlink() && md.is_file())\n        .unwrap_or(false)\n}","tryCatchPattern":"match validate_source_path(&src) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {\n        let real = std::fs::canonicalize(&src).unwrap_or(src.clone());\n        validate_source_path(&real).map_err(|e2| e2)?; // retry against resolved target\n    }\n    other => other,\n}","preventionTips":["Always pass canonicalized paths (fs::canonicalize) to the validator.","Check file type with symlink_metadata before calling library APIs.","Avoid passing directories to file-oriented validators; use the directory snapshot API.","Audit config paths for distro-installed symlinks."],"tags":["filesystem","symlink","validation","rust"],"backgroundTag":"incompatible-source-type","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}