{"record":{"id":"02280ecba9c28e30","repo":"ogham/exa","slug":"error-path-somehow-contained-a-nul","errorCode":null,"errorMessage":"Error: path somehow contained a NUL?","messagePattern":"Error: path somehow contained a NUL\\?","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"src/fs/feature/xattr.rs","lineNumber":64,"sourceCode":"    No,\n}\n\n/// Extended attribute\n#[derive(Debug, Clone)]\npub struct Attribute {\n    pub name: String,\n    pub size: usize,\n}\n\n\n#[cfg(any(target_os = \"macos\", target_os = \"linux\"))]\npub fn list_attrs(lister: &lister::Lister, path: &Path) -> io::Result<Vec<Attribute>> {\n    use std::ffi::CString;\n\n    let c_path = match path.to_str().and_then(|s| CString::new(s).ok()) {\n        Some(cstring) => cstring,\n        None => {\n            return Err(io::Error::new(io::ErrorKind::Other, \"Error: path somehow contained a NUL?\"));\n        }\n    };\n\n    let bufsize = lister.listxattr_first(&c_path);\n    match bufsize.cmp(&0) {\n        Ordering::Less     => return Err(io::Error::last_os_error()),\n        Ordering::Equal    => return Ok(Vec::new()),\n        Ordering::Greater  => {},\n    }\n\n    let mut buf = vec![0_u8; bufsize as usize];\n    let err = lister.listxattr_second(&c_path, &mut buf, bufsize);\n\n    match err.cmp(&0) {\n        Ordering::Less     => return Err(io::Error::last_os_error()),\n        Ordering::Equal    => return Ok(Vec::new()),\n        Ordering::Greater  => {},\n    }","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/ogham/exa/blob/3d1edbb47052cb416ef9478106c3907586da5150/src/fs/feature/xattr.rs#L46-L82","documentation":"This io::Error comes from exa's extended-attribute module (src/fs/feature/xattr.rs). Before calling the OS listxattr functions, exa converts the file path to a C string; the conversion is rejected when path.to_str() returns None (the path is not valid UTF-8) or when CString::new finds an interior NUL byte. The message names only the NUL case, so it is misleading: on Unix a NUL byte cannot appear in a real filename, and in practice this error almost always means the filename contained non-UTF-8 bytes. It is returned as an Err of kind ErrorKind::Other, not a panic, so the caller sees it as a failed attributes() / list_attrs() call.","triggerScenarios":"Calling Path::attributes() or Path::symlink_attributes() (the FileAttributes trait), or running exa with --extended/--all with xattrs enabled, on a Linux/macOS system where a listed filename has bytes that are not valid UTF-8 (for example latin-1 names or a byte sequence that is invalid UTF-8). The same match arm also fires for a path that truly contains an embedded NUL, which essentially only happens for programmatically built paths, not real directory entries.","commonSituations":"Listing old archives, media directories, or files created by non-UTF-8 programs (Windows latin-1 names copied via SMB, misconfigured locale). Also triggered when a wrapper script builds paths from unvalidated input. Note the source loses the original bytes: it goes through path.to_str(), so any non-UTF-8 path is reported with the wrong 'NUL' message.","solutions":["If you are the library consumer: validate or fix the filename first. Rename the offending file to a valid UTF-8 name, or run exa in a locale/terminal that handles the bytes, e.g. with LC_ALL=C or a UTF-8 locale so the name round-trips.","If you hit this as a user of the exa binary: drop --extended for that directory, or rename the file (find it with: find . -name '*[! -~]*' or ls -b to show escapes).","If you maintain this code: build the CString from the raw OS bytes instead of a &str, so only real NUL bytes fail. Use std::os::unix::ffi::OsStrExt::as_bytes, and return io::ErrorKind::InvalidInput with an accurate message distinguishing the non-UTF-8 and NUL cases.","As a defensive change, map the failure to the OS convention: io::Error::from_raw_os_error(libc::EINVAL) so callers can match on the error kind."],"exampleFix":"// before (src/fs/feature/xattr.rs)\nlet c_path = match path.to_str().and_then(|s| CString::new(s).ok()) {\n    Some(cstring) => cstring,\n    None => return Err(io::Error::new(io::ErrorKind::Other, \"Error: path somehow contained a NUL?\")),\n};\n\n// after: use the raw OS bytes; only a real NUL byte can fail now\n#[cfg(unix)]\nlet c_path = {\n    use std::os::unix::ffi::OsStrExt;\n    match CString::new(path.as_os_str().as_bytes()) {\n        Ok(cstring) => cstring,\n        Err(_) => return Err(io::Error::new(io::ErrorKind::InvalidInput,\n                                           \"path contains an interior NUL byte\")),\n    }\n};","handlingStrategy":"validation","validationCode":"// Before calling attributes()/symlink_attributes() on a user-supplied path:\nuse std::ffi::OsStr;\nuse std::path::Path;\n\nfn check_path_for_xattrs(path: &Path) -> Result<(), std::io::Error> {\n    // Case 1: not valid UTF-8 (the common real-world trigger for this message)\n    if path.to_str().is_none() {\n        return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput,\n            format!(\"path is not valid UTF-8: {:?}\", path.as_os_str())));\n    }\n    // Case 2: an actual interior NUL byte (only possible in synthetic paths)\n    if path.to_str().map_or(false, |s| s.contains('\\0')) {\n        return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput,\n            \"path contains an interior NUL byte\"));\n    }\n    Ok(())\n}","typeGuard":"fn is_xattr_listable(path: &Path) -> bool {\n    // Mirror of the check in src/fs/feature/xattr.rs list_attrs():\n    // the CString conversion must succeed.\n    path.to_str().map_or(false, |s| !s.contains('\\0'))\n}","tryCatchPattern":"// Treat attribute listing as optional metadata, not a fatal error:\nmatch path.attributes() {\n    Ok(attrs) => { /* render xattrs */ }\n    Err(e) if e.kind() == std::io::ErrorKind::Other\n            && e.to_string().contains(\"NUL\") => {\n        // non-UTF-8 or NUL-containing path: skip extended attributes for this file\n    }\n    Err(e) => { /* report or skip */ }\n}","preventionTips":["Normalize or reject non-UTF-8 filenames at the boundary where your program accepts paths (argument parsing, directory iteration).","When iterating directories, work with OsString/Path end to end; never round-trip through String, which is what makes this conversion fail.","If you list arbitrary user files, wrap each file's attribute lookup in its own error handling so one odd filename cannot stop the whole listing.","In your own wrappers, convert paths with std::os::unix::ffi::OsStrExt::as_bytes instead of to_str(); that removes the non-UTF-8 failure mode entirely."],"tags":["rust","filesystem","xattr","encoding","non-utf8","unix"],"backgroundTag":"non-utf8-path-encoding","analyzedSha":"3d1edbb47052cb416ef9478106c3907586da5150","analyzedAt":"2026-08-16T22:11:59.736Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}