]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-tools/src/xattr.rs
update to first proxmox crate split
[proxmox-backup.git] / pbs-tools / src / xattr.rs
CommitLineData
2dcdd3b4
CE
1//! Wrapper functions for the libc xattr calls
2
27a3decb 3use std::ffi::CStr;
2dcdd3b4 4use std::os::unix::io::RawFd;
27a3decb 5
2dcdd3b4 6use nix::errno::Errno;
f35197f4 7
6ef1b649
WB
8use proxmox_io::vec;
9use proxmox_lang::c_str;
f35197f4 10
1e3d9b10 11/// `"security.capability"` as a CStr to avoid typos.
9094186a
WB
12///
13/// This cannot be `const` until `const_cstr_unchecked` is stable.
14#[inline]
1e3d9b10 15pub fn xattr_name_fcaps() -> &'static CStr {
9094186a
WB
16 c_str!("security.capability")
17}
2dcdd3b4 18
c443f58b
WB
19/// `"system.posix_acl_access"` as a CStr to avoid typos.
20///
21/// This cannot be `const` until `const_cstr_unchecked` is stable.
22#[inline]
23pub fn xattr_acl_access() -> &'static CStr {
24 c_str!("system.posix_acl_access")
25}
26
27/// `"system.posix_acl_default"` as a CStr to avoid typos.
28///
29/// This cannot be `const` until `const_cstr_unchecked` is stable.
30#[inline]
31pub fn xattr_acl_default() -> &'static CStr {
32 c_str!("system.posix_acl_default")
33}
34
27a3decb
WB
35/// Result of `flistxattr`, allows iterating over the attributes as a list of `&CStr`s.
36///
37/// Listing xattrs produces a list separated by zeroes, inherently making them available as `&CStr`
38/// already, so we make use of this fact and reflect this in the interface.
39pub struct ListXAttr {
40 data: Vec<u8>,
41}
42
43impl ListXAttr {
44 fn new(data: Vec<u8>) -> Self {
45 Self { data }
46 }
47}
48
49impl<'a> IntoIterator for &'a ListXAttr {
50 type Item = &'a CStr;
51 type IntoIter = ListXAttrIter<'a>;
52
53 fn into_iter(self) -> Self::IntoIter {
54 ListXAttrIter {
55 data: &self.data,
56 at: 0,
57 }
58 }
59}
60
61/// Iterator over the extended attribute entries in a `ListXAttr`.
62pub struct ListXAttrIter<'a> {
63 data: &'a [u8],
64 at: usize,
65}
66
67impl<'a> Iterator for ListXAttrIter<'a> {
68 type Item = &'a CStr;
69
70 fn next(&mut self) -> Option<&'a CStr> {
71 let data = &self.data[self.at..];
72 let next = data.iter().position(|b| *b == 0)? + 1;
73 self.at += next;
74 Some(unsafe { CStr::from_bytes_with_nul_unchecked(&data[..next]) })
75 }
76}
77
78/// Return a list of extended attributes accessible as an iterator over items of type `&CStr`.
79pub fn flistxattr(fd: RawFd) -> Result<ListXAttr, nix::errno::Errno> {
2dcdd3b4
CE
80 // Initial buffer size for the attribute list, if content does not fit
81 // it gets dynamically increased until big enough.
82 let mut size = 256;
8ea3b1d1 83 let mut buffer = vec::undefined(size);
2dcdd3b4 84 let mut bytes = unsafe {
449e4a66 85 libc::flistxattr(fd, buffer.as_mut_ptr() as *mut libc::c_char, buffer.len())
2dcdd3b4
CE
86 };
87 while bytes < 0 {
88 let err = Errno::last();
89 match err {
90 Errno::ERANGE => {
91 // Buffer was not big enough to fit the list, retry with double the size
9af76ef0 92 size = size.checked_mul(2).ok_or(Errno::ENOMEM)?;
2dcdd3b4
CE
93 },
94 _ => return Err(err),
95 }
96 // Retry to read the list with new buffer
97 buffer.resize(size, 0);
98 bytes = unsafe {
449e4a66 99 libc::flistxattr(fd, buffer.as_mut_ptr() as *mut libc::c_char, buffer.len())
2dcdd3b4
CE
100 };
101 }
27a3decb 102 buffer.truncate(bytes as usize);
2dcdd3b4 103
27a3decb 104 Ok(ListXAttr::new(buffer))
2dcdd3b4
CE
105}
106
27a3decb
WB
107/// Get an extended attribute by name.
108///
109/// Extended attributes may not contain zeroes, which we enforce in the API by using a `&CStr`
110/// type.
111pub fn fgetxattr(fd: RawFd, name: &CStr) -> Result<Vec<u8>, nix::errno::Errno> {
2dcdd3b4 112 let mut size = 256;
8ea3b1d1 113 let mut buffer = vec::undefined(size);
2dcdd3b4 114 let mut bytes = unsafe {
27a3decb 115 libc::fgetxattr(fd, name.as_ptr(), buffer.as_mut_ptr() as *mut core::ffi::c_void, buffer.len())
2dcdd3b4
CE
116 };
117 while bytes < 0 {
118 let err = Errno::last();
119 match err {
120 Errno::ERANGE => {
121 // Buffer was not big enough to fit the value, retry with double the size
9af76ef0 122 size = size.checked_mul(2).ok_or(Errno::ENOMEM)?;
2dcdd3b4
CE
123 },
124 _ => return Err(err),
125 }
126 buffer.resize(size, 0);
127 bytes = unsafe {
449e4a66 128 libc::fgetxattr(fd, name.as_ptr() as *const libc::c_char, buffer.as_mut_ptr() as *mut core::ffi::c_void, buffer.len())
2dcdd3b4
CE
129 };
130 }
131 buffer.resize(bytes as usize, 0);
132
133 Ok(buffer)
134}
135
9094186a
WB
136/// Set an extended attribute on a file descriptor.
137pub fn fsetxattr(fd: RawFd, name: &CStr, data: &[u8]) -> Result<(), nix::errno::Errno> {
2dcdd3b4
CE
138 let flags = 0 as libc::c_int;
139 let result = unsafe {
9094186a 140 libc::fsetxattr(fd, name.as_ptr(), data.as_ptr() as *const libc::c_void, data.len(), flags)
2dcdd3b4
CE
141 };
142 if result < 0 {
9094186a 143 return Err(Errno::last());
2dcdd3b4
CE
144 }
145
146 Ok(())
147}
148
9094186a 149pub fn fsetxattr_fcaps(fd: RawFd, fcaps: &[u8]) -> Result<(), nix::errno::Errno> {
2dcdd3b4 150 // TODO casync checks and removes capabilities if they are set
9094186a 151 fsetxattr(fd, xattr_name_fcaps(), fcaps)
2dcdd3b4
CE
152}
153
27a3decb 154pub fn is_security_capability(name: &CStr) -> bool {
9094186a 155 name.to_bytes() == xattr_name_fcaps().to_bytes()
bee8d8ea
CE
156}
157
c443f58b
WB
158pub fn is_acl(name: &CStr) -> bool {
159 name.to_bytes() == xattr_acl_access().to_bytes()
160 || name.to_bytes() == xattr_acl_default().to_bytes()
161}
162
357e4614
CE
163/// Check if the passed name buffer starts with a valid xattr namespace prefix
164/// and is within the length limit of 255 bytes
27a3decb
WB
165pub fn is_valid_xattr_name(c_name: &CStr) -> bool {
166 let name = c_name.to_bytes();
357e4614
CE
167 if name.is_empty() || name.len() > 255 {
168 return false;
169 }
170 if name.starts_with(b"user.") || name.starts_with(b"trusted.") {
171 return true;
172 }
fea23d03
DC
173 // samba saves windows ACLs there
174 if name == b"security.NTACL" {
175 return true;
176 }
27a3decb 177 is_security_capability(c_name)
bee8d8ea 178}
de61bc92
CE
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
9094186a 184 use std::ffi::CString;
de61bc92
CE
185 use std::fs::OpenOptions;
186 use std::os::unix::io::AsRawFd;
9094186a 187
de61bc92
CE
188 use nix::errno::Errno;
189
6ef1b649 190 use proxmox_lang::c_str;
9094186a 191
de61bc92
CE
192 #[test]
193 fn test_fsetxattr_fgetxattr() {
a3399f43 194 let path = "./test-xattrs.txt";
de61bc92
CE
195 let file = OpenOptions::new()
196 .write(true)
197 .create(true)
198 .open(&path)
199 .unwrap();
200
201 let fd = file.as_raw_fd();
202
9094186a
WB
203 assert!(fsetxattr(fd, c_str!("user.attribute0"), b"value0").is_ok());
204 assert!(fsetxattr(fd, c_str!("user.empty"), b"").is_ok());
44c54845
DM
205
206 if nix::unistd::Uid::current() != nix::unistd::ROOT {
9094186a 207 assert_eq!(fsetxattr(fd, c_str!("trusted.attribute0"), b"value0"), Err(Errno::EPERM));
44c54845
DM
208 }
209
27a3decb
WB
210 let v0 = fgetxattr(fd, c_str!("user.attribute0")).unwrap();
211 let v1 = fgetxattr(fd, c_str!("user.empty")).unwrap();
de61bc92
CE
212
213 assert_eq!(v0, b"value0".as_ref());
214 assert_eq!(v1, b"".as_ref());
27a3decb 215 assert_eq!(fgetxattr(fd, c_str!("user.attribute1")), Err(Errno::ENODATA));
de61bc92
CE
216
217 std::fs::remove_file(&path).unwrap();
218 }
68740774
CE
219
220 #[test]
221 fn test_is_valid_xattr_name() {
27a3decb
WB
222 let too_long = CString::new(vec![b'a'; 265]).unwrap();
223
224 assert!(!is_valid_xattr_name(&too_long));
225 assert!(!is_valid_xattr_name(c_str!("system.attr")));
226 assert!(is_valid_xattr_name(c_str!("user.attr")));
227 assert!(is_valid_xattr_name(c_str!("trusted.attr")));
9094186a 228 assert!(is_valid_xattr_name(super::xattr_name_fcaps()));
68740774 229 }
de61bc92 230}