Skip to content

Commit ffd03b3

Browse files
committed
Auto merge of #1418 - divergentdave:file-sync, r=RalfJung
Add file sync shims This PR adds shim implementations for these related file syncing functions. * `fsync`, for POSIX targets, backed by `File::sync_all()` * `fdatasync`, for POSIX targets, backed by `File::sync_data()` * `fcntl` with command `F_FULLFSYNC`, for macOS targets, backed by `File::sync_all()` * `sync_file_range`, for Linux targets, backed by `File::sync_data()`
2 parents 09b5755 + a60c130 commit ffd03b3

File tree

5 files changed

+177
-1
lines changed

5 files changed

+177
-1
lines changed

src/shims/foreign_items/posix.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
146146
// "lseek" is only used on macOS which is 64bit-only, so `i64` always works.
147147
this.write_scalar(Scalar::from_i64(result), dest)?;
148148
}
149+
"fsync" => {
150+
let &[fd] = check_arg_count(args)?;
151+
let result = this.fsync(fd)?;
152+
this.write_scalar(Scalar::from_i32(result), dest)?;
153+
}
154+
"fdatasync" => {
155+
let &[fd] = check_arg_count(args)?;
156+
let result = this.fdatasync(fd)?;
157+
this.write_scalar(Scalar::from_i32(result), dest)?;
158+
}
149159

150160
// Allocation
151161
"posix_memalign" => {

src/shims/foreign_items/posix/linux.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
5454
// fadvise is only informational, we can ignore it.
5555
this.write_null(dest)?;
5656
}
57+
"sync_file_range" => {
58+
let &[fd, offset, nbytes, flags] = check_arg_count(args)?;
59+
let result = this.sync_file_range(fd, offset, nbytes, flags)?;
60+
this.write_scalar(Scalar::from_i32(result), dest)?;
61+
}
5762

5863
// Time related shims
5964
"clock_gettime" => {

src/shims/fs.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,18 @@ impl Default for DirHandler {
234234
}
235235
}
236236

237+
fn maybe_sync_file(file: &File, writable: bool, operation: fn(&File) -> std::io::Result<()>) -> std::io::Result<i32> {
238+
if !writable && cfg!(windows) {
239+
// sync_all() and sync_data() will return an error on Windows hosts if the file is not opened
240+
// for writing. (FlushFileBuffers requires that the file handle have the
241+
// GENERIC_WRITE right)
242+
Ok(0i32)
243+
} else {
244+
let result = operation(file);
245+
result.map(|_| 0i32)
246+
}
247+
}
248+
237249
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
238250
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
239251
fn open(
@@ -377,6 +389,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
377389
fh.insert_fd_with_min_fd(FileHandle { file: duplicated, writable }, start)
378390
});
379391
this.try_unwrap_io_result(fd_result)
392+
} else if this.tcx.sess.target.target.target_os == "macos"
393+
&& cmd == this.eval_libc_i32("F_FULLFSYNC")?
394+
{
395+
let &[_, _] = check_arg_count(args)?;
396+
if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.get(&fd) {
397+
let io_result = maybe_sync_file(file, *writable, File::sync_all);
398+
this.try_unwrap_io_result(io_result)
399+
} else {
400+
this.handle_not_found()
401+
}
380402
} else {
381403
throw_unsup_format!("the {:#x} command is not supported for `fcntl`)", cmd);
382404
}
@@ -1103,6 +1125,77 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
11031125
this.handle_not_found()
11041126
}
11051127
}
1128+
1129+
fn fsync(&mut self, fd_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1130+
// On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
1131+
// underlying disk to finish writing. In the interest of host compatibility,
1132+
// we conservatively implement this with `sync_all`, which
1133+
// *does* wait for the disk.
1134+
1135+
let this = self.eval_context_mut();
1136+
1137+
this.check_no_isolation("fsync")?;
1138+
1139+
let fd = this.read_scalar(fd_op)?.to_i32()?;
1140+
if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.get(&fd) {
1141+
let io_result = maybe_sync_file(file, *writable, File::sync_all);
1142+
this.try_unwrap_io_result(io_result)
1143+
} else {
1144+
this.handle_not_found()
1145+
}
1146+
}
1147+
1148+
fn fdatasync(&mut self, fd_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1149+
let this = self.eval_context_mut();
1150+
1151+
this.check_no_isolation("fdatasync")?;
1152+
1153+
let fd = this.read_scalar(fd_op)?.to_i32()?;
1154+
if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.get(&fd) {
1155+
let io_result = maybe_sync_file(file, *writable, File::sync_data);
1156+
this.try_unwrap_io_result(io_result)
1157+
} else {
1158+
this.handle_not_found()
1159+
}
1160+
}
1161+
1162+
fn sync_file_range(
1163+
&mut self,
1164+
fd_op: OpTy<'tcx, Tag>,
1165+
offset_op: OpTy<'tcx, Tag>,
1166+
nbytes_op: OpTy<'tcx, Tag>,
1167+
flags_op: OpTy<'tcx, Tag>,
1168+
) -> InterpResult<'tcx, i32> {
1169+
let this = self.eval_context_mut();
1170+
1171+
this.check_no_isolation("sync_file_range")?;
1172+
1173+
let fd = this.read_scalar(fd_op)?.to_i32()?;
1174+
let offset = this.read_scalar(offset_op)?.to_i64()?;
1175+
let nbytes = this.read_scalar(nbytes_op)?.to_i64()?;
1176+
let flags = this.read_scalar(flags_op)?.to_i32()?;
1177+
1178+
if offset < 0 || nbytes < 0 {
1179+
let einval = this.eval_libc("EINVAL")?;
1180+
this.set_last_error(einval)?;
1181+
return Ok(-1);
1182+
}
1183+
let allowed_flags = this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_BEFORE")?
1184+
| this.eval_libc_i32("SYNC_FILE_RANGE_WRITE")?
1185+
| this.eval_libc_i32("SYNC_FILE_RANGE_WAIT_AFTER")?;
1186+
if flags & allowed_flags != flags {
1187+
let einval = this.eval_libc("EINVAL")?;
1188+
this.set_last_error(einval)?;
1189+
return Ok(-1);
1190+
}
1191+
1192+
if let Some(FileHandle { file, writable }) = this.machine.file_handler.handles.get(&fd) {
1193+
let io_result = maybe_sync_file(file, *writable, File::sync_data);
1194+
this.try_unwrap_io_result(io_result)
1195+
} else {
1196+
this.handle_not_found()
1197+
}
1198+
}
11061199
}
11071200

