Skip to content

Commit a27511f

Browse files
committed
feat(sync): remove unwrap and align with GNU behavior
1 parent 2a314c7 commit a27511f

File tree

3 files changed

+105
-11
lines changed

3 files changed

+105
-11
lines changed

.vscode/cspell.dictionaries/jargon.wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ semiprimes
128128
setcap
129129
setfacl
130130
setfattr
131+
SETFL
131132
shortcode
132133
shortcodes
133134
siginfo

src/uu/sync/src/sync.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,16 @@ static ARG_FILES: &str = "files";
2929

3030
#[cfg(unix)]
3131
mod platform {
32+
#[cfg(any(target_os = "linux", target_os = "android"))]
33+
use nix::fcntl::{FcntlArg, OFlag, fcntl};
3234
use nix::unistd::sync;
3335
#[cfg(any(target_os = "linux", target_os = "android"))]
3436
use nix::unistd::{fdatasync, syncfs};
3537
#[cfg(any(target_os = "linux", target_os = "android"))]
3638
use std::fs::File;
39+
#[cfg(any(target_os = "linux", target_os = "android"))]
40+
use uucore::error::FromIo;
41+
3742
use uucore::error::UResult;
3843

3944
pub fn do_sync() -> UResult<()> {
@@ -44,7 +49,9 @@ mod platform {
4449
#[cfg(any(target_os = "linux", target_os = "android"))]
4550
pub fn do_syncfs(files: Vec<String>) -> UResult<()> {
4651
for path in files {
47-
let f = File::open(path).unwrap();
52+
let f = File::open(&path).map_err_context(|| path.clone())?;
53+
// Reset O_NONBLOCK flag if it was set (matches GNU behavior)
54+
let _ = fcntl(&f, FcntlArg::F_SETFL(OFlag::empty()));
4855
syncfs(f)?;
4956
}
5057
Ok(())
@@ -53,7 +60,9 @@ mod platform {
5360
#[cfg(any(target_os = "linux", target_os = "android"))]
5461
pub fn do_fdatasync(files: Vec<String>) -> UResult<()> {
5562
for path in files {
56-
let f = File::open(path).unwrap();
63+
let f = File::open(&path).map_err_context(|| path.clone())?;
64+
// Reset O_NONBLOCK flag if it was set (matches GNU behavior)
65+
let _ = fcntl(&f, FcntlArg::F_SETFL(OFlag::empty()));
5766
fdatasync(f)?;
5867
}
5968
Ok(())
@@ -157,15 +166,17 @@ mod platform {
157166

158167
pub fn do_syncfs(files: Vec<String>) -> UResult<()> {
159168
for path in files {
160-
flush_volume(
161-
Path::new(&path)
162-
.components()
163-
.next()
164-
.unwrap()
165-
.as_os_str()
166-
.to_str()
167-
.unwrap(),
168-
)?;
169+
let maybe_first = Path::new(&path).components().next();
170+
let vol_name = match maybe_first {
171+
Some(c) => c.as_os_str().to_string_lossy().into_owned(),
172+
None => {
173+
return Err(USimpleError::new(
174+
1,
175+
translate!("sync-error-no-such-file", "file" => path),
176+
));
177+
}
178+
};
179+
flush_volume(&vol_name)?;
169180
}
170181
Ok(())
171182
}

tests/by-util/test_sync.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,85 @@ fn test_sync_no_permission_file() {
9090
ts.ccmd("chmod").arg("0200").arg(f).succeeds();
9191
ts.ucmd().arg(f).succeeds();
9292
}
93+
94+
#[cfg(any(target_os = "linux", target_os = "android"))]
95+
#[test]
96+
fn test_sync_data_nonblock_flag_reset() {
97+
// Test that O_NONBLOCK flag is properly reset when syncing files
98+
// This verifies the fix in commit 05612a4792ba2a5ad509cf995b91e868fc261229
99+
use std::fs::File;
100+
use std::io::Write;
101+
use uutests::util::TestScenario;
102+
use uutests::util_name;
103+
104+
let ts = TestScenario::new(util_name!());
105+
let at = &ts.fixtures;
106+
let test_file = "test_file.txt";
107+
108+
// Create a test file
109+
at.write(test_file, "test content");
110+
111+
// Run sync --data with the file - should succeed
112+
ts.ucmd().arg("--data").arg(test_file).succeeds();
113+
}
114+
115+
#[cfg(any(target_os = "linux", target_os = "android"))]
116+
#[test]
117+
fn test_sync_fs_nonblock_flag_reset() {
118+
// Test that O_NONBLOCK flag is properly reset when syncing filesystems
119+
use std::fs;
120+
use tempfile::tempdir;
121+
122+
let temporary_directory = tempdir().unwrap();
123+
let temporary_path = fs::canonicalize(temporary_directory.path()).unwrap();
124+
125+
// Run sync --file-system with the path - should succeed
126+
new_ucmd!()
127+
.arg("--file-system")
128+
.arg(&temporary_path)
129+
.succeeds();
130+
}
131+
132+
#[cfg(any(target_os = "linux", target_os = "android"))]
133+
#[test]
134+
fn test_sync_fdatasync_error_handling() {
135+
// Test that fdatasync properly handles file opening errors
136+
// This verifies the error handling fix in commit 05612a4792ba2a5ad509cf995b91e868fc261229
137+
new_ucmd!()
138+
.arg("--data")
139+
.arg("/nonexistent/path/to/file")
140+
.fails()
141+
.stderr_contains("error opening");
142+
}
143+
144+
#[cfg(target_os = "macos")]
145+
#[test]
146+
fn test_sync_syncfs_error_handling_macos() {
147+
// Test that syncfs properly handles invalid paths on macOS
148+
// This verifies the error handling fix in commit 05612a4792ba2a5ad509cf995b91e868fc261229
149+
new_ucmd!()
150+
.arg("--file-system")
151+
.arg("/nonexistent/path/to/file")
152+
.fails()
153+
.stderr_contains("error opening");
154+
}
155+
156+
#[test]
157+
fn test_sync_multiple_files() {
158+
// Test syncing multiple files at once
159+
use std::fs;
160+
use tempfile::tempdir;
161+
162+
let temporary_directory = tempdir().unwrap();
163+
let temp_path = temporary_directory.path();
164+
165+
// Create multiple test files
166+
let file1 = temp_path.join("file1.txt");
167+
let file2 = temp_path.join("file2.txt");
168+
169+
fs::write(&file1, "content1").unwrap();
170+
fs::write(&file2, "content2").unwrap();
171+
172+
// Sync both files
173+
new_ucmd!().arg("--data").arg(&file1).arg(&file2).succeeds();
174+
}

0 commit comments

Comments
 (0)