Skip to content

Commit 78a7482

Browse files
impl posix_fallocate + add tests
1 parent 37c660f commit 78a7482

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

@@ -1202,6 +1203,65 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
12021203
}
12031204
}
12041205

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