Repository: Stebalien/xattr (language: rust)

Issue:
Port to use rustix
`rustix` is a safe wrapper around raw system calls on Linux and `libc` on other platforms. It supports I/O safety out of the box, and using raw system calls makes it faster than `libc` in many cases. It would be nice if this crate could use `rustix`.

I can write a PR for this; however since this crate uses `AsRawFd` instead of `AsFd` it would be a breaking change. Let me know if this is desired/


Discussion / hints:
It should be possible without a breaking change. Basically:

1. Call `self.as_raw_fd()`.
2. Call `BorrowedFd::borrow_raw(my_fd)`.
3. Pass the `BorrowedFd` into the rustix function (it implements `AsFd`).

I'd like to consider reworking the extension trait anyways, but that's a larger change.
So yes, I'd be interested if you can make it a non-breaking change. Otherwise, I'll still consider it, but I'd probably want to take the chance to make other braking changes at the same time.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 0c55ed19..ff0a6cf5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,8 +20,19 @@ default = ["unsupported"]
 # platforms. This would make sense if you absolutely need xattr support.
 unsupported = []
 
-[dependencies]
-libc = "0.2.147"
+[dependencies.rustix]
+version = "0.38.25"
+default-features = false
+features = ["fs", "std"]
+
+# Enable the `std` feature here so that `std::os::raw` is used.
+[target.'cfg(target_os = "linux")'.dependencies.linux-raw-sys]
+version = "0.4.11"
+default-features = false
+features = ["std"]
+
+[target.'cfg(any(target_os = "macos", target_os = "freebsd", target_os = "netbsd"))'.dependencies]
+libc = "0.2.150"
 
 [dev-dependencies]
 tempfile = "3"
diff --git a/src/lib.rs b/src/lib.rs
index 0bfa2569..a5ffe063 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -33,7 +33,7 @@ mod util;
 use std::ffi::OsStr;
 use std::fs::File;
 use std::io;
-use std::os::unix::io::AsRawFd;
+use std::os::unix::io::{AsRawFd, BorrowedFd};
 use std::path::Path;
 
 pub use error::UnsupportedPlatformError;
@@ -84,7 +84,9 @@ pub trait FileExt: AsRawFd {
     where
         N: AsRef<OsStr>,
     {
-        util::extract_noattr(sys::get_fd(self.as_raw_fd(), name.as_ref()))
+        // SAFETY: Implement I/O safety later.
+        let fd = unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) };
+        util::extract_noattr(sys::get_fd(fd, name.as_ref()))
     }
 
     /// Set an extended attribute on the specified file.
@@ -92,7 +94,8 @@ pub trait FileExt: AsRawFd {
     where
         N: AsRef<OsStr>,
     {
-        sys::set_fd(self.as_raw_fd(), name.as_ref(), value)
+        let fd = unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) };
+        sys::set_fd(fd, name.as_ref(), value)
     }
 
     /// Remove an extended attribute from the specified file.
@@ -100,7 +103,8 @@ pub trait FileExt: AsRawFd {
     where
         N: AsRef<OsStr>,
     {
-        sys::remove_fd(self.as_raw_fd(), name.as_ref())
+        let fd = unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) };
+        sys::remove_fd(fd, name.as_ref())
     }
 
     /// List extended attributes attached to the specified file.
@@ -108,7 +112,8 @@ pub trait FileExt: AsRawFd {
     /// Note: this may not list *all* attributes. Speficially, it definitely won't list any trusted
     /// attributes unless you are root and it may not list system attributes.
     fn list_xattr(&self) -> io::Result<XAttrs> {
-        sys::list_fd(self.as_raw_fd())
+        let fd = unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) };
+        sys::list_fd(fd)
     }
 }
 
diff --git a/src/sys/bsd.rs b/src/sys/bsd.rs
index 612e1e85..322329e7 100644
--- a/src/sys/bsd.rs
+++ b/src/sys/bsd.rs
@@ -4,8 +4,9 @@ use std::ffi::{CString, OsStr, OsString};
 use std::io;
 use std::mem;
 use std::os::unix::ffi::{OsStrExt, OsStringExt};
-use std::os::unix::io::RawFd;
+use std::os::unix::io::{BorrowedFd, AsRawFd};
 use std::path::Path;
