Skip to content

Commit c16dcc7

Browse files
authored
Fix the fadvise handling of large offsets on FreeBSD. (#1318)
* Fix the `fadvise` handling of large offsets on FreeBSD. On FreeBSD, when a large `u64` converts to a negative offset, we add some special-case behavior to hide the fact that this conversion is happening. Clarify the comments, update the code to do a proper no-op in the case of a negative offset, and use the correct length in the case of an overflowing offset+length. * It appears FreeBSD doesn't like it when offset+len is i64::MAX+1. * Handle negative lengths.
1 parent a876d83 commit c16dcc7

2 files changed

Lines changed: 29 additions & 7 deletions

File tree

src/backend/libc/fs/syscalls.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,15 +1217,35 @@ pub(crate) fn fadvise(fd: BorrowedFd<'_>, offset: u64, len: u64, advice: Advice)
12171217
let offset = offset as i64;
12181218
let len = len as i64;
12191219

1220-
// FreeBSD returns `EINVAL` on invalid offsets; emulate the POSIX behavior.
1220+
// Our public API uses `u64` following the [Rust convention], but the
1221+
// underlying host APIs use a signed `off_t`. Converting these values may
1222+
// turn a very large value into a negative value.
1223+
//
1224+
// On FreeBSD, this could cause `posix_fadvise` to fail with
1225+
// `Errrno::INVAL`. Because we don't expose the signed type in our API, we
1226+
// also avoid exposing this artifact of casting an unsigned value to the
1227+
// signed type. To do this, we use a no-op call in this case.
1228+
//
1229+
// [Rust convention]: https://doc.rust-lang.org/stable/std/io/enum.SeekFrom.html#variant.Start
12211230
#[cfg(target_os = "freebsd")]
1222-
let offset = if (offset as i64) < 0 {
1223-
i64::MAX
1224-
} else {
1225-
offset
1226-
};
1231+
if offset < 0 {
1232+
if len < 0 {
1233+
return Err(io::Errno::INVAL);
1234+
}
1235+
1236+
return fadvise_noop(fd);
1237+
1238+
#[cold]
1239+
fn fadvise_noop(fd: BorrowedFd<'_>) -> io::Result<()> {
1240+
// Us an `fcntl` to report `Errno::EBADF` if needed, but otherwise
1241+
// do nothing.
1242+
fcntl_getfl(fd).map(|_| ())
1243+
}
1244+
}
12271245

1228-
// FreeBSD returns `EINVAL` on overflow; emulate the POSIX behavior.
1246+
// Similarly, on FreeBSD, if `offset + len` would overflow an `off_t` in
1247+
// a way that users using a `u64` interface wouldn't be aware of, reduce
1248+
// the length so that we only operate on the range that doesn't overflow.
12291249
#[cfg(target_os = "freebsd")]
12301250
let len = if len > 0 && offset.checked_add(len).is_none() {
12311251
i64::MAX - offset

src/fs/fadvise.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ use backend::fs::types::Advice;
88
/// # References
99
/// - [POSIX]
1010
/// - [Linux]
11+
/// - [FreeBSD]
1112
///
1213
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_fadvise.html
1314
/// [Linux]: https://man7.org/linux/man-pages/man2/posix_fadvise.2.html
15+
/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=posix_fadvise&sektion=2
1416
#[inline]
1517
#[doc(alias = "posix_fadvise")]
1618
pub fn fadvise<Fd: AsFd>(fd: Fd, offset: u64, len: u64, advice: Advice) -> io::Result<()> {

0 commit comments

Comments
 (0)