1use crate::constants::*;
8use crate::error::FormatError;
9
10const XATTR_PREFIXES: &[(u8, &str)] = &[
17 (1, "user."),
18 (2, "system.posix_acl_access"),
19 (3, "system.posix_acl_default"),
20 (4, "trusted."),
21 (6, "security."),
22 (7, "system."),
23 (8, "system.richacl"),
24];
25
26#[inline]
32fn align_up(n: usize, align: usize) -> usize {
33 (n + align - 1) & !(align - 1)
34}
35
36#[derive(Debug, Clone)]
43pub struct ExtendedAttribute {
44 pub name: String,
46 pub index: u8,
48 pub value: Vec<u8>,
50}
51
52impl ExtendedAttribute {
53 pub fn new(full_name: &str, value: Vec<u8>) -> Self {
57 let (index, suffix) = Self::compress_name(full_name);
58 Self {
59 name: suffix,
60 index,
61 value,
62 }
63 }
64
65 pub fn compress_name(name: &str) -> (u8, String) {
69 let mut best_index = 0u8;
70 let mut best_prefix_len = 0usize;
71
72 for &(idx, prefix) in XATTR_PREFIXES {
73 if name.starts_with(prefix) && prefix.len() > best_prefix_len {
74 best_index = idx;
75 best_prefix_len = prefix.len();
76 }
77 }
78
79 let suffix = &name[best_prefix_len..];
80 (best_index, suffix.to_string())
81 }
82
83 pub fn decompress_name(index: u8, suffix: &str) -> String {
85 for &(idx, prefix) in XATTR_PREFIXES {
86 if idx == index {
87 return format!("{}{}", prefix, suffix);
88 }
89 }
90 suffix.to_string()
92 }
93
94 pub fn entry_size(&self) -> u32 {
97 align_up(self.name.len() + 16, 4) as u32
98 }
99
100 pub fn value_size(&self) -> u32 {
102 align_up(self.value.len(), 4) as u32
103 }
104
105 pub fn total_size(&self) -> u32 {
107 self.entry_size() + self.value_size()
108 }
109
110 pub fn hash(&self) -> u32 {
116 let mut h = 0u32;
118 for &b in self.name.as_bytes() {
119 h = (h << NAME_HASH_SHIFT) ^ (h >> (8 * 4 - NAME_HASH_SHIFT)) ^ (b as u32);
120 }
121
122 let value = &self.value;
125 let full_words = value.len() / 4;
126 for i in 0..full_words {
127 let off = i * 4;
128 let word = u32::from_le_bytes([
129 value[off],
130 value[off + 1],
131 value[off + 2],
132 value[off + 3],
133 ]);
134 h = (h << VALUE_HASH_SHIFT) ^ (h >> (8 * 4 - VALUE_HASH_SHIFT)) ^ word;
135 }
136
137 let tail = value.len() % 4;
139 if tail > 0 {
140 let off = full_words * 4;
141 let mut bytes = [0u8; 4];
142 bytes[..tail].copy_from_slice(&value[off..]);
143 let word = u32::from_le_bytes(bytes);
144 h = (h << VALUE_HASH_SHIFT) ^ (h >> (8 * 4 - VALUE_HASH_SHIFT)) ^ word;
145 }
146
147 h
148 }
149}
150
151const NAME_HASH_SHIFT: u32 = 5;
153const VALUE_HASH_SHIFT: u32 = 16;
155
156pub struct XattrState {
163 inode_capacity: u32,
165 block_capacity: u32,
167 inline_attrs: Vec<ExtendedAttribute>,
169 block_attrs: Vec<ExtendedAttribute>,
171 used_inline: u32,
173 used_block: u32,
175 inode_number: u32,
177}
178
179impl XattrState {
180 pub fn new(inode: u32, inode_capacity: u32, block_capacity: u32) -> Self {
182 Self {
183 inode_capacity,
184 block_capacity,
185 inline_attrs: Vec::new(),
186 block_attrs: Vec::new(),
187 used_inline: XATTR_INODE_HEADER_SIZE,
189 used_block: XATTR_BLOCK_HEADER_SIZE,
191 inode_number: inode,
192 }
193 }
194
195 pub fn add(&mut self, attr: ExtendedAttribute) -> Result<(), FormatError> {
198 let total = attr.total_size();
199
200 if self.used_inline + total + 4 <= self.inode_capacity {
203 self.used_inline += total;
204 self.inline_attrs.push(attr);
205 return Ok(());
206 }
207
208 if self.used_block + total + 4 <= self.block_capacity {
210 self.used_block += total;
211 self.block_attrs.push(attr);
212 return Ok(());
213 }
214
215 Err(FormatError::XattrInsufficientSpace(self.inode_number))
216 }
217
218 pub fn has_inline(&self) -> bool {
220 !self.inline_attrs.is_empty()
221 }
222
223 pub fn has_block(&self) -> bool {
225 !self.block_attrs.is_empty()
226 }
227
228 pub fn write_inline(&self) -> Result<Vec<u8>, FormatError> {
236 let capacity = self.inode_capacity as usize;
237 let mut buf = vec![0u8; capacity];
238
239 buf[0..4].copy_from_slice(&XATTR_HEADER_MAGIC.to_le_bytes());
241
242 let mut entry_offset = XATTR_INODE_HEADER_SIZE as usize;
243 let mut value_end = capacity;
244
245 for attr in &self.inline_attrs {
246 let val_size_aligned = align_up(attr.value.len(), 4);
248 value_end -= val_size_aligned;
249
250 let rel_value_offset = value_end - XATTR_INODE_HEADER_SIZE as usize;
253
254 if entry_offset + 16 + attr.name.len() > buf.len() {
256 return Err(FormatError::MalformedXattrBuffer);
257 }
258 write_xattr_entry(
259 &mut buf[entry_offset..],
260 &attr.name,
261 attr.index,
262 rel_value_offset as u16,
263 attr.value.len() as u32,
264 0, );
266 entry_offset += align_up(16 + attr.name.len(), 4);
267
268 buf[value_end..value_end + attr.value.len()].copy_from_slice(&attr.value);
270 }
271
272 Ok(buf)
273 }
274
275 pub fn write_block(&self) -> Result<Vec<u8>, FormatError> {
282 let capacity = self.block_capacity as usize;
283 let mut buf = vec![0u8; capacity];
284
285 buf[0..4].copy_from_slice(&XATTR_HEADER_MAGIC.to_le_bytes());
287 buf[4..8].copy_from_slice(&1u32.to_le_bytes());
289 buf[8..12].copy_from_slice(&1u32.to_le_bytes());
291 let mut sorted: Vec<&ExtendedAttribute> = self.block_attrs.iter().collect();
295 sorted.sort_by(|a, b| {
296 a.index.cmp(&b.index)
297 .then_with(|| a.name.len().cmp(&b.name.len()))
298 .then_with(|| a.name.cmp(&b.name))
299 });
300
301 let mut entry_offset = XATTR_BLOCK_HEADER_SIZE as usize;
302 let mut value_end = capacity;
303
304 for attr in &sorted {
305 let val_size_aligned = align_up(attr.value.len(), 4);
307 value_end -= val_size_aligned;
308
309 let rel_value_offset = value_end;
312
313 if entry_offset + 16 + attr.name.len() > buf.len() {
314 return Err(FormatError::MalformedXattrBuffer);
315 }
316 write_xattr_entry(
317 &mut buf[entry_offset..],
318 &attr.name,
319 attr.index,
320 rel_value_offset as u16,
321 attr.value.len() as u32,
322 attr.hash(),
323 );
324 entry_offset += align_up(16 + attr.name.len(), 4);
325
326 buf[value_end..value_end + attr.value.len()].copy_from_slice(&attr.value);
328 }
329
330 Ok(buf)
331 }
332}
333
334fn write_xattr_entry(
336 buf: &mut [u8],
337 name: &str,
338 name_index: u8,
339 value_offset: u16,
340 value_size: u32,
341 hash: u32,
342) {
343 let name_bytes = name.as_bytes();
344
345 buf[0] = name_bytes.len() as u8;
347 buf[1] = name_index;
349 buf[2..4].copy_from_slice(&value_offset.to_le_bytes());
351 buf[4..8].copy_from_slice(&0u32.to_le_bytes());
353 buf[8..12].copy_from_slice(&value_size.to_le_bytes());
355 buf[12..16].copy_from_slice(&hash.to_le_bytes());
357 buf[16..16 + name_bytes.len()].copy_from_slice(name_bytes);
359 }
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_compress_name_user_prefix() {
368 let (idx, suffix) = ExtendedAttribute::compress_name("user.mime_type");
369 assert_eq!(idx, 1);
370 assert_eq!(suffix, "mime_type");
371 }
372
373 #[test]
374 fn test_compress_name_security_prefix() {
375 let (idx, suffix) = ExtendedAttribute::compress_name("security.selinux");
376 assert_eq!(idx, 6);
377 assert_eq!(suffix, "selinux");
378 }
379
380 #[test]
381 fn test_compress_name_system_posix_acl() {
382 let (idx, suffix) = ExtendedAttribute::compress_name("system.posix_acl_access");
385 assert_eq!(idx, 2);
386 assert_eq!(suffix, "");
387 }
388
389 #[test]
390 fn test_compress_name_system_generic() {
391 let (idx, suffix) = ExtendedAttribute::compress_name("system.something");
392 assert_eq!(idx, 7);
393 assert_eq!(suffix, "something");
394 }
395
396 #[test]
397 fn test_compress_name_no_match() {
398 let (idx, suffix) = ExtendedAttribute::compress_name("unknown.attr");
399 assert_eq!(idx, 0);
400 assert_eq!(suffix, "unknown.attr");
401 }
402
403 #[test]
404 fn test_decompress_name() {
405 assert_eq!(
406 ExtendedAttribute::decompress_name(1, "mime_type"),
407 "user.mime_type"
408 );
409 assert_eq!(
410 ExtendedAttribute::decompress_name(6, "selinux"),
411 "security.selinux"
412 );
413 assert_eq!(
414 ExtendedAttribute::decompress_name(2, ""),
415 "system.posix_acl_access"
416 );
417 assert_eq!(
419 ExtendedAttribute::decompress_name(99, "foo"),
420 "foo"
421 );
422 }
423
424 #[test]
425 fn test_entry_and_value_sizes() {
426 let attr = ExtendedAttribute::new("user.x", vec![0u8; 10]);
427 assert_eq!(attr.entry_size(), 20);
429 assert_eq!(attr.value_size(), 12);
431 assert_eq!(attr.total_size(), 32);
432 }
433
434 #[test]
435 fn test_hash_deterministic() {
436 let attr = ExtendedAttribute::new("user.test", b"hello".to_vec());
437 let h1 = attr.hash();
438 let h2 = attr.hash();
439 assert_eq!(h1, h2);
440 assert_ne!(h1, 0);
441 }
442
443 #[test]
444 fn test_xattr_state_inline() {
445 let mut state = XattrState::new(11, INODE_EXTRA_SIZE, 4096);
446 let attr = ExtendedAttribute::new("user.x", vec![1, 2, 3]);
447 state.add(attr).unwrap();
448
449 assert!(state.has_inline());
450 assert!(!state.has_block());
451
452 let buf = state.write_inline().unwrap();
453 assert_eq!(buf.len(), INODE_EXTRA_SIZE as usize);
454 let magic = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
456 assert_eq!(magic, XATTR_HEADER_MAGIC);
457 }
458
459 #[test]
460 fn test_xattr_state_overflow_to_block() {
461 let mut state = XattrState::new(11, XATTR_INODE_HEADER_SIZE, 4096);
463 let attr = ExtendedAttribute::new("user.large", vec![0u8; 100]);
464 state.add(attr).unwrap();
465
466 assert!(!state.has_inline());
467 assert!(state.has_block());
468
469 let buf = state.write_block().unwrap();
470 assert_eq!(buf.len(), 4096);
471 let magic = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
473 assert_eq!(magic, XATTR_HEADER_MAGIC);
474 let refcount = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]);
476 assert_eq!(refcount, 1);
477 }
478
479 #[test]
480 fn test_xattr_state_insufficient_space() {
481 let mut state = XattrState::new(11, XATTR_INODE_HEADER_SIZE, XATTR_BLOCK_HEADER_SIZE);
483 let attr = ExtendedAttribute::new("user.big", vec![0u8; 100]);
484 let result = state.add(attr);
485 assert!(result.is_err());
486 }
487
488 #[test]
489 fn test_compress_decompress_roundtrip() {
490 let names = [
492 "user.custom_key",
493 "security.selinux",
494 "trusted.overlay.opaque",
495 "system.posix_acl_access",
496 "system.posix_acl_default",
497 "system.richacl",
498 "system.other",
499 ];
500 for full_name in names {
501 let (idx, suffix) = ExtendedAttribute::compress_name(full_name);
502 let reconstructed = ExtendedAttribute::decompress_name(idx, &suffix);
503 assert_eq!(
504 reconstructed, full_name,
505 "roundtrip failed for {full_name}"
506 );
507 }
508 }
509
510 #[test]
511 fn test_hash_different_values() {
512 let a = ExtendedAttribute::new("user.test", b"value_a".to_vec());
514 let b = ExtendedAttribute::new("user.test", b"value_b".to_vec());
515 assert_ne!(a.hash(), b.hash());
516 }
517
518 #[test]
519 fn test_hash_different_names() {
520 let a = ExtendedAttribute::new("user.alpha", b"same".to_vec());
522 let b = ExtendedAttribute::new("user.beta", b"same".to_vec());
523 assert_ne!(a.hash(), b.hash());
524 }
525
526 #[test]
527 fn test_hash_empty_value() {
528 let attr = ExtendedAttribute::new("user.empty", Vec::new());
530 assert_ne!(attr.hash(), 0);
531 }
532
533 #[test]
534 fn test_hash_value_with_trailing_bytes() {
535 let attr = ExtendedAttribute::new("user.tail", vec![1, 2, 3, 4, 5]);
537 let h = attr.hash();
538 assert_ne!(h, 0);
539 assert_eq!(h, attr.hash());
541 }
542
543 #[test]
544 fn test_entry_size_alignment() {
545 let attr = ExtendedAttribute::new("user.abcd", vec![0]);
548 assert_eq!(attr.entry_size(), 20);
549
550 let attr = ExtendedAttribute::new("user.abcde", vec![0]);
552 assert_eq!(attr.entry_size(), 24);
553
554 let attr = ExtendedAttribute::new("system.posix_acl_access", vec![0]);
557 assert_eq!(attr.entry_size(), 16);
558 }
559
560 #[test]
561 fn test_value_size_alignment() {
562 let attr = ExtendedAttribute::new("user.x", Vec::new());
564 assert_eq!(attr.value_size(), 0);
565
566 let attr = ExtendedAttribute::new("user.x", vec![42]);
568 assert_eq!(attr.value_size(), 4);
569
570 let attr = ExtendedAttribute::new("user.x", vec![0; 4]);
572 assert_eq!(attr.value_size(), 4);
573
574 let attr = ExtendedAttribute::new("user.x", vec![0; 5]);
576 assert_eq!(attr.value_size(), 8);
577 }
578
579 #[test]
580 fn test_xattr_state_multiple_inline() {
581 let mut state = XattrState::new(11, INODE_EXTRA_SIZE, 4096);
583 state
584 .add(ExtendedAttribute::new("user.a", vec![1]))
585 .unwrap();
586 state
587 .add(ExtendedAttribute::new("user.b", vec![2]))
588 .unwrap();
589 state
590 .add(ExtendedAttribute::new("user.c", vec![3]))
591 .unwrap();
592
593 assert!(state.has_inline());
594 assert!(!state.has_block());
595
596 let buf = state.write_inline().unwrap();
597 let magic = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
598 assert_eq!(magic, XATTR_HEADER_MAGIC);
599
600 assert!(buf[4..].iter().any(|&b| b != 0));
602 }
603
604 #[test]
605 fn test_xattr_state_mixed_inline_and_block() {
606 let inline_cap = 32;
612 let mut state = XattrState::new(11, inline_cap, 4096);
613 state
614 .add(ExtendedAttribute::new("user.a", vec![1]))
615 .unwrap();
616 state
618 .add(ExtendedAttribute::new("user.b", vec![2]))
619 .unwrap();
620
621 assert!(state.has_inline());
622 assert!(state.has_block());
623 }
624
625 #[test]
626 fn test_write_block_sorted_output() {
627 let mut state = XattrState::new(11, XATTR_INODE_HEADER_SIZE, 4096);
629 state
631 .add(ExtendedAttribute::new("user.zzz", vec![3]))
632 .unwrap();
633 state
634 .add(ExtendedAttribute::new("security.aaa", vec![1]))
635 .unwrap();
636 state
637 .add(ExtendedAttribute::new("user.aaa", vec![2]))
638 .unwrap();
639
640 let buf = state.write_block().unwrap();
641 let first_entry_index = buf[32 + 1]; let second_entry_index = buf[32 + 1 + align_up(16 + 3, 4) as usize]; assert!(
649 first_entry_index <= second_entry_index,
650 "entries should be sorted by name_index: {} vs {}",
651 first_entry_index,
652 second_entry_index,
653 );
654 }
655
656 fn align_up(n: usize, align: usize) -> usize {
658 (n + align - 1) & !(align - 1)
659 }
660}