Introduce VmPrinter to write kernel generated data

This commit is contained in:
Chen Chengjun 2025-09-10 11:59:16 +00:00 committed by Tate, Hongliang Tian
parent 12f2f6bb54
commit 8c36964bb9
10 changed files with 222 additions and 54 deletions

1
Cargo.lock generated
View File

@ -283,6 +283,7 @@ dependencies = [
name = "aster-systree"
version = "0.1.0"
dependencies = [
"aster-util",
"bitflags 2.9.1",
"component",
"inherit-methods-macro",

View File

@ -9,6 +9,7 @@ edition = "2021"
bitflags = "2.5"
ostd = { path = "../../../ostd" }
component = { path = "../../libs/comp-sys/component" }
aster-util = { path = "../../libs/aster-util" }
inherit-methods-macro = { git = "https://github.com/asterinas/inherit-methods-macro", rev = "98f7e3e"}
spin = "0.9"

View File

@ -31,6 +31,7 @@ mod utils;
use alloc::{borrow::Cow, sync::Arc};
use aster_util::printer::VmPrinterError;
use component::{init_component, ComponentInitError};
use spin::Once;
@ -101,3 +102,11 @@ impl core::fmt::Display for Error {
}
}
}
impl From<VmPrinterError> for Error {
fn from(value: VmPrinterError) -> Self {
match value {
VmPrinterError::PageFault => Error::PageFault,
}
}
}

View File

