fork: import redox-scheme 0.11.2 as tracked tree with -rb1 deps

Red Bear OS needs a local fork of redox-scheme to enforce the -rb1
version policy end-to-end. crates.io redox-scheme 0.11.2 pins
redox_syscall = "0.9.0" exactly and libredox = "0.1.18" exactly,
which Cargo refuses to satisfy with the local -rb1 forks.

The fork is implemented as a TRACKED TREE under local/sources/redox-scheme/
on the active 0.2.5 branch (per local/AGENTS.md), not as a separate
git repository or as a separate submodule on its own branch.

The single-repo rule from local/AGENTS.md is preserved: this
directory is a regular subdirectory of the RedBear-OS repo, not a
separate repository.
This commit is contained in:
2026-07-04 10:57:26 +03:00
parent 1957f3ff36
commit 26ecd868f7
8 changed files with 2933 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
target
Cargo.lock
+19
View File
@@ -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"] }
+22
View File
@@ -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"] }
+21
View File
@@ -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.
@@ -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<u64, LockError> {
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<Lock>,
}
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)
);
}
}
+513
View File
@@ -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::<usize>(),
)
};
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::<usize>(),
)
};
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<Self> {
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<Self> {
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<Self> {
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<Request>, 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<Option<Request>> {
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<Response>,
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<bool> {
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<usize> {
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::<syscall::data::NewFdParams>(),
)
};
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<Self> {
Poll::Ready(Self::ok(status, req))
}
#[inline]
pub fn ready_err(err: i32, req: impl IntoTag) -> Poll<Self> {
Poll::Ready(Self::err(err, req))
}
pub fn new(status: Result<usize>, 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<usize>, 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<OpenResult>, 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<Request>],
behavior: SignalBehavior,
) -> Result<usize> {
let len = buf.len().checked_mul(size_of::<Request>()).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::<Request>(), 0);
Ok(bytes_read / size_of::<Request>())
}
// 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<usize> {
let bytes = unsafe {
core::slice::from_raw_parts(
buf.as_ptr().cast(),
buf.len().checked_mul(size_of::<Response>()).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::<Response>(), 0);
Ok(bytes_written / size_of::<Response>())
}
File diff suppressed because it is too large Load Diff
+213
View File
@@ -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<Request>,
responses_to_write: VecDeque<Response>,
states: HashMap<Id, (CallerCtx, Op)>,
ready_queue: VecDeque<Id>,
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<bool> {
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<Guard, S: SchemeSync>(
&mut self,
// TODO: Maybe change this to &mut SchemeSync, requires converting e.g. audiod
mut acquire_scheme: impl FnMut() -> Guard,
) where
Guard: Deref<Target = S> + 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<bool> {
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<Id, (CallerCtx, Op)>,
) -> Result<ControlFlow<Response, (CallerCtx, Op)>> {
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<S, G>(&mut self, mut acquire_scheme: impl FnMut() -> G) -> Result<()>
where
S: SchemeSync,
G: Deref<Target = S> + 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<S, G>(&mut self, acquire_scheme: impl FnMut() -> G) -> Result<()>
where
S: SchemeSync,
G: Deref<Target = S> + 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<bool> {
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()),
}
}
}