Skip to content

Commit 3626dff

Browse files
impl posix_fallocate + add tests
1 parent 3ab6735 commit 3626dff

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

src/shims/unix/foreign_items.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,41 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
486486
// fadvise is only informational, we can ignore it.
487487
this.write_null(dest)?;
488488
}
489+
490+
// only macos doesn't support `posix_fallocate`
491+
"posix_fallocate" if &*this.tcx.sess.target.os != "macos" => {
492+
let [fd, offset, len] = this.check_shim_sig(
493+
shim_sig!(extern "C" fn(i32, libc::off_t, libc::off_t) -> i32),
494+
link_name,
495+
abi,
496+
args,
497+
)?;
498+
499+
let fd = this.read_scalar(fd)?.to_i32()?;
500+
let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
501+
let len = this.read_scalar(len)?.to_int(len.layout.size)?;
502+
503+
let result = this.posix_fallocate(fd, offset, len)?;
504+
this.write_scalar(result, dest)?;
505+
}
506+
507+
// only macos doesn't support `posix_fallocate`
508+
"posix_fallocate64" if &*this.tcx.sess.target.os != "macos" => {
509+
let [fd, offset, len] = this.check_shim_sig(
510+
shim_sig!(extern "C" fn(i32, libc::off64_t, libc::off64_t) -> i32),
511+
link_name,
512+
abi,
513+
args,
514+
)?;
515+
516+
let fd = this.read_scalar(fd)?.to_i32()?;
517+
let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
518+
let len = this.read_scalar(len)?.to_int(len.layout.size)?;
519+
520+
let result = this.posix_fallocate(fd, offset, len)?;
521+
this.write_scalar(result, dest)?;
522+
}
523+
489524
"realpath" => {
490525
let [path, resolved_path] = this.check_shim_sig(
491526
shim_sig!(extern "C" fn(*const _, *mut _) -> *mut _),

src/shims/unix/fs.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::fs::{
66
remove_file, rename,
77
};
88
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
9+
use std::os::unix::fs::MetadataExt;
910
use std::path::{Path, PathBuf};
1011
use std::time::SystemTime;
1112

@@ -1197,6 +1198,65 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
11971198
}
11981199
}
11991200

1201+
fn posix_fallocate(
1202+
&mut self,
1203+
fd_num: i32,
1204+
offset: i128,
1205+
len: i128,
1206+
) -> InterpResult<'tcx, Scalar> {
1207+
let this = self.eval_context_mut();
1208+
1209+
// According to the man page of `possix_fallocate`, it returns the error code instead
1210+
// of setting `errno`.
1211+
let ebadf = Scalar::from_i32(this.eval_libc_i32("EBADF"));
1212+
let einval = Scalar::from_i32(this.eval_libc_i32("EINVAL"));
1213+
let enodev = Scalar::from_i32(this.eval_libc_i32("ENODEV"));
1214+
1215+
// Reject if isolation is enabled.
1216+
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1217+
this.reject_in_isolation("`posix_fallocate`", reject_with)?;
1218+
// Set error code as "EBADF" (bad fd)
1219+
return interp_ok(ebadf);
1220+
}
1221+
1222+
let Some(fd) = this.machine.fds.get(fd_num) else {
1223+
return interp_ok(ebadf);
1224+
};
1225+
1226+
let file = match fd.downcast::<FileHandle>() {
1227+
Some(file_handle) => file_handle,
1228+
// Man page specifies to return ENODEV if `fd` is not a regular file.
1229+
None => return interp_ok(enodev),
1230+
};
1231+
1232+
// EINVAL is returned when: "offset was less than 0, or len was less than or equal to 0".
1233+
if offset < 0 || len <= 0 {
1234+
return interp_ok(einval);
1235+
}
1236+
1237+
if file.writable {
1238+
let current_size = match file.file.metadata() {
1239+
Ok(metadata) => metadata.size(),
1240+
Err(err) => return this.io_error_to_errnum(err),
1241+
};
1242+
let new_size = match offset.checked_add(len) {
1243+
Some(size) => size.try_into().unwrap(), // We just checked negative `offset` and `len`.
1244+
None => return interp_ok(einval),
1245+
};
1246+
// `posix_fallocate` only specifies increasing the file size.
1247+
if current_size < new_size {
1248+
let result = file.file.set_len(new_size);
1249+
let result = this.try_unwrap_io_result(result.map(|_| 0i32))?;
1250+
interp_ok(Scalar::from_i32(result))
1251+
} else {
1252+
interp_ok(Scalar::from_i32(0))
1253+
}
1254+
} else {
1255+
// The file is not writable.
1256+
interp_ok(ebadf)
1257+
}
1258+
}
1259+
12001260
fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
12011261
// On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
12021262
// underlying disk to finish writing. In the interest of host compatibility,

