Skip to content

Commit ff31ebe

Browse files
authored
Merge pull request #4545 from RalfJung/zst-readwrite
unix read/write: fix zero-size handling
2 parents 2e6f57e + adc5665 commit ff31ebe

File tree

3 files changed

+28
-9
lines changed

3 files changed

+28
-9
lines changed

src/shims/unix/fd.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
264264
return this.set_last_error_and_return(LibcError("EBADF"), dest);
265265
};
266266

267+
// Handle the zero-sized case. The man page says:
268+
// > If count is zero, read() may detect the errors described below. In the absence of any
269+
// > errors, or if read() does not check for errors, a read() with a count of 0 returns zero
270+
// > and has no other effects.
271+
if count == 0 {
272+
this.write_null(dest)?;
273+
return interp_ok(());
274+
}
267275
// Non-deterministically decide to further reduce the count, simulating a partial read (but
268-
// never to 0, that has different behavior).
276+
// never to 0, that would indicate EOF).
269277
let count =
270278
if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
271-
count / 2
279+
count / 2 // since `count` is at least 2, the result is still at least 1
272280
} else {
273281
count
274282
};
@@ -338,8 +346,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
338346
return this.set_last_error_and_return(LibcError("EBADF"), dest);
339347
};
340348

341-
// Non-deterministically decide to further reduce the count, simulating a partial write (but
342-
// never to 0, that has different behavior).
349+
// Handle the zero-sized case. The man page says:
350+
// > If count is zero and fd refers to a regular file, then write() may return a failure
351+
// > status if one of the errors below is detected. If no errors are detected, or error
352+
// > detection is not performed, 0 is returned without causing any other effect. If count
353+
// > is zero and fd refers to a file other than a regular file, the results are not
354+
// > specified.
355+
if count == 0 {
356+
// For now let's not open the can of worms of what exactly "not specified" could mean...
357+
this.write_null(dest)?;
358+
return interp_ok(());
359+
}
360+
// Non-deterministically decide to further reduce the count, simulating a partial write.
361+
// We avoid reducing the write size to 0: the docs seem to be entirely fine with that,
362+
// but the standard library is not (https://github.com/rust-lang/rust/issues/145959).
343363
let count =
344364
if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
345365
count / 2

tests/pass/shims/fs.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ fn test_file() {
7272

7373
// Writing to a file opened for reading should error (and not stop interpretation). std does not
7474
// categorize the error so we don't check for details.
75-
file.write(&[]).unwrap_err();
75+
file.write(&[0]).unwrap_err();
76+
// However, writing 0 bytes can succeed or fail.
77+
let _ignore = file.write(&[]);
7678

7779
// Removing file should succeed.
7880
remove_file(&path).unwrap();

tests/utils/libc.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@ pub unsafe fn write_all(
3434
if res < 0 {
3535
return res;
3636
}
37-
if res == 0 {
38-
// EOF?
39-
break;
40-
}
37+
// Apparently a return value of 0 is just a short write, nothing special (unlike reads).
4138
written_so_far += res as libc::size_t;
4239
}
4340
return written_so_far as libc::ssize_t;

0 commit comments

Comments
 (0)