From f4a51e1ce9ad8f414b394fe7d71ca46dd6793b1c Mon Sep 17 00:00:00 2001 From: vvsv Date: Fri, 5 Dec 2025 16:22:12 +0000 Subject: [PATCH] Support open and fstatfs for anonymous pipes --- kernel/src/fs/pipe/anony_pipe.rs | 355 +++++++++++++++---------------- kernel/src/fs/pipe/common.rs | 5 +- kernel/src/fs/pipe/mod.rs | 2 +- kernel/src/fs/pipe/named_pipe.rs | 24 ++- kernel/src/fs/ramfs/memfd.rs | 2 +- kernel/src/syscall/open.rs | 17 +- kernel/src/syscall/statfs.rs | 13 +- 7 files changed, 221 insertions(+), 197 deletions(-) diff --git a/kernel/src/fs/pipe/anony_pipe.rs b/kernel/src/fs/pipe/anony_pipe.rs index 7540aebca..8c3db7400 100644 --- a/kernel/src/fs/pipe/anony_pipe.rs +++ b/kernel/src/fs/pipe/anony_pipe.rs @@ -3,17 +3,24 @@ use core::{ fmt::Display, sync::atomic::{AtomicU32, Ordering}, + time::Duration, }; +use inherit_methods_macro::inherit_methods; + use crate::{ events::IoEvents, fs::{ file_handle::FileLike, file_table::FdFlags, + notify::FsEventPublisher, path::RESERVED_MOUNT_ID, - pipe::common::{PipeReader, PipeWriter}, - pseudofs::{PseudoInode, pipefs_singleton}, - utils::{AccessMode, CreationFlags, Inode, InodeType, StatusFlags, mkmod}, + pipe::{named_pipe::NamedPipeHandle, NamedPipe}, + pseudofs::{pipefs_singleton, PseudoInode}, + utils::{ + mkmod, AccessMode, CreationFlags, FileSystem, Inode, InodeIo, InodeMode, InodeType, + Metadata, StatusFlags, + }, }, prelude::*, process::{ @@ -22,62 +29,121 @@ use crate::{ }, }; -const DEFAULT_PIPE_BUF_SIZE: usize = 65536; - /// Creates a pair of connected pipe file handles with the default capacity. -pub fn new_file_pair() -> Result<(Arc, Arc)> { - new_file_pair_with_capacity(DEFAULT_PIPE_BUF_SIZE) +pub fn new_file_pair() -> Result<(Arc, Arc)> { + let pipe_inode = Arc::new(AnonPipeInode::new()); + let reader = AnonPipeFile::open( + pipe_inode.clone(), + AccessMode::O_RDONLY, + StatusFlags::empty(), + )?; + let writer = AnonPipeFile::open(pipe_inode, AccessMode::O_WRONLY, StatusFlags::empty())?; + + Ok((Arc::new(reader), Arc::new(writer))) } -fn new_file_pair_with_capacity( - capacity: usize, -) -> Result<(Arc, Arc)> { - let (reader, writer) = super::common::new_pair_with_capacity(capacity); - - let pseudo_inode = { - Arc::new(PseudoInode::new( - 0, - InodeType::NamedPipe, - mkmod!(u+rw), - Uid::new_root(), - Gid::new_root(), - aster_block::BLOCK_SIZE, - Arc::downgrade(pipefs_singleton()), - )) - }; - - Ok(( - PipeReaderFile::new(reader, StatusFlags::empty(), pseudo_inode.clone())?, - PipeWriterFile::new(writer, StatusFlags::empty(), pseudo_inode)?, - )) -} - -/// A file handle for reading from a pipe. -pub struct PipeReaderFile { - reader: PipeReader, +/// An anonymous pipe file. +pub struct AnonPipeFile { + /// The opened pipe handle. `None` if the file is opened as a path. + handle: Option>, + pipe_inode: Arc, status_flags: AtomicU32, - pseudo_inode: Arc, } -impl PipeReaderFile { - fn new( - reader: PipeReader, +impl AnonPipeFile { + pub fn open( + pipe_inode: Arc, + access_mode: AccessMode, status_flags: StatusFlags, - pseudo_inode: Arc, - ) -> Result> { + ) -> Result { check_status_flags(status_flags)?; - Ok(Arc::new(Self { - reader, + let handle = if !status_flags.contains(StatusFlags::O_PATH) { + let handle = pipe_inode + .pipe + .open_handle(access_mode, status_flags, false)?; + Some(handle) + } else { + None + }; + + Ok(Self { + handle, + pipe_inode, status_flags: AtomicU32::new(status_flags.bits()), - pseudo_inode, - })) + }) } } -impl Pollable for PipeReaderFile { +impl Pollable for AnonPipeFile { fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents { - self.reader.poll(mask, poller) + if let Some(handle) = &self.handle { + handle.poll(mask, poller) + } else { + IoEvents::NVAL + } + } +} + +impl FileLike for AnonPipeFile { + fn read(&self, writer: &mut VmWriter) -> Result { + let Some(handle) = &self.handle else { + return_errno_with_message!(Errno::EBADF, "the file is opened as a path"); + }; + + if !handle.access_mode().is_readable() { + return_errno_with_message!(Errno::EBADF, "the file is not opened readable"); + } + + handle.read_at(0, writer, self.status_flags()) + } + + fn write(&self, reader: &mut VmReader) -> Result { + let Some(handle) = &self.handle else { + return_errno_with_message!(Errno::EBADF, "the file is opened as a path"); + }; + + if !handle.access_mode().is_writable() { + return_errno_with_message!(Errno::EBADF, "the file is not opened writable"); + } + + handle.write_at(0, reader, self.status_flags()) + } + + fn status_flags(&self) -> StatusFlags { + StatusFlags::from_bits_truncate(self.status_flags.load(Ordering::Relaxed)) + } + + fn set_status_flags(&self, new_flags: StatusFlags) -> Result<()> { + check_status_flags(new_flags)?; + + self.status_flags.store(new_flags.bits(), Ordering::Relaxed); + Ok(()) + } + + fn access_mode(&self) -> AccessMode { + if let Some(handle) = &self.handle { + handle.access_mode() + } else { + // The file is opened with `O_PATH`. We follow Linux to report `O_RDONLY` here. + AccessMode::O_RDONLY + } + } + + fn inode(&self) -> &Arc { + &self.pipe_inode + } + + fn dump_proc_fdinfo(self: Arc, fd_flags: FdFlags) -> Box { + let mut flags = self.status_flags().bits() | self.access_mode() as u32; + if fd_flags.contains(FdFlags::CLOEXEC) { + flags |= CreationFlags::O_CLOEXEC.bits(); + } + + Box::new(FdInfo { + flags, + ino: self.inode().ino(), + }) } } @@ -96,135 +162,6 @@ impl Display for FdInfo { } } -impl FileLike for PipeReaderFile { - fn read(&self, writer: &mut VmWriter) -> Result { - if !writer.has_avail() { - // Even the peer endpoint (`PipeWriter`) has been closed, reading an empty buffer is - // still fine. - return Ok(0); - } - - if self.status_flags().contains(StatusFlags::O_NONBLOCK) { - self.reader.try_read(writer) - } else { - self.wait_events(IoEvents::IN, None, || self.reader.try_read(writer)) - } - } - - fn status_flags(&self) -> StatusFlags { - StatusFlags::from_bits_truncate(self.status_flags.load(Ordering::Relaxed)) - } - - fn set_status_flags(&self, new_flags: StatusFlags) -> Result<()> { - check_status_flags(new_flags)?; - - self.status_flags.store(new_flags.bits(), Ordering::Relaxed); - Ok(()) - } - - fn access_mode(&self) -> AccessMode { - AccessMode::O_RDONLY - } - - fn inode(&self) -> &Arc { - &self.pseudo_inode - } - - fn dump_proc_fdinfo(self: Arc, fd_flags: FdFlags) -> Box { - let mut flags = self.status_flags().bits() | self.access_mode() as u32; - if fd_flags.contains(FdFlags::CLOEXEC) { - flags |= CreationFlags::O_CLOEXEC.bits(); - } - - Box::new(FdInfo { - flags, - ino: self.inode().ino(), - }) - } -} - -impl Drop for PipeReaderFile { - fn drop(&mut self) { - self.reader.peer_shutdown(); - } -} - -/// A file handle for writing to a pipe. -pub struct PipeWriterFile { - writer: PipeWriter, - status_flags: AtomicU32, - pseudo_inode: Arc, -} - -impl PipeWriterFile { - fn new( - writer: PipeWriter, - status_flags: StatusFlags, - pseudo_inode: Arc, - ) -> Result> { - check_status_flags(status_flags)?; - - Ok(Arc::new(Self { - writer, - status_flags: AtomicU32::new(status_flags.bits()), - pseudo_inode, - })) - } -} - -impl Pollable for PipeWriterFile { - fn poll(&self, mask: IoEvents, poller: Option<&mut PollHandle>) -> IoEvents { - self.writer.poll(mask, poller) - } -} - -impl FileLike for PipeWriterFile { - fn write(&self, reader: &mut VmReader) -> Result { - if !reader.has_remain() { - // Even the peer endpoint (`PipeReader`) has been closed, writing an empty buffer is - // still fine. - return Ok(0); - } - - if self.status_flags().contains(StatusFlags::O_NONBLOCK) { - self.writer.try_write(reader) - } else { - self.wait_events(IoEvents::OUT, None, || self.writer.try_write(reader)) - } - } - - fn status_flags(&self) -> StatusFlags { - StatusFlags::from_bits_truncate(self.status_flags.load(Ordering::Relaxed)) - } - - fn set_status_flags(&self, new_flags: StatusFlags) -> Result<()> { - check_status_flags(new_flags)?; - - self.status_flags.store(new_flags.bits(), Ordering::Relaxed); - Ok(()) - } - - fn access_mode(&self) -> AccessMode { - AccessMode::O_WRONLY - } - - fn inode(&self) -> &Arc { - &self.pseudo_inode - } - - fn dump_proc_fdinfo(self: Arc, fd_flags: FdFlags) -> Box { - let mut flags = self.status_flags().bits() | self.access_mode() as u32; - if fd_flags.contains(FdFlags::CLOEXEC) { - flags |= CreationFlags::O_CLOEXEC.bits(); - } - - Box::new(FdInfo { - flags, - ino: self.inode().ino(), - }) - } -} - fn check_status_flags(status_flags: StatusFlags) -> Result<()> { if status_flags.contains(StatusFlags::O_DIRECT) { // "O_DIRECT .. Older kernels that do not support this flag will indicate this via an @@ -240,12 +177,70 @@ fn check_status_flags(status_flags: StatusFlags) -> Result<()> { Ok(()) } -impl Drop for PipeWriterFile { - fn drop(&mut self) { - self.writer.shutdown(); +/// An anonymous pipe inode. +pub struct AnonPipeInode { + /// The underlying named pipe backend. + pipe: NamedPipe, + pseudo_inode: PseudoInode, +} + +impl AnonPipeInode { + fn new() -> Self { + let pipe = NamedPipe::new(); + + let pseudo_inode = PseudoInode::new( + 0, + InodeType::NamedPipe, + mkmod!(u+rw), + Uid::new_root(), + Gid::new_root(), + aster_block::BLOCK_SIZE, + Arc::downgrade(pipefs_singleton()), + ); + + Self { pipe, pseudo_inode } } } +#[inherit_methods(from = "self.pseudo_inode")] +impl InodeIo for AnonPipeInode { + fn read_at( + &self, + _offset: usize, + _writer: &mut VmWriter, + _status: StatusFlags, + ) -> Result; + fn write_at( + &self, + _offset: usize, + _reader: &mut VmReader, + _status: StatusFlags, + ) -> Result; +} + +#[inherit_methods(from = "self.pseudo_inode")] +impl Inode for AnonPipeInode { + fn size(&self) -> usize; + fn resize(&self, _new_size: usize) -> Result<()>; + fn metadata(&self) -> Metadata; + fn fs_event_publisher(&self) -> &FsEventPublisher; + fn ino(&self) -> u64; + fn type_(&self) -> InodeType; + fn mode(&self) -> Result; + fn set_mode(&self, mode: InodeMode) -> Result<()>; + fn owner(&self) -> Result; + fn set_owner(&self, uid: Uid) -> Result<()>; + fn group(&self) -> Result; + fn set_group(&self, gid: Gid) -> Result<()>; + fn atime(&self) -> Duration; + fn set_atime(&self, time: Duration); + fn mtime(&self) -> Duration; + fn set_mtime(&self, time: Duration); + fn ctime(&self) -> Duration; + fn set_ctime(&self, time: Duration); + fn fs(&self) -> Arc; +} + #[cfg(ktest)] mod test { use alloc::sync::Arc; @@ -264,10 +259,10 @@ mod test { fn test_blocking(write: W, read: R, ordering: Ordering) where - W: FnOnce(Arc) + Send + 'static, - R: FnOnce(Arc) + Send + 'static, + W: FnOnce(Arc) + Send + 'static, + R: FnOnce(Arc) + Send + 'static, { - let (reader, writer) = new_file_pair_with_capacity(2).unwrap(); + let (reader, writer) = new_file_pair().unwrap(); let signal_writer = Arc::new(AtomicBool::new(false)); let signal_reader = signal_writer.clone(); diff --git a/kernel/src/fs/pipe/common.rs b/kernel/src/fs/pipe/common.rs index a7591203e..408b9f54b 100644 --- a/kernel/src/fs/pipe/common.rs +++ b/kernel/src/fs/pipe/common.rs @@ -15,7 +15,10 @@ use crate::{ util::ring_buffer::{RbConsumer, RbProducer, RingBuffer}, }; +#[cfg(not(ktest))] const DEFAULT_PIPE_BUF_SIZE: usize = 65536; +#[cfg(ktest)] +const DEFAULT_PIPE_BUF_SIZE: usize = 2; /// Maximum number of bytes guaranteed to be written to a pipe atomically. /// @@ -34,7 +37,7 @@ pub(super) fn new_pair() -> (PipeReader, PipeWriter) { new_pair_with_capacity(DEFAULT_PIPE_BUF_SIZE) } -pub(super) fn new_pair_with_capacity(capacity: usize) -> (PipeReader, PipeWriter) { +fn new_pair_with_capacity(capacity: usize) -> (PipeReader, PipeWriter) { let (producer, consumer) = RingBuffer::new(capacity).split(); let (producer_state, consumer_state) = Endpoint::new_pair(EndpointState::default(), EndpointState::default()); diff --git a/kernel/src/fs/pipe/mod.rs b/kernel/src/fs/pipe/mod.rs index bf263031b..8af49dc39 100644 --- a/kernel/src/fs/pipe/mod.rs +++ b/kernel/src/fs/pipe/mod.rs @@ -4,7 +4,7 @@ //! //! This module provides both anonymous and named pipes for inter-process communication. -pub use anony_pipe::new_file_pair; +pub use anony_pipe::{new_file_pair, AnonPipeFile, AnonPipeInode}; pub use named_pipe::NamedPipe; mod anony_pipe; diff --git a/kernel/src/fs/pipe/named_pipe.rs b/kernel/src/fs/pipe/named_pipe.rs index c10735845..9baf840a5 100644 --- a/kernel/src/fs/pipe/named_pipe.rs +++ b/kernel/src/fs/pipe/named_pipe.rs @@ -22,12 +22,16 @@ use crate::{ /// /// Once a handle for a `NamedPipe` exists, the corresponding pipe object will /// not be dropped. -struct NamedPipeHandle { +pub(super) struct NamedPipeHandle { inner: Arc, access_mode: AccessMode, } impl NamedPipeHandle { + pub(super) fn access_mode(&self) -> AccessMode { + self.access_mode + } + fn new(inner: Arc, access_mode: AccessMode) -> Box { Box::new(Self { inner, access_mode }) } @@ -169,10 +173,20 @@ impl NamedPipe { access_mode: AccessMode, status_flags: StatusFlags, ) -> Result> { + Ok(self.open_handle(access_mode, status_flags, true)?) + } + + /// Opens the pipe and returns a `NamedPipeHandle`. + pub(super) fn open_handle( + &self, + access_mode: AccessMode, + status_flags: StatusFlags, + is_named_pipe: bool, + ) -> Result> { let mut pipe = self.pipe.lock(); let pipe_obj = pipe.get_or_create_pipe_obj(); - let handle: Box = match access_mode { + let handle = match access_mode { AccessMode::O_RDONLY => { pipe.read_count += 1; @@ -185,7 +199,8 @@ impl NamedPipe { let has_writer = pipe_obj.num_writer.load(Ordering::Relaxed) > 0; let handle = NamedPipeHandle::new(pipe_obj, access_mode); - if !status_flags.contains(StatusFlags::O_NONBLOCK) && !has_writer { + // Reference: + if is_named_pipe && !status_flags.contains(StatusFlags::O_NONBLOCK) && !has_writer { let old_write_count = pipe.write_count; drop(pipe); self.wait_queue.pause_until(|| { @@ -207,7 +222,8 @@ impl NamedPipe { let has_reader = pipe_obj.num_reader.load(Ordering::Relaxed) > 0; let handle = NamedPipeHandle::new(pipe_obj, access_mode); - if !has_reader { + // Reference: + if is_named_pipe && !has_reader { if status_flags.contains(StatusFlags::O_NONBLOCK) { return_errno_with_message!(Errno::ENXIO, "no reader is present"); } diff --git a/kernel/src/fs/ramfs/memfd.rs b/kernel/src/fs/ramfs/memfd.rs index f9c2e9291..4d3453bc9 100644 --- a/kernel/src/fs/ramfs/memfd.rs +++ b/kernel/src/fs/ramfs/memfd.rs @@ -297,7 +297,7 @@ impl MemfdFile { }) } - pub fn open_from_inode(inode: Arc, open_args: OpenArgs) -> Result { + pub fn open(inode: Arc, open_args: OpenArgs) -> Result { let inode: Arc = inode; let status_flags = open_args.status_flags; let access_mode = open_args.access_mode; diff --git a/kernel/src/syscall/open.rs b/kernel/src/syscall/open.rs index 2cfa84906..d7ac8e644 100644 --- a/kernel/src/syscall/open.rs +++ b/kernel/src/syscall/open.rs @@ -8,6 +8,7 @@ use crate::{ file_table::{FdFlags, FileDesc}, fs_resolver::{AT_FDCWD, FsPath, FsResolver, LookupResult, PathOrInode}, inode_handle::InodeHandle, + pipe::{AnonPipeFile, AnonPipeInode}, ramfs::memfd::{MemfdFile, MemfdInode}, utils::{AccessMode, CreationFlags, InodeMode, InodeType, OpenArgs, StatusFlags}, }, @@ -92,11 +93,17 @@ fn do_open( LookupResult::Resolved(target) => match target { PathOrInode::Path(path) => Arc::new(path.open(open_args)?), PathOrInode::Inode(inode) => { - // TODO: Support re-opening anonymous pipes. - let memfd_inode = Arc::downcast::(inode).map_err(|_| { - Error::with_message(Errno::ENXIO, "the inode is not re-openable") - })?; - Arc::new(MemfdFile::open_from_inode(memfd_inode.clone(), open_args)?) + if let Ok(memfd_inode) = Arc::downcast::(inode.clone()) { + Arc::new(MemfdFile::open(memfd_inode, open_args)?) + } else if let Ok(pipe_inode) = Arc::downcast::(inode) { + Arc::new(AnonPipeFile::open( + pipe_inode, + open_args.access_mode, + open_args.status_flags, + )?) + } else { + return_errno_with_message!(Errno::ENXIO, "the inode is not re-openable") + } } }, LookupResult::AtParent(result) => { diff --git a/kernel/src/syscall/statfs.rs b/kernel/src/syscall/statfs.rs index b0e963fcf..28f944882 100644 --- a/kernel/src/syscall/statfs.rs +++ b/kernel/src/syscall/statfs.rs @@ -20,16 +20,19 @@ pub fn sys_statfs(path_ptr: Vaddr, statfs_buf_ptr: Vaddr, ctx: &Context) -> Resu path_name, statfs_buf_ptr, ); - let path = { + let fs = { let path_name = path_name.to_string_lossy(); let fs_path = FsPath::try_from(path_name.as_ref())?; - ctx.thread_local + let path_or_inode = ctx + .thread_local .borrow_fs() .resolver() .read() - .lookup(&fs_path)? + .lookup_inode(&fs_path)?; + path_or_inode.inode().fs() }; - let statfs = Statfs::from(path.fs().sb()); + + let statfs = Statfs::from(fs.sb()); user_space.write_val(statfs_buf_ptr, &statfs)?; Ok(SyscallReturn::Return(0)) } @@ -40,7 +43,7 @@ pub fn sys_fstatfs(fd: FileDesc, statfs_buf_ptr: Vaddr, ctx: &Context) -> Result let fs = { let mut file_table = ctx.thread_local.borrow_file_table_mut(); let file = get_file_fast!(&mut file_table, fd); - file.as_inode_handle_or_err()?.path().fs() + file.inode().fs() }; let statfs = Statfs::from(fs.sb());