diff --git a/src/uu/sync/locales/en-US.ftl b/src/uu/sync/locales/en-US.ftl index d0b520bcb95..b473c03d913 100644 --- a/src/uu/sync/locales/en-US.ftl +++ b/src/uu/sync/locales/en-US.ftl @@ -9,6 +9,7 @@ sync-help-data = sync only file data, no unneeded metadata (Linux only) sync-error-data-needs-argument = --data needs at least one argument sync-error-opening-file = error opening { $file } sync-error-no-such-file = error opening { $file }: No such file or directory +sync-error-syncing-file = error syncing { $file } # Warning messages sync-warning-fcntl-failed = warning: failed to reset O_NONBLOCK flag for { $file }: { $error } diff --git a/src/uu/sync/locales/fr-FR.ftl b/src/uu/sync/locales/fr-FR.ftl index f0d00db03ba..e2db79b90fc 100644 --- a/src/uu/sync/locales/fr-FR.ftl +++ b/src/uu/sync/locales/fr-FR.ftl @@ -9,6 +9,7 @@ sync-help-data = synchroniser seulement les données des fichiers, pas les méta sync-error-data-needs-argument = --data nécessite au moins un argument sync-error-opening-file = erreur lors de l'ouverture de { $file } sync-error-no-such-file = erreur lors de l'ouverture de { $file } : Aucun fichier ou répertoire de ce type +sync-error-syncing-file = erreur lors de la synchronisation de { $file } # Messages d'avertissement sync-warning-fcntl-failed = avertissement : échec de la réinitialisation du drapeau O_NONBLOCK pour { $file } : { $error } diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 0e5b864b9cd..87873cedab5 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -35,7 +35,11 @@ mod platform { #[cfg(any(target_os = "linux", target_os = "android"))] use nix::unistd::{fdatasync, syncfs}; #[cfg(any(target_os = "linux", target_os = "android"))] - use std::fs::File; + use std::fs::{File, OpenOptions}; + #[cfg(any(target_os = "linux", target_os = "android"))] + use std::os::unix::fs::OpenOptionsExt; + #[cfg(any(target_os = "linux", target_os = "android"))] + use uucore::display::Quotable; #[cfg(any(target_os = "linux", target_os = "android"))] use uucore::error::FromIo; #[cfg(any(target_os = "linux", target_os = "android"))] @@ -57,7 +61,11 @@ mod platform { /// Logs a warning if fcntl fails but doesn't abort the operation. #[cfg(any(target_os = "linux", target_os = "android"))] fn open_and_reset_nonblock(path: &str) -> UResult { - let f = File::open(path).map_err_context(|| path.to_string())?; + let f = OpenOptions::new() + .read(true) + .custom_flags(OFlag::O_NONBLOCK.bits()) + .open(path) + .map_err_context(|| path.to_string())?; // Reset O_NONBLOCK flag if it was set (matches GNU behavior) // This is non-critical, so we log errors but don't fail if let Err(e) = fcntl(&f, FcntlArg::F_SETFL(OFlag::empty())) { @@ -73,7 +81,9 @@ mod platform { pub fn do_syncfs(files: Vec) -> UResult<()> { for path in files { let f = open_and_reset_nonblock(&path)?; - syncfs(f)?; + syncfs(f).map_err_context( + || translate!("sync-error-syncing-file", "file" => path.quote()), + )?; } Ok(()) } @@ -82,7 +92,9 @@ mod platform { pub fn do_fdatasync(files: Vec) -> UResult<()> { for path in files { let f = open_and_reset_nonblock(&path)?; - fdatasync(f)?; + fdatasync(f).map_err_context( + || translate!("sync-error-syncing-file", "file" => path.quote()), + )?; } Ok(()) } diff --git a/tests/by-util/test_sync.rs b/tests/by-util/test_sync.rs index 9c3df3a1e61..fe979cc0df1 100644 --- a/tests/by-util/test_sync.rs +++ b/tests/by-util/test_sync.rs @@ -167,3 +167,24 @@ fn test_sync_multiple_files() { // Sync both files new_ucmd!().arg("--data").arg(&file1).arg(&file2).succeeds(); } + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[test] +fn test_sync_data_fifo_fails_immediately() { + use std::time::Duration; + use uutests::util::TestScenario; + use uutests::util_name; + + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.mkfifo("test-fifo"); + + ts.ucmd() + .arg("--data") + .arg(at.plus_as_string("test-fifo")) + .timeout(Duration::from_secs(2)) + .fails() + .stderr_contains("error syncing") + .stderr_contains("test-fifo") + .stderr_contains("Invalid input"); +}