@ -3,9 +3,10 @@
use alloc::{borrow::Cow, string::ToString, sync::Arc, vec::Vec};
use core::fmt::Debug;
use aster_util::printer::VmPrinter;
use inherit_methods_macro::inherit_methods;
use ostd::{
mm::{FallibleVmRead, FallibleVmWrite, VmReader, VmWriter},
mm::{FallibleVmRead, VmReader, VmWriter},
prelude::ktest,
};
@ -43,7 +44,7 @@ impl DeviceNode {
}
inherit_sys_branch_node!(DeviceNode, fields, {
fn read_attr(&self, name: &str, writer: &mut VmWriter) -> Result<usize> {
fn read_attr_at(&self, name: &str, offset: usize, writer: &mut VmWriter) -> Result<usize> {
// Check if attribute exists
if !self.fields.attr_set().contains(name) {
return Err(Error::NotFound);
@ -61,10 +62,11 @@ inherit_sys_branch_node!(DeviceNode, fields, {
_ => "",
};
let mut printer = VmPrinter::new_skip(writer, offset);
// Write the value to the provided writer
writer
.write_fallible(&mut (value.as_bytes()).into())
.map_err(|_| Error::AttributeError)
write!(printer, "{}", value)?;
Ok(printer.bytes_written())
}
fn write_attr(&self, name: &str, reader: &mut VmReader) -> Result<usize> {

View File

@ -6,12 +6,11 @@ use alloc::{
collections::BTreeMap,
string::String,
sync::{Arc, Weak},
vec,
};
use inherit_methods_macro::inherit_methods;
use ostd::{
mm::{FallibleVmWrite, VmReader, VmWriter},
mm::{VmReader, VmWriter},
sync::RwLock,
};
use spin::Once;
@ -269,30 +268,6 @@ impl<T: SysSymlink> SymlinkNodeFields<T> {
}
}
macro_rules! impl_default_read_attr_at {
() => {
fn read_attr_at(&self, name: &str, offset: usize, writer: &mut VmWriter) -> Result<usize> {
let (attr_buffer, attr_len) = {
let attr_buffer_len = writer.avail().checked_add(offset).ok_or(Error::Overflow)?;
let mut buffer = vec![0; attr_buffer_len];
let len = self.read_attr(
name,
&mut VmWriter::from(buffer.as_mut_slice()).to_fallible(),
)?;
(buffer, len)
};
if attr_len <= offset {
return Ok(0);
}
writer
.write_fallible(VmReader::from(attr_buffer.as_slice()).skip(offset))
.map_err(|_| Error::AttributeError)
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! _inner_impl_sys_node {
@ -356,15 +331,17 @@ pub trait _InheritSysLeafNode<T: SysNode> {
self.field().init_parent(parent);
}
fn read_attr(&self, _name: &str, _writer: &mut VmWriter) -> Result<usize> {
Err(Error::AttributeError)
fn read_attr(&self, name: &str, writer: &mut VmWriter) -> Result<usize> {
self.read_attr_at(name, 0, writer)
}
fn write_attr(&self, _name: &str, _reader: &mut VmReader) -> Result<usize> {
Err(Error::AttributeError)
}
impl_default_read_attr_at!();
fn read_attr_at(&self, _name: &str, _offset: usize, _writer: &mut VmWriter) -> Result<usize> {
Err(Error::AttributeError)
}
fn write_attr_at(&self, name: &str, _offset: usize, reader: &mut VmReader) -> Result<usize> {
// In general, the `offset` for attribute write operations is ignored directly.
@ -404,6 +381,12 @@ pub trait _InheritSysLeafNode<T: SysNode> {
/// method with the same name from the target `field` or, in the absence of a method with the same
/// name, the default implementation provided by the trait.
///
/// Note that for the `SysNode` trait, `read_attr` can be automatically implemented in terms of `read_attr_at`,
/// and `write_attr_at` can be automatically implemented in terms of `write_attr` since most
/// sysfs attributes do not support partial writes and will ignore the `offset`. Therefore, it is **recommended**
/// to override the `read_attr_at` and `write_attr` methods. In addition, users can use
/// [`aster_util::printer::VmPrinter`] to easily handle the `offset` when overriding the `read_attr_at` method.
///
/// ## Examples
///
/// ```rust
@ -488,15 +471,17 @@ pub trait _InheritSysBranchNode<T: SysBranchNode> {
self.field().init_parent(parent);
}
fn read_attr(&self, _name: &str, _writer: &mut VmWriter) -> Result<usize> {
Err(Error::AttributeError)
fn read_attr(&self, name: &str, writer: &mut VmWriter) -> Result<usize> {
self.read_attr_at(name, 0, writer)
}
fn write_attr(&self, _name: &str, _reader: &mut VmReader) -> Result<usize> {
Err(Error::AttributeError)
}
impl_default_read_attr_at!();
fn read_attr_at(&self, _name: &str, _offset: usize, _writer: &mut VmWriter) -> Result<usize> {
Err(Error::AttributeError)
}
fn write_attr_at(&self, name: &str, _offset: usize, reader: &mut VmReader) -> Result<usize> {
// In general, the `offset` for attribute write operations is ignored directly.

View File

@ -10,6 +10,7 @@ extern crate alloc;
pub mod coeff;
pub mod dup;
pub mod mem_obj_slice;
pub mod printer;
pub mod safe_ptr;
pub mod slot_vec;
pub mod union_read_ptr;

View File

@ -0,0 +1,158 @@
// SPDX-License-Identifier: MPL-2.0
use core::fmt::{Arguments, Write};
use ostd::mm::{FallibleVmWrite, VmReader, VmWriter};
/// A specialized printer for formatted text output.
///
/// `VmPrinter` is designed to handle the common pattern in kernel where kernel
/// code needs to generate formatted text output (like status information, statistics,
/// or configuration data) and write it to user space with proper offset handling.
///
/// # Examples
///
/// ```rust,ignore
/// use aster_util::printer::VmPrinter;
/// use ostd::{mm::VmWriter, Pod};
///
/// let mut buf = [0u8; 3];
/// let mut writer = VmWriter::from(buf.as_bytes_mut()).to_fallible();
/// let mut printer = VmPrinter::new_skip(&mut writer, 3);
///
/// let res = writeln!(printer, "val: {}", 123);
/// assert!(res.is_ok());
///
/// assert_eq!(printer.bytes_written(), 3);
/// assert_eq!(&buf, b": 1");
/// ```
pub struct VmPrinter<'a, 'b> {
/// The underlying [`VmWriter`] to write the final output.
writer: &'a mut VmWriter<'b>,
/// Number of bytes to skip from the beginning of the output.
///
/// When content is written through this writer, the first `bytes_to_skip`
/// bytes will be discarded, and only subsequent bytes will be written
/// to the underlying `VmWriter`.
bytes_to_skip: usize,
/// Total number of bytes written to the underlying writer.
bytes_written: usize,
}
impl<'a, 'b> VmPrinter<'a, 'b> {
/// Creates a new `VmPrinter` that prints to `writer`.
fn new(writer: &'a mut VmWriter<'b>) -> Self {
Self {
writer,
bytes_to_skip: 0,
bytes_written: 0,
}
}
/// Creates a new `VmPrinter` that skips the first `bytes_to_skip` bytes and prints the
/// remaining bytes to `writer`.
pub fn new_skip(writer: &'a mut VmWriter<'b>, bytes_to_skip: usize) -> Self {
Self {
writer,
bytes_to_skip,
bytes_written: 0,
}
}
/// Returns the total number of bytes written to the underlying writer.
pub fn bytes_written(&self) -> usize {
self.bytes_written
}
/// Writes formatted content to the underlying writer.
pub fn write_fmt(&mut self, args: Arguments<'_>) -> Result<(), VmPrinterError> {
Write::write_fmt(self, args).map_err(|_| VmPrinterError::PageFault)
}
fn write_bytes(&mut self, bytes: &[u8]) -> core::fmt::Result {
if self.bytes_to_skip >= bytes.len() {
self.bytes_to_skip -= bytes.len();
return Ok(());
}
let bytes_to_write = &bytes[self.bytes_to_skip..];
if self.bytes_to_skip > 0 {
self.bytes_to_skip = 0;
}
let mut reader = VmReader::from(bytes_to_write);
let written_len = self
.writer
.write_fallible(&mut reader)
.map_err(|_| core::fmt::Error)?;
self.bytes_written += written_len;
Ok(())
}
}
impl Write for VmPrinter<'_, '_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.write_bytes(s.as_bytes())
}
}
impl<'a, 'b> From<&'a mut VmWriter<'b>> for VmPrinter<'a, 'b> {
fn from(writer: &'a mut VmWriter<'b>) -> Self {
Self::new(writer)
}
}
/// An error returned by [`VmPrinter::write_fmt`].
pub enum VmPrinterError {
/// Page fault occurred.
PageFault,
}
#[cfg(ktest)]
mod test {
use ostd::{mm::VmWriter, prelude::*, Pod};
use super::*;
#[ktest]
fn basic_write() {
let mut buf = [0u8; 64];
let mut writer = VmWriter::from(buf.as_bytes_mut()).to_fallible();
let mut printer = VmPrinter::from(&mut writer);
let res = writeln!(printer, "test");
assert!(res.is_ok());
assert_eq!(printer.bytes_written(), 5);
assert_eq!(&buf[..5], b"test\n");
}
#[ktest]
fn write_with_skip() {
let mut buf = [0u8; 3];
let mut writer = VmWriter::from(buf.as_bytes_mut()).to_fallible();
let mut printer = VmPrinter::new_skip(&mut writer, 3);
let res = writeln!(printer, "val: {}", 123);
assert!(res.is_ok());
assert_eq!(printer.bytes_written(), 3);
assert_eq!(&buf, b": 1");
}
#[ktest]
fn skip_all_content() {
let mut buf = [0u8; 64];
let mut writer = VmWriter::from(buf.as_bytes_mut()).to_fallible();
let mut printer = VmPrinter::new_skip(&mut writer, 100);
let res = writeln!(printer, "short message");
assert!(res.is_ok());
// Nothing should be written
assert_eq!(printer.bytes_written(), 0);
assert_eq!(buf[0], 0);
}
}

View File

@ -125,7 +125,7 @@ inherit_sys_branch_node!(CgroupSystem, fields, {
// This method should be a no-op for `RootNode`.
}
fn read_attr(&self, _name: &str, _writer: &mut VmWriter) -> Result<usize> {
fn read_attr_at(&self, _name: &str, _offset: usize, _writer: &mut VmWriter) -> Result<usize> {
// TODO: Add support for reading attributes.
Err(Error::AttributeError)
}
@ -147,7 +147,7 @@ inherit_sys_branch_node!(CgroupSystem, fields, {
});
inherit_sys_branch_node!(CgroupNode, fields, {
fn read_attr(&self, _name: &str, _writer: &mut VmWriter) -> Result<usize> {
fn read_attr_at(&self, _name: &str, _offset: usize, _writer: &mut VmWriter) -> Result<usize> {
// TODO: Add support for reading attributes.
Err(Error::AttributeError)
}

View File

@ -17,8 +17,9 @@ use aster_systree::{
Result as SysTreeResult, SymlinkNodeFields, SysAttrSetBuilder, SysObj, SysPerms, SysStr,
SysTree,
};
use aster_util::printer::VmPrinter;
use ostd::{
mm::{FallibleVmRead, FallibleVmWrite, VmReader, VmWriter},
mm::{FallibleVmRead, VmReader, VmWriter},
prelude::ktest,
sync::RwLock,
};
@ -75,7 +76,12 @@ impl MockLeafNode {
}
inherit_sys_leaf_node!(MockLeafNode, fields, {
fn read_attr(&self, name: &str, writer: &mut VmWriter) -> SysTreeResult<usize> {
fn read_attr_at(
&self,
name: &str,
offset: usize,
writer: &mut VmWriter,
) -> SysTreeResult<usize> {
let attr = self
.fields
.attr_set()
@ -86,6 +92,11 @@ inherit_sys_leaf_node!(MockLeafNode, fields, {
}
let data = self.data.read();
let value = data.get(name).ok_or(SysTreeError::NotFound)?; // Should exist if in attrs
let mut printer = VmPrinter::new_skip(writer, offset);
write!(printer, "{}", value)?;
Ok(printer.bytes_written())
}
fn write_attr(&self, name: &str, reader: &mut VmReader) -> SysTreeResult<usize> {
@ -102,7 +113,7 @@ inherit_sys_leaf_node!(MockLeafNode, fields, {
let mut writer = VmWriter::from(&mut buffer[..]);
let read_len = reader
.read_fallible(&mut writer)
.map_err(|_| SysTreeError::AttributeError)?;
.map_err(|_| SysTreeError::PageFault)?;
let new_value = String::from_utf8_lossy(&buffer[..read_len]).to_string();
@ -148,7 +159,12 @@ impl MockBranchNode {
}
inherit_sys_branch_node!(MockBranchNode, fields, {
fn read_attr(&self, name: &str, writer: &mut VmWriter) -> SysTreeResult<usize> {
fn read_attr_at(
&self,
name: &str,
offset: usize,
writer: &mut VmWriter,
) -> SysTreeResult<usize> {
let attr = self
.fields
.attr_set()
@ -161,10 +177,11 @@ inherit_sys_branch_node!(MockBranchNode, fields, {
"branch_attr" => "branch_value",
_ => return Err(SysTreeError::NotFound),
};
let bytes = value.as_bytes();
writer
.write_fallible(&mut bytes.into())
.map_err(|_| SysTreeError::AttributeError)
let mut printer = VmPrinter::new_skip(writer, offset);
write!(printer, "{}", value)?;
Ok(printer.bytes_written())
}
fn write_attr(&self, name: &str, _reader: &mut VmReader) -> SysTreeResult<usize> {

View File

@ -361,13 +361,7 @@ impl<KInode: SysTreeInodeTy + Send + Sync + 'static> Inode for KInode {
return Err(Error::new(Errno::EINVAL));
};
let len = if offset == 0 {
leaf.read_attr(attr.name(), buf)?
} else {
// The `read_attr_at` method is more general than `read_attr`,
// but it could be less efficient. So we only use the more general form when necessary.
leaf.read_attr_at(attr.name(), offset, buf)?
};
let len = leaf.read_attr_at(attr.name(), offset, buf)?;
Ok(len)
}