diff --git a/crates/xgen-adapter-filesystem/src/atomic_commit.rs b/crates/xgen-adapter-filesystem/src/atomic_commit.rs index 2aad9b0..53c1f1d 100644 --- a/crates/xgen-adapter-filesystem/src/atomic_commit.rs +++ b/crates/xgen-adapter-filesystem/src/atomic_commit.rs @@ -146,7 +146,9 @@ fn open_parent(root: &Dir, path: &RelativePath) -> Result<(Dir, String), AtomicC OpenErrorClass::Missing | OpenErrorClass::NotADirectory => { AtomicCommitFailure::ParentNotFound } - OpenErrorClass::Refused => AtomicCommitFailure::OpenParent, + OpenErrorClass::Directory | OpenErrorClass::Refused => { + AtomicCommitFailure::OpenParent + } } })?; if is_windows_reparse_point( diff --git a/crates/xgen-adapter-filesystem/src/path.rs b/crates/xgen-adapter-filesystem/src/path.rs index 918bba8..51c7db4 100644 --- a/crates/xgen-adapter-filesystem/src/path.rs +++ b/crates/xgen-adapter-filesystem/src/path.rs @@ -73,6 +73,9 @@ pub(crate) enum OpenErrorClass { Missing, /// Something other than a directory exists at the name. NotADirectory, + /// A directory exists at the name. Some platforms refuse to open a directory as a file, so + /// this is how a file open learns that the name has the wrong kind. + Directory, /// The name exists but is refused, such as a symbolic link, or the error is not classified. Refused, } @@ -85,15 +88,16 @@ pub(crate) fn classify_open_error( name: &str, error: &std::io::Error, ) -> OpenErrorClass { - match error.kind() { - std::io::ErrorKind::NotFound => OpenErrorClass::Missing, - std::io::ErrorKind::NotADirectory - if !parent - .symlink_metadata(name) - .is_ok_and(|metadata| metadata.file_type().is_symlink()) => - { - OpenErrorClass::NotADirectory - } + if error.kind() == std::io::ErrorKind::NotFound { + return OpenErrorClass::Missing; + } + let file_type = parent + .symlink_metadata(name) + .map(|metadata| metadata.file_type()); + match file_type { + Ok(file_type) if file_type.is_symlink() => OpenErrorClass::Refused, + _ if error.kind() == std::io::ErrorKind::NotADirectory => OpenErrorClass::NotADirectory, + Ok(file_type) if file_type.is_dir() => OpenErrorClass::Directory, _ => OpenErrorClass::Refused, } } diff --git a/crates/xgen-adapter-filesystem/src/query.rs b/crates/xgen-adapter-filesystem/src/query.rs index 72b9a52..65d1056 100644 --- a/crates/xgen-adapter-filesystem/src/query.rs +++ b/crates/xgen-adapter-filesystem/src/query.rs @@ -1009,7 +1009,7 @@ fn open_directory(root: &Dir, path: &RelativePath) -> Result // means the requested path does not exist. OpenErrorClass::NotADirectory if index == last => QueryFailure::NotADirectory, OpenErrorClass::Missing | OpenErrorClass::NotADirectory => QueryFailure::NotFound, - OpenErrorClass::Refused => QueryFailure::Open, + OpenErrorClass::Directory | OpenErrorClass::Refused => QueryFailure::Open, } })?; if is_windows_reparse_point(&directory.dir_metadata().map_err(|_| QueryFailure::Open)?) { @@ -1035,6 +1035,7 @@ fn open_regular_file(root: &Dir, path: &RelativePath) -> Result QueryFailure::NotFound, + OpenErrorClass::Directory => QueryFailure::NotAFile, OpenErrorClass::Refused => QueryFailure::Open, } })?; diff --git a/crates/xgen-adapter-filesystem/src/read_text.rs b/crates/xgen-adapter-filesystem/src/read_text.rs index c065f6f..6d9321b 100644 --- a/crates/xgen-adapter-filesystem/src/read_text.rs +++ b/crates/xgen-adapter-filesystem/src/read_text.rs @@ -270,10 +270,12 @@ impl ReadFailure { } /// A missing component, or a parent component that is not a directory, means the path does not -/// exist. Every other open error stays unclassified beyond "unavailable". +/// exist, and a directory is not a file. Every other open error stays unclassified beyond +/// "unavailable". fn open_failure(parent: &Dir, name: &str, error: &std::io::Error) -> ReadFailure { match classify_open_error(parent, name, error) { OpenErrorClass::Missing | OpenErrorClass::NotADirectory => ReadFailure::NotFound, + OpenErrorClass::Directory => ReadFailure::NonRegular, OpenErrorClass::Refused => ReadFailure::Open, } }