fix: make the SFTP VFS provider's create_dir() and create_new() correctly report "file exists" errors (#3495)

This commit is contained in:
三咲雅 misaki masa 2026-01-03 19:01:38 +08:00 committed by GitHub
parent 81c66a645f
commit e611de5284
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 65 additions and 32 deletions

36
Cargo.lock generated
View file

@ -599,9 +599,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.53"
version = "4.5.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
dependencies = [
"clap_builder",
"clap_derive",
@ -609,9 +609,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.53"
version = "4.5.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
dependencies = [
"anstream",
"anstyle",
@ -2106,9 +2106,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]]
name = "libc"
version = "0.2.178"
version = "0.2.179"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
[[package]]
name = "libcrux-intrinsics"
@ -2889,9 +2889,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pest"
version = "2.8.4"
version = "2.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22"
checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7"
dependencies = [
"memchr",
"ucd-trie",
@ -2899,9 +2899,9 @@ dependencies = [
[[package]]
name = "pest_derive"
version = "2.8.4"
version = "2.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f"
checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed"
dependencies = [
"pest",
"pest_generator",
@ -2909,9 +2909,9 @@ dependencies = [
[[package]]
name = "pest_generator"
version = "2.8.4"
version = "2.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625"
checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5"
dependencies = [
"pest",
"pest_meta",
@ -2922,9 +2922,9 @@ dependencies = [
[[package]]
name = "pest_meta"
version = "2.8.4"
version = "2.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82"
checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365"
dependencies = [
"pest",
"sha2 0.10.9",
@ -4412,9 +4412,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.48.0"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
"bytes",
"libc",
@ -5933,9 +5933,9 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.7"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de9211a9f64b825911bdf0240f58b7a8dac217fe260fc61f080a07f61372fbd5"
checksum = "317f17ff091ac4515f17cc7a190d2769a8c9a96d227de5d64b500b01cda8f2cd"
[[package]]
name = "zune-core"

View file

@ -30,7 +30,7 @@ ansi-to-tui = "8.0.0"
anyhow = "1.0.100"
base64 = "0.22.1"
bitflags = { version = "2.10.0", features = [ "serde" ] }
clap = { version = "4.5.53", features = [ "derive" ] }
clap = { version = "4.5.54", features = [ "derive" ] }
core-foundation-sys = "0.8.7"
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
dirs = "6.0.0"
@ -39,7 +39,7 @@ futures = "0.3.31"
globset = "0.4.18"
hashbrown = { version = "0.16.1", features = [ "serde" ] }
indexmap = { version = "2.12.1", features = [ "serde" ] }
libc = "0.2.178"
libc = "0.2.179"
lru = "0.16.2"
mlua = { version = "0.11.5", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] }
objc2 = "0.6.3"
@ -56,7 +56,7 @@ serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.148"
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
thiserror = "2.0.17"
tokio = { version = "1.48.0", features = [ "full" ] }
tokio = { version = "1.49.0", features = [ "full" ] }
tokio-stream = "0.1.17"
tokio-util = "0.7.17"
toml = { version = "0.9.10" }

View file

@ -42,6 +42,11 @@ impl From<Error> for std::io::Error {
responses::StatusCode::NoConnection => Self::from(std::io::ErrorKind::NotConnected),
responses::StatusCode::ConnectionLost => Self::from(std::io::ErrorKind::ConnectionReset),
responses::StatusCode::OpUnsupported => Self::from(std::io::ErrorKind::Unsupported),
responses::StatusCode::InvalidHandle => Self::from(std::io::ErrorKind::InvalidInput),
responses::StatusCode::NoSuchPath => Self::from(std::io::ErrorKind::NotFound),
responses::StatusCode::FileAlreadyExists => Self::from(std::io::ErrorKind::AlreadyExists),
responses::StatusCode::WriteProtect => Self::from(std::io::ErrorKind::PermissionDenied),
responses::StatusCode::NoMedia => Self::from(std::io::ErrorKind::Other),
},
Error::Packet(e) => Self::new(std::io::ErrorKind::InvalidData, e),
Error::Timeout => Self::from(std::io::ErrorKind::TimedOut),

View file

@ -28,6 +28,8 @@ impl Status {
pub fn is_eof(&self) -> bool { self.code == StatusCode::Eof }
pub fn is_failure(&self) -> bool { self.code == StatusCode::Failure }
pub(crate) fn connection_lost(id: u32) -> Self {
Self {
id,
@ -40,13 +42,18 @@ impl Status {
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
pub enum StatusCode {
Ok = 0,
Eof = 1,
NoSuchFile = 2,
PermissionDenied = 3,
Failure = 4,
BadMessage = 5,
NoConnection = 6,
ConnectionLost = 7,
OpUnsupported = 8,
Ok = 0,
Eof = 1,
NoSuchFile = 2,
PermissionDenied = 3,
Failure = 4,
BadMessage = 5,
NoConnection = 6,
ConnectionLost = 7,
OpUnsupported = 8,
InvalidHandle = 9,
NoSuchPath = 10,
FileAlreadyExists = 11,
WriteProtect = 12,
NoMedia = 13,
}

View file

@ -68,9 +68,20 @@ impl FileBuilder for Gate {
_ => Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Not an SFTP URL: {url:?}")))?,
};
let conn = Conn { name, config }.roll().await?;
let flags = Flags::from(*self);
let attrs = super::Attrs(self.0.attrs).try_into().unwrap_or_default();
Ok(Conn { name, config }.roll().await?.open(path, flags, &attrs).await?)
let result = conn.open(path, flags, &attrs).await;
if self.0.create_new
&& let Err(yazi_sftp::Error::Status(status)) = &result
&& status.is_failure()
&& conn.lstat(path).await.is_ok()
{
return Err(io::Error::from(io::ErrorKind::AlreadyExists));
}
Ok(result?)
}
fn read(&mut self, read: bool) -> &mut Self {

View file

@ -117,7 +117,17 @@ impl<'a> Provider for Sftp<'a> {
}
async fn create_dir(&self) -> io::Result<()> {
Ok(self.op().await?.mkdir(self.path, Attrs::default()).await?)
let op = self.op().await?;
let result = op.mkdir(self.path, Attrs::default()).await;
if let Err(yazi_sftp::Error::Status(status)) = &result
&& status.is_failure()
&& op.lstat(self.path).await.is_ok()
{
return Err(io::Error::from(io::ErrorKind::AlreadyExists));
}
Ok(result?)
}
async fn hard_link<P>(&self, to: P) -> io::Result<()>