diff --git a/Cargo.lock b/Cargo.lock index cf806cd78..1134a1c59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2834,6 +2834,7 @@ version = "0.0.15" dependencies = [ "clap", "libc", + "nix", "uucore", "winapi", ] diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index b5a9e14be..0d804e85b 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -19,6 +19,9 @@ clap = { version = "3.2", features = ["wrap_help", "cargo"] } libc = "0.2.132" uucore = { version=">=0.0.15", package="uucore", path="../../uucore", features=["wide"] } +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +nix = "0.25" + [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "std", "winbase", "winerror"] } diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 4cf5eafb4..5b08f45cb 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -10,6 +10,12 @@ extern crate libc; use clap::{crate_version, Arg, Command}; +#[cfg(any(target_os = "linux", target_os = "android"))] +use nix::errno::Errno; +#[cfg(any(target_os = "linux", target_os = "android"))] +use nix::fcntl::{open, OFlag}; +#[cfg(any(target_os = "linux", target_os = "android"))] +use nix::sys::stat::Mode; use std::path::Path; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; @@ -174,11 +180,40 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } for f in &files { - if !Path::new(&f).exists() { - return Err(USimpleError::new( - 1, - format!("cannot stat {}: No such file or directory", f.quote()), - )); + // 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 + } + } + } + } + } + #[cfg(not(any(target_os = "linux", target_os = "android")))] + { + if !Path::new(&f).exists() { + return Err(USimpleError::new( + 1, + format!("cannot stat {}: No such file or directory", f.quote()), + )); + } } } diff --git a/tests/by-util/test_sync.rs b/tests/by-util/test_sync.rs index e57690e49..7f2cd4b66 100644 --- a/tests/by-util/test_sync.rs +++ b/tests/by-util/test_sync.rs @@ -52,3 +52,32 @@ fn test_sync_data_but_not_file() { .fails() .stderr_contains("sync: --data needs at least one argument"); } + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(feature = "chmod")] +#[test] +fn test_sync_no_permission_dir() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + let dir = "foo"; + at.mkdir_all(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"); + let result = ts.ucmd().arg(dir).fails(); + result.stderr_contains("sync: error opening 'foo': Permission denied"); +} + +#[cfg(not(target_os = "windows"))] +#[cfg(feature = "chmod")] +#[test] +fn test_sync_no_permission_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + let f = "file"; + at.touch(f); + + ts.ccmd("chmod").arg("0200").arg(f).succeeds(); + ts.ucmd().arg(f).succeeds(); +}