diff --git a/local/sources/redox-scheme/.gitignore b/local/sources/redox-scheme/.gitignore new file mode 100644 index 0000000000..a9d37c560c --- /dev/null +++ b/local/sources/redox-scheme/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/local/sources/redox-scheme/Cargo.toml b/local/sources/redox-scheme/Cargo.toml new file mode 100644 index 0000000000..1256443340 --- /dev/null +++ b/local/sources/redox-scheme/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "redox-scheme" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +# Local fork: upstream 0.11.2 + Red Bear -rb1 version policy. +# See local/AGENTS.md § "Category 2 — Local forks of upstream packages". +version = "0.11.2-rb1" +edition = "2024" +license = "MIT" +exclude = ["target"] +repository = "https://gitlab.redox-os.org/redox-os/redox-scheme" +description = "Library for writing Redox scheme daemons" + +[features] +default = ["std"] +std = [] + +[dependencies] +redox_syscall = "0.9.0-rb1" +libredox = { version = "0.1.18-rb1", default-features = false, features = ["redox_syscall", "call"] } diff --git a/local/sources/redox-scheme/Cargo.toml.orig b/local/sources/redox-scheme/Cargo.toml.orig new file mode 100644 index 0000000000..a1d461a392 --- /dev/null +++ b/local/sources/redox-scheme/Cargo.toml.orig @@ -0,0 +1,22 @@ +[package] +name = "redox-scheme" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +# Local fork: upstream 0.11.2 + Red Bear -rb1 version policy. +# See local/AGENTS.md § "Category 2 — Local forks of upstream packages". +version = "0.11.2-rb1" +edition = "2024" +license = "MIT" +exclude = ["target"] +repository = "https://gitlab.redox-os.org/redox-os/redox-scheme" +description = "Library for writing Redox scheme daemons" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +# TODO: hashbrown separately? +default = ["std"] +std = [] + +[dependencies] +redox_syscall = "0.9.0-rb1" +libredox = { version = "0.1.18-rb1", default-features = false, features = ["redox_syscall", "call"] } diff --git a/local/sources/redox-scheme/LICENSE b/local/sources/redox-scheme/LICENSE new file mode 100644 index 0000000000..fec648fd0f --- /dev/null +++ b/local/sources/redox-scheme/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 4lDO2 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/local/sources/redox-scheme/src/advisory_flock.rs b/local/sources/redox-scheme/src/advisory_flock.rs new file mode 100644 index 0000000000..399763307b --- /dev/null +++ b/local/sources/redox-scheme/src/advisory_flock.rs @@ -0,0 +1,415 @@ +// Missing: F_SETLKW, F_OFD_SETLKW, and deadlock-detection + +use std::collections::LinkedList; + +const OFFSET_MAX: u64 = i64::MAX as u64; + +fn span_end(start: u64, len: u64) -> Result { + if len == 0 { + Ok(OFFSET_MAX) + } else { + if len - 1 > OFFSET_MAX - start { + return Err(LockError::Overflow); + } + Ok(start + (len - 1)) + } +} + +#[derive(Debug)] +pub struct LockSet { + list: LinkedList, +} + +impl LockSet { + pub fn new() -> Self { + Self { + list: LinkedList::new(), + } + } + + /// # Note + /// + /// According to POSIX Issue 8: + /// * Request for a *shared* lock shall fail if the file descriptor is not + /// open for *reading*. + /// + /// * Request for an *exclusive* lock shall fail if the file descriptor is + /// not open for *writing*. + /// + /// The caller **must** ensure that the above conditions are met before + /// calling this method. + pub fn lock( + &mut self, + kind: LockKind, + owner: LockOwner, + start: u64, + len: u64, + ) -> Result<(), LockError> { + let new = Lock::new(kind, owner, start, span_end(start, len)?); + if let Some(_conflict) = self.list.iter().find(|lck| lck.conflicts_with(&new)) { + return Err(LockError::Conflict); + } + + // Locks are grouped by owner. Find the first lock in the lock set with + // the same owner as the new lock. + let mut cursor = self.list.cursor_front_mut(); + while let Some(curr) = cursor.current() + && curr.owner != new.owner + { + cursor.move_next(); + } + + if cursor.current().is_none() { + // No lock with the same owner, insert at the end. + cursor.insert_before(new); + return Ok(()); + } + + while let Some(curr) = cursor.current() + && curr.owner == new.owner + { + if new.start > curr.end + 1 { + // The `new` lock comes after `curr` lock. + cursor.move_next(); + continue; + } else if new.end + 1 < curr.start { + // The `new` lock comes before `curr` lock. Break and insert it + // *before* `curr` lock. + break; + } + + // Now `new` and `curr` overlap or adjacent + if curr.kind == new.kind { + // Merge + curr.start = new.start.min(curr.start); + curr.end = new.end.max(curr.end); + return Ok(()); + } else { + if new.start <= curr.start && new.end >= curr.end { + *curr = new; + return Ok(()); + } else if start > curr.start && new.end < curr.end { + let right = Lock::new(curr.kind, curr.owner, new.end + 1, curr.end); + curr.end = new.start - 1; + cursor.insert_after(new); + cursor.move_next(); + cursor.insert_after(right); + return Ok(()); + } else if start > curr.start { + curr.end = start - 1; + cursor.insert_after(new); + return Ok(()); + } else if new.end < curr.end { + curr.start = new.end + 1; + break; + } + } + } + + cursor.insert_before(new); + Ok(()) + } + + // TODO: take out common logic from lock and unlock + pub fn unlock(&mut self, owner: LockOwner, start: u64, len: u64) -> Result<(), LockError> { + let end = span_end(start, len)?; + let mut cursor = self.list.cursor_front_mut(); + while let Some(curr) = cursor.current() + && curr.owner != owner + { + cursor.move_next(); + } + + if cursor.current().is_none() { + return Err(LockError::NotPresent); + } + + let mut success = false; + while let Some(curr) = cursor.current() + && curr.owner == owner + { + if start > curr.end + 1 { + // The `new` lock comes after `curr` lock. + cursor.move_next(); + continue; + } else if end + 1 < curr.start { + // The `new` lock comes before `curr` lock. Break and insert it + // *before* `curr` lock. + break; + } + + success = true; + if start <= curr.start && end >= curr.end { + // Unlock range covers the entire lock. Cursor is moved to point + // to the next element by `remove_cursor` + cursor.remove_current(); + continue; + } else if start > curr.start && end < curr.end { + // Unlock range is strictly inside the lock. + let right = Lock::new(curr.kind, curr.owner, end, curr.end); + curr.end = start - 1; + cursor.insert_after(right); + return Ok(()); + } else if start > curr.start { + // Unlock covers the tail of this lock. Shrink from right. + curr.end = start - 1; + } else if end < curr.end { + // Unlock covers the head of this lock. Shrink from left. + curr.start = end + 1; + } + + cursor.move_next(); + } + + if success { + Ok(()) + } else { + Err(LockError::NotPresent) + } + } + + pub fn get_lock( + &mut self, + kind: LockKind, + owner: LockOwner, + start: u64, + len: u64, + ) -> Option<&Lock> { + let query = Lock::new(kind, owner, start, start + (len - 1)); + self.list.iter().find(|lck| lck.overlaps_with(&query)) + } + + /// `info` should be `None` for OFD-owned locks. + pub fn on_close(&mut self, owner: LockOwner) -> Result<(), LockError> { + let mut cursor = self.list.cursor_front_mut(); + while let Some(curr) = cursor.current() { + // Closing any file descriptor drops all POSIX locks that the process holds on that + // file, even if the lock was originally acquired using a different file + // descriptor. + let should_remove = owner == curr.owner; + + if should_remove { + cursor.remove_current(); + return Ok(()); + } else { + cursor.move_next(); + } + } + + Err(LockError::NotPresent) + } +} + +#[derive(Debug, PartialEq)] +pub enum LockError { + Conflict, + NotPresent, + Overflow, +} + +#[derive(Debug)] +pub struct Lock { + pub kind: LockKind, + pub owner: LockOwner, + // [start, end) + pub start: u64, + pub end: u64, +} + +impl Lock { + pub fn new(kind: LockKind, owner: LockOwner, start: u64, end: u64) -> Self { + Self { + kind, + owner, + start, + end, + } + } + + #[inline] + pub fn conflicts_with(&self, other: &Lock) -> bool { + self.owner != other.owner + && self.overlaps_with(other) + // Each byte in the file can be either locked with one or more + // shared locks or with a single exclusive lock. + && (self.kind.is_exclusive() || other.kind.is_exclusive()) + } + + #[inline] + pub fn overlaps_with(&self, other: &Lock) -> bool { + self.start < other.end && self.end > other.start + } + + #[inline] + // FIXME: would this work for whole file locks with start=0 and end=0 + pub fn len(&self) -> u64 { + self.end - self.start + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum LockOwner { + Process { pid: usize, inode: usize }, + Resource { fd: usize }, +} + +impl LockOwner { + pub fn is_process(&self) -> bool { + matches!(self, Self::Process { .. }) + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum LockKind { + Shared, + Exclusive, +} + +impl LockKind { + #[inline] + pub fn is_exclusive(&self) -> bool { + matches!(self, Self::Exclusive) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn mixed_owners() { + let mut set = LockSet::new(); + assert!( + set.lock( + LockKind::Exclusive, + LockOwner::Process { pid: 1, inode: 1 }, + 0, + 2 + ) + .is_ok() + ); + // According to POSIX Issue 8, an **exclusive** process or OFD-owned + // lock prevents any **other** process or OFD-owned lock from being + // placed on any overlapping byte range. + assert!( + !set.lock(LockKind::Shared, LockOwner::Resource { fd: 1 }, 0, 2) + .is_ok() + ); + assert!( + !set.lock(LockKind::Exclusive, LockOwner::Resource { fd: 1 }, 0, 2) + .is_ok() + ); + + assert!( + !set.lock( + LockKind::Shared, + LockOwner::Process { pid: 2, inode: 2 }, + 0, + 2 + ) + .is_ok() + ); + assert!( + !set.lock( + LockKind::Exclusive, + LockOwner::Process { pid: 2, inode: 2 }, + 0, + 2 + ) + .is_ok() + ); + // Similarly, a **shared** process or OFD-owned lock prevents any + // **other exclusive** process or OFD-owned lock from being placed on + // any overlapping byte range. + assert!( + set.lock( + LockKind::Shared, + LockOwner::Process { pid: 1, inode: 1 }, + 2, + 2 + ) + .is_ok() + ); + assert!( + !set.lock( + LockKind::Exclusive, + LockOwner::Process { pid: 2, inode: 2 }, + 2, + 2 + ) + .is_ok() + ); + assert!( + !set.lock(LockKind::Exclusive, LockOwner::Resource { fd: 1 }, 2, 2) + .is_ok() + ); + // A **shared** process or OFD-owned lock does **not** prevent any + // **other shared** process or OFD-owned lock from being placed on any + // overlapping byte range. + assert!( + set.lock( + LockKind::Shared, + LockOwner::Process { pid: 2, inode: 2 }, + 2, + 2 + ) + .is_ok() + ); + assert!( + set.lock(LockKind::Shared, LockOwner::Resource { fd: 1 }, 2, 2) + .is_ok() + ); + } + + #[test] + fn same_owner() { + let mut set = LockSet::new(); + assert!( + set.lock( + LockKind::Exclusive, + LockOwner::Process { pid: 1, inode: 1 }, + 0, + 8 + ) + .is_ok() + ); + // Requests from the **same** owner as the existing lock are allowed. + assert!( + set.lock( + LockKind::Shared, + LockOwner::Process { pid: 1, inode: 1 }, + 4, + 4 + ) + .is_ok() + ); + assert!( + set.unlock(LockOwner::Process { pid: 1, inode: 1 }, 4, 8) + .is_ok() + ); + assert!( + set.unlock(LockOwner::Process { pid: 1, inode: 1 }, 0, 8) + .is_ok() + ); + } + + #[test] + fn test_on_close() { + let mut set = LockSet::new(); + assert!( + set.lock( + LockKind::Exclusive, + LockOwner::Process { pid: 1, inode: 1 }, + 0, + 10 + ) + .is_ok() + ); + assert!( + set.on_close(LockOwner::Process { pid: 1, inode: 1 }) + .is_ok() + ); + assert!( + set.on_close(LockOwner::Process { pid: 1, inode: 1 }) == Err(LockError::NotPresent) + ); + } +} diff --git a/local/sources/redox-scheme/src/lib.rs b/local/sources/redox-scheme/src/lib.rs new file mode 100644 index 0000000000..7952b0f53d --- /dev/null +++ b/local/sources/redox-scheme/src/lib.rs @@ -0,0 +1,513 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![feature(linked_list_cursors)] // advisory_flock.rs + +extern crate alloc; +use alloc::collections::vec_deque::VecDeque; +use alloc::vec::Vec; + +use core::mem; +use core::task::Poll; + +use libredox::flag; +use syscall::error::{EINTR, EWOULDBLOCK, Error, Result}; +use syscall::flag::{ + CallFlags, FmoveFdFlags, FobtainFdFlags, RecvFdFlags, SchemeSocketCall, SendFdFlags, +}; +use syscall::schemev2::{Cqe, CqeOpcode, NewFdFlags, Opcode, Sqe}; + +#[cfg(feature = "std")] +pub(crate) mod advisory_flock; +pub mod scheme; + +#[cfg(feature = "std")] +pub mod wrappers; + +pub struct CallerCtx { + pub pid: usize, + pub uid: u32, + pub gid: u32, + pub id: Id, +} + +#[derive(Debug)] +pub enum OpenResult { + ThisScheme { number: usize, flags: NewFdFlags }, + OtherScheme { fd: usize }, + OtherSchemeMultiple { num_fds: usize }, + WouldBlock, +} + +use core::mem::{MaybeUninit, size_of}; + +use self::scheme::IntoTag; + +#[repr(transparent)] +#[derive(Debug, Default)] +pub struct Request { + sqe: Sqe, +} + +#[derive(Clone, Copy, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] +pub struct Id(u32); + +#[derive(Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] +pub struct Tag(Id); + +impl Tag { + pub fn id(&self) -> Id { + self.0 + } +} + +#[derive(Debug)] +pub struct CancellationRequest { + pub id: Id, +} + +#[repr(transparent)] +#[derive(Debug)] +pub struct CallRequest { + inner: Request, +} + +#[repr(transparent)] +#[derive(Debug)] +pub struct SendFdRequest { + inner: Request, +} + +#[repr(transparent)] +#[derive(Debug)] +pub struct RecvFdRequest { + inner: Request, +} + +pub enum RequestKind { + Call(CallRequest), + Cancellation(CancellationRequest), + SendFd(SendFdRequest), + RecvFd(RecvFdRequest), + MsyncMsg, + MunmapMsg, + MmapMsg, + OnClose { id: usize }, + OnDetach { id: usize, pid: usize }, +} + +impl CallRequest { + #[inline] + pub fn request(&self) -> &Request { + &self.inner + } + #[inline] + pub fn request_id(&self) -> Id { + Id(self.inner.sqe.tag) + } +} + +impl SendFdRequest { + #[inline] + pub fn request(&self) -> &Request { + &self.inner + } + #[inline] + pub fn request_id(&self) -> Id { + Id(self.inner.sqe.tag) + } + + pub fn id(&self) -> usize { + self.inner.sqe.args[0] as usize + } + + pub fn flags(&self) -> SendFdFlags { + SendFdFlags::from_bits_retain(self.inner.sqe.args[1] as usize) + } + pub fn arg(&self) -> u64 { + self.inner.sqe.args[2] + } + + pub fn metadata(&self) -> &[u64] { + &self.inner.sqe.args[2..3] + } + + pub fn num_fds(&self) -> usize { + self.inner.sqe.args[3] as usize + } + + pub fn obtain_fd( + &self, + socket: &Socket, + flags: FobtainFdFlags, + dst_fds: &mut [usize], + ) -> Result<()> { + assert!(!flags.contains(FobtainFdFlags::MANUAL_FD)); + + let request_id = self.request_id().0; + let metadata: [u64; 2] = [SchemeSocketCall::ObtainFd as u64, request_id as u64]; + + let mut call_flags = CallFlags::FD; + if flags.contains(FobtainFdFlags::EXCLUSIVE) { + call_flags |= CallFlags::FD_EXCLUSIVE; + } + if flags.contains(FobtainFdFlags::UPPER_TBL) { + call_flags |= CallFlags::FD_UPPER; + } + + let dst_fds_bytes: &mut [u8] = unsafe { + core::slice::from_raw_parts_mut( + dst_fds.as_mut_ptr() as *mut u8, + dst_fds.len() * mem::size_of::(), + ) + }; + + socket.inner.call_ro(dst_fds_bytes, call_flags, &metadata)?; + + Ok(()) + } +} + +impl RecvFdRequest { + #[inline] + pub fn request(&self) -> &Request { + &self.inner + } + #[inline] + pub fn request_id(&self) -> Id { + Id(self.inner.sqe.tag) + } + + pub fn id(&self) -> usize { + self.inner.sqe.args[0] as usize + } + + pub fn flags(&self) -> RecvFdFlags { + RecvFdFlags::from_bits_retain(self.inner.sqe.args[1] as usize) + } + pub fn num_fds(&self) -> usize { + self.inner.sqe.args[2] as usize + } + + pub fn metadata(&self) -> &[u64] { + &self.inner.sqe.args[3..5] + } + + pub fn move_fd(&self, socket: &Socket, flags: FmoveFdFlags, fds: &[usize]) -> Result<()> { + let metadata: [u64; 2] = [SchemeSocketCall::MoveFd as u64, self.request_id().0 as u64]; + + let fds_bytes: &[u8] = unsafe { + core::slice::from_raw_parts( + fds.as_ptr() as *mut u8, + fds.len() * mem::size_of::(), + ) + }; + + let mut call_flags = CallFlags::FD; + if flags.contains(FmoveFdFlags::EXCLUSIVE) { + call_flags |= CallFlags::FD_EXCLUSIVE; + } + if flags.contains(FmoveFdFlags::CLONE) { + call_flags |= CallFlags::FD_CLONE; + } + + socket.inner.call_wo(fds_bytes, call_flags, &metadata)?; + + Ok(()) + } +} + +impl Request { + #[inline] + pub fn context_id(&self) -> usize { + self.sqe.caller as usize + } + pub fn kind(self) -> RequestKind { + match Opcode::try_from_raw(self.sqe.opcode) { + Some(Opcode::Cancel) => RequestKind::Cancellation(CancellationRequest { + id: Id(self.sqe.tag), + }), + Some(Opcode::Sendfd) => RequestKind::SendFd(SendFdRequest { + inner: Request { sqe: self.sqe }, + }), + Some(Opcode::Recvfd) => RequestKind::RecvFd(RecvFdRequest { + inner: Request { sqe: self.sqe }, + }), + Some(Opcode::Msync) => RequestKind::MsyncMsg, + //Some(Opcode::Munmap) => RequestKind::MunmapMsg, + Some(Opcode::RequestMmap) => RequestKind::MmapMsg, + Some(Opcode::CloseMsg) => RequestKind::OnClose { + id: self.sqe.args[0] as usize, + // TODO: andypython: not currently set by the kernel + // pid: self.sqe.caller as usize, + }, + + Some(Opcode::Detach) => RequestKind::OnDetach { + id: self.sqe.args[0] as usize, + pid: self.sqe.caller as usize, + }, + + _ => RequestKind::Call(CallRequest { + inner: Request { sqe: self.sqe }, + }), + } + } +} + +pub struct Socket { + inner: libredox::Fd, +} + +impl Socket { + pub fn create_inner(cap_fd: usize, nonblock: bool) -> Result { + let mut flags = flag::O_FSYNC | 0x0020_0000 /* O_EXLOCK */; + + if nonblock { + flags |= flag::O_NONBLOCK; + } + + let fd = libredox::call::dup(cap_fd, b"create-scheme")?; + libredox::call::fcntl(fd, syscall::F_SETFD, flag::O_CLOEXEC as usize)?; + + libredox::call::fcntl(fd, syscall::F_SETFL, flags as usize)?; + Ok(Self { + inner: libredox::Fd::new(fd), + }) + } + pub fn create() -> Result { + let cap_fd = + libredox::Fd::open("/scheme/namespace/scheme-creation-cap", flag::O_CLOEXEC, 0)?; + Self::create_inner(cap_fd.raw(), false) + } + pub fn nonblock() -> Result { + let cap_fd = + libredox::Fd::open("/scheme/namespace/scheme-creation-cap", flag::O_CLOEXEC, 0)?; + Self::create_inner(cap_fd.raw(), true) + } + pub unsafe fn from_fd(fd: libredox::Fd) -> Self { + Self { inner: fd } + } + // TODO: trait RequestBuf? + pub fn read_requests(&self, buf: &mut Vec, behavior: SignalBehavior) -> Result<()> { + let num_read = read_requests(self.inner.raw(), buf.spare_capacity_mut(), behavior)?; + unsafe { + buf.set_len(buf.len() + num_read); + } + Ok(()) + } + pub fn next_request(&self, behavior: SignalBehavior) -> Result> { + let mut buf = MaybeUninit::uninit(); + Ok( + if read_requests(self.inner.raw(), core::slice::from_mut(&mut buf), behavior)? > 0 { + Some(unsafe { buf.assume_init() }) + } else { + None + }, + ) + } + // TODO: trait ResponseBuf? + pub fn write_responses( + &self, + buf: &mut VecDeque, + behavior: SignalBehavior, + ) -> Result<()> { + let (slice, _) = buf.as_slices(); + + // NOTE: error only allowed to occur if nothing was written + let n = unsafe { write_responses(self.inner.raw(), slice, behavior)? }; + assert!(buf.len() >= n); + + for resp in buf.iter().take(n) { + if resp.0.flags == CqeOpcode::RespondWithFd as u8 { + let fd = resp.0.result as usize; + let _ = libredox::call::close(fd); + } + } + + buf.drain(..n).for_each(core::mem::forget); + + Ok(()) + } + pub fn write_response(&self, resp: Response, behavior: SignalBehavior) -> Result { + let success = unsafe { write_responses(self.inner.raw(), &[resp], behavior)? } > 0; + if success && resp.0.flags == CqeOpcode::RespondWithFd as u8 { + let fd = resp.0.result as usize; + let _ = libredox::call::close(fd); + } + Ok(success) + } + pub fn inner(&self) -> &libredox::Fd { + &self.inner + } + pub fn into_inner(self) -> libredox::Fd { + self.inner + } + pub fn create_this_scheme_fd( + &self, + offset: u64, + number: usize, + flags: usize, + internal_flags: u8, + ) -> Result { + let new_fd_params = syscall::data::NewFdParams { + offset, + number, + flags, + internal_flags, + }; + let buf: &[u8] = unsafe { + core::slice::from_raw_parts( + &new_fd_params as *const _ as *const u8, + core::mem::size_of::(), + ) + }; + Ok(self.inner().dup(buf)?) + } +} + +#[repr(transparent)] +#[derive(Clone, Copy, Default)] +pub struct Response(Cqe); + +impl Response { + #[inline] + pub fn err(err: i32, req: impl IntoTag) -> Self { + Self::new(Err(Error::new(err)), req) + } + #[inline] + pub fn ok(status: usize, req: impl IntoTag) -> Self { + Self::new(Ok(status), req) + } + #[inline] + pub fn ready_ok(status: usize, req: impl IntoTag) -> Poll { + Poll::Ready(Self::ok(status, req)) + } + #[inline] + pub fn ready_err(err: i32, req: impl IntoTag) -> Poll { + Poll::Ready(Self::err(err, req)) + } + + pub fn new(status: Result, req: impl IntoTag) -> Self { + Self(Cqe { + flags: CqeOpcode::RespondRegular as u8, + extra_raw: [0_u8; 3], + result: Error::mux(status) as u64, + tag: req.into_tag().0.0, + }) + } + + pub fn new_notify_on_detach(status: Result, req: impl IntoTag) -> Self { + Self(Cqe { + flags: CqeOpcode::RespondAndNotifyOnDetach as u8, + extra_raw: [0_u8; 3], + result: Error::mux(status) as u64, + tag: req.into_tag().0.0, + }) + } + + pub fn open_dup_like(res: Result, req: impl IntoTag) -> Response { + match res { + Ok(OpenResult::ThisScheme { number, flags }) => { + Response::new(Ok(number), req).with_extra([flags.bits(), 0, 0]) + } + Err(e) => Response::new(Err(e), req), + Ok(OpenResult::OtherScheme { fd }) => Response::return_external_fd(fd, req), + Ok(OpenResult::OtherSchemeMultiple { num_fds }) => { + Response::return_external_multiple_fds(num_fds, req) + } + Ok(OpenResult::WouldBlock) => Response::new(Err(Error::new(EWOULDBLOCK)), req), + } + } + pub fn return_external_fd(fd: usize, req: impl IntoTag) -> Self { + Self(Cqe { + flags: CqeOpcode::RespondWithFd as u8, + extra_raw: [0_u8; 3], + result: fd as u64, + tag: req.into_tag().0.0, + }) + } + pub fn return_external_multiple_fds(num_fds: usize, req: impl IntoTag) -> Self { + Self(Cqe { + flags: CqeOpcode::RespondWithMultipleFds as u8, + extra_raw: [0_u8; 3], + result: num_fds as u64, + tag: req.into_tag().0.0, + }) + } + pub fn with_extra(self, extra: [u8; 3]) -> Self { + Self(Cqe { + extra_raw: extra, + ..self.0 + }) + } + pub fn post_fevent(id: usize, flags: usize) -> Self { + Self(Cqe { + flags: CqeOpcode::SendFevent as u8, + extra_raw: [0_u8; 3], + tag: flags as u32, + result: id as u64, + }) + } +} + +pub enum SignalBehavior { + Interrupt, + Restart, +} + +/// Read requests into a possibly uninitialized buffer. +#[inline] +pub fn read_requests( + socket: usize, + buf: &mut [MaybeUninit], + behavior: SignalBehavior, +) -> Result { + let len = buf.len().checked_mul(size_of::()).unwrap(); + + let bytes_read = loop { + match libredox::call::read(socket, unsafe { + core::slice::from_raw_parts_mut(buf.as_mut_ptr().cast(), len) + }) { + Ok(n) => break n, + Err(error) if error.errno() == EINTR => match behavior { + SignalBehavior::Restart => continue, + SignalBehavior::Interrupt => return Err(error.into()), + }, + Err(err) => return Err(err.into()), + } + }; + + debug_assert_eq!(bytes_read % size_of::(), 0); + + Ok(bytes_read / size_of::()) +} + +// Write responses to a raw socket +// +// SAFETY +// +// Every Response can only be written once, otherwise double frees can occur. +#[inline] +pub unsafe fn write_responses( + socket: usize, + buf: &[Response], + behavior: SignalBehavior, +) -> Result { + let bytes = unsafe { + core::slice::from_raw_parts( + buf.as_ptr().cast(), + buf.len().checked_mul(size_of::()).unwrap(), + ) + }; + + let bytes_written = loop { + match libredox::call::write(socket, bytes) { + Ok(n) => break n, + Err(error) if error.errno() == EINTR => match behavior { + SignalBehavior::Restart => continue, + SignalBehavior::Interrupt => return Err(error.into()), + }, + Err(err) => return Err(err.into()), + } + }; + debug_assert_eq!(bytes_written % size_of::(), 0); + Ok(bytes_written / size_of::()) +} diff --git a/local/sources/redox-scheme/src/scheme.rs b/local/sources/redox-scheme/src/scheme.rs new file mode 100644 index 0000000000..2fe793541d --- /dev/null +++ b/local/sources/redox-scheme/src/scheme.rs @@ -0,0 +1,1728 @@ +#![allow(async_fn_in_trait)] + +use core::fmt::{self, Debug}; +use core::mem::size_of; +use syscall::dirent::DirentBuf; +use syscall::schemev2::{Opcode, Sqe, SqeFlags}; +use syscall::{Stat, StatVfs, StdFsCallMeta, TimeSpec, error::*, flag::*}; + +use crate::{ + CallRequest, CallerCtx, Id, OpenResult, RecvFdRequest, Response, SendFdRequest, Socket, Tag, +}; + +pub struct OpPathLike { + req: Tag, + path: *const str, // &req + pub flags: Flags, +} +impl OpPathLike { + pub fn path(&self) -> &str { + // SAFETY: borrowed from self.req + unsafe { &*self.path } + } +} +impl Debug for OpPathLike { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OpPathLike") + .field("path", &self.path()) + .field("flags", &self.flags) + .finish() + } +} + +pub struct OpFdPathLike { + pub fd: usize, + pub fcntl_flags: u32, + inner: OpPathLike, +} + +impl OpFdPathLike { + pub fn path(&self) -> &str { + self.inner.path() + } + pub fn flags(&self) -> &F { + &self.inner.flags + } +} + +impl Debug for OpFdPathLike { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OpFdPathLike") + .field("fd", &self.fd) + .field("path", &self.path()) + .field("flags", &self.inner.flags) + .field("fcntl_flags", &self.fcntl_flags) + .finish() + } +} + +pub struct OpCall { + req: Tag, + fds: [usize; 2], + num_fds: usize, + payload: *mut [u8], // &req + metadata: [u64; 3], +} +impl OpCall { + pub fn fd(&self) -> usize { + *self.fds().first().expect("There is no target id") + } + pub fn fds(&self) -> &[usize] { + &self.fds[..self.num_fds] + } + pub fn fds_and_payload_and_metadata(&mut self) -> (&[usize], &mut [u8], &[u64]) { + unsafe { + ( + &self.fds[..self.num_fds], + &mut *self.payload, + &self.metadata, + ) + } + } + pub fn payload_and_metadata(&mut self) -> (&mut [u8], &[u64]) { + // SAFETY: borrows &self.req + unsafe { (&mut *self.payload, &self.metadata) } + } + pub fn payload(&mut self) -> &mut [u8] { + self.payload_and_metadata().0 + } + pub fn metadata(&self) -> &[u64] { + &self.metadata + } +} +impl Debug for OpCall { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OpCall") + .field("fd", &self.fds()) + // TODO: debug first and last few bytes, collapse middle to ... + .field("payload", &self.payload) + .field("metadata", &self.metadata()) + .finish() + } +} +#[derive(Debug)] +pub struct OpQueryRead { + pub fd: usize, + req: Tag, + buf: *mut T, +} +impl OpQueryRead { + pub fn buf(&mut self) -> &mut T { + // SAFETY: borrows &mut self.req + unsafe { &mut *self.buf } + } +} +#[derive(Debug)] +pub struct OpQueryWrite { + pub fd: usize, + req: Tag, + buf: *const T, +} +impl OpQueryWrite { + pub fn buf(&self) -> &T { + // SAFETY: borrows &self.req + unsafe { &*self.buf } + } +} +#[derive(Debug)] +pub struct OpGetdents { + req: Tag, + pub fd: usize, + buf: *mut [u8], + pub header_size: u16, + pub opaque_offset: u64, +} +impl OpGetdents { + pub fn raw_buf(&mut self) -> &mut [u8] { + // SAFETY: borrows + unsafe { &mut *self.buf } + } + pub fn buf(&mut self) -> Option> { + let sz = self.header_size; + DirentBuf::new(self.raw_buf(), sz) + } +} +#[derive(Debug)] +pub struct OpRead { + req: Tag, + pub fd: usize, + pub offset: u64, + pub flags: u32, + buf: *mut [u8], +} +impl OpRead { + pub fn buf(&mut self) -> &mut [u8] { + // SAFETY: Borrows &mut self.req + unsafe { &mut *self.buf } + } +} +#[derive(Debug)] +pub struct OpWrite { + req: Tag, + pub fd: usize, + pub offset: u64, + pub flags: u32, + buf: *const [u8], +} +impl OpWrite { + pub fn buf(&self) -> &[u8] { + // SAFETY: Borrows &self.req + unsafe { &*self.buf } + } +} + +#[non_exhaustive] +#[derive(Debug)] +pub enum Op { + OpenAt(OpFdPathLike), + UnlinkAt(OpFdPathLike), + Dup(OpQueryWrite<[u8]>), + Read(OpRead), + Write(OpWrite), + Fsize { + req: Tag, + fd: usize, + }, + Fchmod { + req: Tag, + fd: usize, + new_mode: u16, + }, + Fchown { + req: Tag, + fd: usize, + new_uid: u32, + new_gid: u32, + }, + Fcntl { + req: Tag, + fd: usize, + cmd: usize, + arg: usize, + }, + Fevent { + req: Tag, + fd: usize, + req_flags: EventFlags, + }, + Flink(OpQueryWrite), + Fpath(OpQueryRead<[u8]>), + Frename(OpQueryWrite), + Fstat(OpQueryRead), + FstatVfs(OpQueryRead), + Fsync { + req: Tag, + fd: usize, + }, + Ftruncate { + req: Tag, + fd: usize, + new_sz: u64, + }, + Futimens(OpQueryWrite<[TimeSpec]>), + + MmapPrep { + req: Tag, + fd: usize, + offset: u64, + len: usize, + flags: MapFlags, + }, + Munmap { + req: Tag, + fd: usize, + offset: u64, + len: usize, + flags: MunmapFlags, + }, + + Call(OpCall), + StdFsCall(OpCall), + + Getdents(OpGetdents), + + Recvfd(RecvFdRequest), +} + +impl Op { + /// Decode the raw SQE into an Op with borrowed buffers passed as slices. + /// + /// # Safety + /// + /// Any borrowed buffers will be unmapped whenever a response is sent, which unlike the + /// move-based CallRequest API, needs to be managed manually by the caller. + pub unsafe fn from_sqe_unchecked(sqe: &Sqe) -> Option { + let req = Tag(Id(sqe.tag)); + let opcode = Opcode::try_from_raw(sqe.opcode)?; + let args = sqe.args; + + let [a, b, c, d, e, f] = args.map(|a| a as usize); + use core::{slice, str}; + + Some(match opcode { + Opcode::OpenAt => Op::OpenAt(OpFdPathLike { + fd: a, + fcntl_flags: e as u32, + inner: OpPathLike { + req, + path: unsafe { + str::from_utf8_unchecked(slice::from_raw_parts(b as *const u8, c)) + }, + flags: d, + }, + }), + Opcode::UnlinkAt => Op::UnlinkAt(OpFdPathLike { + fd: a, + fcntl_flags: 0, + inner: OpPathLike { + req, + path: unsafe { + str::from_utf8_unchecked(slice::from_raw_parts(b as *const u8, c)) + }, + flags: d, + }, + }), + Opcode::Dup => Op::Dup(OpQueryWrite { + req, + fd: a, + buf: unsafe { slice::from_raw_parts(b as *const u8, c) }, + }), + Opcode::Read => Op::Read(OpRead { + req, + fd: a, + buf: unsafe { slice::from_raw_parts_mut(b as *mut u8, c) }, + offset: args[3], + flags: args[4] as u32, + }), + Opcode::Write => Op::Write(OpWrite { + req, + fd: a, + buf: unsafe { slice::from_raw_parts(b as *const u8, c) }, + offset: args[3], + flags: args[4] as u32, + }), + + // TODO: 64-bit offset on 32-bit platforms + Opcode::Fsize => Op::Fsize { req, fd: a }, + Opcode::Fchmod => Op::Fchmod { + req, + fd: a, + new_mode: b as u16, + }, + Opcode::Fchown => Op::Fchown { + req, + fd: a, + new_uid: b as u32, + new_gid: c as u32, + }, + Opcode::Fcntl => Op::Fcntl { + req, + fd: a, + cmd: b, + arg: c, + }, + Opcode::Fevent => Op::Fevent { + req, + fd: a, + req_flags: EventFlags::from_bits_retain(b), + }, + Opcode::Flink => Op::Flink(OpQueryWrite { + req, + fd: a, + buf: unsafe { str::from_utf8_unchecked(slice::from_raw_parts(b as *const u8, c)) }, + }), + Opcode::Fpath => Op::Fpath(OpQueryRead { + req, + fd: a, + buf: unsafe { slice::from_raw_parts_mut(b as *mut u8, c) }, + }), + Opcode::Frename => Op::Frename(OpQueryWrite { + req, + fd: a, + buf: unsafe { str::from_utf8_unchecked(slice::from_raw_parts(b as *const u8, c)) }, + }), + Opcode::Fstat => { + assert!(c >= size_of::()); + Op::Fstat(OpQueryRead { + req, + fd: a, + buf: unsafe { &mut *(b as *mut Stat) }, + }) + } + Opcode::Fstatvfs => { + assert!(c >= size_of::()); + Op::FstatVfs(OpQueryRead { + req, + fd: a, + buf: unsafe { &mut *(b as *mut StatVfs) }, + }) + } + Opcode::Fsync => Op::Fsync { req, fd: a }, + Opcode::Ftruncate => Op::Ftruncate { + req, + fd: a, + new_sz: args[1], + }, + Opcode::Futimens => { + assert!(c <= 2 * size_of::()); + Op::Futimens(OpQueryWrite { + req, + fd: a, + buf: unsafe { + slice::from_raw_parts(b as *const TimeSpec, c / size_of::()) + }, + }) + } + + Opcode::Call => { + if sqe.sqe_flags.contains(SqeFlags::MULTIPLE_IDS) { + Op::Call(OpCall { + req, + fds: [a, f], + num_fds: 2, + payload: unsafe { slice::from_raw_parts_mut(b as *mut u8, c) }, + metadata: [sqe.args[3], sqe.args[4], 0], + }) + } else { + Op::Call(OpCall { + req, + fds: [a, 0], + num_fds: 1, + payload: unsafe { slice::from_raw_parts_mut(b as *mut u8, c) }, + metadata: [sqe.args[3], sqe.args[4], sqe.args[5]], + }) + } + } + Opcode::StdFsCall => { + if sqe.sqe_flags.contains(SqeFlags::MULTIPLE_IDS) { + Op::StdFsCall(OpCall { + req, + fds: [a, f], + num_fds: 2, + payload: unsafe { slice::from_raw_parts_mut(b as *mut u8, c) }, + metadata: [sqe.args[3], sqe.args[4], 0], + }) + } else { + Op::StdFsCall(OpCall { + req, + fds: [a, 0], + num_fds: 1, + payload: unsafe { slice::from_raw_parts_mut(b as *mut u8, c) }, + metadata: [sqe.args[3], sqe.args[4], sqe.args[5]], + }) + } + } + Opcode::Getdents => Op::Getdents(OpGetdents { + req, + fd: a, + buf: unsafe { slice::from_raw_parts_mut(b as *mut u8, c) }, + header_size: sqe.args[3] as u16, + opaque_offset: sqe.args[4], + }), + + Opcode::MmapPrep => Op::MmapPrep { + req, + fd: a, + offset: args[3], + len: b, + flags: MapFlags::from_bits_retain(c), + }, + Opcode::Munmap => Op::Munmap { + req, + fd: a, + offset: args[3], + len: b, + flags: MunmapFlags::from_bits_retain(c), + }, + + _ => return None, + }) + } + pub fn is_explicitly_nonblock(&self) -> bool { + let flags = match self { + Self::Read(r) => r.flags, + Self::Write(w) => w.flags, + Self::OpenAt(o) => o.fcntl_flags, + _ => 0, + }; + flags as usize & O_NONBLOCK != 0 + } + pub fn file_id(&self) -> Option { + Some(match self { + Op::UnlinkAt(op) => op.fd, + Op::OpenAt(op) => op.fd, + Op::Dup(op) => op.fd, + Op::Read(op) => op.fd, + Op::Write(op) => op.fd, + Op::Fsize { fd, .. } + | Op::Fchmod { fd, .. } + | Op::Fchown { fd, .. } + | Op::Fcntl { fd, .. } + | Op::Fevent { fd, .. } + | Op::Fsync { fd, .. } + | Op::Ftruncate { fd, .. } + | Op::MmapPrep { fd, .. } + | Op::Munmap { fd, .. } => *fd, + Op::Flink(op) => op.fd, + Op::Fpath(op) => op.fd, + Op::Frename(op) => op.fd, + Op::Fstat(op) => op.fd, + Op::FstatVfs(op) => op.fd, + Op::Futimens(op) => op.fd, + Op::Call(op) => op.fd(), + Op::StdFsCall(op) => op.fd(), + Op::Getdents(op) => op.fd, + Op::Recvfd(req) => req.id(), + }) + } +} +impl CallRequest { + pub fn caller(&self) -> CallerCtx { + let sqe = &self.inner.sqe; + + CallerCtx { + pid: sqe.caller as usize, + uid: sqe.args[5] as u32, + gid: (sqe.args[5] >> 32) as u32, + id: Id(sqe.tag), + } + } + pub fn op(self) -> Result { + match unsafe { Op::from_sqe_unchecked(&self.inner.sqe) } { + Some(op) => Ok(op), + None => Err(self), + } + } + pub async fn handle_async(self, s: &mut impl SchemeAsync, state: &mut SchemeState) -> Response { + let caller = self.caller(); + + let op = match self.op() { + Ok(op) => op, + Err(this) => return Response::new(Err(Error::new(ENOSYS)), this), + }; + + op.handle_async(caller, s, state).await + } + pub fn handle_sync(self, s: &mut impl SchemeSync, state: &mut SchemeState) -> Response { + let caller = self.caller(); + + let op = match self.op() { + Ok(op) => op, + Err(this) => return Response::new(Err(Error::new(ENOSYS)), this), + }; + op.handle_sync(caller, s, state) + } +} + +impl SendFdRequest { + pub fn caller(&self) -> CallerCtx { + let sqe = &self.inner.sqe; + + CallerCtx { + pid: sqe.caller as usize, + uid: sqe.args[5] as u32, + gid: (sqe.args[5] >> 32) as u32, + id: self.request_id(), + } + } +} + +impl RecvFdRequest { + pub fn op(self) -> Op { + Op::Recvfd(self) + } + pub fn caller(&self) -> CallerCtx { + let sqe = &self.inner.sqe; + + CallerCtx { + pid: sqe.caller as usize, + uid: sqe.args[5] as u32, + gid: (sqe.args[5] >> 32) as u32, + id: self.request_id(), + } + } +} + +#[derive(Debug)] +pub enum SchemeResponse { + Regular(Result), + RegularAndNotifyOnDetach(Result), + Opened(Result), +} + +impl From> for SchemeResponse { + fn from(value: Result) -> Self { + Self::Regular(value) + } +} +impl Op { + pub fn handle_sync( + mut self, + caller: CallerCtx, + s: &mut impl SchemeSync, + state: &mut SchemeState, + ) -> Response { + match self.handle_sync_dont_consume(&caller, s, state) { + SchemeResponse::Opened(open) => Response::open_dup_like(open, self), + SchemeResponse::RegularAndNotifyOnDetach(status) => { + Response::new_notify_on_detach(status, self) + } + SchemeResponse::Regular(reg) => Response::new(reg, self), + } + } + pub fn handle_sync_dont_consume( + &mut self, + caller: &CallerCtx, + s: &mut impl SchemeSync, + state: &mut SchemeState, + ) -> SchemeResponse { + match *self { + Op::OpenAt(ref req) => { + let res = s.openat( + req.fd, + req.path(), + req.inner.flags, + req.fcntl_flags, + &caller, + ); + return SchemeResponse::Opened(res); + } + Op::UnlinkAt(ref req) => s + .unlinkat(req.fd, req.path(), req.inner.flags, &caller) + .map(|()| 0) + .into(), + Op::Dup(ref req) => { + let res = s.dup(req.fd, req.buf(), &caller); + return SchemeResponse::Opened(res); + } + Op::Read(ref mut req) => { + let OpRead { + fd, offset, flags, .. + } = *req; + s.read(fd, req.buf(), offset, flags, &caller).into() + } + Op::Write(ref req) => s + .write(req.fd, req.buf(), req.offset, req.flags, &caller) + .into(), + + // TODO: Don't convert to usize + Op::Fsize { fd, .. } => s.fsize(fd, &caller).map(|l| l as usize).into(), + + Op::Fchmod { fd, new_mode, .. } => s.fchmod(fd, new_mode, &caller).map(|()| 0).into(), + Op::Fchown { + fd, + new_uid, + new_gid, + .. + } => s.fchown(fd, new_uid, new_gid, &caller).map(|()| 0).into(), + Op::Fcntl { fd, cmd, arg, .. } => s.fcntl(fd, cmd, arg, &caller).into(), + Op::Fevent { fd, req_flags, .. } => { + s.fevent(fd, req_flags, &caller).map(|f| f.bits()).into() + } + Op::Flink(ref req) => s.flink(req.fd, req.buf(), &caller).into(), + Op::Fpath(ref mut req) => s.fpath(req.fd, req.buf(), &caller).into(), + Op::Frename(ref req) => s.frename(req.fd, req.buf(), &caller).into(), + Op::Fstat(ref mut req) => s.fstat(req.fd, req.buf(), &caller).map(|()| 0).into(), + Op::FstatVfs(ref mut req) => s.fstatvfs(req.fd, req.buf(), &caller).map(|()| 0).into(), + Op::Fsync { fd, .. } => s.fsync(fd, &caller).map(|()| 0).into(), + Op::Ftruncate { fd, new_sz, .. } => s.ftruncate(fd, new_sz, &caller).map(|()| 0).into(), + Op::Futimens(ref req) => s.futimens(req.fd, req.buf(), &caller).map(|()| 0).into(), + + Op::MmapPrep { + fd, + offset, + len, + flags, + .. + } => s.mmap_prep(fd, offset, len, flags, &caller).into(), + Op::Munmap { + fd, + offset, + len, + flags, + .. + } => s.munmap(fd, offset, len, flags, &caller).map(|()| 0).into(), + + Op::Call(ref mut req) => { + let (fds, payload, metadata) = req.fds_and_payload_and_metadata(); + if fds.len() >= 2 { + s.call_multiple_ids(fds, payload, metadata, &caller).into() + } else { + s.call(fds[0], payload, metadata, &caller).into() + } + } + + Op::StdFsCall(ref mut req) => { + let fd = req.fd(); + let (fds, payload, metadata) = req.fds_and_payload_and_metadata(); + let Some(kind) = StdFsCallKind::try_from_raw(metadata[0] as u8) else { + return Err(Error::new(EOPNOTSUPP)).into(); + }; + let std_fs_call_meta = StdFsCallMeta::new(kind, metadata[1], metadata[2]); + if let Some(resp) = state.std_fs_call( + fd, + kind, + s.inode(fd).ok(), + std_fs_call_meta, + payload, + &caller, + ) { + return resp; + } else { + s.translate_std_fs_call(fds, kind, payload, std_fs_call_meta, &caller) + .into() + } + } + + Op::Getdents(ref mut req) => { + let OpGetdents { + fd, opaque_offset, .. + } = *req; + let Some(buf) = req.buf() else { + return Err(Error::new(EINVAL)).into(); + }; + let buf_res = s.getdents(fd, buf, opaque_offset); + buf_res.map(|b| b.finalize()).into() + } + Op::Recvfd(ref req) => { + let res = s.on_recvfd(req); + return SchemeResponse::Opened(res); + } + } + } + // XXX: Although this has not yet been benchmarked, it likely makes sense for the + // readiness-based (or non-blockable) and completion-based APIs to diverge, as it is imperative + // that futures stay small. + pub async fn handle_async( + self, + caller: CallerCtx, + s: &mut impl SchemeAsync, + state: &mut SchemeState, + ) -> Response { + let (res, tag) = match self { + Op::OpenAt(req) => { + let res = s + .openat( + req.fd, + req.path(), + req.inner.flags, + req.fcntl_flags, + &caller, + ) + .await; + return Response::open_dup_like(res, req); + } + Op::UnlinkAt(req) => ( + s.unlinkat(req.fd, req.path(), req.inner.flags, &caller) + .await + .map(|()| 0) + .into(), + req.into_tag(), + ), + Op::Dup(req) => { + let res = s.dup(req.fd, req.buf(), &caller).await; + return Response::open_dup_like(res, req); + } + Op::Read(mut req) => { + let OpRead { + fd, offset, flags, .. + } = req; + ( + s.read(fd, req.buf(), offset, flags, &caller).await, + req.into_tag(), + ) + } + Op::Write(req) => ( + s.write(req.fd, req.buf(), req.offset, req.flags, &caller) + .await, + req.into_tag(), + ), + + // TODO: Don't convert to usize + Op::Fsize { req, fd } => (s.fsize(fd, &caller).await.map(|l| l as usize), req), + + Op::Fchmod { req, fd, new_mode } => { + (s.fchmod(fd, new_mode, &caller).await.map(|()| 0), req) + } + Op::Fchown { + req, + fd, + new_uid, + new_gid, + } => ( + s.fchown(fd, new_uid, new_gid, &caller).await.map(|()| 0), + req, + ), + Op::Fcntl { req, fd, cmd, arg } => (s.fcntl(fd, cmd, arg, &caller).await, req), + Op::Fevent { req, fd, req_flags } => ( + s.fevent(fd, req_flags, &caller).await.map(|f| f.bits()), + req, + ), + Op::Flink(req) => (s.flink(req.fd, req.buf(), &caller).await, req.into_tag()), + Op::Fpath(mut req) => (s.fpath(req.fd, req.buf(), &caller).await, req.into_tag()), + Op::Frename(req) => (s.frename(req.fd, req.buf(), &caller).await, req.into_tag()), + Op::Fstat(mut req) => ( + s.fstat(req.fd, req.buf(), &caller).await.map(|()| 0), + req.into_tag(), + ), + Op::FstatVfs(mut req) => ( + s.fstatvfs(req.fd, req.buf(), &caller).await.map(|()| 0), + req.into_tag(), + ), + Op::Fsync { req, fd } => (s.fsync(fd, &caller).await.map(|()| 0), req), + Op::Ftruncate { req, fd, new_sz } => { + (s.ftruncate(fd, new_sz, &caller).await.map(|()| 0), req) + } + Op::Futimens(req) => ( + s.futimens(req.fd, req.buf(), &caller).await.map(|()| 0), + req.into_tag(), + ), + + Op::MmapPrep { + req, + fd, + offset, + len, + flags, + } => (s.mmap_prep(fd, offset, len, flags, &caller).await, req), + Op::Munmap { + req, + fd, + offset, + len, + flags, + } => ( + s.munmap(fd, offset, len, flags, &caller).await.map(|()| 0), + req, + ), + + Op::Call(mut req) => { + let (fds, payload, metadata) = req.fds_and_payload_and_metadata(); + let res = if fds.len() >= 2 { + s.call_multiple_ids(fds, payload, metadata, &caller).await + } else { + s.call(fds[0], payload, metadata, &caller).await + }; + + (res, req.into_tag()) + } + + Op::StdFsCall(mut req) => { + let fd = req.fd(); + let (fds, payload, metadata) = req.fds_and_payload_and_metadata(); + let Some(kind) = StdFsCallKind::try_from_raw(metadata[0] as u8) else { + return Response::err(EOPNOTSUPP, req); + }; + let std_fs_call_meta = StdFsCallMeta::new(kind, metadata[1], metadata[2]); + if let Some(resp) = state.std_fs_call( + fd, + kind, + s.inode(fd).await.ok(), + std_fs_call_meta, + payload, + &caller, + ) { + match resp { + SchemeResponse::Regular(status) => return Response::new(status, req), + SchemeResponse::RegularAndNotifyOnDetach(status) => { + return Response::new_notify_on_detach(status, req); + } + _ => unreachable!(), + } + } else { + ( + s.translate_std_fs_call(fds, kind, payload, std_fs_call_meta, &caller) + .await, + req.into_tag(), + ) + } + } + + Op::Getdents(mut req) => { + let OpGetdents { + fd, opaque_offset, .. + } = req; + let Some(buf) = req.buf() else { + return Response::err(EINVAL, req); + }; + let buf_res = s.getdents(fd, buf, opaque_offset).await; + (buf_res.map(|b| b.finalize()), req.into_tag()) + } + Op::Recvfd(req) => { + let res = s.on_recvfd(&req).await; + return Response::open_dup_like(res, req); + } + }; + Response::new(res, tag) + } +} + +#[allow(unused_variables)] +pub trait SchemeAsync { + /* Scheme operations */ + fn scheme_root(&mut self) -> Result { + Err(Error::new(ENOSYS)) + } + + async fn openat( + &mut self, + fd: usize, + path: &str, + flags: usize, + fcntl_flags: u32, + ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn inode(&self, id: usize) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn unlinkat( + &mut self, + fd: usize, + path: &str, + flags: usize, + ctx: &CallerCtx, + ) -> Result<()> { + Err(Error::new(ENOENT)) + } + + /* Resource operations */ + async fn dup(&mut self, old_id: usize, buf: &[u8], ctx: &CallerCtx) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + fcntl_flags: u32, + ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EBADF)) + } + + async fn write( + &mut self, + id: usize, + buf: &[u8], + offset: u64, + fcntl_flags: u32, + ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EBADF)) + } + + async fn fsize(&mut self, id: usize, ctx: &CallerCtx) -> Result { + Err(Error::new(ESPIPE)) + } + + async fn fchmod(&mut self, id: usize, new_mode: u16, ctx: &CallerCtx) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + + async fn fchown( + &mut self, + id: usize, + new_uid: u32, + new_gid: u32, + ctx: &CallerCtx, + ) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + + async fn fcntl(&mut self, id: usize, cmd: usize, arg: usize, ctx: &CallerCtx) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn fevent( + &mut self, + id: usize, + flags: EventFlags, + ctx: &CallerCtx, + ) -> Result { + Ok(EventFlags::empty()) + } + + async fn flink(&mut self, id: usize, path: &str, ctx: &CallerCtx) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn fpath(&mut self, id: usize, buf: &mut [u8], ctx: &CallerCtx) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn frename(&mut self, id: usize, path: &str, ctx: &CallerCtx) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn fstat(&mut self, id: usize, stat: &mut Stat, ctx: &CallerCtx) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + + async fn fstatvfs(&mut self, id: usize, stat: &mut StatVfs, ctx: &CallerCtx) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + + async fn fsync(&mut self, id: usize, ctx: &CallerCtx) -> Result<()> { + Ok(()) + } + + async fn ftruncate(&mut self, id: usize, len: u64, ctx: &CallerCtx) -> Result<()> { + Err(Error::new(EBADF)) + } + + async fn futimens(&mut self, id: usize, times: &[TimeSpec], ctx: &CallerCtx) -> Result<()> { + Err(Error::new(EBADF)) + } + + async fn relpathat( + &mut self, + id: usize, + dir_id: usize, + path: &mut [u8], + ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EBADF)) + } + + async fn call( + &mut self, + id: usize, + payload: &mut [u8], + metadata: &[u64], + ctx: &CallerCtx, // Only pid and id are correct here, uid/gid are not used + ) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn call_multiple_ids( + &mut self, + ids: &[usize], + payload: &mut [u8], + metadata: &[u64], + ctx: &CallerCtx, // Only pid and id are correct here, uid/gid are not used + ) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn std_fs_call( + &mut self, + id: usize, + kind: StdFsCallKind, + payload: &mut [u8], + metadata: StdFsCallMeta, + ctx: &CallerCtx, // Only pid and id are correct here, uid/gid are not used + ) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn getdents<'buf>( + &mut self, + id: usize, + buf: DirentBuf<&'buf mut [u8]>, + opaque_offset: u64, + ) -> Result> { + Err(Error::new(ENOTDIR)) + } + + async fn mmap_prep( + &mut self, + id: usize, + offset: u64, + size: usize, + flags: MapFlags, + ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn munmap( + &mut self, + id: usize, + offset: u64, + size: usize, + flags: MunmapFlags, + ctx: &CallerCtx, + ) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + + async fn on_recvfd(&mut self, recvfd_request: &RecvFdRequest) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + async fn translate_std_fs_call( + &mut self, + ids: &[usize], + kind: StdFsCallKind, + payload: &mut [u8], + metadata: StdFsCallMeta, + ctx: &CallerCtx, + ) -> Result { + use syscall::flag::StdFsCallKind::*; + match kind { + Relpathat => { + let (Some(target_id), Some(dir_id)) = (ids.first(), ids.get(1)) else { + return Err(Error::new(EINVAL)); + }; + self.relpathat(*target_id, *dir_id, payload, ctx).await + } + _ => { + if ids.len() != 1 { + return Err(Error::new(EINVAL)); + } + let id = ids[0]; + match kind { + Fchmod => self.fchmod(id, metadata.arg1 as u16, ctx).await.map(|_| 0), + // TODO: Handle inside redox-scheme + // Since this currently requires get_proc_credentials, + // fchown cannot be supported on the redox-scheme side. + Fchown => self + .std_fs_call(id, kind, payload, metadata, ctx) + .await + .map(|_| 0), + Getdents => { + let Some(buf) = DirentBuf::new(payload, metadata.arg2 as u16) else { + return Err(Error::new(EINVAL)); + }; + self.getdents(id, buf, metadata.arg1) + .await + .map(|buf_res| buf_res.finalize()) + } + Fstat => { + if payload.len() != size_of::() { + return Err(Error::new(EINVAL)); + } + let stat = payload.as_mut_ptr() as *mut Stat; + self.fstat(id, unsafe { &mut *stat }, ctx).await.map(|_| 0) + } + Fstatvfs => { + if payload.len() != size_of::() { + return Err(Error::new(EINVAL)); + } + let stat = payload.as_mut_ptr() as *mut StatVfs; + self.fstatvfs(id, unsafe { &mut *stat }, ctx) + .await + .map(|_| 0) + } + Fsync => self.fsync(id, ctx).await.map(|_| 0), + Ftruncate => self.ftruncate(id, metadata.arg1, ctx).await.map(|_| 0), + Futimens => { + if payload.len() != 2 * size_of::() { + return Err(Error::new(EINVAL)); + } + let times = unsafe { + core::slice::from_raw_parts( + payload.as_ptr() as *const TimeSpec, + payload.len() / size_of::(), + ) + }; + self.futimens(id, times, ctx).await.map(|_| 0) + } + /* TODO: Support Unlinkat using std_fs_call + Unlinkat => self.std_fs_call(fd, kind, payload, metadata, &caller).map(|_| 0) + */ + _ => Err(Error::new(EOPNOTSUPP)), + } + } + } + } +} +#[allow(unused_variables)] +pub trait SchemeSync { + /* Scheme operations */ + fn scheme_root(&mut self) -> Result { + Err(Error::new(ENOSYS)) + } + fn openat( + &mut self, + fd: usize, + path: &str, + flags: usize, + fcntl_flags: u32, + ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn unlinkat(&mut self, fd: usize, path: &str, flags: usize, ctx: &CallerCtx) -> Result<()> { + Err(Error::new(ENOENT)) + } + + fn inode(&self, id: usize) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + /* Resource operations */ + fn dup(&mut self, old_id: usize, buf: &[u8], ctx: &CallerCtx) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn read( + &mut self, + id: usize, + buf: &mut [u8], + offset: u64, + fcntl_flags: u32, + ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EBADF)) + } + + fn write( + &mut self, + id: usize, + buf: &[u8], + offset: u64, + fcntl_flags: u32, + ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EBADF)) + } + + fn fsize(&mut self, id: usize, ctx: &CallerCtx) -> Result { + Err(Error::new(ESPIPE)) + } + + fn fchmod(&mut self, id: usize, new_mode: u16, ctx: &CallerCtx) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + + fn fchown(&mut self, id: usize, new_uid: u32, new_gid: u32, ctx: &CallerCtx) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + + fn fcntl(&mut self, id: usize, cmd: usize, arg: usize, ctx: &CallerCtx) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn fevent(&mut self, id: usize, flags: EventFlags, ctx: &CallerCtx) -> Result { + Ok(EventFlags::empty()) + } + + fn flink(&mut self, id: usize, path: &str, ctx: &CallerCtx) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn fpath(&mut self, id: usize, buf: &mut [u8], ctx: &CallerCtx) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn frename(&mut self, id: usize, path: &str, ctx: &CallerCtx) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn fstat(&mut self, id: usize, stat: &mut Stat, ctx: &CallerCtx) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + + fn fstatvfs(&mut self, id: usize, stat: &mut StatVfs, ctx: &CallerCtx) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + + fn fsync(&mut self, id: usize, ctx: &CallerCtx) -> Result<()> { + Ok(()) + } + + fn ftruncate(&mut self, id: usize, len: u64, ctx: &CallerCtx) -> Result<()> { + Err(Error::new(EBADF)) + } + + fn futimens(&mut self, id: usize, times: &[TimeSpec], ctx: &CallerCtx) -> Result<()> { + Err(Error::new(EBADF)) + } + + fn relpathat( + &mut self, + id: usize, + dir_id: usize, + path: &mut [u8], + ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EBADF)) + } + + fn call( + &mut self, + id: usize, + payload: &mut [u8], + metadata: &[u64], + ctx: &CallerCtx, // Only pid and id are correct here, uid/gid are not used + ) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn call_multiple_ids( + &mut self, + ids: &[usize], + payload: &mut [u8], + metadata: &[u64], + ctx: &CallerCtx, // Only pid and id are correct here, uid/gid are not used + ) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn std_fs_call( + &mut self, + id: usize, + kind: StdFsCallKind, + payload: &mut [u8], + metadata: StdFsCallMeta, + ctx: &CallerCtx, // Only pid and id are correct here, uid/gid are not used + ) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn getdents<'buf>( + &mut self, + id: usize, + buf: DirentBuf<&'buf mut [u8]>, + opaque_offset: u64, + ) -> Result> { + Err(Error::new(ENOTDIR)) + } + + fn mmap_prep( + &mut self, + id: usize, + offset: u64, + size: usize, + flags: MapFlags, + ctx: &CallerCtx, + ) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn munmap( + &mut self, + id: usize, + offset: u64, + size: usize, + flags: MunmapFlags, + ctx: &CallerCtx, + ) -> Result<()> { + Err(Error::new(EOPNOTSUPP)) + } + + fn on_close(&mut self, id: usize) {} + + fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn on_recvfd(&mut self, recvfd_request: &RecvFdRequest) -> Result { + Err(Error::new(EOPNOTSUPP)) + } + + fn translate_std_fs_call( + &mut self, + ids: &[usize], + kind: StdFsCallKind, + payload: &mut [u8], + metadata: StdFsCallMeta, + ctx: &CallerCtx, + ) -> Result { + use syscall::flag::StdFsCallKind::*; + match kind { + Relpathat => { + if ids.len() != 2 { + return Err(Error::new(EINVAL)); + } + self.relpathat(ids[0], ids[1], payload, ctx) + } + _ => { + if ids.len() != 1 { + return Err(Error::new(EINVAL)); + } + let id = ids[0]; + match kind { + Fchmod => self.fchmod(id, metadata.arg1 as u16, ctx).map(|_| 0), + // TODO: Handle inside redox-scheme + // Since this currently requires get_proc_credentials, + // fchown cannot be supported on the redox-scheme side. + Fchown => self + .std_fs_call(id, kind, payload, metadata, ctx) + .map(|_| 0), + Getdents => { + let Some(buf) = DirentBuf::new(payload, metadata.arg2 as u16) else { + return Err(Error::new(EINVAL)); + }; + self.getdents(id, buf, metadata.arg1) + .map(|buf_res| buf_res.finalize()) + } + Fstat => { + if payload.len() != size_of::() { + return Err(Error::new(EINVAL)); + } + let stat = payload.as_mut_ptr() as *mut Stat; + self.fstat(id, unsafe { &mut *stat }, ctx).map(|_| 0) + } + Fstatvfs => { + if payload.len() != size_of::() { + return Err(Error::new(EINVAL)); + } + let stat = payload.as_mut_ptr() as *mut StatVfs; + self.fstatvfs(id, unsafe { &mut *stat }, ctx).map(|_| 0) + } + Fsync => self.fsync(id, ctx).map(|_| 0), + Ftruncate => self.ftruncate(id, metadata.arg1, ctx).map(|_| 0), + Futimens => { + if payload.len() != 2 * size_of::() { + return Err(Error::new(EINVAL)); + } + let times = unsafe { + core::slice::from_raw_parts( + payload.as_ptr() as *const TimeSpec, + payload.len() / size_of::(), + ) + }; + self.futimens(id, times, ctx).map(|_| 0) + } + /* TODO: Support Unlinkat using std_fs_call + Unlinkat => self.std_fs_call(fd, kind, payload, metadata, &caller).map(|_| 0) + */ + _ => Err(Error::new(EOPNOTSUPP)), + } + } + } + } +} + +pub fn register_sync_scheme( + socket: &Socket, + name: &str, + scheme: &mut S, +) -> Result<()> { + let cap_id = scheme.scheme_root()?; + register_scheme_inner(socket, name, cap_id) +} + +pub fn register_async_scheme( + socket: &Socket, + name: &str, + scheme: &mut S, +) -> Result<()> { + let cap_id = scheme.scheme_root()?; + register_scheme_inner(socket, name, cap_id) +} + +pub fn register_scheme_inner(socket: &Socket, name: &str, cap_id: usize) -> Result<()> { + let cap_fd = socket.create_this_scheme_fd(0, cap_id, 0, 0)?; + let current_namespace_fd = libredox::call::getns()?; + Ok(libredox::call::register_scheme_to_ns( + current_namespace_fd, + name, + cap_fd, + )?) +} + +pub trait IntoTag { + fn into_tag(self) -> Tag; + fn req_id(&self) -> Id; +} +impl IntoTag for Tag { + fn into_tag(self) -> Tag { + self + } + fn req_id(&self) -> Id { + self.0 + } +} +impl IntoTag for CallRequest { + fn into_tag(self) -> Tag { + Tag(self.request_id()) + } + fn req_id(&self) -> Id { + self.request_id() + } +} +impl IntoTag for SendFdRequest { + fn into_tag(self) -> Tag { + Tag(self.request_id()) + } + fn req_id(&self) -> Id { + self.request_id() + } +} +impl IntoTag for RecvFdRequest { + fn into_tag(self) -> Tag { + Tag(self.request_id()) + } + fn req_id(&self) -> Id { + self.request_id() + } +} +macro_rules! trivial_into { + [$($name:ident,)*] => { + $( + impl IntoTag for $name { + #[inline] + fn into_tag(self) -> Tag { + self.req + } + #[inline] + fn req_id(&self) -> Id { + self.req.req_id() + } + } + )* + } +} +trivial_into![OpCall, OpRead, OpWrite, OpGetdents,]; +impl IntoTag for OpQueryWrite { + fn into_tag(self) -> Tag { + self.req + } + fn req_id(&self) -> Id { + self.req.0 + } +} +impl IntoTag for OpQueryRead { + fn into_tag(self) -> Tag { + self.req + } + fn req_id(&self) -> Id { + self.req.0 + } +} +impl IntoTag for OpPathLike { + fn into_tag(self) -> Tag { + self.req + } + fn req_id(&self) -> Id { + self.req.0 + } +} +impl IntoTag for OpFdPathLike { + fn into_tag(self) -> Tag { + self.inner.req + } + fn req_id(&self) -> Id { + self.inner.req.0 + } +} +impl IntoTag for Op { + fn into_tag(self) -> Tag { + use Op::*; + match self { + OpenAt(op) => op.into_tag(), + UnlinkAt(op) => op.into_tag(), + Dup(op) => op.into_tag(), + Read(op) => op.into_tag(), + Write(op) => op.into_tag(), + Fsize { req, .. } + | Fchmod { req, .. } + | Fchown { req, .. } + | Fcntl { req, .. } + | Fevent { req, .. } + | Fsync { req, .. } + | Ftruncate { req, .. } + | MmapPrep { req, .. } + | Munmap { req, .. } => req, + Flink(op) => op.into_tag(), + Fpath(op) => op.into_tag(), + Frename(op) => op.into_tag(), + Fstat(op) => op.into_tag(), + FstatVfs(op) => op.into_tag(), + Futimens(op) => op.into_tag(), + Call(op) => op.into_tag(), + StdFsCall(op) => op.into_tag(), + Getdents(op) => op.into_tag(), + Recvfd(req) => req.into_tag(), + } + } + fn req_id(&self) -> Id { + use Op::*; + match self { + OpenAt(op) => op.req_id(), + UnlinkAt(op) => op.req_id(), + Dup(op) => op.req_id(), + Read(op) => op.req_id(), + Write(op) => op.req_id(), + Fsize { req, .. } + | Fchmod { req, .. } + | Fchown { req, .. } + | Fcntl { req, .. } + | Fevent { req, .. } + | Fsync { req, .. } + | Ftruncate { req, .. } + | MmapPrep { req, .. } + | Munmap { req, .. } => req.req_id(), + Flink(op) => op.req_id(), + Fpath(op) => op.req_id(), + Frename(op) => op.req_id(), + Fstat(op) => op.req_id(), + FstatVfs(op) => op.req_id(), + Futimens(op) => op.req_id(), + Call(op) => op.req_id(), + StdFsCall(op) => op.req_id(), + Getdents(op) => op.req_id(), + Recvfd(req) => req.req_id(), + } + } +} + +#[cfg(not(feature = "std"))] +pub struct SchemeState {} +#[cfg(not(feature = "std"))] +impl SchemeState { + pub fn new() -> Self { + Self {} + } + fn std_fs_call( + &mut self, + id: usize, + kind: StdFsCallKind, + inode: Option, + metadata: StdFsCallMeta, + payload: &mut [u8], + ctx: &CallerCtx, + ) -> Option { + None + } + pub fn on_close(&mut self, id: usize) {} + + pub fn on_detach(&mut self, _id: usize, _inode: usize, _pid: usize) {} +} + +#[cfg(feature = "std")] +use crate::advisory_flock::{LockError, LockKind, LockOwner, LockSet}; +#[cfg(feature = "std")] +pub struct SchemeState { + flocks: LockSet, +} +#[cfg(feature = "std")] +impl SchemeState { + pub fn new() -> Self { + Self { + flocks: LockSet::new(), + } + } + + fn std_fs_call( + &mut self, + id: usize, + kind: StdFsCallKind, + inode: Option, + metadata: StdFsCallMeta, + payload: &mut [u8], + ctx: &CallerCtx, + ) -> Option { + match kind { + StdFsCallKind::Lock => Some({ + let inode = inode?; + let (owner, start, len, kind) = ( + if metadata.arg1 & (1 << 63) == (1 << 63) { + LockOwner::Resource { fd: id } + } else { + LockOwner::Process { + pid: ctx.pid, + inode, + } + }, + (metadata.arg1 & !(1 << 63)), + (metadata.arg2 & !(1 << 63)), + if metadata.arg2 & (1 << 63) == (1 << 63) { + LockKind::Exclusive + } else { + LockKind::Shared + }, + ); + + match self.flocks.lock(kind, owner, start, len) { + Ok(()) if owner.is_process() => SchemeResponse::RegularAndNotifyOnDetach(Ok(0)), + Ok(()) if !owner.is_process() => SchemeResponse::Regular(Ok(0)), + Err(LockError::Conflict) => { + SchemeResponse::Regular(Err(Error::new(EWOULDBLOCK))) + } + _ => unreachable!(), + } + }), + + StdFsCallKind::Unlock => Some({ + let inode = inode?; + let (owner, start, len) = ( + if metadata.arg1 & (1 << 63) == (1 << 63) { + LockOwner::Resource { fd: id } + } else { + LockOwner::Process { + pid: ctx.pid, + inode, + } + }, + (metadata.arg1 & !(1 << 63)), + metadata[2], + ); + + match self.flocks.unlock(owner, start, len) { + Ok(()) | Err(LockError::NotPresent) => SchemeResponse::Regular(Ok(0)), + _ => unreachable!(), + } + }), + + StdFsCallKind::GetLock => Some({ + let inode = inode?; + let (owner, start, len, kind) = ( + if metadata.arg1 & (1 << 63) == (1 << 63) { + LockOwner::Resource { fd: id } + } else { + LockOwner::Process { + pid: ctx.pid, + inode, + } + }, + (metadata.arg1 & !(1 << 63)), + (metadata.arg2 & !(1 << 63)), + if metadata.arg2 & (1 << 63) == (1 << 63) { + LockKind::Exclusive + } else { + LockKind::Shared + }, + ); + match self.flocks.get_lock(kind, owner, start, len) { + Some(lck) => { + assert!(payload.len() == size_of::() * 2); + + match lck.owner { + LockOwner::Process { + pid, + inode: lck_inode, + } if pid == ctx.pid && lck_inode == inode => { + return Some(SchemeResponse::Regular(Err(Error::new(ESRCH)))); + } + LockOwner::Resource { fd } if fd == id => { + return Some(SchemeResponse::Regular(Err(Error::new(ESRCH)))); + } + _ => {} + } + + let payload = unsafe { &mut *(payload.as_mut_ptr() as *mut [u64; 2]) }; + payload[0] = lck.start as u64; + payload[1] = + lck.len() as u64 | if lck.kind.is_exclusive() { 1 << 63 } else { 0 }; + + match lck.owner { + LockOwner::Resource { .. } => SchemeResponse::Regular(Ok(0)), + LockOwner::Process { pid, .. } => SchemeResponse::Regular(Ok(pid)), + } + } + None => SchemeResponse::Regular(Err(Error::new(ESRCH))), + } + }), + + _ => None, + } + } + + pub fn on_close(&mut self, id: usize) { + let _ = self.flocks.on_close(LockOwner::Resource { fd: id }); + } + + pub fn on_detach(&mut self, _id: usize, inode: usize, pid: usize) { + let _ = self.flocks.on_close(LockOwner::Process { pid, inode }); + } +} diff --git a/local/sources/redox-scheme/src/wrappers.rs b/local/sources/redox-scheme/src/wrappers.rs new file mode 100644 index 0000000000..0826ccf936 --- /dev/null +++ b/local/sources/redox-scheme/src/wrappers.rs @@ -0,0 +1,213 @@ +use core::ops::{Deref, DerefMut}; +use std::collections::{HashMap, VecDeque}; +use std::ops::ControlFlow; + +use libredox::error::Error as LError; + +use syscall::Result; +use syscall::error::{self as errno, ECANCELED, EIO, EOPNOTSUPP, Error}; + +use crate::scheme::{Op, SchemeResponse, SchemeState, SchemeSync}; +use crate::{CallerCtx, Id, Request, RequestKind, Response, SignalBehavior, Socket}; + +pub struct ReadinessBased<'sock> { + // TODO: VecDeque for both when it implements spare_capacity + requests_read: Vec, + responses_to_write: VecDeque, + + states: HashMap, + ready_queue: VecDeque, + + socket: &'sock Socket, + state: SchemeState, +} +impl<'sock> ReadinessBased<'sock> { + pub fn new(socket: &'sock Socket, queue_size: usize) -> Self { + Self { + requests_read: Vec::with_capacity(queue_size), + responses_to_write: VecDeque::with_capacity(queue_size), + states: HashMap::new(), + socket, + ready_queue: VecDeque::new(), + state: SchemeState::new(), + } + } + pub fn read_requests(&mut self) -> Result { + assert!(self.requests_read.is_empty()); + + match self + .socket + .read_requests(&mut self.requests_read, SignalBehavior::Interrupt) + { + // TODO: write ECANCELED or similar for everything in self.states? + Ok(()) if self.requests_read.is_empty() => Ok(false), // EOF + Ok(()) + | Err(Error { + errno: errno::EINTR | errno::EWOULDBLOCK | errno::EAGAIN, + }) => Ok(true), + Err(err) => return Err(err), + } + } + pub fn process_requests( + &mut self, + // TODO: Maybe change this to &mut SchemeSync, requires converting e.g. audiod + mut acquire_scheme: impl FnMut() -> Guard, + ) where + Guard: Deref + DerefMut, + { + for request in self.requests_read.drain(..) { + let req = match request.kind() { + RequestKind::Call(c) => c, + RequestKind::Cancellation(req) => { + if let Some((_caller, op)) = self.states.remove(&req.id) { + self.responses_to_write + .push_back(Response::err(ECANCELED, op)); + } + continue; + } + RequestKind::OnClose { id } => { + // TODO: state.on_close() + acquire_scheme().on_close(id); + continue; + } + RequestKind::SendFd(sendfd_request) => { + let result = acquire_scheme().on_sendfd(&sendfd_request); + let response = Response::new(result, sendfd_request); + self.responses_to_write.push_back(response); + continue; + } + RequestKind::RecvFd(recvfd_request) => { + let result = acquire_scheme().on_recvfd(&recvfd_request); + let caller = recvfd_request.caller(); + + if let Err(Error { + errno: errno::EWOULDBLOCK, + }) = result + { + self.states.insert(caller.id, (caller, recvfd_request.op())); + continue; + } + let response = Response::open_dup_like(result, recvfd_request); + self.responses_to_write.push_back(response); + continue; + } + _ => continue, + }; + let caller = req.caller(); + let mut op = match req.op() { + Ok(op) => op, + Err(req) => { + self.responses_to_write + .push_back(Response::err(EOPNOTSUPP, req)); + continue; + } + }; + let resp = + match op.handle_sync_dont_consume(&caller, &mut *acquire_scheme(), &mut self.state) + { + SchemeResponse::Opened(Err(Error { + errno: errno::EWOULDBLOCK, + })) + | SchemeResponse::Regular(Err(Error { + errno: errno::EWOULDBLOCK, + })) if !op.is_explicitly_nonblock() => { + self.states.insert(caller.id, (caller, op)); + continue; + } + SchemeResponse::Regular(r) => Response::new(r, op), + SchemeResponse::RegularAndNotifyOnDetach(status) => { + Response::new_notify_on_detach(status, op) + } + SchemeResponse::Opened(o) => Response::open_dup_like(o, op), + }; + self.responses_to_write.push_back(resp); + } + } + // TODO: Doesn't scale. Instead, provide an API for some form of queue. + // TODO: panic if id isn't present? + pub fn poll_request(&mut self, id: Id, scheme: &mut impl SchemeSync) -> Result { + Ok( + match Self::poll_request_inner(id, scheme, &mut self.state, &mut self.states)? { + ControlFlow::Continue((caller, op)) => { + self.states.insert(id, (caller, op)); + false + } + ControlFlow::Break(resp) => { + self.responses_to_write.push_back(resp); + true + } + }, + ) + } + + fn poll_request_inner( + id: Id, + scheme: &mut impl SchemeSync, + state: &mut SchemeState, + states: &mut HashMap, + ) -> Result> { + let (caller, mut op) = states.remove(&id).ok_or(Error::new(EIO))?; + let resp = match op.handle_sync_dont_consume(&caller, scheme, state) { + SchemeResponse::Opened(Err(Error { + errno: errno::EWOULDBLOCK, + })) + | SchemeResponse::Regular(Err(Error { + errno: errno::EWOULDBLOCK, + })) if !op.is_explicitly_nonblock() => { + return Ok(ControlFlow::Continue((caller, op))); + } + SchemeResponse::Regular(r) => Response::new(r, op), + SchemeResponse::Opened(o) => Response::open_dup_like(o, op), + SchemeResponse::RegularAndNotifyOnDetach(status) => { + Response::new_notify_on_detach(status, op) + } + }; + Ok(ControlFlow::Break(resp)) + } + pub fn poll_ready_requests(&mut self, mut acquire_scheme: impl FnMut() -> G) -> Result<()> + where + S: SchemeSync, + G: Deref + DerefMut, + { + for id in self.ready_queue.drain(..) { + match Self::poll_request_inner( + id, + &mut *acquire_scheme(), + &mut self.state, + &mut self.states, + )? { + ControlFlow::Break(resp) => { + self.responses_to_write.push_back(resp); + } + ControlFlow::Continue((caller, op)) => { + self.states.insert(id, (caller, op)); + } + } + } + Ok(()) + } + pub fn poll_all_requests(&mut self, acquire_scheme: impl FnMut() -> G) -> Result<()> + where + S: SchemeSync, + G: Deref + DerefMut, + { + // TODO: implement waker-like API + self.ready_queue.clear(); + self.ready_queue.extend(self.states.keys().copied()); + self.poll_ready_requests(acquire_scheme) + } + pub fn write_responses(&mut self) -> Result { + match self + .socket + .write_responses(&mut self.responses_to_write, SignalBehavior::Restart) + { + // TODO: write ECANCELED or similar for everything in self.states? + //Ok(()) if !self.responses_to_write.is_empty() => Ok(false), // EOF, FIXME + Ok(()) + | Err(Error { + errno: errno::EINTR | errno::EWOULDBLOCK | errno::EAGAIN, + }) => Ok(true), + Err(err) => return Err(LError::from(err).into()), + } + } +}