unix read/write: fix zero-size handling

This commit is contained in:
Ralf Jung 2025-08-28 14:58:44 +02:00
parent f44d957dee
commit 01ed1052d8
3 changed files with 28 additions and 9 deletions

View file

@ -264,11 +264,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
};
// Handle the zero-sized case. The man page says:
// > If count is zero, read() may detect the errors described below. In the absence of any
// > errors, or if read() does not check for errors, a read() with a count of 0 returns zero
// > and has no other effects.
if count == 0 {
this.write_null(dest)?;
return interp_ok(());
}
// Non-deterministically decide to further reduce the count, simulating a partial read (but
// never to 0, that has different behavior).
// never to 0, that would indicate EOF).
let count =
if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
count / 2
count / 2 // since `count` is at least 2, the result is still at least 1
} else {
count
};
@ -338,8 +346,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
return this.set_last_error_and_return(LibcError("EBADF"), dest);
};
// Non-deterministically decide to further reduce the count, simulating a partial write (but
// never to 0, that has different behavior).
// Handle the zero-sized case. The man page says:
// > If count is zero and fd refers to a regular file, then write() may return a failure
// > status if one of the errors below is detected. If no errors are detected, or error
// > detection is not performed, 0 is returned without causing any other effect. If count
// > is zero and fd refers to a file other than a regular file, the results are not
// > specified.
if count == 0 {
// For now let's not open the can of worms of what exactly "not specified" could mean...
this.write_null(dest)?;
return interp_ok(());
}
// Non-deterministically decide to further reduce the count, simulating a partial write.
// We avoid reducing the write size to 0: the docs seem to be entirely fine with that,
// but the standard library is not (https://github.com/rust-lang/rust/issues/145959).
let count =
if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
count / 2

View file

@ -72,7 +72,9 @@ fn test_file() {
// Writing to a file opened for reading should error (and not stop interpretation). std does not
// categorize the error so we don't check for details.
file.write(&[]).unwrap_err();
file.write(&[0]).unwrap_err();
// However, writing 0 bytes can succeed or fail.
let _ignore = file.write(&[]);
// Removing file should succeed.
remove_file(&path).unwrap();

View file

@ -34,10 +34,7 @@ pub unsafe fn write_all(
if res < 0 {
return res;
}
if res == 0 {
// EOF?
break;
}
// Apparently a return value of 0 is just a short write, nothing special (unlike reads).
written_so_far += res as libc::size_t;
}
return written_so_far as libc::ssize_t;