Repository: bytecodealliance/rustix (language: rust)

Issue:
Consider replacing `len: u64` with `len: Option<NonZeroU64>` in `fs::fadvise`
At the moment the [`rustix::fs::fadvise`](https://docs.rs/rustix/latest/rustix/fs/fn.fadvise.html) function uses a `u64` as the type of it's `len` parameter. POSIX, however, specifies for zero to have the special value, meaning the whole file instead of a specific length.

> If len is zero, all data from offset to the largest possible value of the file offset for that file shall be specified.
> -- <cite>[POSIX](https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_fadvise.html)</cite>

> [...] extending for len bytes (or until the end of the file if len is 0) within the file referred to by fd.
> -- <cite>[Linux](https://man7.org/linux/man-pages/man2/posix_fadvise.2.html)

I'm not sure whether rustix usually abstracts over things like that or whether other similar apis simply use a `u64` as a parameter. I would think a `Option<NonZeroU64>`  would make this difference between passing `0` and `5` for example more clear to the reader, without providing a performance degradation.

Otherwise, at least hinting at the possibility of passing `0` for `len` in the documentation would probably be enough for most cases as well.



Discussion / hints:

Fix patch:
diff --git a/CHANGES.md b/CHANGES.md
index 8df8801cf..c8a1de780 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -223,5 +223,12 @@ specific socket types using `From`/`Into`/`TryFrom`/`TryInto` conversions.
 
 [`SocketAddrAny`]: https://docs.rs/rustix/1.0.0/rustix/net/struct.SocketAddrAny.html
 
+The `len` parameter to [`rustix::fs::fadvise`] has changed from `u64` to
+`Option<NonZeroU64>`, to reflect that zero is a special case meaning the
+advice applies to the end of the file. To convert an arbitrary `u64` value to
+`Option<NonZeroU64>`, use `NonZeroU64::new`.
+
+[`rustix::fs::fadvise`]: https://docs.rs/rustix/1.0.0/rustix/fs/fn.fadvise.html
+
 All explicitly deprecated functions and types have been removed. Their
 deprecation messages will have identified alternatives.
diff --git a/src/backend/libc/fs/syscalls.rs b/src/backend/libc/fs/syscalls.rs
index 468b9dfed..74108a7bb 100644
--- a/src/backend/libc/fs/syscalls.rs
+++ b/src/backend/libc/fs/syscalls.rs
@@ -10,17 +10,6 @@ use crate::ffi::CString;
 use crate::ffi::{self, CStr};
 #[cfg(not(any(target_os = "espidf", target_os = "vita")))]
 use crate::fs::Access;
-#[cfg(not(any(
-    apple,
-    netbsdlike,
-    target_os = "solaris",
-    target_os = "dragonfly",
-    target_os = "espidf",
-    target_os = "haiku",
-    target_os = "redox",
-    target_os = "vita",
-)))]
-use crate::fs::Advice;
 #[cfg(not(any(target_os = "espidf", target_os = "redox")))]
 use crate::fs::AtFlags;
 #[cfg(not(any(
@@ -75,6 +64,17 @@ use {
     crate::backend::conv::nonnegative_ret,
     crate::fs::{copyfile_state_t, CloneFlags, CopyfileFlags},
 };
+#[cfg(not(any(
+    apple,
+    netbsdlike,
+    target_os = "solaris",
+    target_os = "dragonfly",
+    target_os = "espidf",
+    target_os = "haiku",
+    target_os = "redox",
+    target_os = "vita",
+)))]
+use {crate::fs::Advice, core::num::NonZeroU64};
 #[cfg(any(apple, linux_kernel, target_os = "hurd"))]
 use {crate::fs::XattrFlags, core::mem::size_of, core::ptr::null_mut};
 #[cfg(linux_kernel)]
@@ -1213,9 +1213,17 @@ pub(crate) fn copy_file_range(
     target_os = "redox",
     target_os = "vita",
 )))]
