1use std::ffi::{OsStr, OsString};
2use std::io;
3use std::mem;
4use std::os::unix::ffi::OsStrExt;
5use std::os::unix::io::BorrowedFd;
6use std::path::Path;
7
8use rustix::fs as rfs;
9
10use crate::util::allocate_loop;
11
12#[cfg(not(target_os = "macos"))]
13pub const ENOATTR: i32 = rustix::io::Errno::NODATA.raw_os_error();
14
15#[cfg(target_os = "macos")]
16pub const ENOATTR: i32 = rustix::io::Errno::NOATTR.raw_os_error();
17
18pub const ERANGE: i32 = rustix::io::Errno::RANGE.raw_os_error();
19
20/// An iterator over a set of extended attributes names.
21#[derive(Default)]
22pub struct XAttrs {
23 data: Box<[u8]>,
24 offset: usize,
25}
26
27impl Clone for XAttrs {
28 fn clone(&self) -> Self {
29 XAttrs {
30 data: Vec::from(&*self.data).into_boxed_slice(),
31 offset: self.offset,
32 }
33 }
34 fn clone_from(&mut self, other: &XAttrs) {
35 self.offset = other.offset;
36
37 let mut data: Vec = mem::replace(&mut self.data, src:Box::new([])).into_vec();
38 data.extend(iter:other.data.iter().cloned());
39 self.data = data.into_boxed_slice();
40 }
41}
42
43// Yes, I could avoid these allocations on linux/macos. However, if we ever want to be freebsd
44// compatible, we need to be able to prepend the namespace to the extended attribute names.
45// Furthermore, borrowing makes the API messy.
46impl Iterator for XAttrs {
47 type Item = OsString;
48 fn next(&mut self) -> Option<OsString> {
49 let data: &[u8] = &self.data[self.offset..];
50 if data.is_empty() {
51 None
52 } else {
53 // always null terminated (unless empty).
54 let end: usize = data.iter().position(|&b: u8| b == 0u8).unwrap();
55 self.offset += end + 1;
56 Some(OsStr::from_bytes(&data[..end]).to_owned())
57 }
58 }
59
60 fn size_hint(&self) -> (usize, Option<usize>) {
61 if self.data.len() == self.offset {
62 (0, Some(0))
63 } else {
64 (1, None)
65 }
66 }
67}
68
69pub fn get_fd(fd: BorrowedFd<'_>, name: &OsStr) -> io::Result<Vec<u8>> {
70 allocate_loop(|buf: &mut [u8]| rfs::fgetxattr(fd, name, value:buf))
71}
72
73pub fn set_fd(fd: BorrowedFd<'_>, name: &OsStr, value: &[u8]) -> io::Result<()> {
74 rfs::fsetxattr(fd, name, value, flags:rfs::XattrFlags::empty())?;
75 Ok(())
76}
77
78pub fn remove_fd(fd: BorrowedFd<'_>, name: &OsStr) -> io::Result<()> {
79 rfs::fremovexattr(fd, name)?;
80 Ok(())
81}
82
83pub fn list_fd(fd: BorrowedFd<'_>) -> io::Result<XAttrs> {
84 let vec: Vec = allocate_loop(|buf: &mut [u8]| rfs::flistxattr(fd, list:buf))?;
85 Ok(XAttrs {
86 data: vec.into_boxed_slice(),
87 offset: 0,
88 })
89}
90
91pub fn get_path(path: &Path, name: &OsStr, deref: bool) -> io::Result<Vec<u8>> {
92 allocate_loop(|buf: &mut [u8]| {
93 let getxattr_func: fn(&Path, &OsStr, &mut [u8]) -> … = if deref { rfs::getxattr } else { rfs::lgetxattr };
94 let size: usize = getxattr_func(path, name, buf)?;
95 io::Result::Ok(size)
96 })
97}
98
99pub fn set_path(path: &Path, name: &OsStr, value: &[u8], deref: bool) -> io::Result<()> {
100 let setxattr_func: fn(&Path, &OsStr, &[u8], XattrFlags) -> … = if deref { rfs::setxattr } else { rfs::lsetxattr };
101 setxattr_func(path, name, value, rfs::XattrFlags::empty())?;
102 Ok(())
103}
104
105pub fn remove_path(path: &Path, name: &OsStr, deref: bool) -> io::Result<()> {
106 let removexattr_func: fn(&Path, &OsStr) -> Result<…, …> = if deref {
107 rfs::removexattr
108 } else {
109 rfs::lremovexattr
110 };
111 removexattr_func(path, name)?;
112 Ok(())
113}
114
115pub fn list_path(path: &Path, deref: bool) -> io::Result<XAttrs> {
116 let vec: Vec = allocate_loop(|buf: &mut [u8]| {
117 if deref {
118 rfs::listxattr(path, list:buf)
119 } else {
120 rfs::llistxattr(path, list:buf)
121 }
122 })?;
123 Ok(XAttrs {
124 data: vec.into_boxed_slice(),
125 offset: 0,
126 })
127}
128