1use std::ffi::c_char;
8use std::ffi::CString;
9use std::os::unix::ffi::OsStrExt;
10use std::path::Path;
11
12use anyhow::bail;
13use anyhow::Context;
14use anyhow::Result;
15use zerocopy::FromBytes;
16use zerocopy::Immutable;
17use zerocopy::IntoBytes;
18use zerocopy::KnownLayout;
19
20use crate::inode::Inode;
21
22fn listxattr(path: &CString) -> Result<Vec<Vec<u8>>> {
23 let size = unsafe { libc::llistxattr(path.as_ptr(), std::ptr::null_mut(), 0) };
25 if size < 0 {
26 bail!(
27 "failed to get xattr size: {}",
28 std::io::Error::last_os_error()
29 );
30 }
31
32 if size == 0 {
33 return Ok(vec![]);
35 }
36
37 let mut buf = vec![0 as c_char; size as usize];
38
39 let size = unsafe { libc::llistxattr(path.as_ptr(), buf.as_mut_ptr(), buf.len()) };
41 if size < 0 {
42 bail!(
43 "failed to list of xattr: {}",
44 std::io::Error::last_os_error()
45 );
46 }
47
48 buf.pop(); #[cfg_attr(
52 any(target_arch = "aarch64", target_arch = "riscv64"),
53 allow(clippy::unnecessary_cast)
54 )]
55 let keys = buf
56 .split(|c| *c == 0)
57 .map(|v| v.iter().map(|c| *c as u8).collect::<Vec<_>>())
58 .collect::<Vec<Vec<_>>>();
59
60 Ok(keys)
61}
62
63fn lgetxattr(path: &CString, name: &CString) -> Result<Vec<u8>> {
64 let size = unsafe { libc::lgetxattr(path.as_ptr(), name.as_ptr(), std::ptr::null_mut(), 0) };
66 if size < 0 {
67 bail!(
68 "failed to get xattr size for {:?}: {}",
69 name,
70 std::io::Error::last_os_error()
71 );
72 }
73 let mut buf = vec![0; size as usize];
74 let size = unsafe {
76 libc::lgetxattr(
77 path.as_ptr(),
78 name.as_ptr(),
79 buf.as_mut_ptr() as *mut libc::c_void,
80 buf.len(),
81 )
82 };
83 if size < 0 {
84 bail!(
85 "failed to get xattr for {:?}: {}",
86 name,
87 std::io::Error::last_os_error()
88 );
89 }
90
91 Ok(buf)
92}
93
94pub fn dump_xattrs(path: &Path) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
99 let mut path_vec = path.as_os_str().as_bytes().to_vec();
100 path_vec.push(0);
101 let path_str = CString::from_vec_with_nul(path_vec)?;
102
103 let keys = listxattr(&path_str).context("failed to listxattr")?;
104
105 let mut kvs = vec![];
106 for key in keys {
107 let mut key_vec = key.to_vec();
108 key_vec.push(0);
109 let name = CString::from_vec_with_nul(key_vec)?;
110
111 let buf = lgetxattr(&path_str, &name).context("failed to getxattr")?;
112 kvs.push((key.to_vec(), buf));
113 }
114
115 Ok(kvs)
116}
117
118pub fn set_xattr(path: &Path, key: &str, value: &str) -> Result<()> {
120 let mut path_bytes = path
121 .as_os_str()
122 .as_bytes()
123 .iter()
124 .map(|i| *i as c_char)
125 .collect::<Vec<_>>();
126 path_bytes.push(0); let mut key_vec = key.bytes().collect::<Vec<_>>();
130 key_vec.push(0);
131 let name = CString::from_vec_with_nul(key_vec)?;
132 let v = value.bytes().collect::<Vec<_>>();
133
134 let size = unsafe {
137 libc::lsetxattr(
138 path_bytes.as_ptr(),
139 name.as_ptr(),
140 v.as_ptr() as *const libc::c_void,
141 v.len(),
142 0,
143 )
144 };
145 if size != 0 {
146 bail!(
147 "failed to set xattr for {:?}: {}",
148 path,
149 std::io::Error::last_os_error()
150 );
151 }
152 Ok(())
153}
154
155#[repr(C)]
156#[derive(Default, Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
157pub(crate) struct XattrEntry {
158 name_len: u8,
159 name_index: u8,
160 value_offs: u16,
161 value_inum: u32,
162 value_size: u32,
163 hash: u32,
164 }
166
167impl XattrEntry {
168 pub(crate) fn new_with_name<'a>(
170 name: &'a [u8],
171 value: &[u8],
172 value_offs: u16,
173 ) -> Result<(Self, &'a [u8])> {
174 let (name_index, key_str) = Self::split_key_prefix(name);
175 let name_len = key_str.len() as u8;
176 let value_size = value.len() as u32;
177 Ok((
178 XattrEntry {
179 name_len,
180 name_index,
181 value_offs,
182 value_inum: 0,
183 value_size,
184 hash: 0,
185 },
186 key_str,
187 ))
188 }
189
190 fn split_key_prefix(name: &[u8]) -> (u8, &[u8]) {
193 for (name_index, key_prefix) in [
195 (1, "user."),
196 (2, "system.posix_acl_access"),
197 (3, "system.posix_acl_default"),
198 (4, "trusted."),
199 (6, "security."),
201 (7, "system."),
202 (8, "system.richacl"),
203 ] {
204 let prefix_bytes = key_prefix.as_bytes();
205 if name.starts_with(prefix_bytes) {
206 return (name_index, &name[prefix_bytes.len()..]);
207 }
208 }
209 (0, name)
210 }
211}
212
213#[derive(Default, Debug, PartialEq, Eq)]
215pub struct InlineXattrs {
216 pub entry_table: Vec<u8>,
217 pub values: Vec<u8>,
218}
219
220fn align<T: Clone + Default>(mut v: Vec<T>, alignment: usize) -> Vec<T> {
221 let aligned = v.len().next_multiple_of(alignment);
222 v.extend(vec![T::default(); aligned - v.len()]);
223 v
224}
225
226const XATTR_HEADER_MAGIC: u32 = 0xEA020000;
227
228impl InlineXattrs {
229 pub fn from_path(path: &Path) -> Result<Self> {
231 let v = dump_xattrs(path).with_context(|| format!("failed to get xattr for {path:?}"))?;
232
233 let mut entry_table = vec![];
235 let mut values = vec![];
236 let mut val_offset = Inode::INODE_RECORD_SIZE
250 - std::mem::size_of::<Inode>()
251 - std::mem::size_of_val(&XATTR_HEADER_MAGIC);
252
253 entry_table.extend(XATTR_HEADER_MAGIC.to_le_bytes());
254 for (name, value) in v {
255 let aligned_val_len = value.len().next_multiple_of(4);
256
257 if entry_table.len()
258 + values.len()
259 + std::mem::size_of::<XattrEntry>()
260 + aligned_val_len
261 > Inode::XATTR_AREA_SIZE
262 {
263 bail!("Xattr entry is too large");
264 }
265
266 val_offset -= aligned_val_len;
267 let (entry, name) = XattrEntry::new_with_name(&name, &value, val_offset as u16)?;
268 entry_table.extend(entry.as_bytes());
269 entry_table.extend(name);
270 entry_table = align(entry_table, 4);
271 values.push(align(value, 4));
272 }
273 let values = values.iter().rev().flatten().copied().collect::<Vec<_>>();
274
275 Ok(Self {
276 entry_table,
277 values,
278 })
279 }
280}
281
282#[cfg(test)]
283pub(crate) mod tests {
284 use std::collections::BTreeMap;
285 use std::fs::File;
286
287 use tempfile::tempdir;
288
289 use super::*;
290
291 fn to_char_array(s: &str) -> Vec<u8> {
292 s.bytes().collect()
293 }
294
295 #[test]
296 fn test_attr_name_index() {
297 assert_eq!(
298 XattrEntry::split_key_prefix(b"user.foo"),
299 (1, "foo".as_bytes())
300 );
301 assert_eq!(
302 XattrEntry::split_key_prefix(b"trusted.bar"),
303 (4, "bar".as_bytes())
304 );
305 assert_eq!(
306 XattrEntry::split_key_prefix(b"security.abcdefgh"),
307 (6, "abcdefgh".as_bytes())
308 );
309
310 assert_eq!(
312 XattrEntry::split_key_prefix(b"system.posix_acl_access"),
313 (2, "".as_bytes())
314 );
315 assert_eq!(
316 XattrEntry::split_key_prefix(b"system.posix_acl_default"),
317 (3, "".as_bytes())
318 );
319 assert_eq!(
320 XattrEntry::split_key_prefix(b"system.abcdefgh"),
321 (7, "abcdefgh".as_bytes())
322 );
323
324 assert_eq!(
326 XattrEntry::split_key_prefix(b"invalid.foo"),
327 (0, "invalid.foo".as_bytes())
328 );
329 }
330
331 #[test]
332 fn test_get_xattr_empty() {
333 let td = tempdir().unwrap();
334 let test_path = td.path().join("test.txt");
335
336 File::create(&test_path).unwrap();
338
339 let kvs = dump_xattrs(&test_path).unwrap();
340 assert_eq!(kvs.len(), 0);
341 }
342
343 #[test]
344 fn test_inline_xattr_from_path() {
345 let td = tempdir().unwrap();
346 let test_path = td.path().join("test.txt");
347 File::create(&test_path).unwrap();
348
349 let key = "key";
350 let xattr_key = &format!("user.{key}");
351 let value = "value";
352
353 set_xattr(&test_path, xattr_key, value).unwrap();
354
355 let xattrs = InlineXattrs::from_path(&test_path).unwrap();
356 let entry = XattrEntry {
357 name_len: key.len() as u8,
358 name_index: 1,
359 value_offs: (Inode::INODE_RECORD_SIZE
360 - std::mem::size_of::<Inode>()
361 - std::mem::size_of_val(&XATTR_HEADER_MAGIC)
362 - value.len().next_multiple_of(4)) as u16,
363 value_size: value.len() as u32,
364 value_inum: 0,
365 ..Default::default()
366 };
367 assert_eq!(
368 xattrs.entry_table,
369 align(
370 [
371 XATTR_HEADER_MAGIC.to_le_bytes().to_vec(),
372 entry.as_bytes().to_vec(),
373 key.as_bytes().to_vec(),
374 ]
375 .concat(),
376 4
377 ),
378 );
379 assert_eq!(xattrs.values, align(value.as_bytes().to_vec(), 4),);
380 }
381
382 #[test]
383 fn test_too_many_values_for_inline_xattr() {
384 let td = tempdir().unwrap();
385 let test_path = td.path().join("test.txt");
386 File::create(&test_path).unwrap();
387
388 let mut xattr_pairs = vec![];
390 for i in 0..10 {
391 xattr_pairs.push((format!("user.foo{i}"), "bar"));
392 }
393
394 for (key, value) in &xattr_pairs {
395 set_xattr(&test_path, key, value).unwrap();
396 }
397
398 InlineXattrs::from_path(&test_path).unwrap_err();
400 }
401
402 #[test]
403 fn test_get_xattr() {
404 let td = tempdir().unwrap();
405 let test_path = td.path().join("test.txt");
406 File::create(&test_path).unwrap();
407
408 let xattr_pairs = vec![
409 ("user.foo", "bar"),
410 ("user.hash", "09f7e02f1290be211da707a266f153b3"),
411 ("user.empty", ""),
412 ];
413
414 for (key, value) in &xattr_pairs {
415 set_xattr(&test_path, key, value).unwrap();
416 }
417
418 let kvs = dump_xattrs(&test_path).unwrap();
419 assert_eq!(kvs.len(), xattr_pairs.len());
420
421 let xattr_map: BTreeMap<Vec<u8>, Vec<u8>> = kvs.into_iter().collect();
422
423 for (orig_k, orig_v) in xattr_pairs {
424 let k = to_char_array(orig_k);
425 let v = to_char_array(orig_v);
426 let got = xattr_map.get(&k).unwrap();
427 assert_eq!(&v, got);
428 }
429 }
430
431 #[test]
432 fn test_get_xattr_symlink() {
433 let td = tempdir().unwrap();
434
435 let test_path = td.path().join("test.txt");
437 File::create(&test_path).unwrap();
438 set_xattr(&test_path, "user.name", "user.test.txt").unwrap();
439
440 let symlink_path = td.path().join("symlink");
442 std::os::unix::fs::symlink(&test_path, &symlink_path).unwrap();
443
444 let kvs = dump_xattrs(&symlink_path).unwrap();
446 assert_eq!(kvs, vec![]);
447 }
448}