⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/fs/
xattr.rs

1//! Extended Attributes (xattr) -- Per-Inode Metadata Store
2//!
3//! Provides POSIX-compatible extended file attributes with namespace support.
4//! Attributes are stored in-memory (suitable for RamFS/tmpfs) using a global
5//! store keyed by inode number. Each attribute has a namespaced name (e.g.,
6//! "user.mime_type" or "system.selinux") and an arbitrary byte value.
7//!
8//! Syscall-level functions: [`getxattr`], [`setxattr`], [`listxattr`],
9//! [`removexattr`]. Call [`cleanup_inode_xattrs`] when an inode is deleted.
10
11use alloc::{collections::BTreeMap, string::String, vec::Vec};
12
13#[cfg(not(target_arch = "aarch64"))]
14use spin::RwLock;
15
16#[cfg(target_arch = "aarch64")]
17use super::bare_lock::RwLock;
18use crate::error::{FsError, KernelError};
19
20// ---------------------------------------------------------------------------
21// Constants
22// ---------------------------------------------------------------------------
23
24/// Maximum size of a single attribute value (64 KB).
25pub const XATTR_MAX_VALUE_SIZE: usize = 65536;
26
27/// Maximum number of attributes per inode.
28pub const XATTR_MAX_ATTRS_PER_INODE: usize = 256;
29
30/// Maximum length of an attribute name (including namespace prefix).
31pub const XATTR_MAX_NAME_LEN: usize = 255;
32
33/// Flag: fail if the attribute already exists (exclusive create).
34pub const XATTR_CREATE: u32 = 1;
35
36/// Flag: fail if the attribute does not already exist (replace only).
37pub const XATTR_REPLACE: u32 = 2;
38
39// ---------------------------------------------------------------------------
40// Store
41// ---------------------------------------------------------------------------
42
43/// Global extended attribute store.
44///
45/// Maps inode number -> (attribute name -> value).  All access is serialised
46/// through a single `RwLock` which is acceptable for the current single-CPU
47/// boot context.  A per-inode lock design can be adopted later if contention
48/// becomes measurable.
49struct XattrStore {
50    /// inode -> { name -> value }
51    attrs: BTreeMap<u64, BTreeMap<String, Vec<u8>>>,
52}
53
54impl XattrStore {
55    const fn new() -> Self {
56        Self {
57            attrs: BTreeMap::new(),
58        }
59    }
60}
61
62/// Global singleton for the extended attribute store.
63static XATTR_STORE: RwLock<XattrStore> = RwLock::new(XattrStore::new());
64
65// ---------------------------------------------------------------------------
66// Namespace validation
67// ---------------------------------------------------------------------------
68
69/// Recognised xattr namespace prefixes.
70const VALID_NAMESPACES: &[&str] = &["user.", "system."];
71
72/// Validate that `name` begins with a supported namespace prefix and is
73/// otherwise well-formed.
74fn validate_name(name: &str) -> Result<(), KernelError> {
75    if name.is_empty() {
76        return Err(KernelError::InvalidArgument {
77            name: "xattr_name",
78            value: "empty name",
79        });
80    }
81
82    if name.len() > XATTR_MAX_NAME_LEN {
83        return Err(KernelError::InvalidArgument {
84            name: "xattr_name",
85            value: "name too long",
86        });
87    }
88
89    for ns in VALID_NAMESPACES {
90        if name.starts_with(ns) {
91            // The attribute-specific part must not be empty.
92            if name.len() == ns.len() {
93                return Err(KernelError::InvalidArgument {
94                    name: "xattr_name",
95                    value: "empty attribute after namespace",
96                });
97            }
98            return Ok(());
99        }
100    }
101
102    Err(KernelError::InvalidArgument {
103        name: "xattr_name",
104        value: "unsupported namespace",
105    })
106}
107
108// ---------------------------------------------------------------------------
109// Public API
110// ---------------------------------------------------------------------------
111
112/// Retrieve the value of an extended attribute.
113///
114/// Returns the attribute value as a byte vector, or an error if the inode or
115/// attribute name does not exist.
116pub fn getxattr(inode: u64, name: &str) -> Result<Vec<u8>, KernelError> {
117    validate_name(name)?;
118
119    let store = XATTR_STORE.read();
120    let inode_attrs = store
121        .attrs
122        .get(&inode)
123        .ok_or(KernelError::FsError(FsError::NotFound))?;
124
125    inode_attrs
126        .get(name)
127        .cloned()
128        .ok_or(KernelError::FsError(FsError::NotFound))
129}
130
131/// Set (create or replace) an extended attribute.
132///
133/// `flags` controls behaviour when the attribute already exists or not:
134///
135/// | flags | Exists | Does not exist |
136/// |-------|--------|----------------|
137/// | 0     | Replace | Create        |
138/// | `XATTR_CREATE`  | Error | Create |
139/// | `XATTR_REPLACE` | Replace | Error |
140///
141/// Returns an error if value exceeds [`XATTR_MAX_VALUE_SIZE`] or the inode
142/// already has [`XATTR_MAX_ATTRS_PER_INODE`] attributes.
143pub fn setxattr(inode: u64, name: &str, value: &[u8], flags: u32) -> Result<(), KernelError> {
144    validate_name(name)?;
145
146    if value.len() > XATTR_MAX_VALUE_SIZE {
147        return Err(KernelError::InvalidArgument {
148            name: "xattr_value",
149            value: "value too large",
150        });
151    }
152
153    let mut store = XATTR_STORE.write();
154    let inode_attrs = store.attrs.entry(inode).or_default();
155
156    let exists = inode_attrs.contains_key(name);
157
158    // Enforce XATTR_CREATE / XATTR_REPLACE semantics.
159    if flags & XATTR_CREATE != 0 && exists {
160        return Err(KernelError::FsError(FsError::AlreadyExists));
161    }
162    if flags & XATTR_REPLACE != 0 && !exists {
163        return Err(KernelError::FsError(FsError::NotFound));
164    }
165
166    // Enforce per-inode limit (only when inserting a new key).
167    if !exists && inode_attrs.len() >= XATTR_MAX_ATTRS_PER_INODE {
168        return Err(KernelError::ResourceExhausted {
169            resource: "xattr slots",
170        });
171    }
172
173    inode_attrs.insert(String::from(name), Vec::from(value));
174    Ok(())
175}
176
177/// List all extended attribute names for an inode.
178///
179/// Returns an empty vector if the inode has no attributes.
180pub fn listxattr(inode: u64) -> Result<Vec<String>, KernelError> {
181    let store = XATTR_STORE.read();
182    match store.attrs.get(&inode) {
183        Some(inode_attrs) => Ok(inode_attrs.keys().cloned().collect()),
184        None => Ok(Vec::new()),
185    }
186}
187
188/// Remove a single extended attribute.
189///
190/// Returns an error if the attribute does not exist.
191pub fn removexattr(inode: u64, name: &str) -> Result<(), KernelError> {
192    validate_name(name)?;
193
194    let mut store = XATTR_STORE.write();
195    let inode_attrs = store
196        .attrs
197        .get_mut(&inode)
198        .ok_or(KernelError::FsError(FsError::NotFound))?;
199
200    if inode_attrs.remove(name).is_none() {
201        return Err(KernelError::FsError(FsError::NotFound));
202    }
203
204    // If no attributes remain, drop the inode entry to save memory.
205    if inode_attrs.is_empty() {
206        store.attrs.remove(&inode);
207    }
208
209    Ok(())
210}
211
212/// Remove all extended attributes for an inode.
213///
214/// Intended to be called when an inode is deleted.  This is a no-op if the
215/// inode has no attributes.
216pub fn cleanup_inode_xattrs(inode: u64) {
217    let mut store = XATTR_STORE.write();
218    store.attrs.remove(&inode);
219}
220
221/// Return the number of attributes currently stored for `inode`.
222///
223/// Returns 0 if the inode has no attributes.
224pub fn count_xattrs(inode: u64) -> usize {
225    let store = XATTR_STORE.read();
226    store.attrs.get(&inode).map_or(0, BTreeMap::len)
227}
228
229// ---------------------------------------------------------------------------
230// Tests
231// ---------------------------------------------------------------------------
232
233#[cfg(test)]
234mod tests {
235    #[allow(unused_imports)]
236    use alloc::vec;
237
238    use super::*;
239
240    // Use a high inode range to avoid collisions between parallel tests
241    // (each test uses its own inode number).
242    const BASE_INODE: u64 = 0xFFFF_0000;
243
244    // -- validate_name -------------------------------------------------------
245
246    #[test]
247    fn test_validate_name_user_ns() {
248        assert!(validate_name("user.mime_type").is_ok());
249    }
250
251    #[test]
252    fn test_validate_name_system_ns() {
253        assert!(validate_name("system.selinux").is_ok());
254    }
255
256    #[test]
257    fn test_validate_name_bad_ns() {
258        assert!(validate_name("trusted.key").is_err());
259        assert!(validate_name("security.ima").is_err());
260    }
261
262    #[test]
263    fn test_validate_name_empty() {
264        assert!(validate_name("").is_err());
265    }
266
267    #[test]
268    fn test_validate_name_no_attr_after_ns() {
269        assert!(validate_name("user.").is_err());
270        assert!(validate_name("system.").is_err());
271    }
272
273    #[test]
274    fn test_validate_name_too_long() {
275        let long_name = alloc::format!("user.{}", "a".repeat(XATTR_MAX_NAME_LEN));
276        assert!(validate_name(&long_name).is_err());
277    }
278
279    // -- setxattr / getxattr -------------------------------------------------
280
281    #[test]
282    fn test_set_and_get() {
283        let inode = BASE_INODE + 1;
284        // Clean up from potential previous test runs.
285        cleanup_inode_xattrs(inode);
286
287        setxattr(inode, "user.color", b"blue", 0).unwrap();
288        let val = getxattr(inode, "user.color").unwrap();
289        assert_eq!(val, b"blue");
290
291        cleanup_inode_xattrs(inode);
292    }
293
294    #[test]
295    fn test_set_replaces_existing() {
296        let inode = BASE_INODE + 2;
297        cleanup_inode_xattrs(inode);
298
299        setxattr(inode, "user.key", b"v1", 0).unwrap();
300        setxattr(inode, "user.key", b"v2", 0).unwrap();
301        assert_eq!(getxattr(inode, "user.key").unwrap(), b"v2");
302
303        cleanup_inode_xattrs(inode);
304    }
305
306    #[test]
307    fn test_set_create_flag_rejects_existing() {
308        let inode = BASE_INODE + 3;
309        cleanup_inode_xattrs(inode);
310
311        setxattr(inode, "user.key", b"v1", 0).unwrap();
312        let result = setxattr(inode, "user.key", b"v2", XATTR_CREATE);
313        assert_eq!(
314            result.unwrap_err(),
315            KernelError::FsError(FsError::AlreadyExists)
316        );
317
318        cleanup_inode_xattrs(inode);
319    }
320
321    #[test]
322    fn test_set_replace_flag_rejects_new() {
323        let inode = BASE_INODE + 4;
324        cleanup_inode_xattrs(inode);
325
326        let result = setxattr(inode, "user.key", b"v1", XATTR_REPLACE);
327        assert_eq!(result.unwrap_err(), KernelError::FsError(FsError::NotFound));
328
329        cleanup_inode_xattrs(inode);
330    }
331
332    #[test]
333    fn test_set_value_too_large() {
334        let inode = BASE_INODE + 5;
335        cleanup_inode_xattrs(inode);
336
337        let big = vec![0u8; XATTR_MAX_VALUE_SIZE + 1];
338        let result = setxattr(inode, "user.big", &big, 0);
339        assert!(result.is_err());
340
341        cleanup_inode_xattrs(inode);
342    }
343
344    #[test]
345    fn test_set_max_attrs_per_inode() {
346        let inode = BASE_INODE + 6;
347        cleanup_inode_xattrs(inode);
348
349        for i in 0..XATTR_MAX_ATTRS_PER_INODE {
350            let name = alloc::format!("user.attr{}", i);
351            setxattr(inode, &name, b"x", 0).unwrap();
352        }
353
354        // The 257th should fail.
355        let result = setxattr(inode, "user.overflow", b"x", 0);
356        assert_eq!(
357            result.unwrap_err(),
358            KernelError::ResourceExhausted {
359                resource: "xattr slots",
360            }
361        );
362
363        cleanup_inode_xattrs(inode);
364    }
365
366    #[test]
367    fn test_set_invalid_namespace() {
368        let inode = BASE_INODE + 7;
369        let result = setxattr(inode, "trusted.secret", b"val", 0);
370        assert!(result.is_err());
371    }
372
373    // -- getxattr errors -----------------------------------------------------
374
375    #[test]
376    fn test_get_nonexistent_inode() {
377        let inode = BASE_INODE + 8;
378        cleanup_inode_xattrs(inode);
379
380        let result = getxattr(inode, "user.nope");
381        assert_eq!(result.unwrap_err(), KernelError::FsError(FsError::NotFound));
382    }
383
384    #[test]
385    fn test_get_nonexistent_attr() {
386        let inode = BASE_INODE + 9;
387        cleanup_inode_xattrs(inode);
388
389        setxattr(inode, "user.exists", b"yes", 0).unwrap();
390        let result = getxattr(inode, "user.missing");
391        assert_eq!(result.unwrap_err(), KernelError::FsError(FsError::NotFound));
392
393        cleanup_inode_xattrs(inode);
394    }
395
396    // -- listxattr -----------------------------------------------------------
397
398    #[test]
399    fn test_list_empty() {
400        let inode = BASE_INODE + 10;
401        cleanup_inode_xattrs(inode);
402
403        let names = listxattr(inode).unwrap();
404        assert!(names.is_empty());
405    }
406
407    #[test]
408    fn test_list_multiple() {
409        let inode = BASE_INODE + 11;
410        cleanup_inode_xattrs(inode);
411
412        setxattr(inode, "user.a", b"1", 0).unwrap();
413        setxattr(inode, "system.b", b"2", 0).unwrap();
414        setxattr(inode, "user.c", b"3", 0).unwrap();
415
416        let mut names = listxattr(inode).unwrap();
417        names.sort();
418        assert_eq!(names.len(), 3);
419        assert_eq!(names[0], "system.b");
420        assert_eq!(names[1], "user.a");
421        assert_eq!(names[2], "user.c");
422
423        cleanup_inode_xattrs(inode);
424    }
425
426    // -- removexattr ---------------------------------------------------------
427
428    #[test]
429    fn test_remove() {
430        let inode = BASE_INODE + 12;
431        cleanup_inode_xattrs(inode);
432
433        setxattr(inode, "user.del", b"bye", 0).unwrap();
434        removexattr(inode, "user.del").unwrap();
435
436        let result = getxattr(inode, "user.del");
437        assert_eq!(result.unwrap_err(), KernelError::FsError(FsError::NotFound));
438    }
439
440    #[test]
441    fn test_remove_nonexistent() {
442        let inode = BASE_INODE + 13;
443        cleanup_inode_xattrs(inode);
444
445        let result = removexattr(inode, "user.ghost");
446        assert_eq!(result.unwrap_err(), KernelError::FsError(FsError::NotFound));
447    }
448
449    #[test]
450    fn test_remove_cleans_inode_entry() {
451        let inode = BASE_INODE + 14;
452        cleanup_inode_xattrs(inode);
453
454        setxattr(inode, "user.only", b"val", 0).unwrap();
455        removexattr(inode, "user.only").unwrap();
456
457        // After removing the last attribute the inode entry should be gone,
458        // so listxattr returns an empty vec (not an error).
459        let names = listxattr(inode).unwrap();
460        assert!(names.is_empty());
461    }
462
463    // -- cleanup_inode_xattrs ------------------------------------------------
464
465    #[test]
466    fn test_cleanup() {
467        let inode = BASE_INODE + 15;
468        cleanup_inode_xattrs(inode);
469
470        setxattr(inode, "user.a", b"1", 0).unwrap();
471        setxattr(inode, "user.b", b"2", 0).unwrap();
472        assert_eq!(count_xattrs(inode), 2);
473
474        cleanup_inode_xattrs(inode);
475        assert_eq!(count_xattrs(inode), 0);
476    }
477
478    // -- count_xattrs --------------------------------------------------------
479
480    #[test]
481    fn test_count() {
482        let inode = BASE_INODE + 16;
483        cleanup_inode_xattrs(inode);
484
485        assert_eq!(count_xattrs(inode), 0);
486        setxattr(inode, "user.x", b"val", 0).unwrap();
487        assert_eq!(count_xattrs(inode), 1);
488        setxattr(inode, "system.y", b"val", 0).unwrap();
489        assert_eq!(count_xattrs(inode), 2);
490
491        cleanup_inode_xattrs(inode);
492    }
493
494    // -- edge cases ----------------------------------------------------------
495
496    #[test]
497    fn test_empty_value() {
498        let inode = BASE_INODE + 17;
499        cleanup_inode_xattrs(inode);
500
501        setxattr(inode, "user.empty", b"", 0).unwrap();
502        let val = getxattr(inode, "user.empty").unwrap();
503        assert!(val.is_empty());
504
505        cleanup_inode_xattrs(inode);
506    }
507
508    #[test]
509    fn test_max_value_size() {
510        let inode = BASE_INODE + 18;
511        cleanup_inode_xattrs(inode);
512
513        let data = vec![0xABu8; XATTR_MAX_VALUE_SIZE];
514        setxattr(inode, "user.big", &data, 0).unwrap();
515        let val = getxattr(inode, "user.big").unwrap();
516        assert_eq!(val.len(), XATTR_MAX_VALUE_SIZE);
517
518        cleanup_inode_xattrs(inode);
519    }
520
521    #[test]
522    fn test_binary_value() {
523        let inode = BASE_INODE + 19;
524        cleanup_inode_xattrs(inode);
525
526        let binary: Vec<u8> = (0..=255u8).collect();
527        setxattr(inode, "user.bin", &binary, 0).unwrap();
528        assert_eq!(getxattr(inode, "user.bin").unwrap(), binary);
529
530        cleanup_inode_xattrs(inode);
531    }
532
533    #[test]
534    fn test_replace_flag_succeeds_on_existing() {
535        let inode = BASE_INODE + 20;
536        cleanup_inode_xattrs(inode);
537
538        setxattr(inode, "user.key", b"old", 0).unwrap();
539        setxattr(inode, "user.key", b"new", XATTR_REPLACE).unwrap();
540        assert_eq!(getxattr(inode, "user.key").unwrap(), b"new");
541
542        cleanup_inode_xattrs(inode);
543    }
544
545    #[test]
546    fn test_create_flag_succeeds_on_new() {
547        let inode = BASE_INODE + 21;
548        cleanup_inode_xattrs(inode);
549
550        setxattr(inode, "user.fresh", b"val", XATTR_CREATE).unwrap();
551        assert_eq!(getxattr(inode, "user.fresh").unwrap(), b"val");
552
553        cleanup_inode_xattrs(inode);
554    }
555
556    #[test]
557    fn test_multiple_inodes_isolated() {
558        let inode_a = BASE_INODE + 22;
559        let inode_b = BASE_INODE + 23;
560        cleanup_inode_xattrs(inode_a);
561        cleanup_inode_xattrs(inode_b);
562
563        setxattr(inode_a, "user.key", b"from_a", 0).unwrap();
564        setxattr(inode_b, "user.key", b"from_b", 0).unwrap();
565
566        assert_eq!(getxattr(inode_a, "user.key").unwrap(), b"from_a");
567        assert_eq!(getxattr(inode_b, "user.key").unwrap(), b"from_b");
568
569        cleanup_inode_xattrs(inode_a);
570        cleanup_inode_xattrs(inode_b);
571    }
572}