From 2b1ecdcfa65c585470aecdae2b72db2f7a688b72 Mon Sep 17 00:00:00 2001 From: LI Qing Date: Tue, 18 Apr 2023 16:13:52 +0800 Subject: [PATCH] Add support for epoll --- services/libs/jinux-std/src/events/subject.rs | 4 +- .../libs/jinux-std/src/fs/epoll/epoll_file.rs | 485 ++++++++++++++++++ services/libs/jinux-std/src/fs/epoll/mod.rs | 72 +++ services/libs/jinux-std/src/fs/file_handle.rs | 74 +++ .../libs/jinux-std/src/fs/file_handle/file.rs | 43 -- .../libs/jinux-std/src/fs/file_handle/mod.rs | 100 ---- services/libs/jinux-std/src/fs/file_table.rs | 85 +-- services/libs/jinux-std/src/fs/fs_resolver.rs | 4 +- .../{file_handle => }/inode_handle/dyn_cap.rs | 51 +- .../fs/{file_handle => }/inode_handle/mod.rs | 5 +- .../inode_handle/static_cap.rs | 0 services/libs/jinux-std/src/fs/mod.rs | 2 + services/libs/jinux-std/src/fs/pipe.rs | 45 +- .../libs/jinux-std/src/fs/procfs/pid/fd.rs | 15 +- services/libs/jinux-std/src/fs/stdio.rs | 20 +- .../libs/jinux-std/src/fs/utils/channel.rs | 114 ++-- services/libs/jinux-std/src/fs/utils/poll.rs | 50 ++ services/libs/jinux-std/src/prelude.rs | 1 + .../jinux-std/src/process/process_table.rs | 2 +- services/libs/jinux-std/src/syscall/chdir.rs | 6 +- services/libs/jinux-std/src/syscall/epoll.rs | 133 +++++ .../libs/jinux-std/src/syscall/getdents64.rs | 3 +- services/libs/jinux-std/src/syscall/ioctl.rs | 2 +- services/libs/jinux-std/src/syscall/mod.rs | 10 + services/libs/jinux-std/src/syscall/open.rs | 14 +- services/libs/jinux-std/src/syscall/pipe.rs | 5 +- services/libs/jinux-std/src/tty/mod.rs | 8 +- 27 files changed, 1074 insertions(+), 279 deletions(-) create mode 100644 services/libs/jinux-std/src/fs/epoll/epoll_file.rs create mode 100644 services/libs/jinux-std/src/fs/epoll/mod.rs create mode 100644 services/libs/jinux-std/src/fs/file_handle.rs delete mode 100644 services/libs/jinux-std/src/fs/file_handle/file.rs delete mode 100644 services/libs/jinux-std/src/fs/file_handle/mod.rs rename services/libs/jinux-std/src/fs/{file_handle => }/inode_handle/dyn_cap.rs (77%) rename services/libs/jinux-std/src/fs/{file_handle => }/inode_handle/mod.rs (96%) rename services/libs/jinux-std/src/fs/{file_handle => }/inode_handle/static_cap.rs (100%) create mode 100644 services/libs/jinux-std/src/syscall/epoll.rs diff --git a/services/libs/jinux-std/src/events/subject.rs b/services/libs/jinux-std/src/events/subject.rs index be5d044c..56ab957e 100644 --- a/services/libs/jinux-std/src/events/subject.rs +++ b/services/libs/jinux-std/src/events/subject.rs @@ -21,9 +21,9 @@ impl Subject { } /// Unregister an observer. - pub fn unregister_observer(&self, observer: Weak>) { + pub fn unregister_observer(&self, observer: &Weak>) { let mut observers = self.observers.lock(); - observers.retain(|e| !Weak::ptr_eq(&e, &observer)); + observers.retain(|e| !Weak::ptr_eq(&e, observer)); } /// Notify events to all registered observers. diff --git a/services/libs/jinux-std/src/fs/epoll/epoll_file.rs b/services/libs/jinux-std/src/fs/epoll/epoll_file.rs new file mode 100644 index 00000000..a3d0279d --- /dev/null +++ b/services/libs/jinux-std/src/fs/epoll/epoll_file.rs @@ -0,0 +1,485 @@ +use crate::events::Observer; +use crate::fs::file_handle::FileLike; +use crate::fs::file_table::{FdEvents, FileDescripter}; +use crate::fs::utils::{IoEvents, IoctlCmd, Pollee, Poller}; + +use core::sync::atomic::{AtomicBool, Ordering}; +use core::time::Duration; + +use super::*; + +/// A file-like object that provides epoll API. +/// +/// Conceptually, we maintain two lists: one consists of all interesting files, +/// which can be managed by the epoll ctl commands; the other are for ready files, +/// which are files that have some events. A epoll wait only needs to iterate the +/// ready list and poll each file to see if the file is ready for the interesting +/// I/O. +/// +/// To maintain the ready list, we need to monitor interesting events that happen +/// on the files. To do so, the `EpollFile` registers itself as an `Observer` to +/// the monotored files. Thus, we can add a file to the ready list when an interesting +/// event happens on the file. +pub struct EpollFile { + // All interesting entries. + interest: Mutex>>, + // Entries that are probably ready (having events happened). + ready: Mutex>>, + // EpollFile itself is also pollable + pollee: Pollee, + // Any EpollFile is wrapped with Arc when created. + weak_self: Weak, +} + +impl EpollFile { + /// Creates a new epoll file. + pub fn new() -> Arc { + Arc::new_cyclic(|me| Self { + interest: Mutex::new(BTreeMap::new()), + ready: Mutex::new(VecDeque::new()), + pollee: Pollee::new(IoEvents::empty()), + weak_self: me.clone(), + }) + } + + /// Control the interest list of the epoll file. + pub fn control(&self, cmd: &EpollCtl) -> Result<()> { + match *cmd { + EpollCtl::Add(fd, ep_event, ep_flags) => self.add_interest(fd, ep_event, ep_flags), + EpollCtl::Del(fd) => { + self.del_interest(fd)?; + self.unregister_from_file_table_entry(fd); + Ok(()) + } + EpollCtl::Mod(fd, ep_event, ep_flags) => self.mod_interest(fd, ep_event, ep_flags), + } + } + + fn add_interest( + &self, + fd: FileDescripter, + ep_event: EpollEvent, + ep_flags: EpollFlags, + ) -> Result<()> { + self.warn_unsupported_flags(&ep_flags); + + let current = current!(); + let file_table = current.file_table().lock(); + let file_table_entry = file_table.get_entry(fd)?; + let file = file_table_entry.file(); + let weak_file = Arc::downgrade(file); + let mask = ep_event.events; + let entry = EpollEntry::new(fd, weak_file, ep_event, ep_flags, self.weak_self.clone()); + + // Add the new entry to the interest list and start monitering its events + let mut interest = self.interest.lock(); + if interest.contains_key(&fd) { + return_errno_with_message!(Errno::EEXIST, "the fd has been added"); + } + file.register_observer(entry.clone(), IoEvents::all())?; + interest.insert(fd, entry.clone()); + // Register self to the file table entry + file_table_entry.register_observer(self.weak_self.clone() as _); + let file = file.clone(); + drop(file_table); + drop(interest); + + // Add the new entry to the ready list if the file is ready + let events = file.poll(mask, None); + if !events.is_empty() { + self.push_ready(entry); + } + Ok(()) + } + + fn del_interest(&self, fd: FileDescripter) -> Result<()> { + let mut interest = self.interest.lock(); + let entry = interest + .remove(&fd) + .ok_or_else(|| Error::with_message(Errno::ENOENT, "fd is not in the interest list"))?; + + // If this epoll entry is in the ready list, then we should delete it. + // But unfortunately, deleting an entry from the ready list has a + // complexity of O(N). + // + // To optimize the performance, we only mark the epoll entry as + // deleted at this moment. The real deletion happens when the ready list + // is scanned in EpolFile::wait. + entry.set_deleted(); + + let file = match entry.file() { + Some(file) => file, + // TODO: should we warn about it? + None => return Ok(()), + }; + + file.unregister_observer(&(entry as _)).unwrap(); + Ok(()) + } + + fn mod_interest( + &self, + fd: FileDescripter, + new_ep_event: EpollEvent, + new_ep_flags: EpollFlags, + ) -> Result<()> { + self.warn_unsupported_flags(&new_ep_flags); + + // Update the epoll entry + let interest = self.interest.lock(); + let entry = interest + .get(&fd) + .ok_or_else(|| Error::with_message(Errno::ENOENT, "fd is not in the interest list"))?; + if entry.is_deleted() { + return_errno_with_message!(Errno::ENOENT, "fd is not in the interest list"); + } + let new_mask = new_ep_event.events; + entry.update(new_ep_event, new_ep_flags); + let entry = entry.clone(); + drop(interest); + + // Add the updated entry to the ready list if the file is ready + let file = match entry.file() { + Some(file) => file, + None => return Ok(()), + }; + let events = file.poll(new_mask, None); + if !events.is_empty() { + self.push_ready(entry); + } + Ok(()) + } + + fn unregister_from_file_table_entry(&self, fd: FileDescripter) { + let current = current!(); + let file_table = current.file_table().lock(); + if let Ok(entry) = file_table.get_entry(fd) { + entry.unregister_observer(&(self.weak_self.clone() as _)); + } + } + + /// Wait for interesting events happen on the files in the interest list + /// of the epoll file. + /// + /// This method blocks until either some interesting events happen or + /// the timeout expires or a signal arrives. The first case returns + /// `Ok(events)`, where `events` is a `Vec` containing at most `max_events` + /// number of `EpollEvent`s. The second and third case returns errors. + /// + /// When `max_events` equals to zero, the method returns when the timeout + /// expires or a signal arrives. + pub fn wait(&self, max_events: usize, timeout: Option<&Duration>) -> Result> { + let mut ep_events = Vec::new(); + let mut poller = None; + loop { + // Try to pop some ready entries + if self.pop_ready(max_events, &mut ep_events) > 0 { + return Ok(ep_events); + } + + // Return immediately if specifying a timeout of zero + if timeout.is_some() && timeout.as_ref().unwrap().is_zero() { + return Ok(ep_events); + } + + // If no ready entries for now, wait for them + if poller.is_none() { + poller = Some(Poller::new()); + let events = self.pollee.poll(IoEvents::IN, poller.as_ref()); + if !events.is_empty() { + continue; + } + } + + // FIXME: respect timeout parameter + poller.as_ref().unwrap().wait(); + } + } + + fn push_ready(&self, entry: Arc) { + let mut ready = self.ready.lock(); + if entry.is_deleted() { + return; + } + + if !entry.is_ready() { + entry.set_ready(); + ready.push_back(entry); + } + + // Even if the entry is already set to ready, there might be new events that we are interested in. + // Wake the poller anyway. + self.pollee.add_events(IoEvents::IN); + } + + fn pop_ready(&self, max_events: usize, ep_events: &mut Vec) -> usize { + let mut count_events = 0; + let mut ready = self.ready.lock(); + let mut pop_quota = ready.len(); + loop { + // Pop some ready entries per round. + // + // Since the popped ready entries may contain "false positive" and + // we want to return as many results as possible, this has to + // be done in a loop. + let pop_count = (max_events - count_events).min(pop_quota); + if pop_count == 0 { + break; + } + let ready_entries: Vec> = ready + .drain(..pop_count) + .filter(|entry| !entry.is_deleted()) + .collect(); + pop_quota -= pop_count; + + // Examine these ready entries, which are candidates for the results + // to be returned. + for entry in ready_entries { + let (ep_event, ep_flags) = entry.event_and_flags(); + // If this entry's file is ready, save it in the output array. + // EPOLLHUP and EPOLLERR should always be reported. + let ready_events = entry.poll() & (ep_event.events | IoEvents::HUP | IoEvents::ERR); + if !ready_events.is_empty() { + ep_events.push(EpollEvent::new(ready_events, ep_event.user_data)); + count_events += 1; + } + + // If the epoll entry is neither edge-triggered or one-shot, then we should + // keep the entry in the ready list. + if !ep_flags.intersects(EpollFlags::ONE_SHOT | EpollFlags::EDGE_TRIGGER) { + ready.push_back(entry); + } + // Otherwise, the entry is indeed removed the ready list and we should reset + // its ready flag. + else { + entry.reset_ready(); + // For EPOLLONESHOT flag, this entry should also be removed from the interest list + if ep_flags.intersects(EpollFlags::ONE_SHOT) { + self.del_interest(entry.fd()) + .expect("this entry should be in the interest list"); + } + } + } + } + + // Clear the epoll file's events if no ready entries + if ready.len() == 0 { + self.pollee.del_events(IoEvents::IN); + } + count_events + } + + fn warn_unsupported_flags(&self, flags: &EpollFlags) { + if flags.intersects(EpollFlags::EXCLUSIVE | EpollFlags::WAKE_UP) { + warn!("{:?} contains unsupported flags", flags); + } + } +} + +impl Observer for EpollFile { + fn on_events(&self, events: &FdEvents) { + // Delete the file from the interest list if it is closed. + if let FdEvents::Close(fd) = events { + let _ = self.del_interest(*fd); + } + } +} + +impl Drop for EpollFile { + fn drop(&mut self) { + trace!("EpollFile Drop"); + let mut interest = self.interest.lock(); + let fds: Vec<_> = interest + .drain_filter(|_, _| true) + .map(|(fd, entry)| { + entry.set_deleted(); + if let Some(file) = entry.file() { + let _ = file.unregister_observer(&(entry as _)); + } + fd + }) + .collect(); + drop(interest); + + fds.iter() + .for_each(|&fd| self.unregister_from_file_table_entry(fd)); + } +} + +// Implement the common methods required by FileHandle +impl FileLike for EpollFile { + fn read(&self, buf: &mut [u8]) -> Result { + return_errno_with_message!(Errno::EINVAL, "epoll files do not support read"); + } + + fn write(&self, buf: &[u8]) -> Result { + return_errno_with_message!(Errno::EINVAL, "epoll files do not support write"); + } + + fn ioctl(&self, _cmd: IoctlCmd, _arg: usize) -> Result { + return_errno_with_message!(Errno::EINVAL, "epoll files do not support ioctl"); + } + + fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { + self.pollee.poll(mask, poller) + } + + fn register_observer( + &self, + observer: Arc>, + mask: IoEvents, + ) -> Result<()> { + self.pollee.register_observer(observer, mask); + Ok(()) + } + + fn unregister_observer( + &self, + observer: &Arc>, + ) -> Result>> { + self.pollee + .unregister_observer(observer) + .ok_or_else(|| Error::with_message(Errno::ENOENT, "observer is not registered")) + } + + fn as_any_ref(&self) -> &dyn Any { + self + } +} + +/// An epoll entry contained in an epoll file. Each epoll entry is added, modified, +/// or deleted by the `EpollCtl` command. +pub struct EpollEntry { + fd: FileDescripter, + file: Weak, + inner: Mutex, + // Whether the entry is in the ready list + is_ready: AtomicBool, + // Whether the entry has been deleted from the interest list + is_deleted: AtomicBool, + // Refers to the epoll file containing this epoll entry + weak_epoll: Weak, + // An EpollEntry is always contained inside Arc + weak_self: Weak, +} + +struct Inner { + event: EpollEvent, + flags: EpollFlags, +} + +impl EpollEntry { + /// Creates a new epoll entry associated with the given epoll file. + pub fn new( + fd: FileDescripter, + file: Weak, + event: EpollEvent, + flags: EpollFlags, + weak_epoll: Weak, + ) -> Arc { + Arc::new_cyclic(|me| Self { + fd, + file, + inner: Mutex::new(Inner { event, flags }), + is_ready: AtomicBool::new(false), + is_deleted: AtomicBool::new(false), + weak_epoll, + weak_self: me.clone(), + }) + } + + /// Get the epoll file associated with this epoll entry. + pub fn epoll_file(&self) -> Option> { + self.weak_epoll.upgrade() + } + + /// Get an instance of `Arc` that refers to this epoll entry. + pub fn self_arc(&self) -> Arc { + self.weak_self.upgrade().unwrap() + } + + /// Get the file associated with this epoll entry. + /// + /// Since an epoll entry only holds a weak reference to the file, + /// it is possible (albeit unlikely) that the file has been dropped. + pub fn file(&self) -> Option> { + self.file.upgrade() + } + + /// Get the epoll event associated with the epoll entry. + pub fn event(&self) -> EpollEvent { + let inner = self.inner.lock(); + inner.event + } + + /// Get the epoll flags associated with the epoll entry. + pub fn flags(&self) -> EpollFlags { + let inner = self.inner.lock(); + inner.flags + } + + /// Get the epoll event and flags that are associated with this epoll entry. + pub fn event_and_flags(&self) -> (EpollEvent, EpollFlags) { + let inner = self.inner.lock(); + (inner.event, inner.flags) + } + + /// Poll the events of the file associated with this epoll entry. + /// + /// If the returned events is not empty, then the file is considered ready. + pub fn poll(&self) -> IoEvents { + match self.file.upgrade() { + Some(file) => file.poll(IoEvents::all(), None), + None => IoEvents::empty(), + } + } + + /// Update the epoll entry, most likely to be triggered via `EpollCtl::Mod`. + pub fn update(&self, event: EpollEvent, flags: EpollFlags) { + let mut inner = self.inner.lock(); + *inner = Inner { event, flags } + } + + /// Returns whether the epoll entry is in the ready list. + pub fn is_ready(&self) -> bool { + self.is_ready.load(Ordering::Relaxed) + } + + /// Mark the epoll entry as being in the ready list. + pub fn set_ready(&self) { + self.is_ready.store(true, Ordering::Relaxed); + } + + /// Mark the epoll entry as not being in the ready list. + pub fn reset_ready(&self) { + self.is_ready.store(false, Ordering::Relaxed) + } + + /// Returns whether the epoll entry has been deleted from the interest list. + pub fn is_deleted(&self) -> bool { + self.is_deleted.load(Ordering::Relaxed) + } + + /// Mark the epoll entry as having been deleted from the interest list. + pub fn set_deleted(&self) { + self.is_deleted.store(true, Ordering::Relaxed); + } + + /// Get the file descriptor associated with the epoll entry. + pub fn fd(&self) -> FileDescripter { + self.fd + } +} + +impl Observer for EpollEntry { + fn on_events(&self, _events: &IoEvents) { + // Fast path + if self.is_deleted() { + return; + } + + if let Some(epoll_file) = self.epoll_file() { + epoll_file.push_ready(self.self_arc()); + } + } +} diff --git a/services/libs/jinux-std/src/fs/epoll/mod.rs b/services/libs/jinux-std/src/fs/epoll/mod.rs new file mode 100644 index 00000000..e2d4951b --- /dev/null +++ b/services/libs/jinux-std/src/fs/epoll/mod.rs @@ -0,0 +1,72 @@ +use super::file_table::FileDescripter; +use super::utils::IoEvents; +use crate::prelude::*; + +mod epoll_file; + +pub use self::epoll_file::EpollFile; + +/// An epoll control command. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum EpollCtl { + Add(FileDescripter, EpollEvent, EpollFlags), + Del(FileDescripter), + Mod(FileDescripter, EpollEvent, EpollFlags), +} + +bitflags! { + /// Linux's epoll flags. + pub struct EpollFlags: u32 { + const EXCLUSIVE = (1 << 28); + const WAKE_UP = (1 << 29); + const ONE_SHOT = (1 << 30); + const EDGE_TRIGGER = (1 << 31); + } +} + +/// An epoll event. +/// +/// This could be used as either an input of epoll ctl or an output of epoll wait. +/// The memory layout is compatible with that of C's struct epoll_event. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct EpollEvent { + /// I/O events. + /// + /// When `EpollEvent` is used as inputs, this is treated as a mask of events. + /// When `EpollEvent` is used as outputs, this is the active events. + pub events: IoEvents, + /// A 64-bit, user-given data. + pub user_data: u64, +} + +impl EpollEvent { + /// Create a new epoll event. + pub fn new(events: IoEvents, user_data: u64) -> Self { + Self { events, user_data } + } +} + +impl From<&c_epoll_event> for EpollEvent { + fn from(c_event: &c_epoll_event) -> Self { + Self { + events: IoEvents::from_bits_truncate(c_event.events as u32), + user_data: c_event.data, + } + } +} + +impl From<&EpollEvent> for c_epoll_event { + fn from(ep_event: &EpollEvent) -> Self { + Self { + events: ep_event.events.bits() as u32, + data: ep_event.user_data, + } + } +} + +#[derive(Debug, Clone, Copy, Pod)] +#[repr(C)] +pub struct c_epoll_event { + pub events: u32, + pub data: u64, +} diff --git a/services/libs/jinux-std/src/fs/file_handle.rs b/services/libs/jinux-std/src/fs/file_handle.rs new file mode 100644 index 00000000..6f3709ab --- /dev/null +++ b/services/libs/jinux-std/src/fs/file_handle.rs @@ -0,0 +1,74 @@ +//! Opend File Handle + +use crate::events::Observer; +use crate::fs::utils::{IoEvents, IoctlCmd, Metadata, Poller, SeekFrom}; +use crate::prelude::*; +use crate::tty::get_n_tty; + +use core::any::Any; + +/// The basic operations defined on a file +pub trait FileLike: Send + Sync + Any { + fn read(&self, buf: &mut [u8]) -> Result { + return_errno_with_message!(Errno::EINVAL, "read is not supported"); + } + + fn write(&self, buf: &[u8]) -> Result { + return_errno_with_message!(Errno::EINVAL, "write is not supported"); + } + + fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result { + match cmd { + IoctlCmd::TCGETS => { + // FIXME: only a work around + let tty = get_n_tty(); + tty.ioctl(cmd, arg) + } + _ => panic!("Ioctl unsupported"), + } + } + + fn poll(&self, _mask: IoEvents, _poller: Option<&Poller>) -> IoEvents { + IoEvents::empty() + } + + fn flush(&self) -> Result<()> { + Ok(()) + } + + fn metadata(&self) -> Metadata { + panic!("metadata unsupported"); + } + + fn seek(&self, seek_from: SeekFrom) -> Result { + return_errno_with_message!(Errno::EINVAL, "seek is not supported"); + } + + fn clean_for_close(&self) -> Result<()> { + self.flush()?; + Ok(()) + } + + fn register_observer( + &self, + observer: Arc>, + mask: IoEvents, + ) -> Result<()> { + return_errno_with_message!(Errno::EINVAL, "register_observer is not supported") + } + + fn unregister_observer( + &self, + observer: &Arc>, + ) -> Result>> { + return_errno_with_message!(Errno::EINVAL, "unregister_observer is not supported") + } + + fn as_any_ref(&self) -> &dyn Any; +} + +impl dyn FileLike { + pub fn downcast_ref(&self) -> Option<&T> { + self.as_any_ref().downcast_ref::() + } +} diff --git a/services/libs/jinux-std/src/fs/file_handle/file.rs b/services/libs/jinux-std/src/fs/file_handle/file.rs deleted file mode 100644 index 2b6f8779..00000000 --- a/services/libs/jinux-std/src/fs/file_handle/file.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::fs::utils::{IoEvents, IoctlCmd, Metadata, Poller, SeekFrom}; -use crate::prelude::*; -use crate::tty::get_n_tty; - -use core::any::Any; - -/// The basic operations defined on a file -pub trait File: Send + Sync + Any { - fn read(&self, buf: &mut [u8]) -> Result { - panic!("read unsupported"); - } - - fn write(&self, buf: &[u8]) -> Result { - panic!("write unsupported"); - } - - fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result { - match cmd { - IoctlCmd::TCGETS => { - // FIXME: only a work around - let tty = get_n_tty(); - tty.ioctl(cmd, arg) - } - _ => panic!("Ioctl unsupported"), - } - } - - fn poll(&self, _mask: IoEvents, _poller: Option<&Poller>) -> IoEvents { - IoEvents::empty() - } - - fn flush(&self) -> Result<()> { - Ok(()) - } - - fn metadata(&self) -> Metadata { - panic!("metadata unsupported"); - } - - fn seek(&self, seek_from: SeekFrom) -> Result { - panic!("seek unsupported"); - } -} diff --git a/services/libs/jinux-std/src/fs/file_handle/mod.rs b/services/libs/jinux-std/src/fs/file_handle/mod.rs deleted file mode 100644 index 595f3192..00000000 --- a/services/libs/jinux-std/src/fs/file_handle/mod.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! Opend File Handle - -mod file; -mod inode_handle; - -use crate::fs::utils::{IoEvents, Metadata, Poller, SeekFrom}; -use crate::prelude::*; -use crate::rights::{ReadOp, WriteOp}; -use alloc::sync::Arc; - -pub use self::file::File; -pub use self::inode_handle::InodeHandle; - -#[derive(Clone)] -pub struct FileHandle { - inner: Inner, -} - -#[derive(Clone)] -enum Inner { - File(Arc), - Inode(InodeHandle), -} - -impl FileHandle { - pub fn new_file(file: Arc) -> Arc { - let inner = Inner::File(file); - Arc::new(Self { inner }) - } - - pub fn new_inode_handle(inode_handle: InodeHandle) -> Arc { - let inner = Inner::Inode(inode_handle); - Arc::new(Self { inner }) - } - - pub fn as_file(&self) -> Option<&Arc> { - match &self.inner { - Inner::File(file) => Some(file), - _ => None, - } - } - - pub fn as_inode_handle(&self) -> Option<&InodeHandle> { - match &self.inner { - Inner::Inode(inode_handle) => Some(inode_handle), - _ => None, - } - } - - pub fn read(&self, buf: &mut [u8]) -> Result { - match &self.inner { - Inner::File(file) => file.read(buf), - Inner::Inode(inode_handle) => { - let static_handle = inode_handle.clone().to_static::()?; - static_handle.read(buf) - } - } - } - - pub fn write(&self, buf: &[u8]) -> Result { - match &self.inner { - Inner::File(file) => file.write(buf), - Inner::Inode(inode_handle) => { - let static_handle = inode_handle.clone().to_static::()?; - static_handle.write(buf) - } - } - } - - pub fn metadata(&self) -> Metadata { - match &self.inner { - Inner::File(file) => file.metadata(), - Inner::Inode(inode_handle) => inode_handle.dentry().vnode().metadata(), - } - } - - pub fn seek(&self, seek_from: SeekFrom) -> Result { - match &self.inner { - Inner::File(file) => file.seek(seek_from), - Inner::Inode(inode_handle) => inode_handle.seek(seek_from), - } - } - - pub fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { - match &self.inner { - Inner::File(file) => file.poll(mask, poller), - Inner::Inode(inode_handle) => inode_handle.dentry().vnode().poll(mask, poller), - } - } - - pub fn clean_for_close(&self) -> Result<()> { - match &self.inner { - Inner::Inode(_) => { - // Close does not guarantee that the data has been successfully saved to disk. - } - Inner::File(file) => file.flush()?, - } - Ok(()) - } -} diff --git a/services/libs/jinux-std/src/fs/file_table.rs b/services/libs/jinux-std/src/fs/file_table.rs index a2345c00..d52be757 100644 --- a/services/libs/jinux-std/src/fs/file_table.rs +++ b/services/libs/jinux-std/src/fs/file_table.rs @@ -5,7 +5,7 @@ use core::cell::Cell; use jinux_util::slot_vec::SlotVec; use super::{ - file_handle::FileHandle, + file_handle::FileLike, stdio::{Stderr, Stdin, Stdout}, }; @@ -29,18 +29,9 @@ impl FileTable { let stdin = Stdin::new_with_default_console(); let stdout = Stdout::new_with_default_console(); let stderr = Stderr::new_with_default_console(); - table.put(FileTableEntry::new( - FileHandle::new_file(Arc::new(stdin)), - false, - )); - table.put(FileTableEntry::new( - FileHandle::new_file(Arc::new(stdout)), - false, - )); - table.put(FileTableEntry::new( - FileHandle::new_file(Arc::new(stderr)), - false, - )); + table.put(FileTableEntry::new(Arc::new(stdin), false)); + table.put(FileTableEntry::new(Arc::new(stdout), false)); + table.put(FileTableEntry::new(Arc::new(stderr), false)); Self { table, subject: Subject::new(), @@ -73,7 +64,7 @@ impl FileTable { Ok(min_free_fd as FileDescripter) } - pub fn insert(&mut self, item: Arc) -> FileDescripter { + pub fn insert(&mut self, item: Arc) -> FileDescripter { let entry = FileTableEntry::new(item, false); self.table.put(entry) as FileDescripter } @@ -81,32 +72,42 @@ impl FileTable { pub fn insert_at( &mut self, fd: FileDescripter, - item: Arc, - ) -> Option> { + item: Arc, + ) -> Option> { let entry = FileTableEntry::new(item, false); let entry = self.table.put_at(fd as usize, entry); if entry.is_some() { - self.notify_close_fd_event(fd); + let events = FdEvents::Close(fd); + self.notify_fd_events(&events); + entry.as_ref().unwrap().notify_fd_events(&events); } entry.map(|e| e.file) } - pub fn close_file(&mut self, fd: FileDescripter) -> Option> { + pub fn close_file(&mut self, fd: FileDescripter) -> Option> { let entry = self.table.remove(fd as usize); if entry.is_some() { - self.notify_close_fd_event(fd); + let events = FdEvents::Close(fd); + self.notify_fd_events(&events); + entry.as_ref().unwrap().notify_fd_events(&events); } entry.map(|e| e.file) } - pub fn get_file(&self, fd: FileDescripter) -> Result<&Arc> { + pub fn get_file(&self, fd: FileDescripter) -> Result<&Arc> { self.table .get(fd as usize) .map(|entry| &entry.file) .ok_or(Error::with_message(Errno::EBADF, "fd not exits")) } - pub fn fds_and_files(&self) -> impl Iterator)> { + pub fn get_entry(&self, fd: FileDescripter) -> Result<&FileTableEntry> { + self.table + .get(fd as usize) + .ok_or(Error::with_message(Errno::EBADF, "fd not exits")) + } + + pub fn fds_and_files(&self) -> impl Iterator)> { self.table .idxes_and_items() .map(|(idx, entry)| (idx as FileDescripter, &entry.file)) @@ -116,13 +117,12 @@ impl FileTable { self.subject.register_observer(observer); } - pub fn unregister_observer(&self, observer: Weak>) { + pub fn unregister_observer(&self, observer: &Weak>) { self.subject.unregister_observer(observer); } - fn notify_close_fd_event(&self, fd: FileDescripter) { - let events = FdEvents::Close(fd); - self.subject.notify_observers(&events); + fn notify_fd_events(&self, events: &FdEvents) { + self.subject.notify_observers(events); } } @@ -150,17 +150,44 @@ pub enum FdEvents { impl Events for FdEvents {} -#[derive(Clone)] -struct FileTableEntry { - file: Arc, +pub struct FileTableEntry { + file: Arc, close_on_exec: Cell, + subject: Subject, } impl FileTableEntry { - pub fn new(file: Arc, close_on_exec: bool) -> Self { + pub fn new(file: Arc, close_on_exec: bool) -> Self { Self { file, close_on_exec: Cell::new(close_on_exec), + subject: Subject::new(), + } + } + + pub fn file(&self) -> &Arc { + &self.file + } + + pub fn register_observer(&self, epoll: Weak>) { + self.subject.register_observer(epoll); + } + + pub fn unregister_observer(&self, epoll: &Weak>) { + self.subject.unregister_observer(epoll); + } + + pub fn notify_fd_events(&self, events: &FdEvents) { + self.subject.notify_observers(events); + } +} + +impl Clone for FileTableEntry { + fn clone(&self) -> Self { + Self { + file: self.file.clone(), + close_on_exec: self.close_on_exec.clone(), + subject: Subject::new(), } } } diff --git a/services/libs/jinux-std/src/fs/fs_resolver.rs b/services/libs/jinux-std/src/fs/fs_resolver.rs index 275babf0..76a6a3f8 100644 --- a/services/libs/jinux-std/src/fs/fs_resolver.rs +++ b/services/libs/jinux-std/src/fs/fs_resolver.rs @@ -2,8 +2,8 @@ use crate::prelude::*; use alloc::str; use alloc::string::String; -use super::file_handle::InodeHandle; use super::file_table::FileDescripter; +use super::inode_handle::InodeHandle; use super::procfs::ProcFS; use super::ramfs::RamFS; use super::utils::{ @@ -253,7 +253,7 @@ impl FsResolver { let file_table = current.file_table().lock(); let inode_handle = file_table .get_file(fd)? - .as_inode_handle() + .downcast_ref::() .ok_or(Error::with_message(Errno::EBADE, "not inode"))?; Ok(inode_handle.dentry().clone()) } diff --git a/services/libs/jinux-std/src/fs/file_handle/inode_handle/dyn_cap.rs b/services/libs/jinux-std/src/fs/inode_handle/dyn_cap.rs similarity index 77% rename from services/libs/jinux-std/src/fs/file_handle/inode_handle/dyn_cap.rs rename to services/libs/jinux-std/src/fs/inode_handle/dyn_cap.rs index b7c5c754..2634254d 100644 --- a/services/libs/jinux-std/src/fs/file_handle/inode_handle/dyn_cap.rs +++ b/services/libs/jinux-std/src/fs/inode_handle/dyn_cap.rs @@ -36,13 +36,6 @@ impl InodeHandle { Ok(InodeHandle(self.0, R1::new())) } - pub fn read(&self, buf: &mut [u8]) -> Result { - if !self.1.contains(Rights::READ) { - return_errno_with_message!(Errno::EBADF, "File is not readable"); - } - self.0.read(buf) - } - pub fn read_to_end(&self, buf: &mut Vec) -> Result { if !self.1.contains(Rights::READ) { return_errno_with_message!(Errno::EBADF, "File is not readable"); @@ -50,13 +43,6 @@ impl InodeHandle { self.0.read_to_end(buf) } - pub fn write(&self, buf: &[u8]) -> Result { - if !self.1.contains(Rights::WRITE) { - return_errno_with_message!(Errno::EBADF, "File is not writable"); - } - self.0.write(buf) - } - pub fn readdir(&self, visitor: &mut dyn DirentVisitor) -> Result { if !self.1.contains(Rights::READ) { return_errno_with_message!(Errno::EBADF, "File is not readable"); @@ -70,3 +56,40 @@ impl Clone for InodeHandle { Self(self.0.clone(), self.1.clone()) } } + +impl FileLike for InodeHandle { + fn read(&self, buf: &mut [u8]) -> Result { + if !self.1.contains(Rights::READ) { + return_errno_with_message!(Errno::EBADF, "File is not readable"); + } + self.0.read(buf) + } + + fn write(&self, buf: &[u8]) -> Result { + if !self.1.contains(Rights::WRITE) { + return_errno_with_message!(Errno::EBADF, "File is not writable"); + } + self.0.write(buf) + } + + fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { + self.dentry().vnode().poll(mask, poller) + } + + fn metadata(&self) -> Metadata { + self.dentry().vnode().metadata() + } + + fn seek(&self, seek_from: SeekFrom) -> Result { + self.0.seek(seek_from) + } + + fn clean_for_close(&self) -> Result<()> { + // Close does not guarantee that the data has been successfully saved to disk. + Ok(()) + } + + fn as_any_ref(&self) -> &dyn Any { + self + } +} diff --git a/services/libs/jinux-std/src/fs/file_handle/inode_handle/mod.rs b/services/libs/jinux-std/src/fs/inode_handle/mod.rs similarity index 96% rename from services/libs/jinux-std/src/fs/file_handle/inode_handle/mod.rs rename to services/libs/jinux-std/src/fs/inode_handle/mod.rs index 8610379c..24500cf0 100644 --- a/services/libs/jinux-std/src/fs/file_handle/inode_handle/mod.rs +++ b/services/libs/jinux-std/src/fs/inode_handle/mod.rs @@ -3,7 +3,10 @@ mod dyn_cap; mod static_cap; -use crate::fs::utils::{AccessMode, Dentry, DirentVisitor, InodeType, SeekFrom, StatusFlags}; +use crate::fs::file_handle::FileLike; +use crate::fs::utils::{ + AccessMode, Dentry, DirentVisitor, InodeType, IoEvents, Metadata, Poller, SeekFrom, StatusFlags, +}; use crate::prelude::*; use crate::rights::Rights; diff --git a/services/libs/jinux-std/src/fs/file_handle/inode_handle/static_cap.rs b/services/libs/jinux-std/src/fs/inode_handle/static_cap.rs similarity index 100% rename from services/libs/jinux-std/src/fs/file_handle/inode_handle/static_cap.rs rename to services/libs/jinux-std/src/fs/inode_handle/static_cap.rs diff --git a/services/libs/jinux-std/src/fs/mod.rs b/services/libs/jinux-std/src/fs/mod.rs index a2508270..e5bb7d21 100644 --- a/services/libs/jinux-std/src/fs/mod.rs +++ b/services/libs/jinux-std/src/fs/mod.rs @@ -1,7 +1,9 @@ +pub mod epoll; pub mod file_handle; pub mod file_table; pub mod fs_resolver; pub mod initramfs; +pub mod inode_handle; pub mod pipe; pub mod procfs; pub mod ramfs; diff --git a/services/libs/jinux-std/src/fs/pipe.rs b/services/libs/jinux-std/src/fs/pipe.rs index fe7ae33e..4e0aa345 100644 --- a/services/libs/jinux-std/src/fs/pipe.rs +++ b/services/libs/jinux-std/src/fs/pipe.rs @@ -1,6 +1,7 @@ +use crate::events::Observer; use crate::prelude::*; -use super::file_handle::File; +use super::file_handle::FileLike; use super::utils::{Consumer, IoEvents, Poller, Producer}; pub struct PipeReader { @@ -13,7 +14,7 @@ impl PipeReader { } } -impl File for PipeReader { +impl FileLike for PipeReader { fn read(&self, buf: &mut [u8]) -> Result { let is_nonblocking = self.consumer.is_nonblocking(); @@ -41,6 +42,25 @@ impl File for PipeReader { fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { self.consumer.poll(mask, poller) } + + fn register_observer( + &self, + observer: Arc>, + mask: IoEvents, + ) -> Result<()> { + self.consumer.register_observer(observer, mask) + } + + fn unregister_observer( + &self, + observer: &Arc>, + ) -> Result>> { + self.consumer.unregister_observer(observer) + } + + fn as_any_ref(&self) -> &dyn Any { + self + } } pub struct PipeWriter { @@ -53,7 +73,7 @@ impl PipeWriter { } } -impl File for PipeWriter { +impl FileLike for PipeWriter { fn write(&self, buf: &[u8]) -> Result { let is_nonblocking = self.producer.is_nonblocking(); @@ -81,6 +101,25 @@ impl File for PipeWriter { fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { self.producer.poll(mask, poller) } + + fn register_observer( + &self, + observer: Arc>, + mask: IoEvents, + ) -> Result<()> { + self.producer.register_observer(observer, mask) + } + + fn unregister_observer( + &self, + observer: &Arc>, + ) -> Result>> { + self.producer.unregister_observer(observer) + } + + fn as_any_ref(&self) -> &dyn Any { + self + } } fn should_io_return(res: &Result, is_nonblocking: bool) -> bool { diff --git a/services/libs/jinux-std/src/fs/procfs/pid/fd.rs b/services/libs/jinux-std/src/fs/procfs/pid/fd.rs index db422ec2..49c9dc00 100644 --- a/services/libs/jinux-std/src/fs/procfs/pid/fd.rs +++ b/services/libs/jinux-std/src/fs/procfs/pid/fd.rs @@ -1,6 +1,7 @@ use super::*; -use crate::fs::file_handle::FileHandle; +use crate::fs::file_handle::FileLike; use crate::fs::file_table::FileDescripter; +use crate::fs::inode_handle::InodeHandle; /// Represents the inode at `/proc/[pid]/fd`. pub struct FdDirOps(Arc); @@ -62,10 +63,10 @@ impl DirOps for FdDirOps { } /// Represents the inode at `/proc/[pid]/fd/N`. -struct FileSymOps(Arc); +struct FileSymOps(Arc); impl FileSymOps { - pub fn new_inode(file: Arc, parent: Weak) -> Arc { + pub fn new_inode(file: Arc, parent: Weak) -> Arc { ProcSymBuilder::new(Self(file)) .parent(parent) .build() @@ -75,13 +76,11 @@ impl FileSymOps { impl SymOps for FileSymOps { fn read_link(&self) -> Result { - let path = if let Some(inode_handle) = self.0.as_inode_handle() { + let path = if let Some(inode_handle) = self.0.downcast_ref::() { inode_handle.dentry().abs_path() - } else if let Some(file) = self.0.as_file() { - // TODO: get the real path for stdio - String::from("/dev/tty") } else { - unreachable!() + // TODO: get the real path for other FileLike object + String::from("/dev/tty") }; Ok(path) } diff --git a/services/libs/jinux-std/src/fs/stdio.rs b/services/libs/jinux-std/src/fs/stdio.rs index fbc138a3..f19de531 100644 --- a/services/libs/jinux-std/src/fs/stdio.rs +++ b/services/libs/jinux-std/src/fs/stdio.rs @@ -1,7 +1,7 @@ use crate::prelude::*; use crate::tty::{get_n_tty, Tty}; -use super::file_handle::File; +use super::file_handle::FileLike; use super::file_table::FileDescripter; use super::utils::{InodeMode, InodeType, IoEvents, Metadata, Poller, SeekFrom}; @@ -21,7 +21,7 @@ pub struct Stderr { console: Option>, } -impl File for Stdin { +impl FileLike for Stdin { fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { if let Some(console) = self.console.as_ref() { console.poll(mask, poller) @@ -69,8 +69,12 @@ impl File for Stdin { rdev: 0, } } + + fn as_any_ref(&self) -> &dyn Any { + self + } } -impl File for Stdout { +impl FileLike for Stdout { fn ioctl(&self, cmd: super::utils::IoctlCmd, arg: usize) -> Result { if let Some(console) = self.console.as_ref() { console.ioctl(cmd, arg) @@ -110,9 +114,13 @@ impl File for Stdout { rdev: 0, } } + + fn as_any_ref(&self) -> &dyn Any { + self + } } -impl File for Stderr { +impl FileLike for Stderr { fn ioctl(&self, cmd: super::utils::IoctlCmd, arg: usize) -> Result { if let Some(console) = self.console.as_ref() { console.ioctl(cmd, arg) @@ -152,6 +160,10 @@ impl File for Stderr { rdev: 0, } } + + fn as_any_ref(&self) -> &dyn Any { + self + } } impl Stdin { diff --git a/services/libs/jinux-std/src/fs/utils/channel.rs b/services/libs/jinux-std/src/fs/utils/channel.rs index 04543449..02deda2b 100644 --- a/services/libs/jinux-std/src/fs/utils/channel.rs +++ b/services/libs/jinux-std/src/fs/utils/channel.rs @@ -2,6 +2,7 @@ use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use jinux_rights_proc::require; use ringbuf::{HeapConsumer as HeapRbConsumer, HeapProducer as HeapRbProducer, HeapRb}; +use crate::events::Observer; use crate::prelude::*; use crate::rights::*; @@ -48,6 +49,59 @@ pub struct Producer(EndPoint); pub struct Consumer(EndPoint); +macro_rules! impl_common_methods_for_channel { + () => { + pub fn shutdown(&self) { + self.this_end().shutdown() + } + + pub fn is_shutdown(&self) -> bool { + self.this_end().is_shutdown() + } + + pub fn is_peer_shutdown(&self) -> bool { + self.peer_end().is_shutdown() + } + + pub fn status_flags(&self) -> StatusFlags { + self.this_end().status_flags() + } + + pub fn set_status_flags(&self, new_flags: StatusFlags) { + self.this_end().set_status_flags(new_flags) + } + + pub fn is_nonblocking(&self) -> bool { + self.this_end() + .status_flags() + .contains(StatusFlags::O_NONBLOCK) + } + + pub fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { + self.this_end().pollee.poll(mask, poller) + } + + pub fn register_observer( + &self, + observer: Arc>, + mask: IoEvents, + ) -> Result<()> { + self.this_end().pollee.register_observer(observer, mask); + Ok(()) + } + + pub fn unregister_observer( + &self, + observer: &Arc>, + ) -> Result>> { + self.this_end() + .pollee + .unregister_observer(observer) + .ok_or_else(|| Error::with_message(Errno::ENOENT, "the observer is not registered")) + } + }; +} + impl Producer { fn this_end(&self) -> &EndPointInner> { &self.0.common.producer @@ -75,35 +129,7 @@ impl Producer { } } - pub fn shutdown(&self) { - self.this_end().shutdown() - } - - pub fn is_shutdown(&self) -> bool { - self.this_end().is_shutdown() - } - - pub fn is_peer_shutdown(&self) -> bool { - self.peer_end().is_shutdown() - } - - pub fn status_flags(&self) -> StatusFlags { - self.this_end().status_flags() - } - - pub fn set_status_flags(&self, new_flags: StatusFlags) { - self.this_end().set_status_flags(new_flags) - } - - pub fn is_nonblocking(&self) -> bool { - self.this_end() - .status_flags() - .contains(StatusFlags::O_NONBLOCK) - } - - pub fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { - self.this_end().pollee.poll(mask, poller) - } + impl_common_methods_for_channel!(); } impl Producer { @@ -167,35 +193,7 @@ impl Consumer { } } - pub fn shutdown(&self) { - self.this_end().shutdown() - } - - pub fn is_shutdown(&self) -> bool { - self.this_end().is_shutdown() - } - - pub fn is_peer_shutdown(&self) -> bool { - self.peer_end().is_shutdown() - } - - pub fn status_flags(&self) -> StatusFlags { - self.this_end().status_flags() - } - - pub fn set_status_flags(&self, new_flags: StatusFlags) { - self.this_end().set_status_flags(new_flags) - } - - pub fn is_nonblocking(&self) -> bool { - self.this_end() - .status_flags() - .contains(StatusFlags::O_NONBLOCK) - } - - pub fn poll(&self, mask: IoEvents, poller: Option<&Poller>) -> IoEvents { - self.this_end().pollee.poll(mask, poller) - } + impl_common_methods_for_channel!(); } impl Consumer { diff --git a/services/libs/jinux-std/src/fs/utils/poll.rs b/services/libs/jinux-std/src/fs/utils/poll.rs index 53d234f7..905e0a3b 100644 --- a/services/libs/jinux-std/src/fs/utils/poll.rs +++ b/services/libs/jinux-std/src/fs/utils/poll.rs @@ -73,6 +73,56 @@ impl Pollee { } } + /// Register an IoEvents observer. + /// + /// A registered observer will get notified (through its `on_events` method) + /// every time new events specified by the `mask` argument happen on the + /// pollee (through the `add_events` method). + /// + /// If the given observer has already been registered, then its registered + /// event mask will be updated. + /// + /// Note that the observer will always get notified of the events in + /// `IoEvents::ALWAYS_POLL` regardless of the value of `mask`. + /// + /// # Memory leakage + /// + /// Since an `Arc` for each observer is kept internally by a pollee, + /// it is important for the user to call the `unregister_observer` method + /// when the observer is no longer interested in the pollee. Otherwise, + /// the observer will not be dropped. + pub fn register_observer(&self, observer: Arc>, mask: IoEvents) { + let mut pollers = self.inner.pollers.lock(); + let is_new = { + let observer: KeyableArc> = observer.into(); + let mask = mask | IoEvents::ALWAYS_POLL; + pollers.insert(observer, mask).is_none() + }; + if is_new { + self.inner.num_pollers.fetch_add(1, Ordering::Release); + } + } + + /// Unregister an IoEvents observer. + /// + /// If such an observer is found, then the registered observer will be + /// removed from the pollee and returned as the return value. Otherwise, + /// a `None` will be returned. + pub fn unregister_observer( + &self, + observer: &Arc>, + ) -> Option>> { + let observer: KeyableArc> = observer.clone().into(); + let mut pollers = self.inner.pollers.lock(); + let observer = pollers + .remove_entry(&observer) + .map(|(observer, _)| observer.into()); + if observer.is_some() { + self.inner.num_pollers.fetch_sub(1, Ordering::Relaxed); + } + observer + } + /// Add some events to the pollee's state. /// /// This method wakes up all registered pollers that are interested in diff --git a/services/libs/jinux-std/src/prelude.rs b/services/libs/jinux-std/src/prelude.rs index 049cc5bf..6ca7eb73 100644 --- a/services/libs/jinux-std/src/prelude.rs +++ b/services/libs/jinux-std/src/prelude.rs @@ -13,6 +13,7 @@ pub(crate) use alloc::sync::Weak; pub(crate) use alloc::vec; pub(crate) use alloc::vec::Vec; pub(crate) use bitflags::bitflags; +pub(crate) use core::any::Any; pub(crate) use core::ffi::CStr; pub(crate) use jinux_frame::config::PAGE_SIZE; pub(crate) use jinux_frame::sync::{Mutex, MutexGuard}; diff --git a/services/libs/jinux-std/src/process/process_table.rs b/services/libs/jinux-std/src/process/process_table.rs index 9d799810..69c4400d 100644 --- a/services/libs/jinux-std/src/process/process_table.rs +++ b/services/libs/jinux-std/src/process/process_table.rs @@ -68,7 +68,7 @@ pub fn register_observer(observer: Weak>) { PROCESS_TABLE_SUBJECT.register_observer(observer); } -pub fn unregister_observer(observer: Weak>) { +pub fn unregister_observer(observer: &Weak>) { PROCESS_TABLE_SUBJECT.unregister_observer(observer); } diff --git a/services/libs/jinux-std/src/syscall/chdir.rs b/services/libs/jinux-std/src/syscall/chdir.rs index bf306f33..31d1e0dc 100644 --- a/services/libs/jinux-std/src/syscall/chdir.rs +++ b/services/libs/jinux-std/src/syscall/chdir.rs @@ -1,4 +1,6 @@ -use crate::fs::{file_table::FileDescripter, fs_resolver::FsPath, utils::InodeType}; +use crate::fs::{ + file_table::FileDescripter, fs_resolver::FsPath, inode_handle::InodeHandle, utils::InodeType, +}; use crate::log_syscall_entry; use crate::prelude::*; use crate::syscall::constants::MAX_FILENAME_LEN; @@ -38,7 +40,7 @@ pub fn sys_fchdir(fd: FileDescripter) -> Result { let file_table = current.file_table().lock(); let file = file_table.get_file(fd)?; let inode_handle = file - .as_inode_handle() + .downcast_ref::() .ok_or(Error::with_message(Errno::EBADE, "not inode"))?; inode_handle.dentry().clone() }; diff --git a/services/libs/jinux-std/src/syscall/epoll.rs b/services/libs/jinux-std/src/syscall/epoll.rs new file mode 100644 index 00000000..b164a6ed --- /dev/null +++ b/services/libs/jinux-std/src/syscall/epoll.rs @@ -0,0 +1,133 @@ +use core::time::Duration; + +use crate::fs::epoll::{c_epoll_event, EpollCtl, EpollEvent, EpollFile, EpollFlags}; +use crate::fs::file_table::FileDescripter; +use crate::fs::utils::CreationFlags; +use crate::log_syscall_entry; +use crate::prelude::*; +use crate::util::{read_val_from_user, write_val_to_user}; + +use super::SyscallReturn; +use super::{SYS_EPOLL_CREATE1, SYS_EPOLL_CTL, SYS_EPOLL_WAIT}; + +pub fn sys_epoll_create(size: i32) -> Result { + if size <= 0 { + return_errno_with_message!(Errno::EINVAL, "size is not positive"); + } + sys_epoll_create1(0) +} + +pub fn sys_epoll_create1(flags: u32) -> Result { + log_syscall_entry!(SYS_EPOLL_CREATE1); + debug!("flags = 0x{:x}", flags); + + let close_on_exec = { + let flags = CreationFlags::from_bits(flags) + .ok_or_else(|| Error::with_message(Errno::EINVAL, "invalid flags"))?; + if flags == CreationFlags::empty() { + false + } else if flags == CreationFlags::O_CLOEXEC { + true + } else { + // Only O_CLOEXEC is valid + return_errno_with_message!(Errno::EINVAL, "invalid flags"); + } + }; + + let current = current!(); + let epoll_file: Arc = EpollFile::new(); + let mut file_table = current.file_table().lock(); + let fd = file_table.insert(epoll_file); + Ok(SyscallReturn::Return(fd as _)) +} + +pub fn sys_epoll_ctl( + epfd: FileDescripter, + op: i32, + fd: FileDescripter, + event_addr: Vaddr, +) -> Result { + log_syscall_entry!(SYS_EPOLL_CTL); + debug!( + "epfd = {}, op = {}, fd = {}, event_addr = 0x{:x}", + epfd, op, fd, event_addr + ); + + const EPOLL_CTL_ADD: i32 = 1; + const EPOLL_CTL_DEL: i32 = 2; + const EPOLL_CTL_MOD: i32 = 3; + + let cmd = match op { + EPOLL_CTL_ADD => { + let c_epoll_event = read_val_from_user::(event_addr)?; + let event = EpollEvent::from(&c_epoll_event); + let flags = EpollFlags::from_bits_truncate(c_epoll_event.events); + EpollCtl::Add(fd, event, flags) + } + EPOLL_CTL_DEL => EpollCtl::Del(fd), + EPOLL_CTL_MOD => { + let c_epoll_event = read_val_from_user::(event_addr)?; + let event = EpollEvent::from(&c_epoll_event); + let flags = EpollFlags::from_bits_truncate(c_epoll_event.events); + EpollCtl::Mod(fd, event, flags) + } + _ => return_errno_with_message!(Errno::EINVAL, "invalid op"), + }; + + let current = current!(); + let file = { + let file_table = current.file_table().lock(); + file_table.get_file(epfd)?.clone() + }; + let epoll_file = file + .downcast_ref::() + .ok_or(Error::with_message(Errno::EINVAL, "not epoll file"))?; + epoll_file.control(&cmd)?; + + Ok(SyscallReturn::Return(0 as _)) +} + +pub fn sys_epoll_wait( + epfd: FileDescripter, + events_addr: Vaddr, + max_events: i32, + timeout: i32, +) -> Result { + log_syscall_entry!(SYS_EPOLL_WAIT); + + let max_events = { + if max_events <= 0 { + return_errno_with_message!(Errno::EINVAL, "max_events is not positive"); + } + max_events as usize + }; + let timeout = if timeout >= 0 { + Some(Duration::from_millis(timeout as _)) + } else { + None + }; + debug!( + "epfd = {}, events_addr = 0x{:x}, max_events = {}, timeout = {:?}", + epfd, events_addr, max_events, timeout + ); + + let current = current!(); + let file = { + let file_table = current.file_table().lock(); + file_table.get_file(epfd)?.clone() + }; + let epoll_file = file + .downcast_ref::() + .ok_or(Error::with_message(Errno::EINVAL, "not epoll file"))?; + let epoll_events = epoll_file.wait(max_events, timeout.as_ref())?; + + // Write back + let mut write_addr = events_addr; + for epoll_event in epoll_events.iter() { + let c_epoll_event = c_epoll_event::from(epoll_event); + write_val_to_user(write_addr, &c_epoll_event)?; + write_addr += core::mem::size_of::(); + } + + Ok(SyscallReturn::Return(epoll_events.len() as _)) +} diff --git a/services/libs/jinux-std/src/syscall/getdents64.rs b/services/libs/jinux-std/src/syscall/getdents64.rs index b60582c1..cf9e2c78 100644 --- a/services/libs/jinux-std/src/syscall/getdents64.rs +++ b/services/libs/jinux-std/src/syscall/getdents64.rs @@ -1,5 +1,6 @@ use crate::fs::{ file_table::FileDescripter, + inode_handle::InodeHandle, utils::{DirentVisitor, InodeType}, }; use crate::log_syscall_entry; @@ -27,7 +28,7 @@ pub fn sys_getdents64( file_table.get_file(fd)?.clone() }; let inode_handle = file - .as_inode_handle() + .downcast_ref::() .ok_or(Error::with_message(Errno::EBADE, "not inode"))?; if inode_handle.dentry().inode_type() != InodeType::Dir { return_errno!(Errno::ENOTDIR); diff --git a/services/libs/jinux-std/src/syscall/ioctl.rs b/services/libs/jinux-std/src/syscall/ioctl.rs index 61219a67..eb09b5cd 100644 --- a/services/libs/jinux-std/src/syscall/ioctl.rs +++ b/services/libs/jinux-std/src/syscall/ioctl.rs @@ -16,6 +16,6 @@ pub fn sys_ioctl(fd: FileDescripter, cmd: u32, arg: Vaddr) -> Result syscall_handler!(0, sys_gettid), SYS_TIME => syscall_handler!(1, sys_time, args), SYS_FUTEX => syscall_handler!(6, sys_futex, args), + SYS_EPOLL_CREATE => syscall_handler!(1, sys_epoll_create, args), SYS_GETDENTS64 => syscall_handler!(3, sys_getdents64, args), SYS_SET_TID_ADDRESS => syscall_handler!(1, sys_set_tid_address, args), SYS_CLOCK_NANOSLEEP => syscall_handler!(4, sys_clock_nanosleep, args), SYS_EXIT_GROUP => syscall_handler!(1, sys_exit_group, args), + SYS_EPOLL_WAIT => syscall_handler!(4, sys_epoll_wait, args), + SYS_EPOLL_CTL => syscall_handler!(4, sys_epoll_ctl, args), SYS_TGKILL => syscall_handler!(3, sys_tgkill, args), SYS_WAITID => syscall_handler!(5, sys_waitid, args), SYS_OPENAT => syscall_handler!(4, sys_openat, args), @@ -371,6 +380,7 @@ pub fn syscall_dispatch( SYS_READLINKAT => syscall_handler!(4, sys_readlinkat, args), SYS_SET_ROBUST_LIST => syscall_handler!(2, sys_set_robust_list, args), SYS_UTIMENSAT => syscall_handler!(4, sys_utimensat, args), + SYS_EPOLL_CREATE1 => syscall_handler!(1, sys_epoll_create1, args), SYS_PIPE2 => syscall_handler!(2, sys_pipe2, args), SYS_PRLIMIT64 => syscall_handler!(4, sys_prlimit64, args), _ => { diff --git a/services/libs/jinux-std/src/syscall/open.rs b/services/libs/jinux-std/src/syscall/open.rs index a67f0c08..b6ba1bb5 100644 --- a/services/libs/jinux-std/src/syscall/open.rs +++ b/services/libs/jinux-std/src/syscall/open.rs @@ -1,5 +1,5 @@ use crate::fs::{ - file_handle::{File, FileHandle}, + file_handle::FileLike, file_table::FileDescripter, fs_resolver::{FsPath, AT_FDCWD}, }; @@ -39,7 +39,7 @@ pub fn sys_openat( if dirfd == AT_FDCWD && pathname == CString::new("./trace")? { // Debug use: This file is used for output busybox log - let trace_file = FileHandle::new_file(Arc::new(BusyBoxTraceFile) as Arc); + let trace_file = Arc::new(BusyBoxTraceFile); let current = current!(); let mut file_table = current.file_table().lock(); let fd = file_table.insert(trace_file); @@ -47,7 +47,7 @@ pub fn sys_openat( } if dirfd == AT_FDCWD && pathname == CString::new("/dev/tty")? { - let tty_file = FileHandle::new_file(get_n_tty().clone() as Arc); + let tty_file = get_n_tty().clone(); let current = current!(); let mut file_table = current.file_table().lock(); let fd = file_table.insert(tty_file); @@ -60,7 +60,7 @@ pub fn sys_openat( let pathname = pathname.to_string_lossy(); let fs_path = FsPath::new(dirfd, pathname.as_ref())?; let inode_handle = current.fs().read().open(&fs_path, flags, mode)?; - FileHandle::new_inode_handle(inode_handle) + Arc::new(inode_handle) }; let mut file_table = current.file_table().lock(); let fd = file_table.insert(file_handle); @@ -74,9 +74,13 @@ pub fn sys_open(pathname_addr: Vaddr, flags: u32, mode: u16) -> Result Result { debug!("ASH TRACE: {}", core::str::from_utf8(buf)?); Ok(buf.len()) } + + fn as_any_ref(&self) -> &dyn Any { + self + } } diff --git a/services/libs/jinux-std/src/syscall/pipe.rs b/services/libs/jinux-std/src/syscall/pipe.rs index fe9715c8..015f2a1b 100644 --- a/services/libs/jinux-std/src/syscall/pipe.rs +++ b/services/libs/jinux-std/src/syscall/pipe.rs @@ -1,4 +1,3 @@ -use crate::fs::file_handle::FileHandle; use crate::fs::file_table::FileDescripter; use crate::fs::pipe::{PipeReader, PipeWriter}; use crate::fs::utils::{Channel, StatusFlags}; @@ -22,8 +21,8 @@ pub fn sys_pipe2(fds: Vaddr, flags: u32) -> Result { .split(); (PipeReader::new(consumer), PipeWriter::new(producer)) }; - let pipe_reader = FileHandle::new_file(Arc::new(reader)); - let pipe_writer = FileHandle::new_file(Arc::new(writer)); + let pipe_reader = Arc::new(reader); + let pipe_writer = Arc::new(writer); let current = current!(); let mut file_table = current.file_table().lock(); diff --git a/services/libs/jinux-std/src/tty/mod.rs b/services/libs/jinux-std/src/tty/mod.rs index bd534be7..5c7be6f2 100644 --- a/services/libs/jinux-std/src/tty/mod.rs +++ b/services/libs/jinux-std/src/tty/mod.rs @@ -2,7 +2,7 @@ use self::line_discipline::LineDiscipline; use crate::driver::tty::TtyDriver; use crate::fs::utils::{InodeMode, InodeType, IoEvents, Metadata}; use crate::fs::{ - file_handle::File, + file_handle::FileLike, utils::{IoctlCmd, Poller}, }; use crate::prelude::*; @@ -51,7 +51,7 @@ impl Tty { } } -impl File for Tty { +impl FileLike for Tty { fn read(&self, buf: &mut [u8]) -> Result { self.ldisc.read(buf) } @@ -129,6 +129,10 @@ impl File for Tty { rdev: 0, } } + + fn as_any_ref(&self) -> &dyn Any { + self + } } /// FIXME: should we maintain a static console?