feat: Introduce FdTbl and make Context.files use it for separate file tables.

This commit is contained in:
Ibuki Omatsu
2025-07-30 21:33:06 +00:00
committed by Jeremy Soller
parent 66ea2b46ee
commit e3d8ae1b79
8 changed files with 914 additions and 154 deletions
Generated
+2 -2
View File
@@ -234,8 +234,8 @@ checksum = "64072665120942deff5fd5425d6c1811b854f4939e7f1c01ce755f64432bbea7"
[[package]]
name = "redox_syscall"
version = "0.5.12"
source = "git+https://gitlab.redox-os.org/redox-os/syscall.git?branch=master#fe32c6b89dae51e609d5c53880dec1834ec9bde0"
version = "0.5.17"
source = "git+https://gitlab.redox-os.org/redox-os/syscall.git?branch=master#a9880ccf50d093e662f3b31c1aa13f0951cc2b4d"
dependencies = [
"bitflags 2.9.0",
]
+403 -49
View File
@@ -1,4 +1,4 @@
use alloc::{sync::Arc, vec::Vec};
use alloc::{collections::BTreeSet, sync::Arc, vec::Vec};
use arrayvec::ArrayString;
use core::{
mem::{self, size_of},
@@ -6,6 +6,7 @@ use core::{
sync::atomic::{AtomicU32, Ordering},
};
use spin::RwLock;
use syscall::UPPER_FDTBL_TAG;
use syscall::{SigProcControl, Sigcontrol};
#[cfg(feature = "sys_stat")]
@@ -19,10 +20,10 @@ use crate::{
memory::{allocate_p2frame, deallocate_p2frame, Enomem, Frame, RaiiFrame},
paging::{RmmA, RmmArch},
percpu::PercpuBlock,
scheme::{CallerCtx, FileHandle, SchemeNamespace},
scheme::{CallerCtx, FileHandle, SchemeId, SchemeNamespace},
};
use crate::syscall::error::{Error, Result, EAGAIN, ESRCH};
use crate::syscall::error::{Error, Result, EAGAIN, EBADF, EEXIST, EINVAL, EMFILE, ESRCH};
use super::{
empty_cr3,
@@ -121,7 +122,7 @@ pub struct Context {
/// The name of the context
pub name: ArrayString<CONTEXT_NAME_CAPAC>,
/// The open files in the scheme
pub files: Arc<RwLock<Vec<Option<FileDescriptor>>>>,
pub files: Arc<RwLock<FdTbl>>,
/// All contexts except kmain will primarily live in userspace, and enter the kernel only when
/// interrupts or syscalls occur. This flag is set for all contexts but kmain.
pub userspace: bool,
@@ -177,7 +178,7 @@ impl Context {
kstack: None,
addr_space: None,
name: ArrayString::new(),
files: Arc::new(RwLock::new(Vec::new())),
files: Arc::new(RwLock::new(FdTbl::new())),
userspace: false,
fmap_ret: None,
being_sigkilled: false,
@@ -255,64 +256,61 @@ impl Context {
/// Add a file to the lowest available slot greater than or equal to min.
/// Return the file descriptor number or None if no slot was found
pub fn add_file_min(&self, file: FileDescriptor, min: usize) -> Option<FileHandle> {
let mut files = self.files.write();
for (i, file_option) in files.iter_mut().enumerate() {
if file_option.is_none() && i >= min {
*file_option = Some(file);
return Some(FileHandle::from(i));
}
}
let len = files.len();
if len < super::CONTEXT_MAX_FILES {
if len >= min {
files.push(Some(file));
Some(FileHandle::from(len))
} else {
drop(files);
self.insert_file(FileHandle::from(min), file)
}
} else {
None
}
self.files.write().add_file_min(file, min)
}
/// Bulk-add multiple files to the POSIX file table
pub fn bulk_add_files_posix(
&self,
files_to_add: Vec<FileDescriptor>,
) -> Option<Vec<FileHandle>> {
self.files.write().bulk_add_files_posix(files_to_add)
}
/// Bulk-insert multiple files into to the upper file table contiguously
pub fn bulk_insert_files_upper(
&self,
files_to_insert: Vec<FileDescriptor>,
) -> Option<Vec<FileHandle>> {
self.files.write().bulk_insert_files_upper(files_to_insert)
}
/// Bulk-insert multiple files into to the upper file table manually
pub fn bulk_insert_files_upper_manual(
&self,
files_to_insert: Vec<FileDescriptor>,
handles: &[FileHandle],
) -> Result<()> {
self.files
.write()
.bulk_insert_files_upper_manual(files_to_insert, handles)
}
/// Get a file
pub fn get_file(&self, i: FileHandle) -> Option<FileDescriptor> {
let files = self.files.read();
if i.get() < files.len() {
files[i.get()].clone()
} else {
None
}
self.files.read().get_file(i)
}
/// Bulk get files
pub fn bulk_get_files(&self, handles: &[FileHandle]) -> Result<Vec<FileDescriptor>> {
self.files.read().bulk_get_files(handles)
}
/// Insert a file with a specific handle number. This is used by dup2
/// Return the file descriptor number or None if the slot was not empty, or i was invalid
pub fn insert_file(&self, i: FileHandle, file: FileDescriptor) -> Option<FileHandle> {
let mut files = self.files.write();
if i.get() >= super::CONTEXT_MAX_FILES {
return None;
}
if i.get() >= files.len() {
files.resize_with(i.get() + 1, || None);
}
if files[i.get()].is_none() {
files[i.get()] = Some(file);
Some(i)
} else {
None
}
self.files.write().insert_file(i, file)
}
/// Remove a file
// TODO: adjust files vector to smaller size if possible
pub fn remove_file(&self, i: FileHandle) -> Option<FileDescriptor> {
let mut files = self.files.write();
if i.get() < files.len() {
files[i.get()].take()
} else {
None
}
self.files.write().remove_file(i)
}
/// Bulk remove files
pub fn bulk_remove_files(&self, handles: &[FileHandle]) -> Result<Vec<FileDescriptor>> {
self.files.write().bulk_remove_files(handles)
}
pub fn is_current_context(&self) -> bool {
@@ -541,3 +539,359 @@ impl core::fmt::Debug for Kstack {
write!(f, "[kstack at {:?}]", self.base)
}
}
#[derive(Clone, Debug, Default)]
pub struct FdTbl {
pub posix_fdtbl: Vec<Option<FileDescriptor>>,
pub upper_fdtbl: Vec<Option<FileDescriptor>>,
active_count: usize,
}
impl FdTbl {
pub fn new() -> Self {
Self {
posix_fdtbl: Vec::new(),
upper_fdtbl: Vec::new(),
active_count: 0,
}
}
fn strip_tags(index: usize) -> usize {
index & !UPPER_FDTBL_TAG
}
fn select_fdtbl(&self, index: usize) -> (&Vec<Option<FileDescriptor>>, usize) {
if index & UPPER_FDTBL_TAG == 0 {
(&self.posix_fdtbl, index)
} else {
(&self.upper_fdtbl, Self::strip_tags(index))
}
}
fn select_fdtbl_mut(&mut self, index: usize) -> (&mut Vec<Option<FileDescriptor>>, usize) {
if index & UPPER_FDTBL_TAG == 0 {
(&mut self.posix_fdtbl, index)
} else {
(&mut self.upper_fdtbl, Self::strip_tags(index))
}
}
fn validate_handles(&self, handles: &[FileHandle]) -> Result<()> {
let mut checked_handles = BTreeSet::new();
for i in handles {
let index = i.get();
if Self::strip_tags(index) >= super::CONTEXT_MAX_FILES {
return Err(Error::new(EMFILE));
}
if !checked_handles.insert(index) {
return Err(Error::new(EBADF)); // Duplicate handle
}
if !matches!(self.get(index), Some(Some(_))) {
return Err(Error::new(EBADF));
}
}
Ok(())
}
fn validate_free_slots(&self, handles: &[FileHandle]) -> Result<()> {
let mut checked_slots = BTreeSet::new();
for i in handles {
let index = i.get();
if Self::strip_tags(index) >= super::CONTEXT_MAX_FILES {
return Err(Error::new(EMFILE));
}
if !checked_slots.insert(index) {
return Err(Error::new(EINVAL)); // Duplicate slots
}
if matches!(self.get(index), Some(Some(_))) {
return Err(Error::new(EEXIST));
}
}
Ok(())
}
pub fn add_file_min(&mut self, file: FileDescriptor, min: usize) -> Option<FileHandle> {
if self.active_count >= super::CONTEXT_MAX_FILES {
return None;
}
let tag = min & UPPER_FDTBL_TAG;
let (fdtbl, min) = self.select_fdtbl_mut(min);
// Find the first empty slot in the posix_fdtbl starting from `min`.
if let Some((pos, slot)) = fdtbl
.iter_mut()
.enumerate()
.skip(min)
.find(|(_, slot)| slot.is_none())
{
*slot = Some(file);
self.active_count += 1;
return Some(FileHandle::from(pos | tag));
};
let len = fdtbl.len();
// If no empty slot was found, we need to allocate a new slot.
if len >= min {
fdtbl.push(Some(file));
self.active_count += 1;
Some(FileHandle::from(len | tag))
} else {
self.insert_file(FileHandle::from(min | tag), file)
}
}
fn bulk_add_files_posix(
&mut self,
files_to_add: Vec<FileDescriptor>,
) -> Option<Vec<FileHandle>> {
let count = files_to_add.len();
if count == 0 {
return Some(Vec::new());
}
if self.active_count + count > super::CONTEXT_MAX_FILES {
return None;
}
let handles = self.find_free_posix_slots(count);
let max_index = handles[count - 1].get();
if self.posix_fdtbl.len() <= max_index {
// Resize the posix_fdtbl to accommodate the new files.
self.posix_fdtbl.resize(max_index + 1, None);
}
for (&handle, file) in handles.iter().zip(files_to_add) {
let index = handle.get();
self.posix_fdtbl[index] = Some(file);
}
self.active_count += count;
Some(handles)
}
fn insert_file(&mut self, i: FileHandle, file: FileDescriptor) -> Option<FileHandle> {
if self.active_count >= super::CONTEXT_MAX_FILES {
return None;
}
let index = i.get();
let (fdtbl, real_index) = self.select_fdtbl_mut(index);
if real_index >= super::CONTEXT_MAX_FILES {
return None;
}
if real_index >= fdtbl.len() {
fdtbl.resize_with(real_index + 1, || None);
}
if let Some(slot @ None) = fdtbl.get_mut(real_index) {
*slot = Some(file);
self.active_count += 1;
Some(i)
} else {
None
}
}
fn bulk_insert_files_upper(
&mut self,
files_to_insert: Vec<FileDescriptor>,
) -> Option<Vec<FileHandle>> {
let count = files_to_insert.len();
if count == 0 {
return Some(Vec::new());
}
if self.active_count + count > super::CONTEXT_MAX_FILES {
return None;
}
let index = Self::strip_tags(self.find_free_upper_block(count).get());
let mut handles = Vec::with_capacity(count);
for (i, file) in files_to_insert.into_iter().enumerate() {
let current_index = index + i;
self.upper_fdtbl[current_index] = Some(file);
handles.push(FileHandle::from(current_index | UPPER_FDTBL_TAG));
}
self.active_count += count;
Some(handles)
}
fn bulk_insert_files_upper_manual(
&mut self,
files_to_insert: Vec<FileDescriptor>,
handles: &[FileHandle],
) -> Result<()> {
if handles.len() != files_to_insert.len() {
return Err(Error::new(EINVAL));
}
let count = files_to_insert.len();
if count == 0 {
return Ok(());
}
if self.active_count + count > super::CONTEXT_MAX_FILES {
return Err(Error::new(EMFILE));
}
self.validate_free_slots(handles)?;
let max_index = handles
.iter()
.map(|h| Self::strip_tags(h.get()))
.max()
.unwrap_or(0);
if self.upper_fdtbl.len() <= max_index {
self.upper_fdtbl.resize_with(max_index + 1, || None);
}
for (file, &handle) in files_to_insert.into_iter().zip(handles) {
let index = Self::strip_tags(handle.get());
self.upper_fdtbl[index] = Some(file);
}
self.active_count += count;
Ok(())
}
pub fn get(&self, index: usize) -> Option<&Option<FileDescriptor>> {
let (fdtbl, real_index) = self.select_fdtbl(index);
fdtbl.get(real_index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut Option<FileDescriptor>> {
let (fdtbl, real_index) = self.select_fdtbl_mut(index);
fdtbl.get_mut(real_index)
}
pub fn get_file(&self, i: FileHandle) -> Option<FileDescriptor> {
self.get(i.get()).cloned().flatten()
}
fn bulk_get_files(&self, handles: &[FileHandle]) -> Result<Vec<FileDescriptor>> {
// Validate that all handles are valid before proceeding to avoid partial results.
self.validate_handles(handles)?;
let files = handles
.iter()
.map(|&i| self.get_file(i).expect("File should exist"))
.collect();
Ok(files)
}
// TODO: Faster, cleaner mechanism to get descriptor
// Find a file descriptor by scheme id and number.
pub fn find_by_scheme(
&self,
scheme_id: SchemeId,
scheme_number: usize,
) -> Result<FileDescriptor> {
self.iter()
.flatten()
.find(|&context_fd| {
let desc = context_fd.description.read();
desc.scheme == scheme_id && desc.number == scheme_number
})
.map(|fd| fd.clone())
.ok_or(Error::new(EBADF))
}
fn remove_file(&mut self, i: FileHandle) -> Option<FileDescriptor> {
let index = i.get();
let (fdtbl, real_index) = self.select_fdtbl_mut(index);
let removed_file_opt = fdtbl.get_mut(real_index).and_then(|opt| opt.take());
if removed_file_opt.is_some() {
self.active_count -= 1;
}
removed_file_opt
}
fn bulk_remove_files(&mut self, handles: &[FileHandle]) -> Result<Vec<FileDescriptor>> {
// Validate that all handles are valid before proceeding to avoid partial results.
self.validate_handles(handles)?;
let files = handles
.iter()
.map(|&i| self.remove_file(i).expect("File should exist"))
.collect();
Ok(files)
}
fn find_free_posix_slots(&self, count: usize) -> Vec<FileHandle> {
let mut free_slots = Vec::with_capacity(count);
for (i, slot) in self.posix_fdtbl.iter().enumerate() {
if slot.is_none() {
free_slots.push(FileHandle::from(i));
if free_slots.len() == count {
return free_slots;
}
}
}
let mut current_len = self.posix_fdtbl.len();
while free_slots.len() < count {
free_slots.push(FileHandle::from(current_len));
current_len += 1;
}
free_slots
}
fn find_free_upper_block(&mut self, len: usize) -> FileHandle {
let mut start = 0;
let mut count = 0;
for (i, file_opt) in self.upper_fdtbl.iter().enumerate() {
if file_opt.is_none() {
if count == 0 {
start = i;
}
count += 1;
if count == len {
break;
}
} else {
count = 0;
}
}
if count < len {
if count == 0 {
start = self.upper_fdtbl.len();
}
let needed = len - count;
self.upper_fdtbl
.resize(self.upper_fdtbl.len() + needed, None);
}
FileHandle::from(start | UPPER_FDTBL_TAG)
}
pub fn force_close_all(&mut self) {
for file_opt in self.iter_mut() {
if let Some(file) = file_opt.take() {
let _ = file.close();
}
}
self.active_count = 0;
}
}
impl FdTbl {
pub fn iter(&self) -> impl Iterator<Item = &Option<FileDescriptor>> {
self.posix_fdtbl.iter().chain(self.upper_fdtbl.iter())
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Option<FileDescriptor>> {
self.posix_fdtbl
.iter_mut()
.chain(self.upper_fdtbl.iter_mut())
}
}
+20 -10
View File
@@ -13,7 +13,7 @@ use core::{hash::BuildHasherDefault, sync::atomic::AtomicUsize};
use hashbrown::{hash_map::DefaultHashBuilder, HashMap};
use indexmap::IndexMap;
use spin::{Once, RwLock, RwLockReadGuard, RwLockWriteGuard};
use syscall::{CallFlags, EventFlags, MunmapFlags, SendFdFlags};
use syscall::{CallFlags, EventFlags, MunmapFlags};
use crate::{
context::{
@@ -468,15 +468,6 @@ pub trait KernelScheme: Send + Sync + 'static {
Err(Error::new(EBADF))
}
fn ksendfd(
&self,
id: usize,
desc: Arc<RwLock<FileDescription>>,
flags: SendFdFlags,
arg: u64,
) -> Result<usize> {
Err(Error::new(EOPNOTSUPP))
}
fn getdents(
&self,
id: usize,
@@ -535,6 +526,25 @@ pub trait KernelScheme: Send + Sync + 'static {
) -> Result<usize> {
Err(Error::new(EOPNOTSUPP))
}
fn kfdwrite(
&self,
id: usize,
descs: Vec<Arc<RwLock<FileDescription>>>,
flags: CallFlags,
args: u64,
metadata: &[u64],
) -> Result<usize> {
Err(Error::new(EOPNOTSUPP))
}
fn kfdread(
&self,
id: usize,
payload: UserSliceRw,
flags: CallFlags,
metadata: &[u64],
) -> Result<usize> {
Err(Error::new(EOPNOTSUPP))
}
}
#[derive(Debug)]
+8 -4
View File
@@ -3,7 +3,7 @@ use crate::{
context::{
self,
context::{HardBlockedReason, SignalState},
file::{FileDescriptor, InternalFlags},
file::InternalFlags,
memory::{handle_notify_files, AddrSpace, AddrSpaceWrapper, Grant, PageSpan},
Context, Status,
},
@@ -19,6 +19,8 @@ use crate::{
},
};
use crate::context::context::FdTbl;
use super::{CallerCtx, GlobalSchemes, KernelSchemes, OpenResult};
use ::syscall::{ProcSchemeAttrs, SigProcControl, Sigcontrol};
use alloc::{
@@ -108,11 +110,11 @@ enum ContextHandle {
Sighandler,
Start,
NewFiletable {
filetable: Arc<RwLock<Vec<Option<FileDescriptor>>>>,
filetable: Arc<RwLock<FdTbl>>,
data: Box<[u8]>,
},
Filetable {
filetable: Weak<RwLock<Vec<Option<FileDescriptor>>>>,
filetable: Weak<RwLock<FdTbl>>,
data: Box<[u8]>,
},
AddrSpace {
@@ -129,7 +131,7 @@ enum ContextHandle {
CurrentFiletable,
AwaitingFiletableChange {
new_ft: Arc<RwLock<Vec<Option<FileDescriptor>>>>,
new_ft: Arc<RwLock<FdTbl>>,
},
// TODO: Remove this once openat is implemented, or allow openat-via-dup via e.g. the top-level
@@ -320,8 +322,10 @@ impl ProcScheme {
use core::fmt::Write;
let mut data = String::new();
// Only the posix file table is targeted.
for index in filetable
.read()
.posix_fdtbl
.iter()
.enumerate()
.filter_map(|(idx, val)| val.as_ref().map(|_| idx))
+45 -4
View File
@@ -1,4 +1,4 @@
use alloc::{boxed::Box, string::ToString, sync::Arc};
use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec};
use core::{
str,
sync::atomic::{AtomicUsize, Ordering},
@@ -15,13 +15,13 @@ use crate::{
scheme::{
self,
user::{UserInner, UserScheme},
SchemeId, SchemeNamespace,
FileDescription, SchemeId, SchemeNamespace,
},
syscall::{
data::Stat,
error::*,
flag::{EventFlags, MODE_DIR, MODE_FILE, O_CREAT},
usercopy::{UserSliceRo, UserSliceWo},
flag::{CallFlags, EventFlags, MODE_DIR, MODE_FILE, O_CREAT},
usercopy::{UserSliceRo, UserSliceRw, UserSliceWo},
},
};
@@ -330,4 +330,45 @@ impl KernelScheme for RootScheme {
Ok(())
}
fn kfdwrite(
&self,
id: usize,
descs: Vec<Arc<RwLock<FileDescription>>>,
flags: CallFlags,
arg: u64,
metadata: &[u64],
) -> Result<usize> {
let handle = {
let handles = self.handles.read();
let handle = handles.get(&id).ok_or(Error::new(EBADF))?;
handle.clone()
};
match handle {
Handle::Scheme(inner) => inner.call_fdwrite(descs, flags, arg, metadata),
Handle::File(_) => Err(Error::new(EBADF)),
Handle::List { .. } => Err(Error::new(EISDIR)),
}
}
fn kfdread(
&self,
id: usize,
payload: UserSliceRw,
flags: CallFlags,
metadata: &[u64],
) -> Result<usize> {
let handle = {
let handles = self.handles.read();
let handle = handles.get(&id).ok_or(Error::new(EBADF))?;
handle.clone()
};
match handle {
Handle::Scheme(inner) => inner.call_fdread(payload, flags, metadata),
Handle::File(_) => Err(Error::new(EBADF)),
Handle::List { .. } => Err(Error::new(EISDIR)),
}
}
}
+322 -57
View File
@@ -15,8 +15,9 @@ use spin::{Mutex, RwLock};
use spinning_top::RwSpinlock;
use syscall::{
schemev2::{Cqe, CqeOpcode, Opcode, Sqe, SqeFlags},
CallFlags, FobtainFdFlags, MunmapFlags, SendFdFlags, F_SETFL, KSMSG_CANCEL,
MAP_FIXED_NOREPLACE, SKMSG_FOBTAINFD, SKMSG_FRETURNFD, SKMSG_PROVIDE_MMAP,
CallFlags, FmoveFdFlags, FobtainFdFlags, MunmapFlags, RecvFdFlags, SchemeSocketCall,
SendFdFlags, F_SETFL, KSMSG_CANCEL, MAP_FIXED_NOREPLACE, SKMSG_FOBTAINFD, SKMSG_FRETURNFD,
SKMSG_PROVIDE_MMAP,
};
use crate::{
@@ -65,7 +66,7 @@ pub struct UserInner {
enum State {
Waiting {
context: Weak<RwSpinlock<Context>>,
fd: Option<Arc<RwLock<FileDescription>>>,
fds: Option<Vec<Arc<RwLock<FileDescription>>>>,
callee_responsible: PageSpan,
canceling: bool,
},
@@ -78,6 +79,7 @@ enum State {
pub enum Response {
Regular(usize, u8),
Fd(Arc<RwLock<FileDescription>>),
MultipleFds(Option<Vec<Arc<RwLock<FileDescription>>>>),
}
const ONE: NonZeroUsize = match NonZeroUsize::new(1) {
@@ -99,6 +101,10 @@ enum ParsedCqe {
tag: u32,
fd: usize,
},
ResponseWithMultipleFds {
tag: u32,
num_fds: usize,
},
ObtainFd {
tag: u32,
flags: FobtainFdFlags,
@@ -167,7 +173,7 @@ impl ParsedCqe {
}
fn parse_cqe(cqe: &Cqe) -> Result<Self> {
Ok(
match CqeOpcode::try_from_raw(cqe.flags & 0b11).ok_or(Error::new(EINVAL))? {
match CqeOpcode::try_from_raw(cqe.flags & 0b111).ok_or(Error::new(EINVAL))? {
CqeOpcode::RespondRegular => Self::RegularResponse {
tag: cqe.tag,
code: cqe.result as usize,
@@ -177,6 +183,10 @@ impl ParsedCqe {
tag: cqe.tag,
fd: cqe.result as usize,
},
CqeOpcode::RespondWithMultipleFds => Self::ResponseWithMultipleFds {
tag: cqe.tag,
num_fds: cqe.result as usize,
},
CqeOpcode::SendFevent => Self::TriggerFevent {
number: cqe.result as usize,
flags: EventFlags::from_bits(cqe.tag as usize).ok_or(Error::new(EINVAL))?,
@@ -249,24 +259,26 @@ impl UserInner {
match self.call_extended(ctx, None, opcode, args, caller_responsible)? {
Response::Regular(code, _) => Error::demux(code),
Response::Fd(_) => Err(Error::new(EIO)),
Response::MultipleFds(_) => Err(Error::new(EIO)),
}
}
pub fn call_extended(
&self,
ctx: CallerCtx,
fd: Option<Arc<RwLock<FileDescription>>>,
fds: Option<Vec<Arc<RwLock<FileDescription>>>>,
opcode: Opcode,
args: impl Args,
caller_responsible: &mut PageSpan,
) -> Result<Response> {
let next_id = self.next_id()?;
self.call_extended_inner(
fd,
fds,
Sqe {
opcode: opcode as u8,
sqe_flags: SqeFlags::empty(),
_rsvd: 0,
tag: self.next_id()?,
tag: next_id,
caller: ctx.pid as u64,
args: {
let mut a = args.args();
@@ -280,7 +292,7 @@ impl UserInner {
fn call_extended_inner(
&self,
fd: Option<Arc<RwLock<FileDescription>>>,
fds: Option<Vec<Arc<RwLock<FileDescription>>>>,
sqe: Sqe,
caller_responsible: &mut PageSpan,
) -> Result<Response> {
@@ -295,7 +307,7 @@ impl UserInner {
current_context.write().block("UserScheme::call");
states[sqe.tag as usize] = State::Waiting {
context: Arc::downgrade(&current_context),
fd,
fds,
canceling: false,
// This is the part that the scheme handler will deallocate when responding. It
@@ -337,14 +349,14 @@ impl UserInner {
canceling: true,
mut callee_responsible,
context,
fd,
fds,
} => {
let maybe_eintr = eintr_if_sigkill(&mut callee_responsible);
*o = State::Waiting {
canceling: true,
callee_responsible,
context,
fd,
fds,
};
drop(states);
maybe_eintr?;
@@ -354,14 +366,14 @@ impl UserInner {
// spurious wakeup
State::Waiting {
canceling: false,
fd,
fds,
context,
mut callee_responsible,
} => {
let maybe_eintr = eintr_if_sigkill(&mut callee_responsible);
*o = State::Waiting {
canceling: true,
fd,
fds,
context,
callee_responsible,
};
@@ -929,6 +941,9 @@ impl UserInner {
.description,
),
)?,
ParsedCqe::ResponseWithMultipleFds { tag, num_fds } => {
self.respond(tag, Response::MultipleFds(None))?;
}
ParsedCqe::ObtainFd {
tag,
flags,
@@ -940,7 +955,9 @@ impl UserInner {
.get_mut(tag as usize)
.ok_or(Error::new(EINVAL))?
{
State::Waiting { ref mut fd, .. } => fd.take().ok_or(Error::new(ENOENT))?,
State::Waiting { ref mut fds, .. } => {
fds.take().ok_or(Error::new(ENOENT))?.remove(0)
}
_ => return Err(Error::new(ENOENT)),
};
@@ -1039,7 +1056,7 @@ impl UserInner {
Ok(())
}
fn respond(&self, tag: u32, mut response: Response) -> Result<()> {
let to_close;
let to_close: Vec<FileDescription>;
let mut states = self.states.lock();
match states.get_mut(tag as usize) {
@@ -1054,7 +1071,7 @@ impl UserInner {
State::Waiting {
context,
fd,
mut fds,
canceling,
callee_responsible,
} => {
@@ -1078,9 +1095,15 @@ impl UserInner {
*code = Error::mux(Err(Error::new(EIO)));
}
to_close = fd
.and_then(|f| Arc::try_unwrap(f).ok())
.map(RwLock::into_inner);
if let Response::MultipleFds(ref mut response_fds) = response {
*response_fds = fds.take();
}
to_close = fds
.into_iter()
.flatten()
.filter_map(|f| Arc::try_unwrap(f).ok())
.map(RwLock::into_inner)
.collect();
if let Some(context) = context.upgrade() {
context.write().unblock();
@@ -1097,8 +1120,8 @@ impl UserInner {
None => return Err(Error::new(EBADFD)),
}
if let Some(to_close) = to_close {
let _ = to_close.try_close();
for fd in to_close {
let _ = fd.try_close();
}
Ok(())
}
@@ -1155,19 +1178,7 @@ impl UserInner {
let (pid, desc) = {
let context_lock = context::current();
let context = context_lock.read();
// TODO: Faster, cleaner mechanism to get descriptor
let mut desc_res = Err(Error::new(EBADF));
for context_file in context.files.read().iter().flatten() {
let (context_scheme, context_number) = {
let desc = context_file.description.read();
(desc.scheme, desc.number)
};
if context_scheme == self.scheme_id && context_number == file {
desc_res = Ok(context_file.clone());
break;
}
}
let desc = desc_res?;
let desc = context.files.read().find_by_scheme(self.scheme_id, file)?;
(context.pid, desc.description)
};
@@ -1221,6 +1232,7 @@ impl UserInner {
return Err(Error::new(EIO));
}
Response::MultipleFds(_) => return Err(Error::new(EIO)),
};
let file_ref = GrantFileRef {
@@ -1275,6 +1287,196 @@ impl UserInner {
Ok(dst_base.start_address().data())
}
pub fn call_fdwrite(
&self,
descs: Vec<Arc<RwLock<FileDescription>>>,
flags: CallFlags,
_arg: u64,
metadata: &[u64],
) -> Result<usize> {
if metadata.is_empty() {
return Err(Error::new(EINVAL));
}
let Some(verb) = SchemeSocketCall::try_from_raw(metadata[0] as usize) else {
return Err(Error::new(EINVAL));
};
match verb {
SchemeSocketCall::MoveFd => {
if metadata.len() != 2 {
return Err(Error::new(EINVAL));
}
let mut movefd_flags = FmoveFdFlags::empty();
if flags.contains(CallFlags::FD_EXCLUSIVE) {
movefd_flags |= FmoveFdFlags::EXCLUSIVE;
}
if flags.contains(CallFlags::FD_CLONE) {
movefd_flags |= FmoveFdFlags::CLONE;
}
self.handle_movefd(descs, metadata[1] as usize, movefd_flags)
}
_ => Err(Error::new(EINVAL)),
}
}
fn handle_movefd(
&self,
descs: Vec<Arc<RwLock<FileDescription>>>,
request_id: usize,
_flags: FmoveFdFlags,
) -> Result<usize> {
let num_fds = descs.len();
match self
.states
.lock()
.get_mut(request_id)
.ok_or(Error::new(EINVAL))?
{
State::Waiting { ref mut fds, .. } => *fds = Some(descs),
_ => return Err(Error::new(ENOENT)),
};
Ok(num_fds)
}
pub fn call_fdread(
&self,
payload: UserSliceRw,
flags: CallFlags,
metadata: &[u64],
) -> Result<usize> {
if metadata.is_empty() {
return Err(Error::new(EINVAL));
}
log::debug!(
"call_fdread: payload: {} metadata: {}",
payload.len(),
metadata.len()
);
let Some(verb) = SchemeSocketCall::try_from_raw(metadata[0] as usize) else {
return Err(Error::new(EINVAL));
};
match verb {
SchemeSocketCall::ObtainFd => {
if metadata.len() != 2 {
return Err(Error::new(EINVAL));
}
let mut obtainfd_flags = FobtainFdFlags::empty();
if flags.contains(CallFlags::FD_UPPER) {
obtainfd_flags |= FobtainFdFlags::UPPER_TBL;
}
if flags.contains(CallFlags::FD_EXCLUSIVE) {
obtainfd_flags |= FobtainFdFlags::EXCLUSIVE;
}
self.handle_obtainfd(payload, metadata[1] as usize, obtainfd_flags)
}
_ => Err(Error::new(EINVAL)),
}
}
fn handle_obtainfd(
&self,
payload: UserSliceRw,
request_id: usize,
flags: FobtainFdFlags,
) -> Result<usize> {
let descriptions = match self
.states
.lock()
.get_mut(request_id)
.ok_or(Error::new(EINVAL))?
{
State::Waiting { ref mut fds, .. } => fds.take().ok_or(Error::new(ENOENT))?,
_ => return Err(Error::new(ENOENT)),
};
let num_fds = if flags.contains(FobtainFdFlags::UPPER_TBL) {
Self::bulk_insert_fds(descriptions, payload)?
} else {
Self::bulk_add_fds(descriptions, payload)?
};
Ok(num_fds)
}
fn bulk_add_fds(
descriptions: Vec<Arc<RwLock<FileDescription>>>,
payload: UserSliceRw,
) -> Result<usize> {
let cnt = descriptions.len();
if payload.len() != cnt * size_of::<usize>() {
return Err(Error::new(EINVAL));
}
if descriptions.is_empty() {
return Ok(0);
}
let current_lock = context::current();
let current = current_lock.write();
let files: Vec<FileDescriptor> = descriptions
.into_iter()
.map(|description| FileDescriptor {
description,
cloexec: true,
})
.collect();
let handles = current
.bulk_add_files_posix(files)
.ok_or(Error::new(EMFILE))?;
let payload_chunks = payload.in_exact_chunks(size_of::<usize>());
for (handle, chunk) in handles.iter().zip(payload_chunks) {
chunk.copy_from_slice(&handle.get().to_ne_bytes())?;
}
Ok(handles.len())
}
fn bulk_insert_fds(
descriptions: Vec<Arc<RwLock<FileDescription>>>,
payload: UserSliceRw,
) -> Result<usize> {
let cnt = descriptions.len();
if payload.len() != cnt * size_of::<usize>() {
return Err(Error::new(EINVAL));
}
if descriptions.is_empty() {
return Ok(0);
}
let files_iter = descriptions.into_iter().map(|description| FileDescriptor {
description,
cloexec: true,
});
let first_fd = payload
.in_exact_chunks(size_of::<usize>())
.next()
.ok_or(Error::new(EINVAL))?
.read_usize()?;
let current_lock = context::current();
let current = current_lock.write();
if first_fd == usize::MAX {
let files = files_iter.collect::<Vec<_>>();
let handles = current
.bulk_insert_files_upper(files)
.ok_or(Error::new(EMFILE))?;
let payload_chunks = payload.in_exact_chunks(size_of::<usize>());
for (handle, chunk) in handles.iter().zip(payload_chunks) {
chunk.copy_from_slice(&handle.get().to_ne_bytes())?;
}
Ok(handles.len())
} else {
let handles: Vec<FileHandle> = payload
.usizes()
.map(|res| res.map(|i| FileHandle::from(i | syscall::UPPER_FDTBL_TAG)))
.collect::<Result<_, _>>()?;
let files = files_iter.collect::<Vec<_>>();
current.bulk_insert_files_upper_manual(files, &handles)?;
Ok(handles.len())
}
}
}
pub struct CaptureGuard<const READ: bool, const WRITE: bool> {
destroyed: bool,
@@ -1386,6 +1588,7 @@ impl KernelScheme for UserScheme {
)
}),
Response::Fd(desc) => Ok(OpenResult::External(desc)),
Response::MultipleFds(_) => Err(Error::new(EIO)),
}
}
@@ -1418,6 +1621,7 @@ impl KernelScheme for UserScheme {
)
}),
Response::Fd(desc) => Ok(OpenResult::External(desc)),
Response::MultipleFds(_) => Err(Error::new(EIO)),
}
}
@@ -1570,6 +1774,7 @@ impl KernelScheme for UserScheme {
)
}),
Response::Fd(desc) => Ok(OpenResult::External(desc)),
Response::MultipleFds(_) => Err(Error::new(EIO)),
}
}
fn kfpath(&self, file: usize, buf: UserSliceWo) -> Result<usize> {
@@ -1749,29 +1954,7 @@ impl KernelScheme for UserScheme {
match res {
Response::Regular(_, _) => Ok(()),
Response::Fd(_) => Err(Error::new(EIO)),
}
}
fn ksendfd(
&self,
number: usize,
desc: Arc<RwLock<FileDescription>>,
flags: SendFdFlags,
arg: u64,
) -> Result<usize> {
let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
let ctx = context::current().read().caller_ctx();
let res = inner.call_extended(
ctx,
Some(desc),
Opcode::Sendfd,
[number, flags.bits(), arg as usize],
&mut PageSpan::empty(),
)?;
match res {
Response::Regular(res, _) => Error::demux(res),
Response::Fd(_) => Err(Error::new(EIO)),
Response::MultipleFds(_) => Err(Error::new(EIO)),
}
}
fn kcall(
@@ -1811,8 +1994,90 @@ impl KernelScheme for UserScheme {
match res {
Response::Regular(res, _) => Error::demux(res),
Response::Fd(_) => Err(Error::new(EIO)),
Response::MultipleFds(_) => Err(Error::new(EIO)),
}
}
fn kfdwrite(
&self,
number: usize,
descs: Vec<Arc<RwLock<FileDescription>>>,
flags: CallFlags,
arg: u64,
_metadata: &[u64],
) -> Result<usize> {
let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
let mut sendfd_flags = SendFdFlags::empty();
if flags.contains(CallFlags::FD_EXCLUSIVE) {
sendfd_flags |= SendFdFlags::EXCLUSIVE;
}
let ctx = context::current().read().caller_ctx();
let len = descs.len();
let res = inner.call_extended(
ctx,
Some(descs),
Opcode::Sendfd,
[number, sendfd_flags.bits(), arg as usize, len],
&mut PageSpan::empty(),
)?;
match res {
Response::Regular(res, _) => Error::demux(res),
Response::Fd(_) => Err(Error::new(EIO)),
Response::MultipleFds(_) => Err(Error::new(EIO)),
}
}
fn kfdread(
&self,
id: usize,
payload: UserSliceRw,
flags: CallFlags,
_metadata: &[u64],
) -> Result<usize> {
let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?;
if payload.len() % mem::size_of::<usize>() != 0 {
return Err(Error::new(EINVAL));
}
let mut recvfd_flags = RecvFdFlags::empty();
if flags.contains(CallFlags::FD_UPPER) {
recvfd_flags |= RecvFdFlags::UPPER_TBL;
}
let ctx = context::current().read().caller_ctx();
let len = payload.len() / mem::size_of::<usize>();
let res = inner.call_extended(
ctx,
None,
Opcode::Recvfd,
[id, recvfd_flags.bits(), len],
&mut PageSpan::empty(),
)?;
let descriptions_opt = match res {
Response::Regular(res, _) => {
return match Error::demux(res) {
Ok(_) => Err(Error::new(EIO)),
Err(e) => Err(e),
}
}
Response::Fd(_) => return Err(Error::new(EIO)),
Response::MultipleFds(fds) => fds,
};
let num_fds = if let Some(descriptions) = descriptions_opt {
if recvfd_flags.contains(RecvFdFlags::UPPER_TBL) {
UserInner::bulk_insert_fds(descriptions, payload)?
} else {
UserInner::bulk_add_fds(descriptions, payload)?
}
} else {
0
};
Ok(num_fds)
}
}
pub trait Args: Copy {
+110 -21
View File
@@ -1,4 +1,5 @@
//! Filesystem syscalls
use core::mem::size_of;
use core::num::NonZeroUsize;
use alloc::{string::String, sync::Arc, vec::Vec};
@@ -319,6 +320,23 @@ pub fn call(
core::slice::from_raw_parts_mut(meta.as_mut_ptr().cast(), meta.len() * 8)
})?;
match flags {
f if f.contains(CallFlags::WRITE | CallFlags::FD) => {
call_fdwrite(fd, payload, flags, &meta[..copied / 8])
}
f if f.contains(CallFlags::READ | CallFlags::FD) => {
call_fdread(fd, payload, flags, &meta[..copied / 8])
}
_ => call_normal(fd, payload, flags, &meta[..copied / 8]),
}
}
fn call_normal(
fd: FileHandle,
payload: UserSliceRw,
flags: CallFlags,
metadata: &[u64],
) -> Result<usize> {
let file = (match (
context::current().read(),
flags.contains(CallFlags::CONSUME),
@@ -337,18 +355,42 @@ pub fn call(
.ok_or(Error::new(EBADFD))?
.clone();
scheme.kcall(number, payload, flags, &meta[..copied / 8])
scheme.kcall(number, payload, flags, metadata)
}
pub fn sendfd(socket: FileHandle, fd: FileHandle, flags_raw: usize, arg: u64) -> Result<usize> {
let requested_flags = SendFdFlags::from_bits(flags_raw).ok_or(Error::new(EINVAL))?;
fn call_fdwrite(
fd: FileHandle,
payload: UserSliceRw,
flags: CallFlags,
metadata: &[u64],
) -> Result<usize> {
let payload_chunks = payload.in_exact_chunks(size_of::<usize>());
let fds = payload_chunks
.map(|chunk| {
let fd = chunk.read_usize()?;
Ok(FileHandle::from(fd))
})
.collect::<Result<Vec<_>>>()?;
let (scheme, number, desc_to_send) = {
let len = fds.len();
fdwrite_inner(fd, fds, flags, 0, metadata)?;
Ok(len)
}
fn fdwrite_inner(
socket: FileHandle,
target_fds: Vec<FileHandle>,
flags: CallFlags,
arg: u64,
metadata: &[u64],
) -> Result<usize> {
// TODO: Ensure deadlocks can't happen
let (scheme, number, descs_to_send) = {
let current_lock = context::current();
let current = current_lock.read();
// TODO: Ensure deadlocks can't happen
let (scheme, number) = match current
.get_file(socket)
.ok_or(Error::new(EBADF))?
@@ -365,27 +407,74 @@ pub fn sendfd(socket: FileHandle, fd: FileHandle, flags_raw: usize, arg: u64) ->
(
scheme,
number,
current
.remove_file(fd)
.ok_or(Error::new(EBADF))?
.description,
if flags.contains(CallFlags::FD_CLONE) {
current.bulk_get_files(&target_fds)
} else {
current.bulk_remove_files(&target_fds)
}?
.into_iter()
.map(|f| f.description)
.collect(),
)
};
// Inform the scheme whether there are still references to the file description to be sent,
// either in the current file table or in other file tables, regardless of whether EXCLUSIVE is
// requested.
let flags_to_scheme = if Arc::strong_count(&desc_to_send) == 1 {
SendFdFlags::EXCLUSIVE
} else {
if requested_flags.contains(SendFdFlags::EXCLUSIVE) {
return Err(Error::new(EBUSY));
// Inform the scheme whether there are still references to the file description to be sent,
// either in the current file table or in other file tables, regardless of whether EXCLUSIVE is
// requested.
let flags_to_scheme = if flags.contains(CallFlags::FD_EXCLUSIVE) {
for desc in &descs_to_send {
if Arc::strong_count(desc) > 1 {
return Err(Error::new(EBUSY));
}
}
SendFdFlags::empty()
CallFlags::FD_EXCLUSIVE
} else {
CallFlags::empty()
};
scheme.ksendfd(number, desc_to_send, flags_to_scheme, arg)
scheme.kfdwrite(number, descs_to_send, flags_to_scheme, arg, metadata)
}
fn call_fdread(
fd: FileHandle,
payload: UserSliceRw,
flags: CallFlags,
metadata: &[u64],
) -> Result<usize> {
let (scheme, number) = {
let current_lock = context::current();
let current = current_lock.read();
let (scheme, number) = match current
.get_file(fd)
.ok_or(Error::new(EBADF))?
.description
.read()
{
ref desc => (desc.scheme, desc.number),
};
let scheme = scheme::schemes()
.get(scheme)
.ok_or(Error::new(ENODEV))?
.clone();
(scheme, number)
};
scheme.kfdread(number, payload, flags, metadata)
}
pub fn sendfd(socket: FileHandle, fd: FileHandle, flags_raw: usize, arg: u64) -> Result<usize> {
let sendfd_flags = SendFdFlags::from_bits(flags_raw).ok_or(Error::new(EINVAL))?;
let mut call_flags = CallFlags::FD | CallFlags::WRITE;
if sendfd_flags.contains(SendFdFlags::CLONE) {
call_flags |= CallFlags::FD_CLONE;
}
if sendfd_flags.contains(SendFdFlags::EXCLUSIVE) {
call_flags |= CallFlags::FD_EXCLUSIVE;
}
fdwrite_inner(socket, Vec::from([fd]), call_flags, arg, &[])
}
/// File descriptor controls
+4 -7
View File
@@ -14,6 +14,7 @@ use crate::{
syscall::EventFlags,
};
use crate::context::context::FdTbl;
use crate::{
context,
paging::{Page, VirtualAddress, PAGE_SIZE},
@@ -24,14 +25,14 @@ use crate::{
use super::usercopy::UserSliceWo;
pub fn exit_this_context(excp: Option<syscall::Exception>) -> ! {
let close_files;
let mut close_files;
let addrspace_opt;
let context_lock = context::current();
{
let mut context = context_lock.write();
close_files = Arc::try_unwrap(mem::take(&mut context.files))
.map_or_else(|_| Vec::new(), RwLock::into_inner);
.map_or_else(|_| FdTbl::new(), RwLock::into_inner);
addrspace_opt = context
.set_addr_space(None)
.and_then(|a| Arc::try_unwrap(a).ok());
@@ -40,11 +41,7 @@ pub fn exit_this_context(excp: Option<syscall::Exception>) -> ! {
}
// Files must be closed while context is valid so that messages can be passed
for file_opt in close_files.into_iter() {
if let Some(file) = file_opt {
let _ = file.close();
}
}
close_files.force_close_all();
drop(addrspace_opt);
// TODO: Should status == Status::HardBlocked be handled differently?
let owner = {