+use std::ptr;
 
 use libc::{c_int, c_void, size_t, EPERM};
 
@@ -15,16 +16,35 @@ use libc::{
     EXTATTR_NAMESPACE_USER,
 };
 
-use crate::util::{allocate_loop, path_to_c};
+use crate::util::allocate_loop;
 
-const EXTATTR_NAMESPACE_USER_STRING: &'static str = "user";
-const EXTATTR_NAMESPACE_SYSTEM_STRING: &'static str = "system";
-const EXTATTR_NAMESPACE_NAMES: [&'static str; 3] = [
+const EXTATTR_NAMESPACE_USER_STRING: &str = "user";
+const EXTATTR_NAMESPACE_SYSTEM_STRING: &str = "system";
+const EXTATTR_NAMESPACE_NAMES: [&str; 3] = [
     "empty",
     EXTATTR_NAMESPACE_USER_STRING,
     EXTATTR_NAMESPACE_SYSTEM_STRING,
 ];
 
+fn path_to_c(path: &Path) -> io::Result<CString> {
+    match CString::new(path.as_os_str().as_bytes()) {
+        Ok(name) => Ok(name),
+        Err(_) => Err(io::Error::new(io::ErrorKind::NotFound, "file not found")),
+    }
+}
+
+#[inline]
+fn slice_parts(buf: &mut [u8]) -> (*mut c_void, size_t) {
+    if buf.is_empty() {
+        (ptr::null_mut(), 0)
+    } else {
+        (
+            buf.as_mut_ptr().cast(),
+            buf.len() as size_t
+        )
+    }
+}
+
 /// An iterator over a set of extended attributes names.
 pub struct XAttrs {
     user_attrs: Box<[u8]>,
@@ -145,20 +165,29 @@ fn prefix_namespace(attr: &OsStr, ns: c_int) -> OsString {
     OsString::from_vec(v)
 }
 
-pub fn get_fd(fd: RawFd, name: &OsStr) -> io::Result<Vec<u8>> {
+fn cvt(res: libc::ssize_t) -> io::Result<usize> {
+    if res < 0 {
+        Err(io::Error::last_os_error())
+    } else {
+        Ok(res as usize)
+    }
+}
+
+pub fn get_fd(fd: BorrowedFd<'_>, name: &OsStr) -> io::Result<Vec<u8>> {
     let (ns, name) = name_to_ns(name)?;
     unsafe {
-        allocate_loop(|buf, len| {
-            extattr_get_fd(fd, ns, name.as_ptr(), buf as *mut c_void, len as size_t)
+        allocate_loop(|buf| {
+            let (ptr, len) = slice_parts(buf);
+            cvt(extattr_get_fd(fd.as_raw_fd(), ns, name.as_ptr(), ptr, len))
         })
     }
 }
 
-pub fn set_fd(fd: RawFd, name: &OsStr, value: &[u8]) -> io::Result<()> {
+pub fn set_fd(fd: BorrowedFd<'_>, name: &OsStr, value: &[u8]) -> io::Result<()> {
     let (ns, name) = name_to_ns(name)?;
     let ret = unsafe {
         extattr_set_fd(
-            fd,
+            fd.as_raw_fd(),
             ns,
             name.as_ptr(),
             value.as_ptr() as *const c_void,
@@ -172,9 +201,9 @@ pub fn set_fd(fd: RawFd, name: &OsStr, value: &[u8]) -> io::Result<()> {
     }
 }
 
-pub fn remove_fd(fd: RawFd, name: &OsStr) -> io::Result<()> {
+pub fn remove_fd(fd: BorrowedFd<'_>, name: &OsStr) -> io::Result<()> {
     let (ns, name) = name_to_ns(name)?;
-    let ret = unsafe { extattr_delete_fd(fd, ns, name.as_ptr()) };
+    let ret = unsafe { extattr_delete_fd(fd.as_raw_fd(), ns, name.as_ptr()) };
     if ret != 0 {
         Err(io::Error::last_os_error())
     } else {
@@ -182,15 +211,15 @@ pub fn remove_fd(fd: RawFd, name: &OsStr) -> io::Result<()> {
     }
 }
 
-pub fn list_fd(fd: RawFd) -> io::Result<XAttrs> {
+pub fn list_fd(fd: BorrowedFd<'_>) -> io::Result<XAttrs> {
     let sysvec = unsafe {
-        let res = allocate_loop(|buf, len| {
-            extattr_list_fd(
-                fd,
+        let res = allocate_loop(|buf| {
+            let (ptr, len) = slice_parts(buf);
+            cvt(extattr_list_fd(
+                fd.as_raw_fd(),
                 EXTATTR_NAMESPACE_SYSTEM,
-                buf as *mut c_void,
-                len as size_t,
-            )
+                ptr, len
+            ))
         });
         // On FreeBSD, system attributes require root privileges to view. However,
         // to mimic the behavior of listxattr in linux and osx, we need to query
@@ -208,13 +237,13 @@ pub fn list_fd(fd: RawFd) -> io::Result<XAttrs> {
     };
 
     let uservec = unsafe {
-        let res = allocate_loop(|buf, len| {
-            extattr_list_fd(
-                fd,
+        let res = allocate_loop(|buf| {
+            let (ptr, len) = slice_parts(buf);
+            cvt(extattr_list_fd(
+                fd.as_raw_fd(),
                 EXTATTR_NAMESPACE_USER,
-                buf as *mut c_void,
-                len as size_t,
-            )
+                ptr, len
+            ))
         });
         match res {
             Ok(v) => v,
@@ -233,14 +262,14 @@ pub fn get_path(path: &Path, name: &OsStr) -> io::Result<Vec<u8>> {
     let (ns, name) = name_to_ns(name)?;
     let path = path_to_c(path)?;
     unsafe {
-        allocate_loop(|buf, len| {
-            extattr_get_link(
+        allocate_loop(|buf| {
+            let (ptr, len) = slice_parts(buf);
+            cvt(extattr_get_link(
                 path.as_ptr(),
                 ns,
                 name.as_ptr(),
-                buf as *mut c_void,
-                len as size_t,
-            )
+                ptr, len
+            ))
         })
     }
 }
@@ -278,13 +307,13 @@ pub fn remove_path(path: &Path, name: &OsStr) -> io::Result<()> {
 pub fn list_path(path: &Path) -> io::Result<XAttrs> {
     let path = path_to_c(path)?;
     let sysvec = unsafe {
-        let res = allocate_loop(|buf, len| {
-            extattr_list_link(
+        let res = allocate_loop(|buf| {
+            let (ptr, len) = slice_parts(buf);
+            cvt(extattr_list_link(
                 path.as_ptr(),
                 EXTATTR_NAMESPACE_SYSTEM,
-                buf as *mut c_void,
-                len as size_t,
-            )
+                ptr, len
+            ))
         });
         // On FreeBSD, system attributes require root privileges to view. However,
         // to mimic the behavior of listxattr in linux and osx, we need to query
@@ -302,13 +331,13 @@ pub fn list_path(path: &Path) -> io::Result<XAttrs> {
     };
 
     let uservec = unsafe {
-        let res = allocate_loop(|buf, len| {
-            extattr_list_link(
+        let res = allocate_loop(|buf| {
+            let (ptr, len) = slice_parts(buf);
+            cvt(extattr_list_link(
                 path.as_ptr(),
                 EXTATTR_NAMESPACE_USER,
-                buf as *mut c_void,
-                len as size_t,
-            )
+                ptr, len
+            ))
         });
         match res {
             Ok(v) => v,
diff --git a/src/sys/linux_macos.rs b/src/sys/linux_macos.rs
new file mode 100644
index 00000000..34a2bbff
--- /dev/null
+++ b/src/sys/linux_macos.rs
@@ -0,0 +1,151 @@
+use std::ffi::{OsStr, OsString};
+use std::io;
+use std::mem;
+use std::os::unix::ffi::OsStrExt;
+use std::os::unix::io::BorrowedFd;
+use std::path::Path;
+
+use rustix::fs as rfs;
+use rustix::path::Arg;
+
+use crate::util::allocate_loop;
+
+use std::os::raw::c_char;
+
+// Convert an `&mut [u8]` to an `&mut [c_char]`
+#[inline]
+fn as_listxattr_buffer(buf: &mut [u8]) -> &mut [c_char] {
+    // SAFETY: u8 and i8 have the same size and alignment
+    unsafe {
+        &mut *(buf as *mut [u8] as *mut [c_char])
+    }
+}
+
+/// An iterator over a set of extended attributes names.
+pub struct XAttrs {
+    data: Box<[u8]>,
+    offset: usize,
+}
+
+impl Clone for XAttrs {
+    fn clone(&self) -> Self {
+        XAttrs {
+            data: Vec::from(&*self.data).into_boxed_slice(),
+            offset: self.offset,
+        }
+    }
+    fn clone_from(&mut self, other: &XAttrs) {
+        self.offset = other.offset;
+
+        let mut data = mem::replace(&mut self.data, Box::new([])).into_vec();
+        data.extend(other.data.iter().cloned());
+        self.data = data.into_boxed_slice();
+    }
+}
+
+// Yes, I could avoid these allocations on linux/macos. However, if we ever want to be freebsd
+// compatible, we need to be able to prepend the namespace to the extended attribute names.
+// Furthermore, borrowing makes the API messy.
+impl Iterator for XAttrs {
+    type Item = OsString;
+    fn next(&mut self) -> Option<OsString> {
+        let data = &self.data[self.offset..];
+        if data.is_empty() {
+            None
+        } else {
+            // always null terminated (unless empty).
+            let end = data.iter().position(|&b| b == 0u8).unwrap();
+            self.offset += end + 1;
+            Some(OsStr::from_bytes(&data[..end]).to_owned())
+        }
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        if self.data.len() == self.offset {
+            (0, Some(0))
+        } else {
+            (1, None)
+        }
+    }
+}
+
+pub fn get_fd(fd: BorrowedFd<'_>, name: &OsStr) -> io::Result<Vec<u8>> {
+    allocate_loop(|buf| {
+        rfs::fgetxattr(fd, name, buf)
+    })
+}
+
+pub fn set_fd(fd: BorrowedFd<'_>, name: &OsStr, value: &[u8]) -> io::Result<()> {
+    rfs::fsetxattr(fd, name, value, rfs::XattrFlags::empty())?;
+    Ok(())
+}
+
+pub fn remove_fd(fd: BorrowedFd<'_>, name: &OsStr) -> io::Result<()> {
+    rfs::fremovexattr(fd, name)?;
+    Ok(())
+}
+
+pub fn list_fd(fd: BorrowedFd<'_>) -> io::Result<XAttrs> {
+    let vec = allocate_loop(|buf| {
+        rfs::flistxattr(fd, as_listxattr_buffer(buf))
+    })?;
+    Ok(XAttrs {
+        data: vec.into_boxed_slice(),
+        offset: 0,
+    })
+}
+
+pub fn get_path(path: &Path, name: &OsStr) -> io::Result<Vec<u8>> {
+    let path = path.into_c_str()?;
+    let name = name.into_c_str()?;
+
+    allocate_loop(|buf| {
+        #[cfg(target_os = "macos")]
+        {
+            // If an empty slice is passed to lgetxattr on macOS, it returns an error.
+            // Might be a macOS bug, so work around it here by calling the libc manually.
+            if buf.is_empty() {
+                let ret = unsafe {
+                    libc::getxattr(
+                        (&*path).as_ptr(),
+                        (&*name).as_ptr(),
+                        std::ptr::null_mut(),
+                        0,
+                        0,
+                        libc::XATTR_NOFOLLOW
+                    )
+                };
+
+                if ret < 0 {
+                    return Err(io::Error::last_os_error());
+                } else {
+                    return Ok(ret as usize);
+                }
+            }
+        }
+
+        let size = rfs::lgetxattr(&*path, &*name, buf)?;
+        io::Result::Ok(size)
+    })
+}
+
+pub fn set_path(path: &Path, name: &OsStr, value: &[u8]) -> io::Result<()> {
+    rfs::lsetxattr(path, name, value, rfs::XattrFlags::empty())?;
+    Ok(())
+}
+
+pub fn remove_path(path: &Path, name: &OsStr) -> io::Result<()> {
+    rfs::lremovexattr(path, name)?;
+    Ok(())
+}
+
+pub fn list_path(path: &Path) -> io::Result<XAttrs> {
+    let path = path.as_cow_c_str()?;
+    let vec = allocate_loop(|buf| {
+        rfs::llistxattr(&*path, as_listxattr_buffer(buf))
+    })?;
+    Ok(XAttrs {
+        data: vec.into_boxed_slice(),
+        offset: 0,
+    })
+}
diff --git a/src/sys/linux_macos/linux.rs b/src/sys/linux_macos/linux.rs
deleted file mode 100644
index 0d1581d0..00000000
--- a/src/sys/linux_macos/linux.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use libc::{c_char, c_int, c_void, size_t};
-pub use libc::{fgetxattr, flistxattr, fremovexattr, lgetxattr, llistxattr,
-           lremovexattr};
-
-pub unsafe fn fsetxattr(
-    fd: c_int,
-    name: *const c_char,
-    value: *const c_void,
-    size: size_t,
-) -> c_int {
-    use libc::fsetxattr;
-    fsetxattr(fd, name, value, size, 0)
-}
-
-pub unsafe fn lsetxattr(
-    path: *const c_char,
-    name: *const c_char,
-    value: *const c_void,
-    size: size_t,
-) -> c_int {
-    use libc::lsetxattr;
-    lsetxattr(path, name, value, size, 0)
-}
diff --git a/src/sys/linux_macos/macos.rs b/src/sys/linux_macos/macos.rs
deleted file mode 100644
index ab94e87a..00000000
--- a/src/sys/linux_macos/macos.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-use libc::{c_char, c_int, c_void, size_t, ssize_t};
-
-const XATTR_NOFOLLOW: c_int = 0x0001;
-
-#[inline(always)]
-pub unsafe fn fremovexattr(fd: c_int, name: *const c_char) -> c_int {
-    use libc::fremovexattr;
-    fremovexattr(fd, name, 0)
-}
-
-#[inline(always)]
-pub unsafe fn fsetxattr(
-    fd: c_int,
-    name: *const c_char,
-    value: *const c_void,
-    size: size_t,
-) -> c_int {
-    use libc::fsetxattr;
-    fsetxattr(fd, name, value, size, 0, 0)
-}
-
-#[inline(always)]
-pub unsafe fn fgetxattr(
-    fd: c_int,
-    name: *const c_char,
-    value: *mut c_void,
-    size: size_t,
-) -> ssize_t {
-    use libc::fgetxattr;
-    fgetxattr(fd, name, value, size, 0, 0)
-}
-
-#[inline(always)]
-pub unsafe fn flistxattr(fd: c_int, buf: *mut c_char, size: size_t) -> ssize_t {
-    use libc::flistxattr;
-    flistxattr(fd, buf, size, 0)
-}
-
-#[inline(always)]
-pub unsafe fn lremovexattr(path: *const c_char, name: *const c_char) -> c_int {
-    use libc::removexattr;
-    removexattr(path, name, XATTR_NOFOLLOW)
-}
-
-#[inline(always)]
-pub unsafe fn lsetxattr(
-    path: *const c_char,
-    name: *const c_char,
-    value: *const c_void,
-    size: size_t,
-) -> c_int {
-    use libc::setxattr;
-    setxattr(path, name, value, size, 0, XATTR_NOFOLLOW)
-}
-
-#[inline(always)]
-pub unsafe fn lgetxattr(
-    path: *const c_char,
-    name: *const c_char,
-    value: *mut c_void,
-    size: size_t,
-) -> ssize_t {
-    use libc::getxattr;
-    getxattr(path, name, value, size, 0, XATTR_NOFOLLOW)
-}
-
-#[inline(always)]
-pub unsafe fn llistxattr(path: *const c_char, buf: *mut c_char, size: size_t) -> ssize_t {
-    use libc::listxattr;
-    listxattr(path, buf, size, XATTR_NOFOLLOW)
-}
diff --git a/src/sys/linux_macos/mod.rs b/src/sys/linux_macos/mod.rs
deleted file mode 100644
index 7cca3e08..00000000
--- a/src/sys/linux_macos/mod.rs
+++ /dev/null
@@ -1,168 +0,0 @@
-#[cfg(any(target_os = "linux", target_os = "android"))]
-mod linux;
-
-#[cfg(any(target_os = "linux", target_os = "android"))]
-use self::linux::*;
-
-#[cfg(target_os = "macos")]
-mod macos;
-
-#[cfg(target_os = "macos")]
-use self::macos::*;
-
-use std::ffi::{OsStr, OsString};
-use std::io;
-use std::mem;
-use std::os::unix::ffi::OsStrExt;
-use std::os::unix::io::RawFd;
-use std::path::Path;
-
-use libc::{c_char, c_void, size_t};
-
-use crate::util::{allocate_loop, name_to_c, path_to_c};
-
-/// An iterator over a set of extended attributes names.
-pub struct XAttrs {
-    data: Box<[u8]>,
-    offset: usize,
-}
-
-impl Clone for XAttrs {
-    fn clone(&self) -> Self {
-        XAttrs {
-            data: Vec::from(&*self.data).into_boxed_slice(),
-            offset: self.offset,
-        }
-    }
-    fn clone_from(&mut self, other: &XAttrs) {
-        self.offset = other.offset;
-
-        let mut data = mem::replace(&mut self.data, Box::new([])).into_vec();
-        data.extend(other.data.iter().cloned());
-        self.data = data.into_boxed_slice();
-    }
-}
-
-// Yes, I could avoid these allocations on linux/macos. However, if we ever want to be freebsd
-// compatible, we need to be able to prepend the namespace to the extended attribute names.
-// Furthermore, borrowing makes the API messy.
-impl Iterator for XAttrs {
-    type Item = OsString;
-    fn next(&mut self) -> Option<OsString> {
-        let data = &self.data[self.offset..];
-        if data.is_empty() {
-            None
-        } else {
-            // always null terminated (unless empty).
-            let end = data.iter().position(|&b| b == 0u8).unwrap();
-            self.offset += end + 1;
-            Some(OsStr::from_bytes(&data[..end]).to_owned())
-        }
-    }
-
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        if self.data.len() == self.offset {
-            (0, Some(0))
-        } else {
-            (1, None)
-        }
-    }
-}
-
-pub fn get_fd(fd: RawFd, name: &OsStr) -> io::Result<Vec<u8>> {
-    let name = name_to_c(name)?;
-    unsafe {
-        allocate_loop(|buf, len| fgetxattr(fd, name.as_ptr(), buf as *mut c_void, len as size_t))
-    }
-}
-
-pub fn set_fd(fd: RawFd, name: &OsStr, value: &[u8]) -> io::Result<()> {
-    let name = name_to_c(name)?;
-    let ret = unsafe {
-        fsetxattr(
-            fd,
-            name.as_ptr(),
-            value.as_ptr() as *const c_void,
-            value.len() as size_t,
-        )
-    };
-    if ret != 0 {
-        Err(io::Error::last_os_error())
-    } else {
-        Ok(())
-    }
-}
-
-pub fn remove_fd(fd: RawFd, name: &OsStr) -> io::Result<()> {
-    let name = name_to_c(name)?;
-    let ret = unsafe { fremovexattr(fd, name.as_ptr()) };
-    if ret != 0 {
-        Err(io::Error::last_os_error())
-    } else {
-        Ok(())
-    }
-}
-
-pub fn list_fd(fd: RawFd) -> io::Result<XAttrs> {
-    let vec =
-        unsafe { allocate_loop(|buf, len| flistxattr(fd, buf as *mut c_char, len as size_t))? };
-    Ok(XAttrs {
-        data: vec.into_boxed_slice(),
-        offset: 0,
-    })
-}
-
-pub fn get_path(path: &Path, name: &OsStr) -> io::Result<Vec<u8>> {
-    let name = name_to_c(name)?;
-    let path = path_to_c(path)?;
-    unsafe {
-        allocate_loop(|buf, len| {
-            lgetxattr(
-                path.as_ptr(),
-                name.as_ptr(),
-                buf as *mut c_void,
-                len as size_t,
-            )
-        })
-    }
-}
-
-pub fn set_path(path: &Path, name: &OsStr, value: &[u8]) -> io::Result<()> {
-    let name = name_to_c(name)?;
-    let path = path_to_c(path)?;
-    let ret = unsafe {
-        lsetxattr(
-            path.as_ptr(),
-            name.as_ptr(),
-            value.as_ptr() as *const c_void,
-            value.len() as size_t,
-        )
-    };
-    if ret != 0 {
-        Err(io::Error::last_os_error())
-    } else {
-        Ok(())
-    }
-}
-
-pub fn remove_path(path: &Path, name: &OsStr) -> io::Result<()> {
-    let name = name_to_c(name)?;
-    let path = path_to_c(path)?;
-    let ret = unsafe { lremovexattr(path.as_ptr(), name.as_ptr()) };
-    if ret != 0 {
-        Err(io::Error::last_os_error())
-    } else {
-        Ok(())
-    }
-}
-
-pub fn list_path(path: &Path) -> io::Result<XAttrs> {
-    let path = path_to_c(path)?;
-    let vec = unsafe {
-        allocate_loop(|buf, len| llistxattr(path.as_ptr(), buf as *mut c_char, len as size_t))?
-    };
-    Ok(XAttrs {
-        data: vec.into_boxed_slice(),
-        offset: 0,
-    })
-}
diff --git a/src/sys/mod.rs b/src/sys/mod.rs
index 756a3160..4a171b78 100644
--- a/src/sys/mod.rs
+++ b/src/sys/mod.rs
@@ -7,10 +7,6 @@ macro_rules! platforms {
 
             #[cfg(any($(target_os = $platform),*))]
             pub use self::$module::*;
-
-            #[cfg(any($(target_os = $platform),*))]
-            #[allow(deprecated)] // other platforms still use ENOATTR
-            pub const ENOATTR: ::libc::c_int = ::libc::ENOATTR;
         )*
 
         #[cfg(all(feature = "unsupported", not(any($($(target_os = $platform),*),*))))]
@@ -19,9 +15,6 @@ macro_rules! platforms {
 
         #[cfg(all(feature = "unsupported", not(any($($(target_os = $platform),*),*))))]
         pub use self::unsupported::*;
-        #[cfg(all(feature = "unsupported", not(any($($(target_os = $platform),*),*))))]
-        pub const ENOATTR: ::libc::c_int = 0;
-
 
         /// A constant indicating whether or not the target platform is supported.
         ///
diff --git a/src/sys/unsupported.rs b/src/sys/unsupported.rs
index b0169360..6f8bd57a 100644
--- a/src/sys/unsupported.rs
+++ b/src/sys/unsupported.rs
@@ -1,6 +1,6 @@
 use std::ffi::{OsStr, OsString};
 use std::io;
-use std::os::unix::io::RawFd;
+use std::os::unix::io::BorrowedFd;
 use std::path::Path;
 
 use crate::UnsupportedPlatformError;
@@ -20,28 +20,28 @@ impl Iterator for XAttrs {
     }
 }
 
-pub fn get_fd(_: RawFd, _: &OsStr) -> io::Result<Vec<u8>> {
+pub fn get_fd(_: BorrowedFd<'_>, _: &OsStr) -> io::Result<Vec<u8>> {
     Err(io::Error::new(
         io::ErrorKind::Unsupported,
         UnsupportedPlatformError,
     ))
 }
 
-pub fn set_fd(_: RawFd, _: &OsStr, _: &[u8]) -> io::Result<()> {
+pub fn set_fd(_: BorrowedFd<'_>, _: &OsStr, _: &[u8]) -> io::Result<()> {
     Err(io::Error::new(
         io::ErrorKind::Unsupported,
         UnsupportedPlatformError,
     ))
 }
 
-pub fn remove_fd(_: RawFd, _: &OsStr) -> io::Result<()> {
+pub fn remove_fd(_: BorrowedFd<'_>, _: &OsStr) -> io::Result<()> {
     Err(io::Error::new(
         io::ErrorKind::Unsupported,
         UnsupportedPlatformError,
     ))
 }
 
-pub fn list_fd(_: RawFd) -> io::Result<XAttrs> {
+pub fn list_fd(_: BorrowedFd<'_>) -> io::Result<XAttrs> {
     Err(io::Error::new(
         io::ErrorKind::Unsupported,
         UnsupportedPlatformError,
diff --git a/src/util.rs b/src/util.rs
index ea80062e..347355ea 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,65 +1,48 @@
-use std::ffi::CString;
-use std::ffi::OsStr;
 use std::io;
-use std::os::unix::ffi::OsStrExt;
-use std::path::Path;
-use std::ptr;
-
-use libc::{ssize_t, ERANGE};
-
-// Need to use this one as libc only defines this on supported platforms. Given
-// that we want to at least compile on unsupported platforms, we define this in
-// our platform-specific modules.
-use crate::sys::ENOATTR;
-
-#[allow(dead_code)]
-pub fn name_to_c(name: &OsStr) -> io::Result<CString> {
-    match CString::new(name.as_bytes()) {
-        Ok(name) => Ok(name),
-        Err(_) => Err(io::Error::new(
-            io::ErrorKind::InvalidInput,
-            "name must not contain null bytes",
-        )),
-    }
-}
-
-pub fn path_to_c(path: &Path) -> io::Result<CString> {
-    match CString::new(path.as_os_str().as_bytes()) {
-        Ok(name) => Ok(name),
-        Err(_) => Err(io::Error::new(io::ErrorKind::NotFound, "file not found")),
-    }
-}
 
 pub fn extract_noattr(result: io::Result<Vec<u8>>) -> io::Result<Option<Vec<u8>>> {
-    result.map(Some).or_else(|e| match e.raw_os_error() {
-        Some(ENOATTR) => Ok(None),
-        _ => Err(e),
+    #[cfg(target_os = "linux")]
+    const ENOATTR: i32 = rustix::io::Errno::NODATA.raw_os_error();
+    #[cfg(target_os = "macos")]
+    const ENOATTR: i32 = rustix::io::Errno::NOATTR.raw_os_error();
+    #[cfg(any(target_os = "freebsd", target_os = "netbsd"))]
+    const ENOATTR: i32 = libc::ENOATTR;
+
+    result.map(Some).or_else(|e| {
+        if e.raw_os_error() == Some(ENOATTR) {
+            Ok(None)
+        } else {
+            Err(e)
+        }
     })
 }
 
-pub unsafe fn allocate_loop<F: FnMut(*mut u8, usize) -> ssize_t>(mut f: F) -> io::Result<Vec<u8>> {
+pub fn allocate_loop<E, F: FnMut(&mut [u8]) -> Result<usize, E>>(mut f: F) -> io::Result<Vec<u8>>
+where
+    io::Error: From<E>,
+{
+    const ERANGE: i32 = rustix::io::Errno::RANGE.raw_os_error();
+
     let mut vec: Vec<u8> = Vec::new();
     loop {
-        let ret = (f)(ptr::null_mut(), 0);
-        if ret < 0 {
-            return Err(io::Error::last_os_error());
-        } else if ret == 0 {
-            break;
-        }
-        vec.reserve_exact(ret as usize);
+        let ret = f(&mut [])?;
+        vec.resize(ret, 0);
 
-        let ret = (f)(vec.as_mut_ptr(), vec.capacity());
-        if ret >= 0 {
-            vec.set_len(ret as usize);
-            break;
-        } else {
-            let error = io::Error::last_os_error();
-            if error.raw_os_error() == Some(ERANGE) {
-                continue;
+        match f(&mut vec) {
+            Ok(size) => {
+                vec.truncate(size);
+                vec.shrink_to_fit();
+                return Ok(vec);
+            }
+
+            Err(e) => {
+                let err: io::Error = e.into();
+                if err.raw_os_error() == Some(ERANGE) {
+                    continue;
+                } else {
+                    return Err(err);
+                }
             }
-            return Err(error);
         }
     }
-    vec.shrink_to_fit();
-    Ok(vec)
 }


Test patch:
diff --git a/tests/main.rs b/tests/main.rs
index c93987e3..81f45b28 100644
--- a/tests/main.rs
+++ b/tests/main.rs
@@ -106,7 +106,7 @@ fn test_multi() {
     for it in tmp
         .list_xattr()
         .unwrap()
-        .filter(|x| x.as_bytes().starts_with(&*b"user."))
+        .filter(|x| x.as_bytes().starts_with(b"user."))
     {
         assert!(items.remove(&*it));
     }