11081201
/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when

tests/run-pass/fs.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ fn main() {
1414
test_seek();
1515
test_metadata();
1616
test_file_set_len();
17+
test_file_sync();
1718
test_symlink();
1819
test_errors();
1920
test_rename();
@@ -182,6 +183,23 @@ fn test_file_set_len() {
182183
remove_file(&path).unwrap();
183184
}
184185

186+
fn test_file_sync() {
187+
let bytes = b"Hello, World!\n";
188+
let path = prepare_with_content("miri_test_fs_sync.txt", bytes);
189+
190+
// Test that we can call sync_data and sync_all (can't readily test effects of this operation)
191+
let file = OpenOptions::new().write(true).open(&path).unwrap();
192+
file.sync_data().unwrap();
193+
file.sync_all().unwrap();
194+
195+
// Test that we can call sync_data and sync_all on a file opened for reading.
196+
let file = File::open(&path).unwrap();
197+
file.sync_data().unwrap();
198+
file.sync_all().unwrap();
199+
200+
remove_file(&path).unwrap();
201+
}
202+
185203
fn test_symlink() {
186204
let bytes = b"Hello, World!\n";
187205
let path = prepare_with_content("miri_test_fs_link_target.txt", bytes);

tests/run-pass/libc.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ fn test_posix_fadvise() {
1717
use std::io::Write;
1818
use std::os::unix::io::AsRawFd;
1919

20-
let path = tmp().join("miri_test_libc.txt");
20+
let path = tmp().join("miri_test_libc_posix_fadvise.txt");
2121
// Cleanup before test
2222
remove_file(&path).ok();
2323

@@ -40,6 +40,53 @@ fn test_posix_fadvise() {
4040
assert_eq!(result, 0);
4141
}
4242

43+
#[cfg(target_os = "linux")]
44+
fn test_sync_file_range() {
45+
use std::fs::{remove_file, File};
46+
use std::io::Write;
47+
use std::os::unix::io::AsRawFd;
48+
49+
let path = tmp().join("miri_test_libc_sync_file_range.txt");
50+
// Cleanup before test.
51+
remove_file(&path).ok();
52+
53+
// Write to a file.
54+
let mut file = File::create(&path).unwrap();
55+
let bytes = b"Hello, World!\n";
56+
file.write(bytes).unwrap();
57+
58+
// Test calling sync_file_range on the file.
59+
let result_1 = unsafe {
60+
libc::sync_file_range(
61+
file.as_raw_fd(),
62+
0,
63+
0,
64+
libc::SYNC_FILE_RANGE_WAIT_BEFORE
65+
| libc::SYNC_FILE_RANGE_WRITE
66+
| libc::SYNC_FILE_RANGE_WAIT_AFTER,
67+
)
68+
};
69+
drop(file);
70+
71+
// Test calling sync_file_range on a file opened for reading.
72+
let file = File::open(&path).unwrap();
73+
let result_2 = unsafe {
74+
libc::sync_file_range(
75+
file.as_raw_fd(),
76+
0,
77+
0,
78+
libc::SYNC_FILE_RANGE_WAIT_BEFORE
79+
| libc::SYNC_FILE_RANGE_WRITE
80+
| libc::SYNC_FILE_RANGE_WAIT_AFTER,
81+
)
82+
};
83+
drop(file);
84+
85+
remove_file(&path).unwrap();
86+
assert_eq!(result_1, 0);
87+
assert_eq!(result_2, 0);
88+
}
89+
4390
fn test_mutex_libc_init_recursive() {
4491
unsafe {
4592
let mut attr: libc::pthread_mutexattr_t = std::mem::zeroed();
@@ -169,6 +216,9 @@ fn main() {
169216
#[cfg(target_os = "linux")]
170217
test_posix_fadvise();
171218

219+
#[cfg(target_os = "linux")]
220+
test_sync_file_range();
221+
172222
test_mutex_libc_init_recursive();
173223
test_mutex_libc_init_normal();
174224
test_mutex_libc_init_errorcheck();

0 commit comments

Comments
 (0)