tests/pass-dep/libc/libc-fs.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ fn main() {
3737
#[cfg(target_os = "linux")]
3838
test_posix_fadvise();
3939
#[cfg(target_os = "linux")]
40+
test_posix_fallocate::<libc::off_t>(libc::posix_fallocate);
41+
#[cfg(target_os = "linux")]
42+
test_posix_fallocate::<libc::off64_t>(libc::posix_fallocate64);
43+
#[cfg(target_os = "linux")]
4044
test_sync_file_range();
4145
test_isatty();
4246
test_read_and_uninit();
@@ -335,6 +339,74 @@ fn test_posix_fadvise() {
335339
assert_eq!(result, 0);
336340
}
337341

342+
#[cfg(target_os = "linux")]
343+
fn test_posix_fallocate<T: From<i32>>(
344+
posix_fallocate: unsafe extern "C" fn(fd: libc::c_int, offset: T, len: T) -> libc::c_int,
345+
) {
346+
// libc::off_t is i32 in target i686-unknown-linux-gnu
347+
// https://docs.rs/libc/latest/i686-unknown-linux-gnu/libc/type.off_t.html
348+
349+
let test_errors = || {
350+
// invalid fd
351+
let ret = unsafe { posix_fallocate(42, T::from(0), T::from(10)) };
352+
assert_eq!(ret, libc::EBADF);
353+
354+
let path = utils::prepare("miri_test_libc_possix_fallocate_errors.txt");
355+
let file = File::create(&path).unwrap();
356+
357+
// invalid offset
358+
let ret = unsafe { posix_fallocate(file.as_raw_fd(), T::from(-10), T::from(10)) };
359+
assert_eq!(ret, libc::EINVAL);
360+
361+
// invalid len
362+
let ret = unsafe { posix_fallocate(file.as_raw_fd(), T::from(0), T::from(-10)) };
363+
assert_eq!(ret, libc::EINVAL);
364+
365+
// fd not writable
366+
let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
367+
let fd = unsafe { libc::open(c_path.as_ptr(), libc::O_RDONLY) };
368+
let ret = unsafe { posix_fallocate(fd, T::from(0), T::from(10)) };
369+
assert_eq!(ret, libc::EBADF);
370+
};
371+
372+
let test = || {
373+
let bytes = b"hello";
374+
let path = utils::prepare("miri_test_libc_fs_ftruncate.txt");
375+
let mut file = File::create(&path).unwrap();
376+
file.write_all(bytes).unwrap();
377+
file.sync_all().unwrap();
378+
assert_eq!(file.metadata().unwrap().len(), 5);
379+
380+
let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
381+
let fd = unsafe { libc::open(c_path.as_ptr(), libc::O_RDWR) };
382+
383+
// Allocate to a bigger size from offset 0
384+
let mut res = unsafe { posix_fallocate(fd, T::from(0), T::from(10)) };
385+
assert_eq!(res, 0);
386+
assert_eq!(file.metadata().unwrap().len(), 10);
387+
388+
// Write after allocation
389+
file.write(b"dup").unwrap();
390+
file.sync_all().unwrap();
391+
assert_eq!(file.metadata().unwrap().len(), 10);
392+
393+
// Can't truncate to a smaller size with possix_fallocate
394+
res = unsafe { posix_fallocate(fd, T::from(0), T::from(3)) };
395+
assert_eq!(res, 0);
396+
assert_eq!(file.metadata().unwrap().len(), 10);
397+
398+
// Allocate from offset
399+
res = unsafe { posix_fallocate(fd, T::from(7), T::from(7)) };
400+
assert_eq!(res, 0);
401+
assert_eq!(file.metadata().unwrap().len(), 14);
402+
403+
remove_file(&path).unwrap();
404+
};
405+
406+
test_errors();
407+
test();
408+
}
409+
338410
#[cfg(target_os = "linux")]
339411
fn test_sync_file_range() {
340412
use std::io::Write;

0 commit comments

Comments
 (0)