-pub(crate) fn fadvise(fd: BorrowedFd<'_>, offset: u64, len: u64, advice: Advice) -> io::Result<()> {
+pub(crate) fn fadvise(
+    fd: BorrowedFd<'_>,
+    offset: u64,
+    len: Option<NonZeroU64>,
+    advice: Advice,
+) -> io::Result<()> {
     let offset = offset as i64;
-    let len = len as i64;
+    let len = match len {
+        None => 0,
+        Some(len) => len.get() as i64,
+    };
 
     // Our public API uses `u64` following the [Rust convention], but the
     // underlying host APIs use a signed `off_t`. Converting these values may
diff --git a/src/backend/linux_raw/fs/syscalls.rs b/src/backend/linux_raw/fs/syscalls.rs
index 882a47724..7dd19ab09 100644
--- a/src/backend/linux_raw/fs/syscalls.rs
+++ b/src/backend/linux_raw/fs/syscalls.rs
@@ -41,6 +41,7 @@ use crate::fs::{
 };
 use crate::io;
 use core::mem::MaybeUninit;
+use core::num::NonZeroU64;
 #[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))]
 use linux_raw_sys::general::stat as linux_stat64;
 use linux_raw_sys::general::{
@@ -364,7 +365,17 @@ pub(crate) fn fallocate(
 }
 
 #[inline]
-pub(crate) fn fadvise(fd: BorrowedFd<'_>, pos: u64, len: u64, advice: Advice) -> io::Result<()> {
+pub(crate) fn fadvise(
+    fd: BorrowedFd<'_>,
+    pos: u64,
+    len: Option<NonZeroU64>,
+    advice: Advice,
+) -> io::Result<()> {
+    let len = match len {
+        None => 0,
+        Some(len) => len.get(),
+    };
+
     // On ARM, the arguments are reordered so that the `len` and `pos` argument
     // pairs are aligned. And ARM has a custom syscall code for this.
     #[cfg(target_arch = "arm")]
diff --git a/src/fs/fadvise.rs b/src/fs/fadvise.rs
index f4732d55f..93d8e0810 100644
--- a/src/fs/fadvise.rs
+++ b/src/fs/fadvise.rs
@@ -1,10 +1,13 @@
 use crate::{backend, io};
 use backend::fd::AsFd;
 use backend::fs::types::Advice;
+use core::num::NonZeroU64;
 
 /// `posix_fadvise(fd, offset, len, advice)`—Declares an expected access
 /// pattern for a file.
 ///
+/// If `len` is `None`, the advice extends to the end of the file.
+///
 /// # References
 ///  - [POSIX]
 ///  - [Linux]
@@ -15,6 +18,11 @@ use backend::fs::types::Advice;
 /// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=posix_fadvise&sektion=2
 #[inline]
 #[doc(alias = "posix_fadvise")]
-pub fn fadvise<Fd: AsFd>(fd: Fd, offset: u64, len: u64, advice: Advice) -> io::Result<()> {
+pub fn fadvise<Fd: AsFd>(
+    fd: Fd,
+    offset: u64,
+    len: Option<NonZeroU64>,
+    advice: Advice,
+) -> io::Result<()> {
     backend::fs::syscalls::fadvise(fd.as_fd(), offset, len, advice)
 }
diff --git a/src/net/addr.rs b/src/net/addr.rs
index 8b2aa177c..015997e45 100644
--- a/src/net/addr.rs
+++ b/src/net/addr.rs
@@ -155,7 +155,7 @@ mod tests {
     fn test_layouts() {
         assert_eq_size!(SocketAddrLen, c::socklen_t);
 
-        #[cfg(not(windows))]
+        #[cfg(not(any(windows, target_os = "redox")))]
         assert_eq!(
             memoffset::span_of!(c::msghdr, msg_namelen).len(),
             size_of::<SocketAddrLen>()


Test patch:
diff --git a/tests/fs/file.rs b/tests/fs/file.rs
index 9aa6216df..1c0b11cb9 100644
--- a/tests/fs/file.rs
+++ b/tests/fs/file.rs
@@ -1,3 +1,14 @@
+#[cfg(not(any(
+    apple,
+    netbsdlike,
+    target_os = "solaris",
+    target_os = "dragonfly",
+    target_os = "espidf",
+    target_os = "haiku",
+    target_os = "redox",
+)))]
+use core::num::NonZeroU64;
+
 #[cfg(not(target_os = "redox"))]
 #[test]
 fn test_file() {
@@ -88,9 +99,8 @@ fn test_file() {
         target_os = "dragonfly",
         target_os = "espidf",
         target_os = "haiku",
-        target_os = "redox",
     )))]
-    rustix::fs::fadvise(&file, 0, 10, rustix::fs::Advice::Normal).unwrap();
+    rustix::fs::fadvise(&file, 0, NonZeroU64::new(10), rustix::fs::Advice::Normal).unwrap();
 
     rustix::fs::fsync(&file).unwrap();
 
@@ -99,7 +109,6 @@ fn test_file() {
         target_os = "dragonfly",
         target_os = "espidf",
         target_os = "haiku",
-        target_os = "redox",
     )))]
     rustix::fs::fdatasync(&file).unwrap();
 
