diff --git a/Cargo.lock b/Cargo.lock index ad647956..b6e2db62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,6 +221,7 @@ dependencies = [ "rand", "riscv", "spin", + "systree", "takeable", "tdx-guest", "time", @@ -301,6 +302,7 @@ dependencies = [ "log", "ostd", "spin", + "systree", "typeflags-util", ] @@ -1684,6 +1686,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "systree" +version = "0.1.0" +dependencies = [ + "bitflags 2.6.0", + "component", + "ostd", + "spin", +] + [[package]] name = "takeable" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 9cae07a1..46132ee7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "kernel/comps/input", "kernel/comps/network", "kernel/comps/softirq", + "kernel/comps/systree", "kernel/comps/logger", "kernel/comps/mlsdisk", "kernel/comps/time", diff --git a/Components.toml b/Components.toml index 0c84da50..a37329aa 100644 --- a/Components.toml +++ b/Components.toml @@ -11,6 +11,7 @@ time = { name = "aster-time" } framebuffer = { name = "aster-framebuffer" } network = { name = "aster-network" } mlsdisk = { name = "aster-mlsdisk" } +systree = { name = "systree" } [whitelist] [whitelist.nix.main] diff --git a/Makefile b/Makefile index 2093a14a..f21c749e 100644 --- a/Makefile +++ b/Makefile @@ -171,6 +171,7 @@ OSDK_CRATES := \ kernel/comps/input \ kernel/comps/network \ kernel/comps/softirq \ + kernel/comps/systree \ kernel/comps/logger \ kernel/comps/mlsdisk \ kernel/comps/time \ diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 757bcde7..1f7107c2 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -58,6 +58,7 @@ inherit-methods-macro = { git = "https://github.com/asterinas/inherit-methods-ma getset = "0.1.2" takeable = "0.2.2" cfg-if = "1.0" +systree = { path = "comps/systree" } # Fixed point numbers # TODO: fork this crate to rewrite all the (unnecessary) unsafe usage fixed = "1.28.0" diff --git a/kernel/comps/systree/Cargo.toml b/kernel/comps/systree/Cargo.toml new file mode 100644 index 00000000..df93bc7c --- /dev/null +++ b/kernel/comps/systree/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "systree" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitflags = "2.5" +ostd = { path = "../../../ostd" } +component = { path = "../../libs/comp-sys/component" } +spin = "0.9" diff --git a/kernel/comps/systree/src/attr.rs b/kernel/comps/systree/src/attr.rs new file mode 100644 index 00000000..a30d3ea0 --- /dev/null +++ b/kernel/comps/systree/src/attr.rs @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::collections::BTreeMap; +use core::fmt::Debug; + +use bitflags::bitflags; + +use super::{Error, Result, SysStr}; + +bitflags! { + /// Flags defining the properties and permissions of a `SysAttr`. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct SysAttrFlags: u32 { + /// Indicates whether the attribute can be read. + const CAN_READ = 1 << 0; + /// Indicates whether the attribute can be written to. + const CAN_WRITE = 1 << 1; + /// Indicates whether an attribute is a binary one + /// (rather than a textual one). + const IS_BINARY = 1 << 4; + } +} + +impl Default for SysAttrFlags { + fn default() -> Self { + Self::CAN_READ + } +} + +/// An attribute may be fetched or updated via the methods of `SysNode` +/// such as `SysNode::read_attr` and `SysNode::write_attr`. +#[derive(Debug, Clone)] +pub struct SysAttr { + /// Local ID within the node's `SysAttrSet`. Unique within the set. + id: u8, + /// The name of the attribute. Used to look up the attribute in a `SysAttrSet`. + name: SysStr, + /// Flags defining the behavior and permissions of the attribute. + flags: SysAttrFlags, + // Potentially add read/write handler functions or trait objects later + // read_handler: fn(...) -> Result, + // write_handler: fn(...) -> Result, +} + +impl SysAttr { + /// Creates a new attribute. + pub fn new(id: u8, name: SysStr, flags: SysAttrFlags) -> Self { + Self { id, name, flags } + } + + /// Returns the unique ID of the attribute within its set. + pub fn id(&self) -> u8 { + self.id + } + + /// Returns the name of the attribute. + pub fn name(&self) -> &SysStr { + &self.name + } + + /// Returns the flags associated with the attribute. + pub fn flags(&self) -> SysAttrFlags { + self.flags + } +} + +/// A collection of `SysAttr` for a `SysNode`. +/// Manages the attributes associated with a specific node in the `SysTree`. +/// +/// This is an immutable collection - use `SysAttrSetBuilder` to create non-empty sets. +#[derive(Debug, Default, Clone)] +pub struct SysAttrSet { + /// Stores attributes keyed by their name. + attrs: BTreeMap, +} + +impl SysAttrSet { + /// Maximum number of attributes allowed per node (limited by u8 ID space). + pub const CAPACITY: usize = 1 << u8::BITS; + + /// Creates a new, empty attribute set. + /// + /// To create a non-empty attribute set, use `SysAttrSetBuilder`. + pub fn new_empty() -> Self { + Default::default() + } + + /// Retrieves an attribute by its name. + pub fn get(&self, name: &str) -> Option<&SysAttr> { + self.attrs.get(name) + } + + /// Returns an iterator over the attributes in the set. + pub fn iter(&self) -> impl Iterator { + self.attrs.values() + } + + /// Returns the number of attributes in the set. + pub fn len(&self) -> usize { + self.attrs.len() + } + + /// Checks if the attribute set is empty. + pub fn is_empty(&self) -> bool { + self.attrs.is_empty() + } + + /// Checks if an attribute with the given name exists in the set. + pub fn contains(&self, attr_name: &str) -> bool { + self.attrs.contains_key(attr_name) + } +} + +#[derive(Debug, Default)] +pub struct SysAttrSetBuilder { + attrs: BTreeMap, + next_id: u8, +} + +impl SysAttrSetBuilder { + /// Creates a new builder. + pub fn new() -> Self { + Default::default() + } + + /// Adds an attribute definition to the builder. + /// + /// If an attribute with the same name already exists, this is a no-op. + pub fn add(&mut self, name: SysStr, flags: SysAttrFlags) -> &mut Self { + if self.attrs.contains_key(&name) { + return self; + } + + let id = self.next_id; + self.next_id += 1; + let new_attr = SysAttr::new(id, name.clone(), flags); + self.attrs.insert(name, new_attr); + self + } + + /// Consumes the builder and returns the constructed `SysAttrSet`. + /// + /// # Errors + /// Returns `Err` if the capacity limit is reached. + pub fn build(self) -> Result { + if self.attrs.len() > SysAttrSet::CAPACITY { + return Err(Error::PermissionDenied); + } + Ok(SysAttrSet { attrs: self.attrs }) + } +} diff --git a/kernel/comps/systree/src/lib.rs b/kernel/comps/systree/src/lib.rs new file mode 100644 index 00000000..5aae9448 --- /dev/null +++ b/kernel/comps/systree/src/lib.rs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! This crate organizes the kernel information +//! about the entire system in a tree structure called `SysTree`. +//! +//! This crate provides a singleton of `SysTree`, +//! which is the "model" part of Asterinas's +//! model-view-controller (MVC) architecture +//! for organizing and managing device and kernel information. +//! The "view" part is sysfs, +//! a file system that exposes the system information +//! of the in-kernel `SysTree` to the user space. +//! The "controller" part consists of +//! various subsystems, buses, drivers, and kernel modules. +//! The "view" part has read-only access to the "model", +//! whereas the "controller" part can make changes to the "model". +//! This MVC architecture achieves separation of concerns, +//! making the code more modular, maintainable, and easier to understand. + +#![no_std] +#![deny(unsafe_code)] + +extern crate alloc; + +mod attr; +mod node; +#[cfg(ktest)] +mod test; +mod tree; +mod utils; + +use alloc::{borrow::Cow, sync::Arc}; + +use component::{init_component, ComponentInitError}; +use spin::Once; + +pub use self::{ + attr::{SysAttr, SysAttrFlags, SysAttrSet, SysAttrSetBuilder}, + node::{SysBranchNode, SysNode, SysNodeId, SysNodeType, SysObj, SysSymlink}, + tree::SysTree, + utils::{SymlinkNodeFields, SysBranchNodeFields, SysNormalNodeFields, SysObjFields}, +}; + +static SINGLETON: Once> = Once::new(); + +#[init_component] +fn init() -> core::result::Result<(), ComponentInitError> { + SINGLETON.call_once(|| Arc::new(SysTree::new())); + Ok(()) +} + +#[cfg(ktest)] +pub fn init_for_ktest() { + SINGLETON.call_once(|| Arc::new(SysTree::new())); +} + +/// Returns a reference to the global SysTree instance. Panics if not initialized. (Asterinas specific) +pub fn singleton() -> &'static Arc { + SINGLETON.get().expect("SysTree not initialized") +} + +/// An owned string or a static reference to string. +pub type SysStr = Cow<'static, str>; + +pub type Result = core::result::Result; + +#[derive(Debug)] +pub enum Error { + /// Attempted to access a non-existent node + NodeNotFound(SysNodeId), + /// Invalid operation for node type + InvalidNodeOperation(SysNodeType), + /// Attribute operation failed + AttributeError, + /// Permission denied for operation + PermissionDenied, + /// Other internal error + InternalError(&'static str), +} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + Error::NodeNotFound(id) => write!(f, "Node not found: {:?}", id), + Error::InvalidNodeOperation(ty) => { + write!(f, "Invalid operation for node type: {:?}", ty) + } + Error::AttributeError => write!(f, "Attribute error"), + Error::PermissionDenied => write!(f, "Permission denied for operation"), + Error::InternalError(msg) => write!(f, "Internal error: {}", msg), + } + } +} diff --git a/kernel/comps/systree/src/node.rs b/kernel/comps/systree/src/node.rs new file mode 100644 index 00000000..91b55adf --- /dev/null +++ b/kernel/comps/systree/src/node.rs @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::{string::String, sync::Arc, vec, vec::Vec}; +use core::{ + any::Any, + fmt::Debug, + sync::atomic::{AtomicU64, Ordering}, +}; + +use ostd::mm::{VmReader, VmWriter}; + +use super::{Error, Result, SysAttrSet, SysStr}; + +pub const MAX_ATTR_SIZE: usize = 4096; + +/// The three types of nodes in a `SysTree`. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum SysNodeType { + /// A branching node is one that may contain child nodes. + Branch, + /// A leaf node is one that may not contain child nodes. + Leaf, + /// A symlink node, + /// which ia a special kind of leaf node that points to another node, + /// similar to a symbolic link in file systems. + Symlink, +} + +/// A trait that represents a branching node in a `SysTree`. +pub trait SysBranchNode: SysNode { + /// Visits a child node with the given name using a closure. + /// + /// If the child with the given name exists, + /// a reference to the child will be provided to the closure. + /// Otherwise, the closure will be given a `None`. + /// + /// # Efficiency + /// + /// This method is a more efficient, but less convenient version + /// of the `child` method. + /// The method does not require taking the ownership of the child node. + /// So use this method when efficiency is a primary concern, + /// while using the `child` method for the sake of convenience. + /// + /// # Deadlock + /// + /// The implementation of this method depends on the concrete type + /// and probably will hold an internal lock. + /// So the caller should do as little as possible inside the closure. + /// In particular, the caller should _not_ invoke other methods + /// on this object as this might cause deadlock. + fn visit_child_with(&self, name: &str, f: &mut dyn FnMut(Option<&dyn SysNode>)); + + /// Visits child nodes with a minimum ID using a closure. + /// + /// This method iterates over the child nodes + /// whose IDs are no less than a specified minimum value. + /// and provide them to the given closure one at a time. + /// + /// The iteration terminates until there are no unvisited children + /// or the closure returns a `None`. + /// + /// # Efficiency + /// + /// This method is a more efficient, but less convenient version + /// of the `children` method. + /// The method require neither taking the ownership of the child nodes + /// nor doing heap allocations. + /// So use this method when efficiency is a primary concern, + /// while using the `children` method for the sake of convenience. + /// + /// # Deadlock + /// + /// Same as the `visit_child_with` method. + fn visit_children_with( + &self, + min_id: u64, + f: &mut dyn for<'a> FnMut(&'a Arc<(dyn SysObj + 'static)>) -> Option<()>, + ); + + /// Returns a child with a specified name. + fn child(&self, name: &str) -> Option>; + + /// Collects all children into a `Vec`. + fn children(&self) -> Vec> { + let mut children: Vec> = Vec::new(); + self.visit_children_with(0, &mut |child_arc| { + children.push(child_arc.clone()); + Some(()) + }); + children + } + + /// Counts the number of children. + fn count_children(&self) -> usize { + let mut count = 0; + self.visit_children_with(0, &mut |_| { + count += 1; + Some(()) + }); + count + } +} + +/// The trait that abstracts a "normal" node in a `SysTree`. +/// +/// The branching and leaf nodes are considered "normal", +/// whereas the symlink nodes are considered "special". +/// This trait abstracts the common interface of "normal" nodes. +/// In particular, every "normal" node may have associated attributes. +pub trait SysNode: SysObj { + /// Returns the attribute set of a `SysNode`. + fn node_attrs(&self) -> &SysAttrSet; + + /// Reads the value of an attribute. + fn read_attr(&self, name: &str, writer: &mut VmWriter) -> Result; + + /// Writes the value of an attribute. + fn write_attr(&self, name: &str, reader: &mut VmReader) -> Result; + + /// Shows the string value of an attribute. + /// + /// Most attributes are textual, rather binary (see `SysAttrFlags::IS_BINARY`). + /// So using this `show_attr` method is more convenient than + /// the `read_attr` method. + fn show_attr(&self, name: &str) -> Result { + let mut buf: Vec = vec![0; MAX_ATTR_SIZE]; + let mut writer = VmWriter::from(buf.as_mut_slice()).to_fallible(); + let read_len = self.read_attr(name, &mut writer)?; + // Use from_utf8_lossy or handle error properly if strict UTF-8 is needed + let attr_val = + String::from_utf8(buf[..read_len].to_vec()).map_err(|_| Error::AttributeError)?; + Ok(attr_val) + } + + /// Stores the string value of an attribute. + /// + /// Most attributes are textual, rather binary (see `SysAttrFlags::IS_BINARY`). + /// So using this `store_attr` method is more convenient than + /// the `write_attr` method. + fn store_attr(&self, name: &str, new_val: &str) -> Result { + let mut reader = VmReader::from(new_val.as_bytes()).to_fallible(); + self.write_attr(name, &mut reader) + } +} + +/// A trait that abstracts any symlink node in a `SysTree`. +pub trait SysSymlink: SysObj { + /// A path that represents the target node of this symlink node. + fn target_path(&self) -> &str; +} + +/// The base trait for any node in a `SysTree`. +pub trait SysObj: Any + Send + Sync + Debug + 'static { + /// Returns a reference to this object as `Any` for downcasting. + fn as_any(&self) -> &dyn Any; + + /// Attempts to get an Arc to this object as a `SysSymlink`. + fn arc_as_symlink(&self) -> Option> { + None + } + + /// Attempts to get an Arc to this object as a `SysNode`. + fn arc_as_node(&self) -> Option> { + None + } + + /// Attempts to get an Arc to this object as a `SysBranchNode`. + fn arc_as_branch(&self) -> Option> { + None + } + + /// Returns the unique and immutable ID of a node. + fn id(&self) -> &SysNodeId; + + /// Returns the type of a node. + fn type_(&self) -> SysNodeType; + + /// Returns the name of a node. + /// + /// The name is guaranteed _not_ to contain two special characters: + /// `'/'` and `'\0'`. + /// + /// The root node of a `SysTree` has an empty name. + /// All other inodes must have an non-empty name. + fn name(&self) -> SysStr; + + /// Returns whether a node is the root of a `SysTree`. + fn is_root(&self) -> bool { + false + } + + /// Returns the path from the root to this node. + /// + /// The path of a node is the names of all the ancestors concatenated + /// with `/` as the separator. + /// + /// If the node has been attached to a `SysTree`, + /// then the returned path begins with `/`. + /// Otherwise, the returned path does _not_ begin with `/`. + fn path(&self) -> SysStr { + todo!("implement with the parent and name methods") + } +} + +/// The unique ID of a `SysNode`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct SysNodeId(u64); + +impl SysNodeId { + /// Creates a new ID. + pub fn new() -> Self { + static NEXT_ID: AtomicU64 = AtomicU64::new(0); + + let next_id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + // Guard against integer overflow + assert!(next_id <= u64::MAX / 2); + + Self(next_id) + } + + /// Gets the value of the ID. + pub fn as_u64(&self) -> u64 { + self.0 + } +} + +impl Default for SysNodeId { + fn default() -> Self { + Self::new() + } +} diff --git a/kernel/comps/systree/src/tree.rs b/kernel/comps/systree/src/tree.rs new file mode 100644 index 00000000..76066de8 --- /dev/null +++ b/kernel/comps/systree/src/tree.rs @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Defines the main `SysTree` structure and its root node implementation. + +use alloc::{ + borrow::Cow, + collections::BTreeMap, + sync::{Arc, Weak}, + vec::Vec, +}; +use core::any::Any; + +use ostd::{ + mm::{VmReader, VmWriter}, + sync::RwLock, +}; + +use super::{ + attr::SysAttrSet, + node::{SysBranchNode, SysNode, SysNodeId, SysNodeType, SysObj, SysSymlink}, + Error, Result, SysStr, +}; + +#[derive(Debug)] +pub struct SysTree { + root: Arc, + // event_hub: SysEventHub, +} + +impl SysTree { + /// Creates a new `SysTree` instance with a default root node + /// and standard subdirectories like "devices", "block", "kernel". + /// This is intended to be called once for the singleton. + pub(crate) fn new() -> Self { + let root_node = Arc::new_cyclic(|weak_self| RootNode { + id: SysNodeId::new(), + name: "".into(), + attrs: SysAttrSet::new_empty(), + children: RwLock::new(BTreeMap::new()), + self_ref: weak_self.clone(), + }); + + Self { root: root_node } + } + + /// Returns a reference to the root node of the tree. + pub fn root(&self) -> &Arc { + &self.root + } +} + +#[derive(Debug)] +pub struct RootNode { + id: SysNodeId, + name: SysStr, + attrs: SysAttrSet, + children: RwLock>>, + self_ref: Weak, +} + +impl RootNode { + /// Adds a child node. This was part of the concrete RootNode impl in lib.rs. + /// It's not part of the SysBranchNode trait definition. + pub fn add_child(&self, new_child: Arc) -> Result<()> { + let name = new_child.name(); + let mut children_guard = self.children.write(); + if children_guard.contains_key(&name) { + return Err(Error::PermissionDenied); + } + children_guard.insert(name.clone(), new_child); + Ok(()) + } +} + +impl SysObj for RootNode { + fn as_any(&self) -> &dyn Any { + self + } + + fn arc_as_symlink(&self) -> Option> { + None + } + + fn arc_as_node(&self) -> Option> { + self.self_ref + .upgrade() + .map(|arc_self| arc_self as Arc) + } + + fn arc_as_branch(&self) -> Option> { + self.self_ref + .upgrade() + .map(|arc_self| arc_self as Arc) + } + + fn id(&self) -> &SysNodeId { + &self.id + } + + fn type_(&self) -> SysNodeType { + if self.children.read().is_empty() { + return SysNodeType::Leaf; + } + SysNodeType::Branch + } + + fn name(&self) -> SysStr { + self.name.clone() + } + + fn is_root(&self) -> bool { + true + } + + fn path(&self) -> SysStr { + Cow::from("/") + } +} + +impl SysNode for RootNode { + fn node_attrs(&self) -> &SysAttrSet { + &self.attrs + } + + fn read_attr(&self, _name: &str, _writer: &mut VmWriter) -> Result { + Err(Error::AttributeError) + } + + fn write_attr(&self, _name: &str, _reader: &mut VmReader) -> Result { + Err(Error::AttributeError) + } +} + +impl SysBranchNode for RootNode { + fn visit_child_with(&self, name: &str, f: &mut dyn FnMut(Option<&dyn SysNode>)) { + let children_guard = self.children.read(); + children_guard + .get(name) + .map(|child| { + if let Some(node_ref) = child.arc_as_node().as_deref() { + f(Some(node_ref)); + } else { + f(None); + } + }) + .unwrap_or_else(|| f(None)); + } + + fn visit_children_with(&self, _min_id: u64, f: &mut dyn FnMut(&Arc) -> Option<()>) { + let children_guard = self.children.read(); + for child_arc in children_guard.values() { + if f(child_arc).is_none() { + break; + } + } + } + + fn child(&self, name: &str) -> Option> { + self.children.read().get(name).cloned() + } + + fn children(&self) -> Vec> { + self.children.read().values().cloned().collect() + } +} diff --git a/kernel/comps/systree/src/utils.rs b/kernel/comps/systree/src/utils.rs new file mode 100644 index 00000000..36225dad --- /dev/null +++ b/kernel/comps/systree/src/utils.rs @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Utility definitions and helper structs for implementing `SysTree` nodes. + +use alloc::{collections::BTreeMap, string::String, sync::Arc}; +use core::ops::Deref; + +use ostd::sync::RwLock; + +use super::{ + attr::SysAttrSet, + node::{SysNodeId, SysObj}, + Error, Result, SysStr, +}; + +#[derive(Debug)] +pub struct SysObjFields { + id: SysNodeId, + name: SysStr, +} + +impl SysObjFields { + pub fn new(name: SysStr) -> Self { + Self { + id: SysNodeId::new(), + name, + } + } + + pub fn id(&self) -> &SysNodeId { + &self.id + } + + pub fn name(&self) -> &str { + self.name.deref() + } +} + +#[derive(Debug)] +pub struct SysNormalNodeFields { + base: SysObjFields, + attr_set: SysAttrSet, +} + +impl SysNormalNodeFields { + pub fn new(name: SysStr, attr_set: SysAttrSet) -> Self { + Self { + base: SysObjFields::new(name), + attr_set, + } + } + + pub fn id(&self) -> &SysNodeId { + self.base.id() + } + + pub fn name(&self) -> &str { + self.base.name() + } + + pub fn attr_set(&self) -> &SysAttrSet { + &self.attr_set + } +} + +#[derive(Debug)] +pub struct SysBranchNodeFields { + base: SysNormalNodeFields, + pub children: RwLock>>, +} + +impl SysBranchNodeFields { + pub fn new(name: SysStr, attr_set: SysAttrSet) -> Self { + Self { + base: SysNormalNodeFields::new(name, attr_set), + children: RwLock::new(BTreeMap::new()), + } + } + + pub fn id(&self) -> &SysNodeId { + self.base.id() + } + + pub fn name(&self) -> &str { + self.base.name() + } + + pub fn attr_set(&self) -> &SysAttrSet { + self.base.attr_set() + } + + pub fn contains(&self, child_name: &str) -> bool { + let children = self.children.read(); + children.contains_key(child_name) + } + + pub fn add_child(&self, new_child: Arc) -> Result<()> { + let mut children = self.children.write(); + let name = new_child.name(); + if children.contains_key(name.deref()) { + return Err(Error::PermissionDenied); + } + children.insert(name.clone(), new_child); + Ok(()) + } + + pub fn remove_child(&self, child_name: &str) -> Option> { + let mut children = self.children.write(); + children.remove(child_name) + } +} + +#[derive(Debug)] +pub struct SymlinkNodeFields { + base: SysObjFields, + target_path: String, +} + +impl SymlinkNodeFields { + pub fn new(name: SysStr, target_path: String) -> Self { + Self { + base: SysObjFields::new(name), + target_path, + } + } + + pub fn id(&self) -> &SysNodeId { + self.base.id() + } + + pub fn name(&self) -> &str { + self.base.name() + } + + pub fn target_path(&self) -> &str { + &self.target_path + } +} diff --git a/kernel/comps/virtio/Cargo.toml b/kernel/comps/virtio/Cargo.toml index 8c546313..34d2160d 100644 --- a/kernel/comps/virtio/Cargo.toml +++ b/kernel/comps/virtio/Cargo.toml @@ -22,6 +22,7 @@ ostd = { path = "../../../ostd" } component = { path = "../../libs/comp-sys/component" } log = "0.4" int-to-c-enum = { path = "../../libs/int-to-c-enum" } +systree = { path = "../systree" } [lints] workspace = true diff --git a/kernel/src/error.rs b/kernel/src/error.rs index ab4d7046..7ef9b999 100644 --- a/kernel/src/error.rs +++ b/kernel/src/error.rs @@ -330,6 +330,19 @@ impl From for Error { } } +impl From for Error { + fn from(err: systree::Error) -> Self { + use systree::Error::*; + match err { + NodeNotFound(_) => Error::new(Errno::ENOENT), + InvalidNodeOperation(_) => Error::new(Errno::EINVAL), + AttributeError => Error::new(Errno::EIO), + PermissionDenied => Error::new(Errno::EACCES), + InternalError(msg) => Error::with_message(Errno::EIO, msg), + } + } +} + #[macro_export] macro_rules! return_errno { ($errno: expr) => { diff --git a/kernel/src/fs/mod.rs b/kernel/src/fs/mod.rs index 62a104e5..ab38447a 100644 --- a/kernel/src/fs/mod.rs +++ b/kernel/src/fs/mod.rs @@ -15,6 +15,7 @@ pub mod pipe; pub mod procfs; pub mod ramfs; pub mod rootfs; +pub mod sysfs; pub mod thread_info; pub mod utils; diff --git a/kernel/src/fs/rootfs.rs b/kernel/src/fs/rootfs.rs index 030a6e0c..3c0fa41d 100644 --- a/kernel/src/fs/rootfs.rs +++ b/kernel/src/fs/rootfs.rs @@ -11,6 +11,7 @@ use super::{ path::MountNode, procfs::{self, ProcFS}, ramfs::RamFS, + sysfs::{init as sysfs_init, singleton as sysfs_singleton}, utils::{FileSystem, InodeMode, InodeType}, }; use crate::{fs::path::is_dot, prelude::*}; @@ -113,7 +114,11 @@ pub fn init(initramfs_buf: &[u8]) -> Result<()> { // Mount DevFS let dev_dentry = fs.lookup(&FsPath::try_from("/dev")?)?; dev_dentry.mount(RamFS::new())?; - + // Mount SysFS + let sys_dentry = fs.lookup(&FsPath::try_from("/sys")?)?; + sysfs_init(); + let sysfs: Arc = sysfs_singleton().clone(); + sys_dentry.mount(sysfs)?; println!("[kernel] rootfs is ready"); Ok(()) diff --git a/kernel/src/fs/sysfs/fs.rs b/kernel/src/fs/sysfs/fs.rs new file mode 100644 index 00000000..e2294314 --- /dev/null +++ b/kernel/src/fs/sysfs/fs.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::sync::Arc; + +use systree::singleton as systree_singleton; + +use crate::fs::{ + sysfs::inode::SysFsInode, + utils::{FileSystem, FsFlags, Inode, SuperBlock}, + Result, +}; + +/// A file system for exposing kernel information to the user space. +#[derive(Debug)] +pub struct SysFs { + sb: SuperBlock, + root: Arc, +} + +const MAGIC_NUMBER: u64 = 0x62656572; // SYSFS_MAGIC +const BLOCK_SIZE: usize = 4096; +const NAME_MAX: usize = 255; + +impl SysFs { + pub(crate) fn new() -> Arc { + let sb = SuperBlock::new(MAGIC_NUMBER, BLOCK_SIZE, NAME_MAX); + let systree_ref = systree_singleton(); + let root_inode = SysFsInode::new_root(systree_ref); + + Arc::new(Self { + sb, + root: root_inode, + }) + } +} + +impl FileSystem for SysFs { + fn sync(&self) -> Result<()> { + // Sysfs is volatile, sync is a no-op + Ok(()) + } + + fn root_inode(&self) -> Arc { + self.root.clone() + } + + fn sb(&self) -> SuperBlock { + self.sb.clone() + } + + fn flags(&self) -> FsFlags { + FsFlags::empty() + } +} diff --git a/kernel/src/fs/sysfs/inode.rs b/kernel/src/fs/sysfs/inode.rs new file mode 100644 index 00000000..f8194df8 --- /dev/null +++ b/kernel/src/fs/sysfs/inode.rs @@ -0,0 +1,731 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::{ + borrow::Cow, + string::{String, ToString}, + sync::{Arc, Weak}, + vec::Vec, +}; +use core::time::Duration; + +use ostd::sync::RwLock; +use systree::{ + SysAttr, SysAttrFlags, SysBranchNode, SysNode, SysNodeId, SysNodeType, SysObj, SysStr, + SysSymlink, SysTree, +}; + +use crate::{ + events::IoEvents, + fs::{ + device::Device, + utils::{ + DirentVisitor, FallocMode, FileSystem, Inode, InodeMode, InodeType, IoctlCmd, Metadata, + MknodType, + }, + }, + prelude::{VmReader, VmWriter}, + process::{signal::PollHandle, Gid, Uid}, + return_errno_with_message, + time::{clocks::RealTimeCoarseClock, Clock}, + Errno, Error, Result, +}; + +type Ino = u64; + +pub struct SysFsInode { + /// The global SysTree reference, representing the kernel's exported system information tree. + systree: &'static Arc, + /// The corresponding node in the SysTree. + inner_node: InnerNode, + /// The metadata of this inode. + /// + /// Most of the metadata (e.g., file size, timestamps) + /// can be determined upon the creation of an inode, + /// and are thus kept intact inside the immutable `metadata` field. + /// Currently, the only mutable metadata is `mode`, + /// which allows user space to `chmod` an inode on sysfs. + metadata: Metadata, + /// The file mode (permissions) of this inode, protected by a lock. + mode: RwLock, + /// Weak reference to the parent inode. + parent: Weak, + /// Weak self-reference for cyclic data structures. + this: Weak, +} + +#[derive(Debug)] +enum InnerNode { + Branch(Arc), + Leaf(Arc), + Attr(SysAttr, Arc), + Symlink(Arc), +} + +impl SysFsInode { + pub(crate) fn new_root(systree: &'static Arc) -> Arc { + let root_node = systree.root().clone(); // systree.root() returns Arc + let inner_node = InnerNode::Branch(root_node); + let parent = Weak::new(); + Self::new_branch_dir(systree, inner_node, parent) + } + + fn new_attr( + systree: &'static Arc, + attr: SysAttr, + node: Arc, + parent: Weak, + ) -> Arc { + let inner_node = InnerNode::Attr(attr.clone(), node); + let ino = ino::from_inner_node(&inner_node); + let metadata = Self::new_metadata(ino, InodeType::File); + let mode = RwLock::new(Self::flags_to_inode_mode(attr.flags())); + Arc::new_cyclic(|this| Self { + systree, + inner_node, + metadata, + mode, + parent, + this: this.clone(), + }) + } + + fn new_symlink( + systree: &'static Arc, + symlink: Arc, + parent: Weak, + ) -> Arc { + let inner_node = InnerNode::Symlink(symlink); + let ino = ino::from_inner_node(&inner_node); + let metadata = Self::new_metadata(ino, InodeType::SymLink); + let mode = RwLock::new(InodeMode::from_bits_truncate(0o777)); + Arc::new_cyclic(|this| Self { + systree, + inner_node, + metadata, + mode, + parent, + this: this.clone(), + }) + } + + fn new_metadata(ino: u64, type_: InodeType) -> Metadata { + let now = RealTimeCoarseClock::get().read_time(); + Metadata { + dev: 0, + ino, + size: 0, + blk_size: 1024, + blocks: 0, + atime: now, + mtime: now, + ctime: now, + type_, + mode: InodeMode::from_bits_truncate(0o555), + nlinks: 1, + uid: Uid::new_root(), + gid: Gid::new_root(), + rdev: 0, + } + } + + fn flags_to_inode_mode(flags: SysAttrFlags) -> InodeMode { + let mut bits = 0o000; // Start with no permissions + if flags.contains(SysAttrFlags::CAN_READ) { + bits |= 0o444; // Add read permissions if flag is set + } + if flags.contains(SysAttrFlags::CAN_WRITE) { + bits |= 0o222; // Add write permissions if flag is set + } + // Note: Execute permissions (0o111) are typically granted for directories, handled elsewhere. + InodeMode::from_bits_truncate(bits) + } + + fn new_branch_dir( + systree: &'static Arc, + inner_node: InnerNode, // Must be InnerNode::Branch + parent: Weak, + ) -> Arc { + let ino = ino::from_inner_node(&inner_node); + let metadata = Self::new_metadata(ino, InodeType::Dir); + let mode = RwLock::new(InodeMode::from_bits_truncate(0o555)); + Arc::new_cyclic(|this| Self { + systree, + inner_node, + metadata, + mode, + parent, + this: this.clone(), + }) + } + + fn new_leaf_dir( + systree: &'static Arc, + inner_node: InnerNode, // Must be InnerNode::Leaf + parent: Weak, + ) -> Arc { + let ino = ino::from_inner_node(&inner_node); + let metadata = Self::new_metadata(ino, InodeType::Dir); // Leaf nodes are represented as Dirs + let mode = RwLock::new(InodeMode::from_bits_truncate(0o555)); // Read/execute for all + Arc::new_cyclic(|this| Self { + systree, + inner_node, + metadata, + mode, + parent, + this: this.clone(), + }) + } + + pub fn this(&self) -> Arc { + self.this.upgrade().expect("Weak ref invalid") + } + + fn lookup_node_or_attr( + &self, + name: &str, + sysnode: &dyn SysBranchNode, + ) -> Result> { + // Try finding a child node (Branch, Leaf, Symlink) first + if let Some(child_sysnode) = sysnode.child(name) { + let child_type = child_sysnode.type_(); + match child_type { + SysNodeType::Branch => { + let child_branch = child_sysnode + .arc_as_branch() + .ok_or(Error::new(Errno::EIO))?; + let inode = Self::new_branch_dir( + self.systree, + InnerNode::Branch(child_branch), + Arc::downgrade(&self.this()), + ); + return Ok(inode); + } + SysNodeType::Leaf => { + let child_leaf_node = + child_sysnode.arc_as_node().ok_or(Error::new(Errno::EIO))?; + let inode = Self::new_leaf_dir( + self.systree, + InnerNode::Leaf(child_leaf_node), + Arc::downgrade(&self.this()), + ); + return Ok(inode); + } + SysNodeType::Symlink => { + let child_symlink = child_sysnode + .arc_as_symlink() + .ok_or(Error::new(Errno::EIO))?; + let inode = Self::new_symlink( + self.systree, + child_symlink, + Arc::downgrade(&self.this()), + ); + return Ok(inode); + } + } + } else { + // If no child node found, try finding an attribute of the current branch node + let Some(attr) = sysnode.node_attrs().get(name) else { + return_errno_with_message!(Errno::ENOENT, "child node or attribute not found"); + }; + + let parent_node_arc: Arc = match &self.inner_node { + InnerNode::Branch(branch_arc) => branch_arc.clone(), + // This case shouldn't happen if lookup_node_or_attr is called correctly + _ => { + return Err(Error::with_message( + Errno::EIO, + "lookup_node_or_attr called on non-branch inode", + )) + } + }; + + let inode = Self::new_attr( + self.systree, + attr.clone(), + parent_node_arc, + Arc::downgrade(&self.this()), + ); + return Ok(inode); + } + } + + fn lookup_attr(&self, name: &str, sysnode: &dyn SysNode) -> Result> { + // This function is called when the current inode is a Leaf directory + let Some(attr) = sysnode.node_attrs().get(name) else { + return Err(Error::new(Errno::ENOENT)); + }; + + let leaf_node_arc: Arc = match &self.inner_node { + InnerNode::Leaf(leaf_arc) => leaf_arc.clone(), + // This case shouldn't happen if lookup_attr is called correctly + _ => { + return Err(Error::with_message( + Errno::EIO, + "lookup_attr called on non-leaf inode", + )) + } + }; + + let inode = Self::new_attr( + self.systree, + attr.clone(), + leaf_node_arc, + Arc::downgrade(&self.this()), + ); + Ok(inode) + } + + fn new_dentry_iter(&self, min_ino: Ino) -> impl Iterator + '_ { + match &self.inner_node { + InnerNode::Branch(branch_node) => { + let attrs = branch_node.node_attrs().iter().cloned().collect(); + let attr_iter = AttrDentryIter::new(attrs, self.ino(), min_ino); + let child_objs = branch_node.children(); + let node_iter = NodeDentryIter::new(child_objs, min_ino); + let special_iter = ThisAndParentDentryIter::new(self, min_ino); + attr_iter.chain(node_iter).chain(special_iter) + } + InnerNode::Leaf(leaf_node) => { + let attrs = leaf_node.node_attrs().iter().cloned().collect(); + let attr_iter = AttrDentryIter::new(attrs, self.ino(), min_ino); + let node_iter = NodeDentryIter::new(Vec::new(), min_ino); + let special_iter = ThisAndParentDentryIter::new(self, min_ino); + attr_iter.chain(node_iter).chain(special_iter) + } + InnerNode::Attr(_, _) | InnerNode::Symlink(_) => { + let attr_iter = AttrDentryIter::new(Vec::new(), self.ino(), min_ino); + let node_iter = NodeDentryIter::new(Vec::new(), min_ino); + let special_iter = ThisAndParentDentryIter::new(self, min_ino); + attr_iter.chain(node_iter).chain(special_iter) + } + } + } +} + +impl Inode for SysFsInode { + fn type_(&self) -> InodeType { + self.metadata.type_ + } + + fn metadata(&self) -> Metadata { + self.metadata + } + + fn ino(&self) -> u64 { + self.metadata.ino + } + + fn mode(&self) -> Result { + Ok(*self.mode.read()) + } + + fn set_mode(&self, mode: InodeMode) -> Result<()> { + *self.mode.write() = mode; + Ok(()) + } + + fn size(&self) -> usize { + self.metadata.size + } + + fn resize(&self, _new_size: usize) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn atime(&self) -> Duration { + self.metadata.atime + } + fn set_atime(&self, _time: Duration) {} + + fn mtime(&self) -> Duration { + self.metadata.mtime + } + fn set_mtime(&self, _time: Duration) {} + + fn ctime(&self) -> Duration { + self.metadata.ctime + } + fn set_ctime(&self, _time: Duration) {} + + fn owner(&self) -> Result { + Ok(self.metadata.uid) + } + fn set_owner(&self, _uid: Uid) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn group(&self) -> Result { + Ok(self.metadata.gid) + } + fn set_group(&self, _gid: Gid) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn fs(&self) -> Arc { + crate::fs::sysfs::singleton().clone() + } + + fn page_cache(&self) -> Option> { + None + } + + fn read_at(&self, offset: usize, buf: &mut VmWriter) -> Result { + self.read_direct_at(offset, buf) + } + + fn read_direct_at(&self, _offset: usize, buf: &mut VmWriter) -> Result { + // TODO: it is unclear whether we should simply ignore the offset + // or report errors if it is non-zero. + + let InnerNode::Attr(attr, leaf) = &self.inner_node else { + return Err(Error::new(Errno::EINVAL)); + }; + + // TODO: check read permission + Ok(leaf.read_attr(attr.name(), buf)?) + } + + fn write_at(&self, offset: usize, buf: &mut VmReader) -> Result { + self.write_direct_at(offset, buf) + } + + fn write_direct_at(&self, _offset: usize, buf: &mut VmReader) -> Result { + let InnerNode::Attr(attr, leaf) = &self.inner_node else { + return Err(Error::new(Errno::EINVAL)); + }; + + // TODO: check write permission + Ok(leaf.write_attr(attr.name(), buf)?) + } + + fn create(&self, _name: &str, _type_: InodeType, _mode: InodeMode) -> Result> { + Err(Error::new(Errno::EPERM)) + } + + fn mknod(&self, _name: &str, _mode: InodeMode, _dev: MknodType) -> Result> { + Err(Error::new(Errno::EPERM)) + } + + fn link(&self, _old: &Arc, _name: &str) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn unlink(&self, _name: &str) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn rename(&self, _old_name: &str, _target: &Arc, _new_name: &str) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn lookup(&self, name: &str) -> Result> { + if self.type_() != InodeType::Dir { + return Err(Error::new(Errno::ENOTDIR)); + } + + if name == "." { + return Ok(self.this()); + } else if name == ".." { + return Ok(self.parent.upgrade().unwrap_or_else(|| self.this())); + } + + // Dispatch based on the concrete type of the directory inode + match &self.inner_node { + InnerNode::Branch(branch_node) => { + // Current inode is a directory corresponding to a SysBranchNode + self.lookup_node_or_attr(name, branch_node.as_ref()) + } + InnerNode::Leaf(leaf_node) => { + // Current inode is a directory corresponding to a SysNode (Leaf) + // Leaf directories only contain attributes, not other nodes. + self.lookup_attr(name, leaf_node.as_ref()) + } + // Attr and Symlink nodes are not directories + InnerNode::Attr(_, _) | InnerNode::Symlink(_) => Err(Error::new(Errno::ENOTDIR)), + } + } + + fn readdir_at(&self, offset: usize, visitor: &mut dyn DirentVisitor) -> Result { + // Why interpreting the `offset` argument as an inode number? + // + // It may take multiple `getdents` system calls + // -- and thus multiple calls to this method -- + // to list a large directory when the syscall is provided a small buffer. + // Between these calls, + // the directory may have new entries added or existing ones removed + // by some concurrent users that are working on the directory. + // In such situations, + // missing some of the concurrently-added entries is inevitable, + // but reporting the same entry multiple times would be + // very confusing to the user. + // + // To address this issue, + // the `readdir_at` method reports entries starting from a user-given `offset` + // and returns an increment that the next call should be put on the `offset` argument + // to avoid getting duplicated entries. + // + // Different file systems may interpret the meaning of + // the `offset` argument differently: + // one may take it as a _byte_ offset, + // while the other may treat it as an _index_. + // This freedom is guaranteed by Linux as documented in + // [the man page of getdents](https://man7.org/linux/man-pages/man2/getdents.2.html). + // + // Our implementation of sysfs interprets the `offset` + // as an _inode number_. + // By inode numbers, directory entries will have a _stable_ order + // across different calls to `readdir_at`. + // The `new_dentry_iter` is responsible for filtering out entries + // with inode numbers less than `start_ino`. + let start_ino = offset as Ino; + let mut count = 0; + let mut last_ino = start_ino; + + let mut iter = self.new_dentry_iter(start_ino + 1); + + while let Some(dentry) = iter.next() { + // The offset reported back to the caller should be the absolute position + let next_offset = (dentry.ino + 1) as usize; + let res = visitor.visit(&dentry.name, dentry.ino, dentry.type_, next_offset); + + if res.is_err() { + if count == 0 { + return Err(Error::new(Errno::EINVAL)); + } else { + break; + } + } + count += 1; + last_ino = dentry.ino; + } + + if count == 0 { + Ok(0) + } else { + // Return absolute offset instead of an increment + Ok((last_ino + 1) as usize) + } + } + + fn read_link(&self) -> Result { + match &self.inner_node { + InnerNode::Symlink(s) => Ok(s.target_path().to_string()), + _ => Err(Error::new(Errno::EINVAL)), + } + } + + fn write_link(&self, _target: &str) -> Result<()> { + Err(Error::new(Errno::EPERM)) + } + + fn as_device(&self) -> Option> { + None + } + + fn ioctl(&self, _cmd: IoctlCmd, _arg: usize) -> Result { + Err(Error::new(Errno::ENOTTY)) + } + + fn sync_all(&self) -> Result<()> { + Ok(()) + } + + fn sync_data(&self) -> Result<()> { + Ok(()) + } + + fn fallocate(&self, _mode: FallocMode, _offset: usize, _len: usize) -> Result<()> { + Err(Error::new(Errno::EOPNOTSUPP)) + } + + fn poll(&self, mask: IoEvents, _poller: Option<&mut PollHandle>) -> IoEvents { + let mut events = IoEvents::empty(); + if let InnerNode::Attr(attr, _) = &self.inner_node { + if attr.flags().contains(SysAttrFlags::CAN_READ) { + events |= IoEvents::IN; + } + if attr.flags().contains(SysAttrFlags::CAN_WRITE) { + events |= IoEvents::OUT; + } + } + events & mask + } + + fn is_dentry_cacheable(&self) -> bool { + true + } +} + +// Update AttrDentryIter to filter by min_ino +struct AttrDentryIter { + attrs: Vec, + dir_ino: Ino, + min_ino: Ino, + index: usize, +} + +impl AttrDentryIter { + fn new(attrs: Vec, dir_ino: Ino, min_ino: Ino) -> Self { + Self { + attrs, + dir_ino, + min_ino, + index: 0, + } + } +} + +impl Iterator for AttrDentryIter { + type Item = Dentry; + + fn next(&mut self) -> Option { + while self.index < self.attrs.len() { + let attr = &self.attrs[self.index]; + self.index += 1; + let attr_ino = ino::from_dir_ino_and_attr_id(self.dir_ino, attr.id()); + + if attr_ino >= self.min_ino { + // Filter by min_ino + return Some(Dentry { + ino: attr_ino, + name: attr.name().clone(), + type_: InodeType::File, + }); + } + } + None + } +} + +struct NodeDentryIter { + nodes: Vec>, + min_ino: Ino, + index: usize, +} + +impl NodeDentryIter { + fn new(nodes: Vec>, min_ino: Ino) -> Self { + Self { + nodes, + min_ino, + index: 0, + } + } +} + +impl Iterator for NodeDentryIter { + type Item = Dentry; + + fn next(&mut self) -> Option { + while self.index < self.nodes.len() { + let obj = &self.nodes[self.index]; + self.index += 1; + let obj_ino = ino::from_sysnode_id(obj.id()); + + if obj_ino >= self.min_ino { + // Filter by min_ino here + let type_ = match obj.type_() { + SysNodeType::Branch => InodeType::Dir, + // Leaf nodes are presented as directories containing their attributes + SysNodeType::Leaf => InodeType::Dir, + SysNodeType::Symlink => InodeType::SymLink, + }; + return Some(Dentry { + ino: obj_ino, + name: obj.name(), + type_, + }); + } + } + None + } +} + +struct ThisAndParentDentryIter<'a> { + inode: &'a SysFsInode, + min_ino: Ino, + state: u8, // 0 = self, 1 = parent, 2 = done +} + +impl<'a> ThisAndParentDentryIter<'a> { + fn new(inode: &'a SysFsInode, min_ino: Ino) -> Self { + Self { + inode, + min_ino, + state: 0, + } + } +} + +impl<'a> Iterator for ThisAndParentDentryIter<'a> { + type Item = Dentry; + + fn next(&mut self) -> Option { + match self.state { + 0 => { + self.state = 1; + if self.inode.ino() >= self.min_ino { + Some(Dentry { + ino: self.inode.ino(), + name: Cow::from("."), + type_: InodeType::Dir, + }) + } else { + self.next() + } + } + 1 => { + self.state = 2; + let parent_ino = self + .inode + .parent + .upgrade() + .map_or(self.inode.ino(), |p| p.ino()); + if parent_ino >= self.min_ino { + Some(Dentry { + ino: parent_ino, + name: Cow::from(".."), + type_: InodeType::Dir, + }) + } else { + None + } + } + _ => None, + } + } +} + +/// A directory entry of sysfs. +struct Dentry { + pub ino: Ino, + pub name: SysStr, + pub type_: InodeType, +} + +mod ino { + use super::{InnerNode, Ino, SysNodeId}; + + const ATTR_ID_BITS: u8 = 8; + + pub fn from_sysnode_id(node_id: &SysNodeId) -> Ino { + node_id.as_u64() << ATTR_ID_BITS + } + + pub fn from_dir_ino_and_attr_id(dir_ino: Ino, attr_id: u8) -> Ino { + dir_ino + (attr_id as Ino) + } + + pub fn from_inner_node(inner: &InnerNode) -> Ino { + match inner { + InnerNode::Branch(branch_node) => from_sysnode_id(branch_node.id()), + InnerNode::Leaf(leaf_node) => from_sysnode_id(leaf_node.id()), + InnerNode::Symlink(symlink_node) => from_sysnode_id(symlink_node.id()), + InnerNode::Attr(attr, node) => { + // node here is the parent (Branch or Leaf) + let dir_ino = from_sysnode_id(node.id()); // Get parent dir ino + from_dir_ino_and_attr_id(dir_ino, attr.id()) + } + } + } +} diff --git a/kernel/src/fs/sysfs/mod.rs b/kernel/src/fs/sysfs/mod.rs new file mode 100644 index 00000000..6afe00c9 --- /dev/null +++ b/kernel/src/fs/sysfs/mod.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MPL-2.0 + +mod fs; +mod inode; +#[cfg(ktest)] +mod test; + +use alloc::sync::Arc; + +use spin::Once; + +pub use self::{fs::SysFs, inode::SysFsInode}; + +static SYSFS_SINGLETON: Once> = Once::new(); + +/// Returns a reference to the global SysFs instance. Panics if not initialized. +pub fn singleton() -> &'static Arc { + SYSFS_SINGLETON.get().expect("SysFs not initialized") +} + +/// Initializes the SysFs singleton. +/// Ensures that the singleton is created by calling it. +/// Should be called during kernel filesystem initialization, *after* systree::init(). +pub fn init() { + // Ensure systree is initialized first. This should be handled by the kernel's init order. + SYSFS_SINGLETON.call_once(|| SysFs::new()); +} diff --git a/test/Makefile b/test/Makefile index 2cfbf77c..886391d9 100644 --- a/test/Makefile +++ b/test/Makefile @@ -23,6 +23,7 @@ INITRAMFS_EMPTY_DIRS := \ $(INITRAMFS)/opt \ $(INITRAMFS)/proc \ $(INITRAMFS)/dev \ + $(INITRAMFS)/sys \ $(INITRAMFS)/ext2 \ $(INITRAMFS)/exfat INITRAMFS_ALL_DIRS := \