cap_std_ext/
xattrs.rs

1use std::io::Result;
2use std::{
3    ffi::OsStr,
4    os::unix::ffi::OsStrExt,
5    path::{Path, PathBuf},
6};
7
8use cap_tempfile::cap_std::fs::Dir;
9use rustix::buffer::spare_capacity;
10
11use crate::dirext::validate_relpath_no_uplinks;
12
13/// Convert the directory and path pair into a /proc/self/fd path
14/// which is useful for xattr functions in particular to be able
15/// to operate on symlinks.
16///
17/// Absolute paths as well as paths with uplinks (`..`) are an error.
18fn proc_self_path(d: &Dir, path: &Path) -> Result<PathBuf> {
19    use rustix::path::DecInt;
20    use std::os::fd::{AsFd, AsRawFd};
21
22    // Require relative paths here.
23    let path = validate_relpath_no_uplinks(path)?;
24
25    let mut pathbuf = PathBuf::from("/proc/self/fd");
26    pathbuf.push(DecInt::new(d.as_fd().as_raw_fd()));
27    pathbuf.push(path);
28    Ok(pathbuf)
29}
30
31pub(crate) fn impl_getxattr(d: &Dir, path: &Path, key: &OsStr) -> Result<Option<Vec<u8>>> {
32    let path = &proc_self_path(d, path)?;
33
34    // In my experience few extended attributes exceed this
35    let mut buf = Vec::with_capacity(256);
36
37    loop {
38        match rustix::fs::lgetxattr(path, key, spare_capacity(&mut buf)) {
39            Ok(_) => {
40                return Ok(Some(buf));
41            }
42            Err(rustix::io::Errno::NODATA) => {
43                return Ok(None);
44            }
45            Err(rustix::io::Errno::RANGE) => {
46                buf.reserve(buf.capacity().saturating_mul(2));
47            }
48            Err(e) => {
49                return Err(e.into());
50            }
51        }
52    }
53}
54
55/// A list of extended attribute value names
56#[derive(Debug)]
57#[cfg(any(target_os = "android", target_os = "linux"))]
58pub struct XattrList {
59    /// Contents of the return value from the llistxattr system call;
60    /// effectively Vec<OsStr> with an empty value as terminator.
61    /// Not public - we expect callers to invoke the `iter()` method.
62    /// When Rust has lending iterators then we could implement IntoIterator
63    /// in a way that borrows from this value.
64    buf: Vec<u8>,
65}
66
67#[cfg(any(target_os = "android", target_os = "linux"))]
68impl XattrList {
69    /// Return an iterator over the elements of this extended attribute list.
70    pub fn iter(&self) -> impl Iterator<Item = &'_ std::ffi::OsStr> {
71        self.buf.split(|&v| v == 0).filter_map(|v| {
72            // Note this case should only happen once at the end
73            if v.is_empty() {
74                None
75            } else {
76                Some(OsStr::from_bytes(v))
77            }
78        })
79    }
80}
81
82#[cfg(any(target_os = "android", target_os = "linux"))]
83pub(crate) fn impl_listxattrs(d: &Dir, path: &Path) -> Result<XattrList> {
84    let path = &proc_self_path(d, path)?;
85
86    let mut buf = Vec::with_capacity(512);
87
88    loop {
89        match rustix::fs::llistxattr(path, spare_capacity(&mut buf)) {
90            Ok(_) => {
91                return Ok(XattrList { buf });
92            }
93            Err(rustix::io::Errno::RANGE) => {
94                buf.reserve(buf.capacity().saturating_mul(2));
95            }
96            Err(e) => {
97                return Err(e.into());
98            }
99        }
100    }
101}
102
103#[cfg(any(target_os = "android", target_os = "linux"))]
104pub(crate) fn impl_setxattr(d: &Dir, path: &Path, key: &OsStr, value: &[u8]) -> Result<()> {
105    let path = &proc_self_path(d, path)?;
106    rustix::fs::lsetxattr(path, key, value, rustix::fs::XattrFlags::empty())?;
107    Ok(())
108}