@@ -134,7 +143,6 @@ fn test_file() {
         solarish,
         target_os = "haiku",
         target_os = "netbsd",
-        target_os = "redox",
         target_os = "wasi",
     )))]
     {
diff --git a/tests/fs/invalid_offset.rs b/tests/fs/invalid_offset.rs
index 85b964c79..75dfb2ec1 100644
--- a/tests/fs/invalid_offset.rs
+++ b/tests/fs/invalid_offset.rs
@@ -67,6 +67,7 @@ fn invalid_offset_fallocate() {
 )))]
 #[test]
 fn invalid_offset_fadvise() {
+    use core::num::NonZeroU64;
     use rustix::fs::{fadvise, openat, Advice, Mode, OFlags, CWD};
     let tmp = tempfile::tempdir().unwrap();
     let dir = openat(CWD, tmp.path(), OFlags::RDONLY, Mode::empty()).unwrap();
@@ -79,21 +80,63 @@ fn invalid_offset_fadvise() {
     .unwrap();
 
     // `fadvise` never fails on invalid offsets.
-    fadvise(&file, i64::MAX as u64, i64::MAX as u64, Advice::Normal).unwrap();
-    fadvise(&file, u64::MAX, 0, Advice::Normal).unwrap();
-    fadvise(&file, i64::MAX as u64, 1, Advice::Normal).unwrap();
-    fadvise(&file, 1, i64::MAX as u64, Advice::Normal).unwrap();
-    fadvise(&file, i64::MAX as u64 + 1, 0, Advice::Normal).unwrap();
-    fadvise(&file, u64::MAX, i64::MAX as u64, Advice::Normal).unwrap();
+    fadvise(
+        &file,
+        i64::MAX as u64,
+        NonZeroU64::new(i64::MAX as u64),
+        Advice::Normal,
+    )
+    .unwrap();
+    fadvise(&file, u64::MAX, None, Advice::Normal).unwrap();
+    fadvise(&file, i64::MAX as u64, NonZeroU64::new(1), Advice::Normal).unwrap();
+    fadvise(&file, 1, NonZeroU64::new(i64::MAX as u64), Advice::Normal).unwrap();
+    fadvise(&file, i64::MAX as u64 + 1, None, Advice::Normal).unwrap();
+    fadvise(
+        &file,
+        u64::MAX,
+        NonZeroU64::new(i64::MAX as u64),
+        Advice::Normal,
+    )
+    .unwrap();
 
     // `fadvise` fails on invalid lengths.
-    fadvise(&file, u64::MAX, u64::MAX, Advice::Normal).unwrap_err();
-    fadvise(&file, i64::MAX as u64, u64::MAX, Advice::Normal).unwrap_err();
-    fadvise(&file, 0, u64::MAX, Advice::Normal).unwrap_err();
-    fadvise(&file, u64::MAX, i64::MAX as u64 + 1, Advice::Normal).unwrap_err();
-    fadvise(&file, i64::MAX as u64 + 1, u64::MAX, Advice::Normal).unwrap_err();
-    fadvise(&file, i64::MAX as u64, i64::MAX as u64 + 1, Advice::Normal).unwrap_err();
-    fadvise(&file, 0, i64::MAX as u64 + 1, Advice::Normal).unwrap_err();
+    fadvise(&file, u64::MAX, NonZeroU64::new(u64::MAX), Advice::Normal).unwrap_err();
+    fadvise(
+        &file,
+        i64::MAX as u64,
+        NonZeroU64::new(u64::MAX),
+        Advice::Normal,
+    )
+    .unwrap_err();
+    fadvise(&file, 0, NonZeroU64::new(u64::MAX), Advice::Normal).unwrap_err();
+    fadvise(
+        &file,
+        u64::MAX,
+        NonZeroU64::new(i64::MAX as u64 + 1),
+        Advice::Normal,
+    )
+    .unwrap_err();
+    fadvise(
+        &file,
+        i64::MAX as u64 + 1,
+        NonZeroU64::new(u64::MAX),
+        Advice::Normal,
+    )
+    .unwrap_err();
+    fadvise(
+        &file,
+        i64::MAX as u64,
+        NonZeroU64::new(i64::MAX as u64 + 1),
+        Advice::Normal,
+    )
+    .unwrap_err();
+    fadvise(
+        &file,
+        0,
+        NonZeroU64::new(i64::MAX as u64 + 1),
+        Advice::Normal,
+    )
+    .unwrap_err();
 }
 
 #[test]
diff --git a/tests/fs/negative_timestamp.rs b/tests/fs/negative_timestamp.rs
index 84d74c1db..63e2e81b1 100644
--- a/tests/fs/negative_timestamp.rs
+++ b/tests/fs/negative_timestamp.rs
@@ -1,3 +1,4 @@
+#[cfg(not(target_os = "redox"))]
 #[test]
 fn negative_file_timetamp() {
     use rustix::fs::{