sync: open file with nonblock (#10765)

* open file with nonblock and add test

* fix spellcheck

* remove libc dependency; improve errors

---------

Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
This commit is contained in:
Reuben Wong
2026-02-07 22:52:58 +08:00
committed by GitHub
parent 5648194754
commit fafa77588b
4 changed files with 39 additions and 4 deletions
+1
View File
@@ -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 }
+1
View File
@@ -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 }
+16 -4
View File
@@ -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<File> {
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<String>) -> 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<String>) -> 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(())
}
+21
View File
@@ -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");
}