Repository: Stebalien/xattr (language: rust)

Issue:
Make XATTR_NOFOLLOW optional
On Linux, is there a way to call getxattr without the XATTR_NOFOLLOW flag? My use case for it is that `lgetxattr` doesn't work for system properties on a /proc filesystem, eg: `xattr::get("/proc/{PID}/fd/{FD}", "system.sockprotoname")`. Resolving a symlink manually doesn't work because it's not a real filesystem.


Discussion / hints:

Can you open the file than call `file.get_xattr` (import xattr::FileExt first).

I can't open the file because it's a symlink to a non-existent location (`/proc/380463/fd/5` => `socket:[118795940]`). Using `File::open(&fd_path).unwrap();` fails with "No such device or address". strace shows:

```
openat(AT_FDCWD, "/proc/380463/fd/5", O_RDONLY|O_CLOEXEC) = -1 ENXIO (No such device or address)
```
Hm. Yeah, now I see. It sounds like we need `xattr::deref_*` variants. In retrospect, this likely should have been the default but breaking it would require a v2.
Mind submitting a patch?
I.e., adding `fn deref_get`, etc. (or pick a better name if you can think of one)
Sure. It might take me a few weeks to get to it though.

Fix patch:
diff --git a/src/lib.rs b/src/lib.rs
index a5ffe063..d15f1a53 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,9 +8,9 @@
 //! An extension trait [`FileExt`] is provided to directly work with
 //! standard `File` objects and file descriptors.
 //!
-//! NOTE: In case of a symlink as path argument, all methods
-//! in this library work on the symlink itself **without**
-//! de-referencing it.
+//! If the path argument is a symlink, the get/set/list/remove functions
+//! operate on the symlink itself. To operate on the symlink target, use
+//! the _deref variant of these functions.
 //!
 //! ```rust
 //! let mut xattrs = xattr::list("/").unwrap().peekable();
@@ -45,7 +45,16 @@ where
     P: AsRef<Path>,
     N: AsRef<OsStr>,
 {
-    util::extract_noattr(sys::get_path(path.as_ref(), name.as_ref()))
+    util::extract_noattr(sys::get_path(path.as_ref(), name.as_ref(), false))
+}
+
+/// Get an extended attribute for the specified file (dereference symlinks).
+pub fn get_deref<N, P>(path: P, name: N) -> io::Result<Option<Vec<u8>>>
+where
+    P: AsRef<Path>,
+    N: AsRef<OsStr>,
+{
+    util::extract_noattr(sys::get_path(path.as_ref(), name.as_ref(), true))
 }
 
 /// Set an extended attribute on the specified file.
@@ -54,7 +63,16 @@ where
     P: AsRef<Path>,
     N: AsRef<OsStr>,
 {
-    sys::set_path(path.as_ref(), name.as_ref(), value)
+    sys::set_path(path.as_ref(), name.as_ref(), value, false)
+}
+
+/// Set an extended attribute on the specified file (dereference symlinks).
+pub fn set_deref<N, P>(path: P, name: N, value: &[u8]) -> io::Result<()>
+where
+    P: AsRef<Path>,
+    N: AsRef<OsStr>,
+{
+    sys::set_path(path.as_ref(), name.as_ref(), value, true)
 }
 
 /// Remove an extended attribute from the specified file.
@@ -63,7 +81,16 @@ where
     P: AsRef<Path>,
     N: AsRef<OsStr>,
 {
-    sys::remove_path(path.as_ref(), name.as_ref())
+    sys::remove_path(path.as_ref(), name.as_ref(), false)
+}
+
+/// Remove an extended attribute from the specified file (dereference symlinks).
+pub fn remove_deref<N, P>(path: P, name: N) -> io::Result<()>
+where
+    P: AsRef<Path>,
+    N: AsRef<OsStr>,
+{
+    sys::remove_path(path.as_ref(), name.as_ref(), true)
 }
 
 /// List extended attributes attached to the specified file.
@@ -74,7 +101,15 @@ pub fn list<P>(path: P) -> io::Result<XAttrs>
 where
     P: AsRef<Path>,
 {
-    sys::list_path(path.as_ref())
+    sys::list_path(path.as_ref(), false)
+}
+
+/// List extended attributes attached to the specified file (dereference symlinks).
+pub fn list_deref<P>(path: P) -> io::Result<XAttrs>
+where
+    P: AsRef<Path>,
+{
+    sys::list_path(path.as_ref(), true)
 }
 
 /// Extension trait to manipulate extended attributes on `File`-like objects.
