diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 7e953d7dd..0fa81218a 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -18,6 +18,8 @@ use nix::fcntl::{open, OFlag}; use nix::sys::stat::Mode; use std::path::Path; use uucore::display::Quotable; +#[cfg(any(target_os = "linux", target_os = "android"))] +use uucore::error::FromIo; use uucore::error::{UResult, USimpleError}; use uucore::format_usage; @@ -170,29 +172,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Use the Nix open to be able to set the NONBLOCK flags for fifo files #[cfg(any(target_os = "linux", target_os = "android"))] { - match open(Path::new(&f), OFlag::O_NONBLOCK, Mode::empty()) { - Ok(_) => {} - Err(e) => { - if e == Errno::ENOENT { - return Err(USimpleError::new( - 1, - format!("cannot stat {}: No such file or directory", f.quote()), - )); - } - if e == Errno::EACCES { - if Path::new(&f).is_dir() { - return Err(USimpleError::new( - 1, - format!("error opening {}: Permission denied", f.quote()), - )); - } else { - // ignore the issue - // ./target/debug/coreutils sync --data file - } - } + let path = Path::new(&f); + if let Err(e) = open(path, OFlag::O_NONBLOCK, Mode::empty()) { + if e != Errno::EACCES || (e == Errno::EACCES && path.is_dir()) { + return e.map_err_context(|| format!("cannot stat {}", f.quote()))?; } } } + #[cfg(not(any(target_os = "linux", target_os = "android")))] { if !Path::new(&f).exists() { diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index e5f78ef95..fa1c1d74b 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -38,7 +38,7 @@ os_display = "0.1.3" [target.'cfg(unix)'.dependencies] walkdir = { version="2.3.2", optional=true } -nix = { version = "0.25", optional = true, default-features = false, features = ["fs", "uio", "zerocopy"] } +nix = { version = "0.25", default-features = false, features = ["fs", "uio", "zerocopy"] } [dev-dependencies] clap = "4.0" @@ -53,8 +53,8 @@ default = [] # * non-default features encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"] entries = ["libc"] -fs = ["libc", "nix", "winapi-util", "windows-sys"] -fsext = ["libc", "nix", "time", "windows-sys"] +fs = ["libc", "winapi-util", "windows-sys"] +fsext = ["libc", "time", "windows-sys"] lines = [] memo = ["itertools"] mode = ["libc"] @@ -65,4 +65,4 @@ signals = [] utf8 = [] utmpx = ["time", "time/macros", "libc", "dns-lookup"] wide = [] -pipes = ["nix"] +pipes = [] diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index 4cc0b1519..5f6f21b77 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -508,6 +508,60 @@ impl From for Box { } } +/// Enables the conversion from [`Result`] to [`UResult`]. +/// +/// # Examples +/// +/// ``` +/// use uucore::error::FromIo; +/// use nix::errno::Errno; +/// +/// let nix_err = Err::<(), nix::Error>(Errno::EACCES); +/// let uio_result = nix_err.map_err_context(|| String::from("fix me please!")); +/// +/// // prints "fix me please!: Permission denied" +/// println!("{}", uio_result.unwrap_err()); +/// ``` +#[cfg(any(target_os = "linux", target_os = "android"))] +impl FromIo> for Result { + fn map_err_context(self, context: impl FnOnce() -> String) -> UResult { + self.map_err(|e| { + Box::new(UIoError { + context: Some((context)()), + inner: std::io::Error::from_raw_os_error(e as i32), + }) as Box + }) + } +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +impl FromIo> for nix::Error { + fn map_err_context(self, context: impl FnOnce() -> String) -> UResult { + Err(Box::new(UIoError { + context: Some((context)()), + inner: std::io::Error::from_raw_os_error(self as i32), + }) as Box) + } +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +impl From for UIoError { + fn from(f: nix::Error) -> Self { + Self { + context: None, + inner: std::io::Error::from_raw_os_error(f as i32), + } + } +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +impl From for Box { + fn from(f: nix::Error) -> Self { + let u_error: UIoError = f.into(); + Box::new(u_error) as Self + } +} + /// Shorthand to construct [`UIoError`]-instances. /// /// This macro serves as a convenience call to quickly construct instances of @@ -693,3 +747,30 @@ impl Display for ClapErrorWrapper { Ok(()) } } + +#[cfg(test)] +mod tests { + #[test] + #[cfg(any(target_os = "linux", target_os = "android"))] + fn test_nix_error_conversion() { + use super::{FromIo, UIoError}; + use nix::errno::Errno; + use std::io::ErrorKind; + + for (nix_error, expected_error_kind) in [ + (Errno::EACCES, ErrorKind::PermissionDenied), + (Errno::ENOENT, ErrorKind::NotFound), + (Errno::EEXIST, ErrorKind::AlreadyExists), + ] { + let error = UIoError::from(nix_error); + assert_eq!(expected_error_kind, error.inner.kind()); + } + assert_eq!( + "test: Permission denied", + Err::<(), nix::Error>(Errno::EACCES) + .map_err_context(|| String::from("test")) + .unwrap_err() + .to_string() + ) + } +} diff --git a/tests/by-util/test_sync.rs b/tests/by-util/test_sync.rs index 7f2cd4b66..4bf2629c4 100644 --- a/tests/by-util/test_sync.rs +++ b/tests/by-util/test_sync.rs @@ -64,9 +64,9 @@ fn test_sync_no_permission_dir() { ts.ccmd("chmod").arg("0").arg(dir).succeeds(); let result = ts.ucmd().arg("--data").arg(dir).fails(); - result.stderr_contains("sync: error opening 'foo': Permission denied"); + result.stderr_contains("sync: cannot stat 'foo': Permission denied"); let result = ts.ucmd().arg(dir).fails(); - result.stderr_contains("sync: error opening 'foo': Permission denied"); + result.stderr_contains("sync: cannot stat 'foo': Permission denied"); } #[cfg(not(target_os = "windows"))]