Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/shims/unix/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,46 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// fadvise is only informational, we can ignore it.
this.write_null(dest)?;
}

"posix_fallocate" => {
// posix_fallocate is not supported by macos.
this.check_target_os(
&[Os::Linux, Os::FreeBsd, Os::Solaris, Os::Illumos, Os::Android],
link_name,
)?;
let [fd, offset, len] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32, libc::off_t, libc::off_t) -> i32),
link_name,
abi,
args,
)?;

let fd = this.read_scalar(fd)?.to_i32()?;
let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
let len = this.read_scalar(len)?.to_int(len.layout.size)?;

let result = this.posix_fallocate(fd, offset, len)?;
this.write_scalar(result, dest)?;
}

"posix_fallocate64" => {
// posix_fallocate64 is only supported on Linux and Android
this.check_target_os(&[Os::Linux, Os::Android], link_name)?;
let [fd, offset, len] = this.check_shim_sig(
shim_sig!(extern "C" fn(i32, libc::off64_t, libc::off64_t) -> i32),
link_name,
abi,
args,
)?;

let fd = this.read_scalar(fd)?.to_i32()?;
let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
let len = this.read_scalar(len)?.to_int(len.layout.size)?;

let result = this.posix_fallocate(fd, offset, len)?;
this.write_scalar(result, dest)?;
}

"realpath" => {
let [path, resolved_path] = this.check_shim_sig(
shim_sig!(extern "C" fn(*const _, *mut _) -> *mut _),
Expand Down
63 changes: 63 additions & 0 deletions src/shims/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,69 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
}

/// NOTE: According to the man page of `possix_fallocate`, it returns the error code instead
/// of setting `errno`.
fn posix_fallocate(
&mut self,
fd_num: i32,
offset: i128,
len: i128,
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();

// Reject if isolation is enabled.
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
this.reject_in_isolation("`posix_fallocate`", reject_with)?;
// Return error code "EBADF" (bad fd).
return interp_ok(this.eval_libc("EBADF"));
}

let Some(fd) = this.machine.fds.get(fd_num) else {
return interp_ok(this.eval_libc("EBADF"));
};

let file = match fd.downcast::<FileHandle>() {
Some(file_handle) => file_handle,
// Man page specifies to return ENODEV if `fd` is not a regular file.
None => return interp_ok(this.eval_libc("ENODEV")),
};

// EINVAL is returned when: "offset was less than 0, or len was less than or equal to 0".
if offset < 0 || len <= 0 {
return interp_ok(this.eval_libc("EINVAL"));
}

if !file.writable {
// The file is not writable.
return interp_ok(this.eval_libc("EBADF"));
}

let current_size = match file.file.metadata() {
Ok(metadata) => metadata.len(),
Err(err) => return this.io_error_to_errnum(err),
};
let new_size = match offset.checked_add(len) {
// u64::from(i128) can fail if:
// - the value is negative, but we checed this above with `offset < 0 || len <= 0`
// - the value is too big/small to fit in u64, this should not happen since the callers
// check if the value is a `i32` or `i64`.
// So this unwrap is safe to do.
Some(size) => u64::try_from(size).unwrap(),
Comment on lines +1246 to +1252
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually unsure if this is allowed in Miri, the callers to check if they fit inside of the layout.size, which can only be libc::off_t(i32) or libc::off64_t(i64).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh so we are adding two i64 here and hence the result fits in a u64?

Is that what the docs say for this function? I would kind of expect that there would be a "too big" error if the result exceeds i64::MAX.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have asked this sooner, excuse me. The docs do not say anything about integer overflows, but it does specify returning "EFBIG" when this size is larger than the maximum file size. I assumed we should thus return "EFBIG" when the addition overflows.

But I don't know how to get the maximum file size in Miri.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the maximum file size is i64::MAX.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then this should be correct right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it seems wrong.

None => return interp_ok(this.eval_libc("EFBIG")), // Size to big
};
// `posix_fallocate` only specifies increasing the file size.
if current_size < new_size {
let result = match file.file.set_len(new_size) {
Ok(()) => Scalar::from_i32(0),
Err(e) => this.io_error_to_errnum(e)?,
};

interp_ok(result)
} else {
interp_ok(Scalar::from_i32(0))
}
}

fn fsync(&mut self, fd_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
// On macOS, `fsync` (unlike `fcntl(F_FULLFSYNC)`) does not wait for the
// underlying disk to finish writing. In the interest of host compatibility,
Expand Down
72 changes: 72 additions & 0 deletions tests/pass-dep/libc/libc-fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ fn main() {
test_posix_realpath_errors();
#[cfg(target_os = "linux")]
test_posix_fadvise();
#[cfg(not(target_os = "macos"))]
test_posix_fallocate::<libc::off_t>(libc::posix_fallocate);
#[cfg(any(target_os = "linux", target_os = "android"))]
test_posix_fallocate::<libc::off64_t>(libc::posix_fallocate64);
#[cfg(target_os = "linux")]
test_sync_file_range();
test_isatty();
Expand Down Expand Up @@ -335,6 +339,74 @@ fn test_posix_fadvise() {
assert_eq!(result, 0);
}

#[cfg(not(target_os = "macos"))]
fn test_posix_fallocate<T: From<i32>>(
posix_fallocate: unsafe extern "C" fn(fd: libc::c_int, offset: T, len: T) -> libc::c_int,
) {
// libc::off_t is i32 in target i686-unknown-linux-gnu
// https://docs.rs/libc/latest/i686-unknown-linux-gnu/libc/type.off_t.html

let test_errors = || {
// invalid fd
let ret = unsafe { posix_fallocate(42, T::from(0), T::from(10)) };
assert_eq!(ret, libc::EBADF);

let path = utils::prepare("miri_test_libc_posix_fallocate_errors.txt");
let file = File::create(&path).unwrap();

// invalid offset
let ret = unsafe { posix_fallocate(file.as_raw_fd(), T::from(-10), T::from(10)) };
assert_eq!(ret, libc::EINVAL);

// invalid len
let ret = unsafe { posix_fallocate(file.as_raw_fd(), T::from(0), T::from(-10)) };
assert_eq!(ret, libc::EINVAL);

// fd not writable
let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
let fd = unsafe { libc::open(c_path.as_ptr(), libc::O_RDONLY) };
let ret = unsafe { posix_fallocate(fd, T::from(0), T::from(10)) };
assert_eq!(ret, libc::EBADF);
};

let test = || {
let bytes = b"hello";
let path = utils::prepare("miri_test_libc_posix_fallocate.txt");
let mut file = File::create(&path).unwrap();
file.write_all(bytes).unwrap();
file.sync_all().unwrap();
assert_eq!(file.metadata().unwrap().len(), 5);

let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed");
let fd = unsafe { libc::open(c_path.as_ptr(), libc::O_RDWR) };

// Allocate to a bigger size from offset 0
let mut res = unsafe { posix_fallocate(fd, T::from(0), T::from(10)) };
assert_eq!(res, 0);
assert_eq!(file.metadata().unwrap().len(), 10);

// Write after allocation
file.write(b"dup").unwrap();
file.sync_all().unwrap();
assert_eq!(file.metadata().unwrap().len(), 10);

// Can't truncate to a smaller size with possix_fallocate
res = unsafe { posix_fallocate(fd, T::from(0), T::from(3)) };
assert_eq!(res, 0);
assert_eq!(file.metadata().unwrap().len(), 10);

// Allocate from offset
res = unsafe { posix_fallocate(fd, T::from(7), T::from(7)) };
assert_eq!(res, 0);
assert_eq!(file.metadata().unwrap().len(), 14);

remove_file(&path).unwrap();
};

test_errors();
test();
}

#[cfg(target_os = "linux")]
fn test_sync_file_range() {
use std::io::Write;
Expand Down