diff --git a/src/sys/bsd.rs b/src/sys/bsd.rs
index a3f1c091..f1014da0 100644
--- a/src/sys/bsd.rs
+++ b/src/sys/bsd.rs
@@ -11,9 +11,11 @@ use std::ptr;
 use libc::{c_int, c_void, size_t, EPERM};
 
 use libc::{
-    extattr_delete_fd, extattr_delete_link, extattr_get_fd, extattr_get_link, extattr_list_fd,
-    extattr_list_link, extattr_set_fd, extattr_set_link, EXTATTR_NAMESPACE_SYSTEM,
-    EXTATTR_NAMESPACE_USER,
+    extattr_delete_fd, extattr_delete_link, extattr_delete_file,
+    extattr_get_fd, extattr_get_link, extattr_get_file,
+    extattr_list_fd, extattr_list_link, extattr_list_file,
+    extattr_set_fd, extattr_set_link, extattr_set_file,
+    EXTATTR_NAMESPACE_SYSTEM, EXTATTR_NAMESPACE_USER,
 };
 
 use crate::util::allocate_loop;
@@ -260,13 +262,14 @@ pub fn list_fd(fd: BorrowedFd<'_>) -> io::Result<XAttrs> {
     })
 }
 
-pub fn get_path(path: &Path, name: &OsStr) -> io::Result<Vec<u8>> {
+pub fn get_path(path: &Path, name: &OsStr, deref: bool) -> io::Result<Vec<u8>> {
     let (ns, name) = name_to_ns(name)?;
     let path = path_to_c(path)?;
+    let extattr_get_func = if deref { extattr_get_file } else { extattr_get_link };
     unsafe {
         allocate_loop(|buf| {
             let (ptr, len) = slice_parts(buf);
-            cvt(extattr_get_link(
+            cvt(extattr_get_func(
                 path.as_ptr(),
                 ns,
                 name.as_ptr(),
@@ -276,11 +279,12 @@ pub fn get_path(path: &Path, name: &OsStr) -> io::Result<Vec<u8>> {
     }
 }
 
-pub fn set_path(path: &Path, name: &OsStr, value: &[u8]) -> io::Result<()> {
+pub fn set_path(path: &Path, name: &OsStr, value: &[u8], deref: bool) -> io::Result<()> {
     let (ns, name) = name_to_ns(name)?;
     let path = path_to_c(path)?;
+    let extattr_set_func = if deref { extattr_set_file } else { extattr_set_link };
     let ret = unsafe {
-        extattr_set_link(
+        extattr_set_func(
             path.as_ptr(),
             ns,
             name.as_ptr(),
@@ -295,10 +299,11 @@ pub fn set_path(path: &Path, name: &OsStr, value: &[u8]) -> io::Result<()> {
     }
 }
 
-pub fn remove_path(path: &Path, name: &OsStr) -> io::Result<()> {
+pub fn remove_path(path: &Path, name: &OsStr, deref: bool) -> io::Result<()> {
     let (ns, name) = name_to_ns(name)?;
     let path = path_to_c(path)?;
-    let ret = unsafe { extattr_delete_link(path.as_ptr(), ns, name.as_ptr()) };
+    let extattr_delete_func = if deref { extattr_delete_file } else { extattr_delete_link };
+    let ret = unsafe { extattr_delete_func(path.as_ptr(), ns, name.as_ptr()) };
     if ret != 0 {
         Err(io::Error::last_os_error())
     } else {
@@ -306,12 +311,13 @@ pub fn remove_path(path: &Path, name: &OsStr) -> io::Result<()> {
     }
 }
 
-pub fn list_path(path: &Path) -> io::Result<XAttrs> {
+pub fn list_path(path: &Path, deref: bool) -> io::Result<XAttrs> {
     let path = path_to_c(path)?;
+    let extattr_list_func = if deref { extattr_list_file } else { extattr_list_link };
     let sysvec = unsafe {
         let res = allocate_loop(|buf| {
             let (ptr, len) = slice_parts(buf);
-            cvt(extattr_list_link(
+            cvt(extattr_list_func(
                 path.as_ptr(),
                 EXTATTR_NAMESPACE_SYSTEM,
                 ptr, len
@@ -335,7 +341,7 @@ pub fn list_path(path: &Path) -> io::Result<XAttrs> {
     let uservec = unsafe {
         let res = allocate_loop(|buf| {
             let (ptr, len) = slice_parts(buf);
-            cvt(extattr_list_link(
+            cvt(extattr_list_func(
                 path.as_ptr(),
                 EXTATTR_NAMESPACE_USER,
                 ptr, len
diff --git a/src/sys/linux_macos.rs b/src/sys/linux_macos.rs
index 5ffd8cfa..acfa221b 100644
--- a/src/sys/linux_macos.rs
+++ b/src/sys/linux_macos.rs
@@ -101,7 +101,7 @@ pub fn list_fd(fd: BorrowedFd<'_>) -> io::Result<XAttrs> {
     })
 }
 
-pub fn get_path(path: &Path, name: &OsStr) -> io::Result<Vec<u8>> {
+pub fn get_path(path: &Path, name: &OsStr, deref: bool) -> io::Result<Vec<u8>> {
     let path = path.into_c_str()?;
     let name = name.into_c_str()?;
 
@@ -118,7 +118,7 @@ pub fn get_path(path: &Path, name: &OsStr) -> io::Result<Vec<u8>> {
                         std::ptr::null_mut(),
                         0,
                         0,
-                        libc::XATTR_NOFOLLOW
+                        if deref { 0 } else { libc::XATTR_NOFOLLOW },
                     )
                 };
 
@@ -130,25 +130,29 @@ pub fn get_path(path: &Path, name: &OsStr) -> io::Result<Vec<u8>> {
             }
         }
 
-        let size = rfs::lgetxattr(&*path, &*name, buf)?;
+        let getxattr_func = if deref { rfs::getxattr } else { rfs::lgetxattr };
+        let size = getxattr_func(&*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())?;
+pub fn set_path(path: &Path, name: &OsStr, value: &[u8], deref: bool) -> io::Result<()> {
+    let setxattr_func = if deref { rfs::setxattr } else { rfs::lsetxattr };
+    setxattr_func(path, name, value, rfs::XattrFlags::empty())?;
     Ok(())
 }
 
-pub fn remove_path(path: &Path, name: &OsStr) -> io::Result<()> {
-    rfs::lremovexattr(path, name)?;
+pub fn remove_path(path: &Path, name: &OsStr, deref: bool) -> io::Result<()> {
+    let removexattr_func = if deref { rfs::removexattr } else { rfs::lremovexattr };
+    removexattr_func(path, name)?;
     Ok(())
 }
 
-pub fn list_path(path: &Path) -> io::Result<XAttrs> {
+pub fn list_path(path: &Path, deref: bool) -> io::Result<XAttrs> {
+    let listxattr_func = if deref { rfs::listxattr } else { rfs::llistxattr };
     let path = path.as_cow_c_str()?;
     let vec = allocate_loop(|buf| {
-        rfs::llistxattr(&*path, as_listxattr_buffer(buf))
+        listxattr_func(&*path, as_listxattr_buffer(buf))
     })?;
     Ok(XAttrs {
         data: vec.into_boxed_slice(),
diff --git a/src/sys/unsupported.rs b/src/sys/unsupported.rs
index 577131e5..6ac4f096 100644
--- a/src/sys/unsupported.rs
+++ b/src/sys/unsupported.rs
@@ -50,28 +50,28 @@ pub fn list_fd(_: BorrowedFd<'_>) -> io::Result<XAttrs> {
     ))
 }
 
-pub fn get_path(_: &Path, _: &OsStr) -> io::Result<Vec<u8>> {
+pub fn get_path(_: &Path, _: &OsStr, _: bool) -> io::Result<Vec<u8>> {
     Err(io::Error::new(
         io::ErrorKind::Unsupported,
         UnsupportedPlatformError,
     ))
 }
 
-pub fn set_path(_: &Path, _: &OsStr, _: &[u8]) -> io::Result<()> {
+pub fn set_path(_: &Path, _: &OsStr, _: &[u8], _: bool) -> io::Result<()> {
     Err(io::Error::new(
         io::ErrorKind::Unsupported,
         UnsupportedPlatformError,
     ))
 }
 
-pub fn remove_path(_: &Path, _: &OsStr) -> io::Result<()> {
+pub fn remove_path(_: &Path, _: &OsStr, _: bool) -> io::Result<()> {
     Err(io::Error::new(
         io::ErrorKind::Unsupported,
         UnsupportedPlatformError,
     ))
 }
 
-pub fn list_path(_: &Path) -> io::Result<XAttrs> {
+pub fn list_path(_: &Path, _: bool) -> io::Result<XAttrs> {
     Err(io::Error::new(
         io::ErrorKind::Unsupported,
         UnsupportedPlatformError,


Test patch: