Add sysfs implementation

This commit is contained in:
Fabing Li
2025-04-24 10:04:37 +00:00
committed by Tate, Hongliang Tian
parent 3a5f270ee9
commit 79b0866259
19 changed files with 1639 additions and 1 deletions

View File

@ -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"

View File

@ -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<usize>,
// write_handler: fn(...) -> Result<usize>,
}
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<SysStr, SysAttr>,
}
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<Item = &SysAttr> {
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<SysStr, SysAttr>,
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<SysAttrSet> {
if self.attrs.len() > SysAttrSet::CAPACITY {
return Err(Error::PermissionDenied);
}
Ok(SysAttrSet { attrs: self.attrs })
}
}

View File

@ -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<Arc<SysTree>> = 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<SysTree> {
SINGLETON.get().expect("SysTree not initialized")
}
/// An owned string or a static reference to string.
pub type SysStr = Cow<'static, str>;
pub type Result<T> = core::result::Result<T, Error>;
#[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),
}
}
}

View File

@ -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<Arc<dyn SysObj>>;
/// Collects all children into a `Vec`.
fn children(&self) -> Vec<Arc<dyn SysObj>> {
let mut children: Vec<Arc<dyn SysObj>> = 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<usize>;
/// Writes the value of an attribute.
fn write_attr(&self, name: &str, reader: &mut VmReader) -> Result<usize>;
/// 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<String> {
let mut buf: Vec<u8> = 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<usize> {
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<Arc<dyn SysSymlink>> {
None
}
/// Attempts to get an Arc to this object as a `SysNode`.
fn arc_as_node(&self) -> Option<Arc<dyn SysNode>> {
None
}
/// Attempts to get an Arc to this object as a `SysBranchNode`.
fn arc_as_branch(&self) -> Option<Arc<dyn SysBranchNode>> {
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()
}
}

View File

@ -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<RootNode>,
// 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<RootNode> {
&self.root
}
}
#[derive(Debug)]
pub struct RootNode {
id: SysNodeId,
name: SysStr,
attrs: SysAttrSet,
children: RwLock<BTreeMap<SysStr, Arc<dyn SysObj>>>,
self_ref: Weak<Self>,
}
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<dyn SysObj>) -> 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<Arc<dyn SysSymlink>> {
None
}
fn arc_as_node(&self) -> Option<Arc<dyn SysNode>> {
self.self_ref
.upgrade()
.map(|arc_self| arc_self as Arc<dyn SysNode>)
}
fn arc_as_branch(&self) -> Option<Arc<dyn SysBranchNode>> {
self.self_ref
.upgrade()
.map(|arc_self| arc_self as Arc<dyn SysBranchNode>)
}
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<usize> {
Err(Error::AttributeError)
}
fn write_attr(&self, _name: &str, _reader: &mut VmReader) -> Result<usize> {
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<dyn SysObj>) -> 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<Arc<dyn SysObj>> {
self.children.read().get(name).cloned()
}
fn children(&self) -> Vec<Arc<dyn SysObj>> {
self.children.read().values().cloned().collect()
}
}

View File

@ -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<C: SysObj + ?Sized> {
base: SysNormalNodeFields,
pub children: RwLock<BTreeMap<SysStr, Arc<C>>>,
}
impl<C: SysObj + ?Sized> SysBranchNodeFields<C> {
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<C>) -> 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<Arc<C>> {
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
}
}

View File

@ -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