2016 lines
54 KiB
Rust
2016 lines
54 KiB
Rust
use core::{
|
|
mem::size_of,
|
|
ptr::addr_of,
|
|
sync::atomic::{AtomicU32, Ordering},
|
|
};
|
|
|
|
use ioslice::IoSlice;
|
|
use syscall::{
|
|
Call, CallFlags, EINVAL, ERESTART, StdFsCallKind, TimeSpec,
|
|
data::StdFsCallMeta,
|
|
error::{self, EAGAIN, EBADF, EEXIST, EINTR, EMFILE, ENODEV, ESRCH, Error, Result},
|
|
};
|
|
|
|
pub use redox_path::RedoxPath;
|
|
|
|
use crate::{
|
|
DYNAMIC_PROC_INFO, DynamicProcInfo, FILETABLE, RtTcb, Tcb,
|
|
arch::manually_enter_trampoline,
|
|
proc::{FdGuard, FdGuardUpper},
|
|
read_proc_meta,
|
|
signal::tmp_disable_signals,
|
|
};
|
|
use alloc::{collections::btree_set::BTreeSet, vec::Vec};
|
|
use redox_protocols::protocol::{
|
|
F_DUPFD_CLOEXEC, NsDup, O_CLOEXEC, ProcCall, ProcKillTarget, RtSigInfo, ThreadCall, WaitFlags,
|
|
};
|
|
|
|
#[inline]
|
|
fn wrapper<T>(restart: bool, erestart: bool, mut f: impl FnMut() -> Result<T>) -> Result<T> {
|
|
loop {
|
|
let _guard = tmp_disable_signals();
|
|
let rt_sigarea = unsafe { &Tcb::current().unwrap().os_specific };
|
|
let res = f();
|
|
let code = if erestart { ERESTART } else { EINTR };
|
|
|
|
if let Err(err) = res
|
|
&& err == Error::new(code)
|
|
{
|
|
unsafe {
|
|
manually_enter_trampoline();
|
|
}
|
|
if restart && unsafe { (*rt_sigarea.arch.get()).last_sig_was_restart } {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
}
|
|
// TODO: uninitialized memory?
|
|
#[inline]
|
|
pub fn posix_read(fd: usize, buf: &mut [u8]) -> Result<usize> {
|
|
wrapper(true, false, || syscall::read(fd, buf))
|
|
}
|
|
#[inline]
|
|
pub fn posix_write(fd: usize, buf: &[u8]) -> Result<usize> {
|
|
wrapper(true, false, || syscall::write(fd, buf))
|
|
}
|
|
#[inline]
|
|
pub fn posix_kill(target: ProcKillTarget, sig: usize) -> Result<()> {
|
|
if sig > 64 {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
|
|
match wrapper(false, true, || {
|
|
this_proc_call(
|
|
&mut [],
|
|
CallFlags::empty(),
|
|
&[ProcCall::Kill as u64, target.raw() as u64, sig as u64],
|
|
)
|
|
}) {
|
|
Ok(_) | Err(Error { errno: ERESTART }) => Ok(()),
|
|
Err(error) => Err(error),
|
|
}
|
|
}
|
|
#[inline]
|
|
pub fn posix_sigqueue(pid: usize, sig: usize, arg: usize) -> Result<()> {
|
|
let target = ProcKillTarget::from_raw(pid);
|
|
if !matches!(target, ProcKillTarget::SingleProc(_)) {
|
|
return Err(Error::new(ESRCH));
|
|
}
|
|
if sig <= 32 {
|
|
return posix_kill(target, sig);
|
|
}
|
|
let mut siginf = RtSigInfo {
|
|
arg,
|
|
code: -1, // TODO: SI_QUEUE constant
|
|
uid: 0, // TODO
|
|
pid: posix_getpid(),
|
|
};
|
|
match wrapper(false, true, || {
|
|
this_proc_call(
|
|
unsafe { plain::as_mut_bytes(&mut siginf) },
|
|
CallFlags::empty(),
|
|
&[ProcCall::Sigq as u64, pid as u64, sig as u64],
|
|
)
|
|
}) {
|
|
Ok(_)
|
|
| Err(Error {
|
|
errno: error::ERESTART,
|
|
}) => Ok(()),
|
|
Err(error) => Err(error),
|
|
}
|
|
}
|
|
#[inline]
|
|
pub fn posix_getpid() -> u32 {
|
|
// SAFETY: read-only except during program/fork child initialization
|
|
unsafe { addr_of!((*crate::STATIC_PROC_INFO.get()).pid).read() }
|
|
}
|
|
#[inline]
|
|
pub fn posix_getppid() -> u32 {
|
|
this_proc_call(&mut [], CallFlags::empty(), &[ProcCall::Getppid as u64]).expect("cannot fail")
|
|
as u32
|
|
}
|
|
|
|
#[inline]
|
|
pub fn posix_setpriority(which: i32, who: u32, prio: u32) -> Result<(), syscall::Error> {
|
|
if which != 0 {
|
|
return Err(syscall::Error::new(syscall::EINVAL)); // TODO: Add support for PRIO_PGRP and PRIO_PROCESS
|
|
}
|
|
|
|
this_proc_call(
|
|
&mut [],
|
|
CallFlags::empty(),
|
|
&[
|
|
ProcCall::SetProcPriority as u64,
|
|
u64::from(who),
|
|
u64::from(prio),
|
|
],
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
pub fn posix_getpriority(which: i32, who: u32) -> Result<u32, syscall::Error> {
|
|
if which != 0 {
|
|
return Err(syscall::Error::new(syscall::EINVAL));
|
|
}
|
|
|
|
let res = this_proc_call(
|
|
&mut [],
|
|
CallFlags::empty(),
|
|
&[ProcCall::GetProcPriority as u64, u64::from(who)],
|
|
)?;
|
|
|
|
Ok(res as u32)
|
|
}
|
|
|
|
#[inline]
|
|
pub unsafe fn sys_futex_wait(addr: *mut u32, val: u32, deadline: Option<&TimeSpec>) -> Result<()> {
|
|
wrapper(true, false, || {
|
|
unsafe {
|
|
syscall::syscall5(
|
|
syscall::SYS_FUTEX,
|
|
addr as usize,
|
|
syscall::FUTEX_WAIT,
|
|
val as usize,
|
|
deadline.map_or(0, |d| core::ptr::from_ref(d) as usize),
|
|
0,
|
|
)
|
|
}
|
|
.map(|_| ())
|
|
})
|
|
}
|
|
#[inline]
|
|
pub unsafe fn sys_futex_wake(addr: *mut u32, num: u32) -> Result<u32> {
|
|
unsafe {
|
|
syscall::syscall5(
|
|
syscall::SYS_FUTEX,
|
|
addr as usize,
|
|
syscall::FUTEX_WAKE,
|
|
num as usize,
|
|
0,
|
|
0,
|
|
)
|
|
}
|
|
.map(|awoken| awoken as u32)
|
|
}
|
|
pub fn sys_call_ro<T: Call>(
|
|
fd: T,
|
|
payload: &mut [u8],
|
|
flags: CallFlags,
|
|
metadata: &[u64],
|
|
) -> Result<usize> {
|
|
if !flags.contains(CallFlags::FD) {
|
|
return unsafe {
|
|
fd.raw_call(
|
|
payload.as_mut_ptr(),
|
|
payload.len(),
|
|
flags | CallFlags::READ,
|
|
metadata,
|
|
)
|
|
};
|
|
}
|
|
|
|
let _siglock = tmp_disable_signals();
|
|
|
|
if !payload.len().is_multiple_of(size_of::<usize>()) {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
|
|
let fd_slice = unsafe {
|
|
core::slice::from_raw_parts_mut(
|
|
payload.as_mut_ptr().cast::<usize>(),
|
|
payload.len() / size_of::<usize>(),
|
|
)
|
|
};
|
|
|
|
if fd_slice.is_empty() {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
|
|
let is_automated = fd_slice[0] == usize::MAX;
|
|
let mut backup_handles = Vec::with_capacity(fd_slice.len());
|
|
|
|
if !is_automated {
|
|
backup_handles.extend_from_slice(fd_slice);
|
|
}
|
|
|
|
let which = if flags.contains(CallFlags::FD_UPPER) {
|
|
syscall::UPPER_FDTBL_TAG
|
|
} else {
|
|
0
|
|
};
|
|
let entry_flags = if flags.contains(CallFlags::FD_CLOEXEC) {
|
|
O_CLOEXEC
|
|
} else {
|
|
0
|
|
};
|
|
|
|
FILETABLE.lock().bulk_insert(which, fd_slice, entry_flags)?;
|
|
|
|
if is_automated {
|
|
backup_handles.extend_from_slice(fd_slice);
|
|
}
|
|
|
|
let res = unsafe {
|
|
fd.raw_call(
|
|
payload.as_mut_ptr(),
|
|
payload.len(),
|
|
flags | CallFlags::READ,
|
|
metadata,
|
|
)
|
|
};
|
|
|
|
if res.is_err() {
|
|
let mut guard = FILETABLE.lock();
|
|
for &handle in &backup_handles {
|
|
let _ = guard.remove(handle);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
pub fn sys_call_wo<T: Call>(
|
|
fd: T,
|
|
payload: &[u8],
|
|
flags: CallFlags,
|
|
metadata: &[u64],
|
|
) -> Result<usize> {
|
|
if !flags.contains(CallFlags::FD) {
|
|
return unsafe {
|
|
fd.raw_call(
|
|
payload.as_ptr(),
|
|
payload.len(),
|
|
flags | CallFlags::WRITE,
|
|
metadata,
|
|
)
|
|
};
|
|
}
|
|
let _siglock = tmp_disable_signals();
|
|
|
|
if !payload.len().is_multiple_of(size_of::<usize>()) {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
let fd_slice = unsafe {
|
|
core::slice::from_raw_parts(
|
|
payload.as_ptr().cast::<usize>(),
|
|
payload.len() / size_of::<usize>(),
|
|
)
|
|
};
|
|
|
|
let res = unsafe {
|
|
fd.raw_call(
|
|
payload.as_ptr(),
|
|
payload.len(),
|
|
flags | CallFlags::WRITE,
|
|
metadata,
|
|
)
|
|
};
|
|
|
|
if res.is_ok() && !flags.contains(CallFlags::FD_CLONE) {
|
|
let mut guard = FILETABLE.lock();
|
|
for &handle in fd_slice {
|
|
let _ = guard.remove(handle);
|
|
}
|
|
}
|
|
|
|
res
|
|
}
|
|
pub fn sys_call_rw<T: Call>(
|
|
fd: T,
|
|
payload: &mut [u8],
|
|
flags: CallFlags,
|
|
metadata: &[u64],
|
|
) -> Result<usize> {
|
|
unsafe {
|
|
fd.raw_call(
|
|
payload.as_mut_ptr(),
|
|
payload.len(),
|
|
flags | CallFlags::READ | CallFlags::WRITE,
|
|
metadata,
|
|
)
|
|
}
|
|
}
|
|
pub fn sys_call<T: Call>(
|
|
fd: T,
|
|
payload: &mut [u8],
|
|
flags: CallFlags,
|
|
metadata: &[u64],
|
|
) -> Result<usize> {
|
|
unsafe { fd.raw_call(payload.as_mut_ptr(), payload.len(), flags, metadata) }
|
|
}
|
|
|
|
pub fn this_proc_call(payload: &mut [u8], flags: CallFlags, metadata: &[u64]) -> Result<usize> {
|
|
proc_call(
|
|
crate::current_proc_fd().as_raw_fd(),
|
|
payload,
|
|
flags,
|
|
metadata,
|
|
)
|
|
}
|
|
pub fn proc_call(
|
|
proc_fd: usize,
|
|
payload: &mut [u8],
|
|
flags: CallFlags,
|
|
metadata: &[u64],
|
|
) -> Result<usize> {
|
|
sys_call(proc_fd, payload, flags, metadata)
|
|
}
|
|
pub fn thread_call(
|
|
thread_fd: usize,
|
|
payload: &mut [u8],
|
|
flags: CallFlags,
|
|
metadata: &[u64],
|
|
) -> Result<usize> {
|
|
sys_call(thread_fd, payload, flags, metadata)
|
|
}
|
|
pub fn this_thread_call(payload: &mut [u8], flags: CallFlags, metadata: &[u64]) -> Result<usize> {
|
|
thread_call(
|
|
RtTcb::current().thread_fd().as_raw_fd(),
|
|
payload,
|
|
flags,
|
|
metadata,
|
|
)
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub enum WaitpidTarget {
|
|
AnyChild,
|
|
AnyGroupMember,
|
|
SingleProc { pid: usize },
|
|
ProcGroup { pgid: usize },
|
|
}
|
|
impl WaitpidTarget {
|
|
pub fn from_posix_arg(raw: isize) -> Self {
|
|
match raw {
|
|
0 => Self::AnyGroupMember,
|
|
-1 => Self::AnyChild,
|
|
1.. => Self::SingleProc { pid: raw as usize },
|
|
..-1 => Self::ProcGroup {
|
|
pgid: -raw as usize,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn sys_waitpid(target: WaitpidTarget, status: &mut usize, flags: WaitFlags) -> Result<usize> {
|
|
let (call, pid) = match target {
|
|
WaitpidTarget::AnyChild => (ProcCall::Waitpid, 0),
|
|
WaitpidTarget::SingleProc { pid } => (ProcCall::Waitpid, pid),
|
|
WaitpidTarget::AnyGroupMember => (ProcCall::Waitpgid, 0),
|
|
WaitpidTarget::ProcGroup { pgid } => (ProcCall::Waitpgid, pgid),
|
|
};
|
|
wrapper(true, false, || {
|
|
this_proc_call(
|
|
unsafe { plain::as_mut_bytes(status) },
|
|
CallFlags::empty(),
|
|
&[call as u64, pid as u64, flags.bits() as u64],
|
|
)
|
|
})
|
|
}
|
|
pub fn posix_kill_thread(thread_fd: usize, signal: u32) -> Result<()> {
|
|
// TODO: don't hardcode?
|
|
if signal > 64 {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
|
|
match wrapper(false, true, || {
|
|
thread_call(
|
|
thread_fd,
|
|
&mut [],
|
|
CallFlags::empty(),
|
|
&[ThreadCall::SignalThread as u64, signal.into()],
|
|
)
|
|
}) {
|
|
Ok(_) | Err(Error { errno: ERESTART }) => Ok(()),
|
|
Err(error) => Err(error),
|
|
}
|
|
}
|
|
|
|
static UMASK: AtomicU32 = AtomicU32::new(0o022);
|
|
|
|
/// Controls the set of bits removed from the `mode` mask when new file descriptors are created.
|
|
///
|
|
/// Must be validated by the caller
|
|
//
|
|
// TODO: validate here?
|
|
#[inline]
|
|
pub fn swap_umask(mask: u32) -> u32 {
|
|
UMASK.swap(mask, Ordering::AcqRel)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_umask() -> u32 {
|
|
UMASK.load(Ordering::Acquire)
|
|
}
|
|
|
|
/// Real/Effective/Set-User/Group ID
|
|
pub struct Resugid<T> {
|
|
pub ruid: T,
|
|
pub euid: T,
|
|
pub suid: T,
|
|
pub rgid: T,
|
|
pub egid: T,
|
|
pub sgid: T,
|
|
}
|
|
|
|
/// Sets [res][ug]id, fields that are None will be unchanged.
|
|
pub fn posix_setresugid(ids: &Resugid<Option<u32>>, pid: Option<usize>) -> Result<()> {
|
|
// TODO: not sure how "tmp" an IPC call is?
|
|
let _sig_guard = tmp_disable_signals();
|
|
let mut guard = DYNAMIC_PROC_INFO.lock();
|
|
|
|
let mut buf = [0_u8; size_of::<u32>() * 6];
|
|
plain::slice_from_mut_bytes(&mut buf)
|
|
.unwrap()
|
|
.copy_from_slice(&[
|
|
ids.ruid.unwrap_or(u32::MAX),
|
|
ids.euid.unwrap_or(u32::MAX),
|
|
ids.suid.unwrap_or(u32::MAX),
|
|
ids.rgid.unwrap_or(u32::MAX),
|
|
ids.egid.unwrap_or(u32::MAX),
|
|
ids.sgid.unwrap_or(u32::MAX),
|
|
]);
|
|
|
|
if let Some(pid) = pid {
|
|
proc_call(
|
|
pid,
|
|
&mut buf,
|
|
CallFlags::empty(),
|
|
&[ProcCall::SetResugid as u64],
|
|
)?;
|
|
} else {
|
|
this_proc_call(&mut buf, CallFlags::empty(), &[ProcCall::SetResugid as u64])?;
|
|
}
|
|
|
|
if let Some(ruid) = ids.ruid {
|
|
guard.ruid = ruid;
|
|
}
|
|
if let Some(euid) = ids.euid {
|
|
guard.euid = euid;
|
|
}
|
|
if let Some(suid) = ids.suid {
|
|
guard.suid = suid;
|
|
}
|
|
if let Some(rgid) = ids.rgid {
|
|
guard.rgid = rgid;
|
|
}
|
|
if let Some(egid) = ids.egid {
|
|
guard.egid = egid;
|
|
}
|
|
if let Some(sgid) = ids.sgid {
|
|
guard.sgid = sgid;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
pub fn posix_getresugid() -> Resugid<u32> {
|
|
let _sig_guard = tmp_disable_signals();
|
|
let DynamicProcInfo {
|
|
ruid,
|
|
euid,
|
|
suid,
|
|
rgid,
|
|
egid,
|
|
sgid,
|
|
..
|
|
} = *DYNAMIC_PROC_INFO.lock();
|
|
Resugid {
|
|
ruid,
|
|
euid,
|
|
suid,
|
|
rgid,
|
|
egid,
|
|
sgid,
|
|
}
|
|
}
|
|
pub fn getens() -> Result<usize> {
|
|
read_proc_meta(crate::current_proc_fd()).map(|meta| meta.ens as usize)
|
|
}
|
|
pub fn get_proc_credentials(cap_fd: usize, target_pid: usize, buf: &mut [u8]) -> Result<usize> {
|
|
if buf.len() < size_of::<redox_protocols::protocol::ProcMeta>() {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
proc_call(
|
|
cap_fd,
|
|
buf,
|
|
CallFlags::empty(),
|
|
&[ProcCall::GetProcCredentials as u64, target_pid as u64],
|
|
)
|
|
}
|
|
pub fn posix_exit(status: i32) -> ! {
|
|
loop {
|
|
match this_proc_call(
|
|
&mut [],
|
|
CallFlags::empty(),
|
|
&[ProcCall::Exit as u64, (status & 0xFF) as u64],
|
|
) {
|
|
Ok(_) => break,
|
|
// procmgr can sometimes send EAGAIN.
|
|
// cannot disable signals so kernel might send EINTR.
|
|
Err(Error { errno: EINTR } | Error { errno: EAGAIN }) => continue,
|
|
Err(e) => panic!("failed to call proc mgr with Exit: {e}"),
|
|
}
|
|
}
|
|
let _ = syscall::write(1, b"redox-rt: ProcCall::Exit FAILED, abort()ing!\n");
|
|
core::intrinsics::abort();
|
|
}
|
|
pub fn posix_getpgid(pid: usize) -> Result<usize> {
|
|
this_proc_call(
|
|
&mut [],
|
|
CallFlags::empty(),
|
|
&[ProcCall::Setpgid as u64, pid as u64, u64::wrapping_neg(1)],
|
|
)
|
|
}
|
|
pub fn posix_setpgid(pid: usize, pgid: usize) -> Result<()> {
|
|
if pgid == usize::wrapping_neg(1) {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
this_proc_call(
|
|
&mut [],
|
|
CallFlags::empty(),
|
|
&[ProcCall::Setpgid as u64, pid as u64, pgid as u64],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
pub fn posix_getsid(pid: usize) -> Result<usize> {
|
|
this_proc_call(
|
|
&mut [],
|
|
CallFlags::empty(),
|
|
&[ProcCall::Getsid as u64, pid as u64],
|
|
)
|
|
}
|
|
pub fn posix_setsid() -> Result<u32> {
|
|
this_proc_call(&mut [], CallFlags::empty(), &[ProcCall::Setsid as u64])?;
|
|
Ok(posix_getpid())
|
|
}
|
|
pub fn posix_nanosleep(rqtp: &TimeSpec, rmtp: &mut TimeSpec) -> Result<()> {
|
|
wrapper(false, false, || syscall::nanosleep(rqtp, rmtp))?;
|
|
Ok(())
|
|
}
|
|
pub fn setns(fd: usize) -> Option<FdGuardUpper> {
|
|
let mut info = DYNAMIC_PROC_INFO.lock();
|
|
let new_fd_guard = FdGuard::new(fd).to_upper().unwrap();
|
|
info.ns_fd.replace(new_fd_guard)
|
|
}
|
|
pub fn getns() -> Result<usize> {
|
|
let cur_ns = crate::current_namespace_fd()?;
|
|
if cur_ns == usize::MAX {
|
|
Err(Error::new(ENODEV))
|
|
} else {
|
|
Ok(cur_ns)
|
|
}
|
|
}
|
|
|
|
pub fn open<T: AsRef<str>>(path: T, flags: usize) -> Result<usize> {
|
|
let _siglock = tmp_disable_signals();
|
|
let fcntl_flags = flags & syscall::O_FCNTL_MASK;
|
|
let redox_path = RedoxPath::from_absolute(path.as_ref()).ok_or(Error::new(EINVAL))?;
|
|
let (_, reference) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
|
|
let root_fd = FdGuard::new(openat_into_upper(
|
|
crate::current_namespace_fd()?,
|
|
path.as_ref(),
|
|
syscall::O_DIRECTORY | O_CLOEXEC,
|
|
0,
|
|
)?);
|
|
openat_into_posix(root_fd.as_raw_fd(), reference.as_ref(), flags, fcntl_flags)
|
|
}
|
|
pub fn openat<T: AsRef<str>>(
|
|
fd: usize,
|
|
path: T,
|
|
flags: usize,
|
|
fcntl_flags: usize,
|
|
) -> Result<usize> {
|
|
openat_into_posix(fd, path, flags, fcntl_flags)
|
|
}
|
|
fn openat_into_posix<T: AsRef<str>>(
|
|
fd: usize,
|
|
path: T,
|
|
flags: usize,
|
|
fcntl_flags: usize,
|
|
) -> Result<usize> {
|
|
let _siglock = tmp_disable_signals();
|
|
let path = path.as_ref();
|
|
|
|
let out = {
|
|
let mut guard = FILETABLE.lock();
|
|
guard.add_posix(flags)?
|
|
};
|
|
|
|
let res = unsafe {
|
|
syscall::syscall6(
|
|
syscall::SYS_OPENAT_INTO,
|
|
fd,
|
|
path.as_ptr() as usize,
|
|
path.len(),
|
|
flags,
|
|
fcntl_flags,
|
|
out,
|
|
)
|
|
};
|
|
|
|
if res.is_err() {
|
|
let mut guard = FILETABLE.lock();
|
|
let _ = guard.remove(out);
|
|
return res;
|
|
}
|
|
|
|
Ok(out)
|
|
}
|
|
pub fn open_into_upper<T: AsRef<str>>(path: T, flags: usize) -> Result<usize> {
|
|
let _siglock = tmp_disable_signals();
|
|
let fcntl_flags = flags & syscall::O_FCNTL_MASK;
|
|
let redox_path = RedoxPath::from_absolute(path.as_ref()).ok_or(Error::new(EINVAL))?;
|
|
let (_, reference) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
|
|
let root_fd = FdGuard::new(openat_into_upper(
|
|
crate::current_namespace_fd()?,
|
|
path.as_ref(),
|
|
syscall::O_DIRECTORY | O_CLOEXEC,
|
|
0,
|
|
)?);
|
|
openat_into_upper(root_fd.as_raw_fd(), reference.as_ref(), flags, fcntl_flags)
|
|
}
|
|
pub fn dup(fd: usize, buf: &[u8]) -> Result<usize> {
|
|
let _siglock = tmp_disable_signals();
|
|
|
|
let out = {
|
|
let mut guard = FILETABLE.lock();
|
|
guard.add_posix(0)?
|
|
};
|
|
|
|
let res = unsafe {
|
|
syscall::syscall4(
|
|
syscall::SYS_DUP_INTO,
|
|
fd,
|
|
buf.as_ptr() as usize,
|
|
buf.len(),
|
|
out,
|
|
)
|
|
};
|
|
|
|
if res.is_err() {
|
|
let mut guard = FILETABLE.lock();
|
|
let _ = guard.remove(out);
|
|
return res;
|
|
}
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
pub fn dup2(fd: usize, newfd: usize, buf: &[u8]) -> Result<usize> {
|
|
let _siglock = tmp_disable_signals();
|
|
|
|
let out = {
|
|
let mut guard = FILETABLE.lock();
|
|
guard.override_at(fd, newfd)?
|
|
};
|
|
|
|
let res = unsafe {
|
|
syscall::syscall4(
|
|
syscall::SYS_DUP2,
|
|
fd,
|
|
newfd,
|
|
buf.as_ptr() as usize,
|
|
buf.len(),
|
|
)
|
|
};
|
|
|
|
if res.is_err() {
|
|
let mut guard = FILETABLE.lock();
|
|
let _ = guard.remove(out);
|
|
return res;
|
|
}
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
pub fn unlink<T: AsRef<str>>(path: T, flags: usize) -> Result<usize> {
|
|
let _siglock = tmp_disable_signals();
|
|
let path = path.as_ref();
|
|
let fcntl_flags = flags & syscall::O_FCNTL_MASK;
|
|
let redox_path = RedoxPath::from_absolute(path).ok_or(Error::new(EINVAL))?;
|
|
let (_, reference) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
|
|
let root_fd = FdGuard::new(openat_into_upper(
|
|
crate::current_namespace_fd()?,
|
|
path,
|
|
syscall::O_DIRECTORY | O_CLOEXEC,
|
|
0,
|
|
)?);
|
|
let reference_str = reference.as_ref();
|
|
unsafe {
|
|
syscall::syscall4(
|
|
syscall::SYS_UNLINKAT,
|
|
root_fd.as_raw_fd(),
|
|
reference_str.as_ptr() as usize,
|
|
reference_str.len(),
|
|
flags,
|
|
)
|
|
}
|
|
}
|
|
pub fn mkns(names: &[IoSlice]) -> Result<FdGuardUpper> {
|
|
let mut buf = Vec::from((NsDup::ForkNs as usize).to_ne_bytes());
|
|
for name in names {
|
|
let name_bytes = name.as_slice();
|
|
let len = name_bytes.len();
|
|
let _scheme_name = core::str::from_utf8(name_bytes).map_err(|_| Error::new(EINVAL))?;
|
|
buf.extend_from_slice(&len.to_ne_bytes());
|
|
buf.extend_from_slice(name_bytes);
|
|
}
|
|
FdGuard::new(dup_into_upper(crate::current_namespace_fd()?, &buf)?).to_upper()
|
|
}
|
|
pub fn register_scheme_to_ns(ns_fd: usize, name: &str, cap_fd: usize) -> Result<()> {
|
|
let mut buf = alloc::vec::Vec::from((NsDup::IssueRegister as usize).to_ne_bytes());
|
|
buf.extend_from_slice(name.as_bytes());
|
|
let ns_this_scheme = FdGuard::new(crate::sys::dup(ns_fd, &buf)?);
|
|
let cap_bytes = cap_fd.to_ne_bytes();
|
|
ns_this_scheme.call_wo(&cap_bytes, CallFlags::FD, &[])?;
|
|
Ok(())
|
|
}
|
|
pub fn std_fs_call_ro<T: Call>(
|
|
fd: T,
|
|
payload: &mut [u8],
|
|
metadata: &StdFsCallMeta,
|
|
) -> Result<usize> {
|
|
sys_call_ro(fd, payload, CallFlags::STD_FS, metadata)
|
|
}
|
|
pub fn std_fs_call_wo<T: Call>(fd: T, payload: &[u8], metadata: &StdFsCallMeta) -> Result<usize> {
|
|
sys_call_wo(fd, payload, CallFlags::STD_FS, metadata)
|
|
}
|
|
pub fn std_fs_call_rw<T: Call>(
|
|
fd: T,
|
|
payload: &mut [u8],
|
|
metadata: &StdFsCallMeta,
|
|
) -> Result<usize> {
|
|
sys_call_rw(fd, payload, CallFlags::STD_FS, metadata)
|
|
}
|
|
pub fn fstat(fd: usize, stat: &mut syscall::Stat) -> Result<usize> {
|
|
std_fs_call_ro(fd, stat, &StdFsCallMeta::new(StdFsCallKind::Fstat, 0, 0))
|
|
}
|
|
|
|
pub fn fcntl(fd: usize, cmd: usize, arg: usize) -> Result<usize> {
|
|
if cmd == syscall::F_DUPFD || cmd == F_DUPFD_CLOEXEC {
|
|
let _siglock = tmp_disable_signals();
|
|
|
|
let cloexec_flag = if cmd == F_DUPFD_CLOEXEC { O_CLOEXEC } else { 0 };
|
|
|
|
let out = {
|
|
let mut guard = FILETABLE.lock();
|
|
if arg & syscall::UPPER_FDTBL_TAG != 0 {
|
|
guard.insert_upper(cloexec_flag)? | syscall::UPPER_FDTBL_TAG
|
|
} else {
|
|
guard.add_posix(cloexec_flag)?
|
|
}
|
|
};
|
|
|
|
let res = unsafe { syscall::syscall3(syscall::SYS_FCNTL, fd, syscall::F_DUPFD, out) };
|
|
|
|
if res.is_err() {
|
|
let mut guard = FILETABLE.lock();
|
|
let _ = guard.remove(out);
|
|
return res;
|
|
}
|
|
|
|
let actual_fd = res.unwrap();
|
|
if actual_fd != out {
|
|
let mut guard = FILETABLE.lock();
|
|
let _ = guard.remove(out);
|
|
guard.override_at(actual_fd, actual_fd)?;
|
|
if cloexec_flag != 0 {
|
|
guard.set_fd_flags(actual_fd, cloexec_flag)?;
|
|
}
|
|
}
|
|
|
|
return Ok(actual_fd);
|
|
}
|
|
|
|
if cmd == syscall::F_GETFD {
|
|
let _siglock = tmp_disable_signals();
|
|
return FILETABLE.lock().get_fd_flags(fd);
|
|
}
|
|
|
|
if cmd == syscall::F_SETFD {
|
|
let _siglock = tmp_disable_signals();
|
|
|
|
let res = unsafe { syscall::syscall3(syscall::SYS_FCNTL, fd, cmd, arg) };
|
|
res?;
|
|
|
|
FILETABLE.lock().set_fd_flags(fd, arg)?;
|
|
return Ok(0);
|
|
}
|
|
|
|
unsafe { syscall::syscall3(syscall::SYS_FCNTL, fd, cmd, arg) }
|
|
}
|
|
|
|
pub fn openat_into_upper<T: AsRef<str>>(
|
|
fd: usize,
|
|
path: T,
|
|
flags: usize,
|
|
fcntl_flags: usize,
|
|
) -> Result<usize> {
|
|
let _siglock = tmp_disable_signals();
|
|
let path = path.as_ref();
|
|
|
|
let out_idx = {
|
|
let mut guard = FILETABLE.lock();
|
|
guard.insert_upper(flags)?
|
|
};
|
|
let out = out_idx | syscall::UPPER_FDTBL_TAG;
|
|
|
|
let res = unsafe {
|
|
syscall::syscall6(
|
|
syscall::SYS_OPENAT_INTO,
|
|
fd,
|
|
path.as_ptr() as usize,
|
|
path.len(),
|
|
flags,
|
|
fcntl_flags,
|
|
out,
|
|
)
|
|
};
|
|
|
|
if res.is_err() {
|
|
let mut guard = FILETABLE.lock();
|
|
let _ = guard.remove(out);
|
|
return res;
|
|
}
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
pub fn dup_into_upper(fd: usize, buf: &[u8]) -> Result<usize> {
|
|
let _siglock = tmp_disable_signals();
|
|
|
|
let out_idx = {
|
|
let mut guard = FILETABLE.lock();
|
|
guard.insert_upper(0)?
|
|
};
|
|
let out = out_idx | syscall::UPPER_FDTBL_TAG;
|
|
|
|
let res = unsafe {
|
|
syscall::syscall4(
|
|
syscall::SYS_DUP_INTO,
|
|
fd,
|
|
buf.as_ptr() as usize,
|
|
buf.len(),
|
|
out,
|
|
)
|
|
};
|
|
|
|
if res.is_err() {
|
|
let mut guard = FILETABLE.lock();
|
|
let _ = guard.remove(out);
|
|
return res;
|
|
}
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
pub fn dup_into_upper_raw(fd: usize, buf: &[u8]) -> Result<usize> {
|
|
let out_idx = {
|
|
let mut guard = FILETABLE.lock();
|
|
guard.insert_upper(0)?
|
|
};
|
|
let out = out_idx | syscall::UPPER_FDTBL_TAG;
|
|
|
|
let res = unsafe {
|
|
syscall::syscall4(
|
|
syscall::SYS_DUP_INTO,
|
|
fd,
|
|
buf.as_ptr() as usize,
|
|
buf.len(),
|
|
out,
|
|
)
|
|
};
|
|
|
|
if res.is_err() {
|
|
let mut guard = FILETABLE.lock();
|
|
let _ = guard.remove(out);
|
|
return res;
|
|
}
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
pub fn close(fd: usize) -> Result<usize> {
|
|
let _siglock = tmp_disable_signals();
|
|
|
|
let is_upper = (fd & syscall::UPPER_FDTBL_TAG) != 0;
|
|
|
|
let res = unsafe { syscall::syscall1(syscall::SYS_CLOSE, fd) };
|
|
|
|
if res.is_ok() || res.err().is_some_and(|e| e.errno == EBADF) {
|
|
let mut guard = FILETABLE.lock();
|
|
let _ = guard.remove(fd);
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
pub fn close_raw(fd: usize) -> Result<usize> {
|
|
let res = unsafe { syscall::syscall1(syscall::SYS_CLOSE, fd) };
|
|
|
|
if res.is_ok() || res.err().is_some_and(|e| e.errno == EBADF) {
|
|
let mut guard = FILETABLE.lock();
|
|
let _ = guard.remove(fd);
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
pub struct FdTbl {
|
|
fd: Option<FdGuardUpper>,
|
|
posix_fdtbl: PosixFdTbl,
|
|
upper_fdtbl: UpperFdTbl,
|
|
active_count: usize,
|
|
}
|
|
|
|
impl FdTbl {
|
|
pub const CONTEXT_MAX_FILES: u32 = 65_536;
|
|
pub const DEFAULT_CAPACITY: usize = usize::BITS as usize;
|
|
|
|
#[expect(
|
|
clippy::new_without_default,
|
|
reason = "default not expected for this type"
|
|
)]
|
|
pub const fn new() -> Self {
|
|
Self {
|
|
fd: None,
|
|
posix_fdtbl: PosixFdTbl::new(),
|
|
upper_fdtbl: UpperFdTbl::new(),
|
|
active_count: 0,
|
|
}
|
|
}
|
|
|
|
pub fn from_binary_fd(filetable_fd: FdGuardUpper) -> Result<Self> {
|
|
let mut fdtbl = Self::new();
|
|
let files_reader_fd = filetable_fd.as_raw_fd();
|
|
let _ = filetable_fd.lseek(0, syscall::flag::SEEK_SET);
|
|
fdtbl.set_fd(filetable_fd);
|
|
fdtbl.resize(Self::DEFAULT_CAPACITY);
|
|
|
|
let mut reader = crate::proc::FileBufReader::from_fd(files_reader_fd);
|
|
fdtbl.populate(&mut reader)?;
|
|
|
|
// Manually mark the filetable_fd itself as occupied in userspace FILETABLE
|
|
fdtbl.override_at(files_reader_fd, files_reader_fd)?;
|
|
|
|
Ok(fdtbl)
|
|
}
|
|
|
|
pub fn with_capacity(capacity: usize, fd: FdGuardUpper) -> Self {
|
|
Self {
|
|
fd: Some(fd),
|
|
posix_fdtbl: PosixFdTbl::with_capacity(capacity),
|
|
upper_fdtbl: UpperFdTbl::with_capacity(capacity),
|
|
active_count: 0,
|
|
}
|
|
}
|
|
|
|
pub fn fd(&self) -> Option<&FdGuardUpper> {
|
|
self.fd.as_ref()
|
|
}
|
|
|
|
pub fn take(&mut self) -> Option<FdGuardUpper> {
|
|
self.fd.take()
|
|
}
|
|
|
|
pub fn set_fd(&mut self, fd: FdGuardUpper) {
|
|
self.fd = Some(fd);
|
|
}
|
|
|
|
pub fn resize(&mut self, size: usize) {
|
|
self.posix_fdtbl.resize(size);
|
|
self.upper_fdtbl.resize(size);
|
|
}
|
|
|
|
pub fn upper_capacity(&self) -> usize {
|
|
self.upper_fdtbl.capacity()
|
|
}
|
|
|
|
pub fn upper_len(&self) -> usize {
|
|
self.upper_fdtbl.len()
|
|
}
|
|
|
|
fn strip_tags(index: usize) -> usize {
|
|
index & !syscall::UPPER_FDTBL_TAG
|
|
}
|
|
|
|
fn is_upper(index: usize) -> bool {
|
|
(index & syscall::UPPER_FDTBL_TAG) != 0
|
|
}
|
|
|
|
pub(crate) fn populate(&mut self, reader: &mut crate::proc::FileBufReader) -> Result<()> {
|
|
while let Some(fd) = reader.read_le_u64()? {
|
|
let fd = fd as usize;
|
|
self.override_at(fd, fd)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_fd_flags(&self, fd: usize) -> Result<usize> {
|
|
if Self::is_upper(fd) {
|
|
let flags = self.upper_fdtbl.get_flags(fd)?;
|
|
let mut raw_flags = 0;
|
|
if flags & (O_CLOEXEC as u32) != 0 {
|
|
raw_flags |= O_CLOEXEC;
|
|
}
|
|
Ok(raw_flags)
|
|
} else {
|
|
let flags = self.posix_fdtbl.get_flags(fd)?;
|
|
let mut raw_flags = 0;
|
|
if flags.contains(FdFlags::CLOEXEC) {
|
|
raw_flags |= O_CLOEXEC;
|
|
}
|
|
Ok(raw_flags)
|
|
}
|
|
}
|
|
|
|
pub fn set_fd_flags(&mut self, fd: usize, raw_flags: usize) -> Result<()> {
|
|
if Self::is_upper(fd) {
|
|
let old_flags = self.upper_fdtbl.get_flags(fd)?;
|
|
let mut new_flags = old_flags & !(O_CLOEXEC as u32);
|
|
if raw_flags & O_CLOEXEC != 0 {
|
|
new_flags |= O_CLOEXEC as u32;
|
|
}
|
|
self.upper_fdtbl.set_flags(fd, new_flags)?;
|
|
} else {
|
|
let mut new_flags = FdFlags::empty();
|
|
if raw_flags & O_CLOEXEC != 0 {
|
|
new_flags.insert(FdFlags::CLOEXEC);
|
|
}
|
|
self.posix_fdtbl.set_flags(fd, new_flags)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn sync_size(fd: Option<&FdGuardUpper>, new_size: usize, tag: usize) -> Result<()> {
|
|
if let Some(fd) = fd {
|
|
fd.call_wo(
|
|
&[],
|
|
CallFlags::empty(),
|
|
&[
|
|
syscall::FileTableVerb::Resize as u64,
|
|
tag as u64,
|
|
new_size as u64,
|
|
],
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn override_at(&mut self, fd: usize, new_fd: usize) -> Result<usize> {
|
|
let _ = self.remove(new_fd);
|
|
|
|
if Self::is_upper(new_fd) {
|
|
let handle = Self::strip_tags(new_fd);
|
|
self.upper_fdtbl.insert_at(
|
|
handle,
|
|
UpperFdTbl::flags_into_entry(0),
|
|
self.fd.as_ref(),
|
|
)?;
|
|
} else {
|
|
self.posix_fdtbl.insert_at(
|
|
new_fd,
|
|
PosixFdTbl::flags_into_entry(0),
|
|
self.fd.as_ref(),
|
|
)?;
|
|
}
|
|
|
|
self.active_count += 1;
|
|
Ok(new_fd)
|
|
}
|
|
pub fn add_posix(&mut self, entry: usize) -> Result<usize> {
|
|
if self.active_count >= Self::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
|
|
let out_idx = self
|
|
.posix_fdtbl
|
|
.add(PosixFdTbl::flags_into_entry(entry), self.fd.as_ref())?;
|
|
self.active_count += 1;
|
|
|
|
Ok(out_idx)
|
|
}
|
|
|
|
pub fn insert_upper(&mut self, entry: usize) -> Result<usize> {
|
|
if self.active_count >= Self::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
|
|
let old_capacity = self.upper_fdtbl.capacity();
|
|
|
|
let out_idx = self
|
|
.upper_fdtbl
|
|
.insert(UpperFdTbl::flags_into_entry(entry), self.fd.as_ref())?;
|
|
self.active_count += 1;
|
|
|
|
Ok(out_idx)
|
|
}
|
|
|
|
pub fn insert_at_upper(&mut self, new_fd: usize, entry: usize) -> Result<usize> {
|
|
if self.active_count >= Self::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
|
|
let old_capacity = self.upper_fdtbl.capacity();
|
|
if !Self::is_upper(new_fd) {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
let handle = Self::strip_tags(new_fd);
|
|
|
|
let out_idx = self.upper_fdtbl.insert_at(
|
|
handle,
|
|
UpperFdTbl::flags_into_entry(entry),
|
|
self.fd.as_ref(),
|
|
)?;
|
|
self.active_count += 1;
|
|
|
|
Ok(out_idx)
|
|
}
|
|
|
|
pub fn bulk_add(
|
|
&mut self,
|
|
which: usize,
|
|
fd_slice: &mut [usize],
|
|
flags: usize,
|
|
) -> Result<usize> {
|
|
let cnt = fd_slice.len();
|
|
if cnt == 0 {
|
|
return Ok(0);
|
|
}
|
|
|
|
if self.active_count + cnt > Self::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
|
|
if which & syscall::UPPER_FDTBL_TAG == 0 {
|
|
let old_capacity = self.posix_fdtbl.capacity();
|
|
|
|
let initial_flag = PosixFdTbl::flags_into_entry(flags);
|
|
let entries = alloc::vec![initial_flag; cnt];
|
|
|
|
let handles = self.posix_fdtbl.bulk_add_posix(entries, self.fd.as_ref())?;
|
|
self.active_count += cnt;
|
|
|
|
for (i, &handle) in handles.iter().enumerate() {
|
|
fd_slice[i] = handle;
|
|
}
|
|
} else {
|
|
let old_capacity = self.upper_fdtbl.capacity();
|
|
|
|
let entries = alloc::vec![UpperFdTbl::flags_into_entry(flags); cnt];
|
|
let handles = self.upper_fdtbl.bulk_insert(entries, self.fd.as_ref())?;
|
|
self.active_count += cnt;
|
|
|
|
for (i, &handle) in handles.iter().enumerate() {
|
|
fd_slice[i] = handle | syscall::UPPER_FDTBL_TAG;
|
|
}
|
|
}
|
|
|
|
Ok(cnt)
|
|
}
|
|
|
|
pub fn bulk_insert(
|
|
&mut self,
|
|
which: usize,
|
|
fd_slice: &mut [usize],
|
|
flags: usize,
|
|
) -> Result<usize> {
|
|
let cnt = fd_slice.len();
|
|
if cnt == 0 {
|
|
return Ok(0);
|
|
}
|
|
|
|
if fd_slice[0] == usize::MAX {
|
|
return self.bulk_add(which, fd_slice, flags);
|
|
}
|
|
|
|
if self.active_count + cnt > Self::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
|
|
if which & syscall::UPPER_FDTBL_TAG == 0 {
|
|
let old_capacity = self.posix_fdtbl.capacity();
|
|
|
|
let initial_flag = PosixFdTbl::flags_into_entry(flags);
|
|
let entries = alloc::vec![initial_flag; cnt];
|
|
|
|
self.posix_fdtbl
|
|
.bulk_insert_manual(entries, fd_slice, self.fd.as_ref())?;
|
|
self.active_count += cnt;
|
|
} else {
|
|
let old_capacity = self.upper_fdtbl.capacity();
|
|
|
|
let entries = alloc::vec![UpperFdTbl::flags_into_entry(flags); cnt];
|
|
self.upper_fdtbl
|
|
.bulk_insert_manual(entries, fd_slice, self.fd.as_ref())?;
|
|
self.active_count += cnt;
|
|
}
|
|
|
|
Ok(cnt)
|
|
}
|
|
|
|
pub fn remove(&mut self, fd: usize) -> Result<()> {
|
|
if Self::is_upper(fd) {
|
|
let handle = Self::strip_tags(fd);
|
|
if self.upper_fdtbl.remove(handle).is_some() {
|
|
self.active_count -= 1;
|
|
Ok(())
|
|
} else {
|
|
Err(Error::new(EBADF))
|
|
}
|
|
} else {
|
|
if self.posix_fdtbl.remove(fd).is_some() {
|
|
self.active_count -= 1;
|
|
Ok(())
|
|
} else {
|
|
Err(Error::new(EBADF))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct PosixFdTbl {
|
|
table: Vec<FdFlags>,
|
|
lowest_idx: u32,
|
|
}
|
|
|
|
bitflags::bitflags! {
|
|
#[derive(Clone, Copy, Default, PartialEq, Eq)]
|
|
pub struct FdFlags: u8 {
|
|
const VACANT = 0;
|
|
const OCCUPIED = 1 << 0;
|
|
const CLOEXEC = 1 << 1;
|
|
const CLOFORK = 1 << 2;
|
|
}
|
|
}
|
|
|
|
impl PosixFdTbl {
|
|
#[expect(
|
|
clippy::new_without_default,
|
|
reason = "default not expected for this type"
|
|
)]
|
|
pub const fn new() -> Self {
|
|
Self {
|
|
table: Vec::new(),
|
|
lowest_idx: 0,
|
|
}
|
|
}
|
|
|
|
pub fn with_capacity(capacity: usize) -> Self {
|
|
Self {
|
|
table: Vec::with_capacity(capacity),
|
|
lowest_idx: 0,
|
|
}
|
|
}
|
|
|
|
pub fn get_flags(&self, handle: usize) -> Result<FdFlags> {
|
|
self.table
|
|
.get(handle)
|
|
.copied()
|
|
.filter(|flags| flags.contains(FdFlags::OCCUPIED))
|
|
.ok_or(Error::new(EBADF))
|
|
}
|
|
|
|
pub fn set_flags(&mut self, handle: usize, flags: FdFlags) -> Result<()> {
|
|
if !self.is_occupied(handle) {
|
|
return Err(Error::new(EBADF));
|
|
}
|
|
let entry = self.table[handle];
|
|
self.table[handle] = (entry & FdFlags::OCCUPIED) | flags;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn flags_into_entry(flags: usize) -> FdFlags {
|
|
let mut new_entry = FdFlags::OCCUPIED;
|
|
if flags & O_CLOEXEC != 0 {
|
|
new_entry.insert(FdFlags::CLOEXEC);
|
|
}
|
|
/* TODO: Support O_CLOFORK
|
|
if flags & syscall::O_CLOFORK != 0 {
|
|
new_entry.insert(FdFlags::CLOFORK);
|
|
}
|
|
*/
|
|
new_entry
|
|
}
|
|
|
|
fn is_vacant(&self, handle: usize) -> bool {
|
|
self.table
|
|
.get(handle)
|
|
.is_none_or(|&flags| !flags.contains(FdFlags::OCCUPIED))
|
|
}
|
|
|
|
fn is_occupied(&self, handle: usize) -> bool {
|
|
self.table
|
|
.get(handle)
|
|
.is_some_and(|&flags| flags.contains(FdFlags::OCCUPIED))
|
|
}
|
|
|
|
pub fn resize(&mut self, size: usize) {
|
|
self.table.resize(size, FdFlags::VACANT);
|
|
}
|
|
|
|
pub fn capacity(&self) -> usize {
|
|
self.table.capacity()
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
self.table
|
|
.iter()
|
|
.filter(|&&e| e.contains(FdFlags::OCCUPIED))
|
|
.count()
|
|
}
|
|
|
|
pub const fn is_empty(&self) -> bool {
|
|
self.table.is_empty()
|
|
}
|
|
|
|
pub fn with_transaction<T, F>(&mut self, rollback_len: usize, f: F) -> Result<T>
|
|
where
|
|
F: FnOnce(&mut Self) -> Result<T>,
|
|
{
|
|
match f(self) {
|
|
Ok(res) => Ok(res),
|
|
Err(e) => {
|
|
self.table.truncate(rollback_len);
|
|
if (self.lowest_idx as usize) > self.table.len() {
|
|
self.lowest_idx = self.table.len() as u32;
|
|
}
|
|
Err(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update_lowest_idx(&mut self, start_from: usize) {
|
|
let mut next_lowest = start_from;
|
|
while next_lowest < self.table.len() && self.is_occupied(next_lowest) {
|
|
next_lowest += 1;
|
|
}
|
|
self.lowest_idx = next_lowest as u32;
|
|
}
|
|
|
|
fn validate_handles(&self, handles: &[usize]) -> Result<()> {
|
|
let mut checked_handles = BTreeSet::new();
|
|
for &handle in handles {
|
|
if handle >= FdTbl::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
if !checked_handles.insert(handle) || !self.is_occupied(handle) {
|
|
return Err(Error::new(EBADF));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_free_slots(&self, handles: &[usize]) -> Result<()> {
|
|
let mut checked_handles = BTreeSet::new();
|
|
for &handle in handles {
|
|
if handle >= FdTbl::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
if !checked_handles.insert(handle) {
|
|
return Err(Error::new(EBADF));
|
|
}
|
|
if self.is_occupied(handle) {
|
|
return Err(Error::new(EEXIST));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn find_free_posix_slots(&self, count: usize) -> Vec<usize> {
|
|
let mut free_slots = Vec::with_capacity(count);
|
|
|
|
for i in (self.lowest_idx as usize)..self.table.len() {
|
|
if self.is_vacant(i) {
|
|
free_slots.push(i);
|
|
if free_slots.len() == count {
|
|
return free_slots;
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut current_len = self.table.len();
|
|
while free_slots.len() < count {
|
|
free_slots.push(current_len);
|
|
current_len += 1;
|
|
}
|
|
free_slots
|
|
}
|
|
|
|
pub fn add(&mut self, flags: FdFlags, sync_fd: Option<&FdGuardUpper>) -> Result<usize> {
|
|
let handle = self.lowest_idx as usize;
|
|
let old_len = self.table.len();
|
|
let entry_flags = flags | FdFlags::OCCUPIED;
|
|
|
|
if handle >= old_len {
|
|
self.with_transaction(old_len, |this| {
|
|
let new_len = handle + 1;
|
|
FdTbl::sync_size(sync_fd, new_len, 0)?;
|
|
this.table.push(entry_flags);
|
|
this.lowest_idx = (handle + 1) as u32;
|
|
Ok(handle)
|
|
})
|
|
} else {
|
|
self.table[handle] = entry_flags;
|
|
self.update_lowest_idx(handle + 1);
|
|
Ok(handle)
|
|
}
|
|
}
|
|
|
|
pub fn bulk_add_posix(
|
|
&mut self,
|
|
entries: Vec<FdFlags>,
|
|
sync_fd: Option<&FdGuardUpper>,
|
|
) -> Result<Vec<usize>> {
|
|
let count = entries.len();
|
|
if count == 0 {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
let handles = self.find_free_posix_slots(count);
|
|
let max_index = handles[count - 1];
|
|
|
|
if max_index >= FdTbl::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
|
|
let old_len = self.table.len();
|
|
let new_len = if old_len <= max_index {
|
|
max_index + 1
|
|
} else {
|
|
old_len
|
|
};
|
|
|
|
self.with_transaction(old_len, |this| {
|
|
if old_len != new_len {
|
|
FdTbl::sync_size(sync_fd, new_len, 0)?;
|
|
this.resize(new_len);
|
|
}
|
|
|
|
for (&handle, flags) in handles.iter().zip(entries) {
|
|
this.table[handle] = flags | FdFlags::OCCUPIED;
|
|
}
|
|
|
|
let mut next_lowest = this.lowest_idx as usize;
|
|
while next_lowest < this.table.len() && this.is_occupied(next_lowest) {
|
|
next_lowest += 1;
|
|
}
|
|
this.lowest_idx = next_lowest as u32;
|
|
|
|
Ok(handles)
|
|
})
|
|
}
|
|
|
|
pub fn insert_at(
|
|
&mut self,
|
|
handle: usize,
|
|
flags: FdFlags,
|
|
sync_fd: Option<&FdGuardUpper>,
|
|
) -> Result<usize> {
|
|
if handle >= FdTbl::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
let old_len = self.table.len();
|
|
let new_len = if handle >= old_len {
|
|
handle + 1
|
|
} else {
|
|
old_len
|
|
};
|
|
let entry_flags = flags | FdFlags::OCCUPIED;
|
|
|
|
self.with_transaction(old_len, |this| {
|
|
if handle >= old_len {
|
|
FdTbl::sync_size(sync_fd, new_len, 0)?;
|
|
this.resize(handle + 1);
|
|
}
|
|
this.table[handle] = entry_flags;
|
|
|
|
if handle <= this.lowest_idx as usize {
|
|
this.update_lowest_idx(handle);
|
|
}
|
|
Ok(handle)
|
|
})
|
|
}
|
|
|
|
pub fn bulk_insert_manual(
|
|
&mut self,
|
|
entries: Vec<FdFlags>,
|
|
handles: &[usize],
|
|
sync_fd: Option<&FdGuardUpper>,
|
|
) -> Result<()> {
|
|
if handles.len() != entries.len() {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
let count = entries.len();
|
|
if count == 0 {
|
|
return Ok(());
|
|
}
|
|
|
|
self.validate_free_slots(handles)?;
|
|
|
|
let max_index = handles.iter().max().cloned().unwrap_or(0);
|
|
let old_len = self.table.len();
|
|
let new_len = if old_len <= max_index {
|
|
max_index + 1
|
|
} else {
|
|
old_len
|
|
};
|
|
|
|
self.with_transaction(old_len, |this| {
|
|
if old_len <= max_index {
|
|
FdTbl::sync_size(sync_fd, new_len, 0)?;
|
|
this.resize(max_index + 1);
|
|
}
|
|
|
|
for (entry, &index) in entries.into_iter().zip(handles) {
|
|
this.table[index] = entry | FdFlags::OCCUPIED;
|
|
}
|
|
|
|
this.update_lowest_idx(0);
|
|
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
pub fn remove(&mut self, handle: usize) -> Option<FdFlags> {
|
|
if !self.is_occupied(handle) {
|
|
return None;
|
|
}
|
|
|
|
let old_entry = core::mem::replace(&mut self.table[handle], FdFlags::VACANT);
|
|
|
|
if (handle as u32) < self.lowest_idx {
|
|
self.lowest_idx = handle as u32;
|
|
}
|
|
|
|
Some(old_entry)
|
|
}
|
|
|
|
pub fn bulk_remove(&mut self, handles: &[usize]) -> Option<Vec<FdFlags>> {
|
|
self.validate_handles(handles).ok()?;
|
|
|
|
let files = handles
|
|
.iter()
|
|
.map(|&i| self.remove(i).expect("fd should exist"))
|
|
.collect();
|
|
|
|
Some(files)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum FdTblEntry {
|
|
Vacant { next_vacant_idx: u32 },
|
|
Occupied { flag: u32 },
|
|
}
|
|
|
|
impl FdTblEntry {
|
|
pub fn new_occupied(flag: u32) -> Self {
|
|
Self::Occupied { flag }
|
|
}
|
|
}
|
|
|
|
pub struct UpperFdTbl {
|
|
table: Vec<FdTblEntry>,
|
|
len: u32,
|
|
first_vacant_idx: u32,
|
|
}
|
|
|
|
impl UpperFdTbl {
|
|
#[expect(
|
|
clippy::new_without_default,
|
|
reason = "default not expected for this type"
|
|
)]
|
|
pub const fn new() -> Self {
|
|
Self {
|
|
table: Vec::new(),
|
|
len: 0,
|
|
first_vacant_idx: FdTbl::CONTEXT_MAX_FILES,
|
|
}
|
|
}
|
|
|
|
pub fn with_capacity(capacity: usize) -> Self {
|
|
Self {
|
|
table: Vec::with_capacity(capacity),
|
|
len: 0,
|
|
first_vacant_idx: FdTbl::CONTEXT_MAX_FILES,
|
|
}
|
|
}
|
|
|
|
pub fn get_flags(&self, handle: usize) -> Result<u32> {
|
|
let index = Self::strip_tags(handle);
|
|
match self.table.get(index) {
|
|
Some(FdTblEntry::Occupied { flag }) => Ok(*flag),
|
|
_ => Err(Error::new(EBADF)),
|
|
}
|
|
}
|
|
|
|
pub fn set_flags(&mut self, handle: usize, new_flag: u32) -> Result<()> {
|
|
let index = Self::strip_tags(handle);
|
|
match self.table.get_mut(index) {
|
|
Some(entry @ FdTblEntry::Occupied { .. }) => {
|
|
*entry = FdTblEntry::Occupied { flag: new_flag };
|
|
Ok(())
|
|
}
|
|
_ => Err(Error::new(EBADF)),
|
|
}
|
|
}
|
|
|
|
pub fn flags_into_entry(flags: usize) -> FdTblEntry {
|
|
FdTblEntry::new_occupied(flags as u32)
|
|
}
|
|
|
|
fn strip_tags(index: usize) -> usize {
|
|
index & !syscall::UPPER_FDTBL_TAG
|
|
}
|
|
|
|
pub fn resize(&mut self, size: usize) {
|
|
self.table.resize(
|
|
size,
|
|
FdTblEntry::Vacant {
|
|
next_vacant_idx: FdTbl::CONTEXT_MAX_FILES,
|
|
},
|
|
);
|
|
self.rebuild_free_list();
|
|
}
|
|
|
|
pub fn capacity(&self) -> usize {
|
|
self.table.capacity()
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
self.len as usize
|
|
}
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
self.len == 0
|
|
}
|
|
|
|
pub fn with_transaction<T, F>(&mut self, rollback_len: usize, f: F) -> Result<T>
|
|
where
|
|
F: FnOnce(&mut Self) -> Result<T>,
|
|
{
|
|
match f(self) {
|
|
Ok(res) => Ok(res),
|
|
Err(e) => {
|
|
self.table.truncate(rollback_len);
|
|
Err(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn validate_handles(&self, handles: &[usize]) -> Result<()> {
|
|
let mut checked_handles = BTreeSet::new();
|
|
for &handle in handles {
|
|
let handle = Self::strip_tags(handle);
|
|
if Self::strip_tags(handle) >= FdTbl::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
if !checked_handles.insert(handle) {
|
|
return Err(Error::new(EBADF)); // Duplicate handle
|
|
}
|
|
if !matches!(self.table.get(handle), Some(FdTblEntry::Occupied { .. })) {
|
|
return Err(Error::new(EBADF));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_free_slots(&self, handles: &[usize]) -> Result<()> {
|
|
let mut checked_handles = BTreeSet::new();
|
|
for &handle in handles {
|
|
let handle = Self::strip_tags(handle);
|
|
if handle >= FdTbl::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
if !checked_handles.insert(handle) {
|
|
return Err(Error::new(EBADF)); // Duplicate handle
|
|
}
|
|
if matches!(self.table.get(handle), Some(FdTblEntry::Occupied { .. })) {
|
|
return Err(Error::new(EEXIST));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn find_free_block(&self, len: usize) -> usize {
|
|
let mut start = 0;
|
|
let mut count = 0;
|
|
|
|
for (i, entry) in self.table.iter().enumerate() {
|
|
if matches!(entry, FdTblEntry::Vacant { .. }) {
|
|
if count == 0 {
|
|
start = i;
|
|
}
|
|
count += 1;
|
|
if count == len {
|
|
return start;
|
|
}
|
|
} else {
|
|
count = 0;
|
|
}
|
|
}
|
|
|
|
if count == 0 { self.table.len() } else { start }
|
|
}
|
|
|
|
pub fn insert(&mut self, entry: FdTblEntry, sync_fd: Option<&FdGuardUpper>) -> Result<usize> {
|
|
let handle = self.first_vacant_idx as usize;
|
|
|
|
if self.first_vacant_idx == FdTbl::CONTEXT_MAX_FILES {
|
|
let old_len = self.table.len();
|
|
let new_len = old_len + 1;
|
|
self.with_transaction(old_len, |this| {
|
|
FdTbl::sync_size(sync_fd, new_len, syscall::UPPER_FDTBL_TAG)?;
|
|
this.table.push(entry);
|
|
this.len += 1;
|
|
Ok(old_len)
|
|
})
|
|
} else {
|
|
if let FdTblEntry::Vacant { next_vacant_idx } = self.table[handle] {
|
|
self.first_vacant_idx = next_vacant_idx;
|
|
self.table[handle] = entry;
|
|
self.len += 1;
|
|
Ok(handle)
|
|
} else {
|
|
unreachable!();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn bulk_insert(
|
|
&mut self,
|
|
entries: Vec<FdTblEntry>,
|
|
sync_fd: Option<&FdGuardUpper>,
|
|
) -> Result<Vec<usize>> {
|
|
let count = entries.len();
|
|
if count == 0 {
|
|
return Ok(Vec::new());
|
|
}
|
|
if self.len() + count > FdTbl::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
|
|
let old_len = self.table.len();
|
|
|
|
let start_index = self.find_free_block(count);
|
|
let needed_len = start_index + count;
|
|
|
|
let new_len = if old_len < needed_len {
|
|
needed_len
|
|
} else {
|
|
old_len
|
|
};
|
|
|
|
self.with_transaction(old_len, |this| {
|
|
if old_len != new_len {
|
|
FdTbl::sync_size(sync_fd, new_len, syscall::UPPER_FDTBL_TAG)?;
|
|
this.resize(new_len);
|
|
}
|
|
|
|
let mut handles = Vec::with_capacity(count);
|
|
for (i, entry) in entries.into_iter().enumerate() {
|
|
let current_index = start_index + i;
|
|
this.table[current_index] = entry;
|
|
handles.push(current_index);
|
|
}
|
|
|
|
this.len += count as u32;
|
|
|
|
this.rebuild_free_list();
|
|
|
|
Ok(handles)
|
|
})
|
|
}
|
|
|
|
pub fn insert_at(
|
|
&mut self,
|
|
handle: usize,
|
|
entry: FdTblEntry,
|
|
sync_fd: Option<&FdGuardUpper>,
|
|
) -> Result<usize> {
|
|
let old_len = self.table.len();
|
|
let new_len = if handle >= old_len {
|
|
handle + 1
|
|
} else {
|
|
old_len
|
|
};
|
|
|
|
self.with_transaction(old_len, |this| {
|
|
if handle >= old_len {
|
|
FdTbl::sync_size(sync_fd, new_len, syscall::UPPER_FDTBL_TAG)?;
|
|
this.resize(handle + 1);
|
|
}
|
|
|
|
let vacant = matches!(this.table[handle], FdTblEntry::Vacant { .. });
|
|
if vacant {
|
|
this.len += 1;
|
|
}
|
|
|
|
this.table[handle] = entry;
|
|
|
|
if vacant {
|
|
this.rebuild_free_list();
|
|
}
|
|
Ok(handle)
|
|
})
|
|
}
|
|
|
|
pub fn bulk_insert_manual(
|
|
&mut self,
|
|
entries: Vec<FdTblEntry>,
|
|
handles: &[usize],
|
|
sync_fd: Option<&FdGuardUpper>,
|
|
) -> Result<()> {
|
|
if handles.len() != entries.len() {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
let count = entries.len();
|
|
if count == 0 {
|
|
return Ok(());
|
|
}
|
|
if self.len() + count > FdTbl::CONTEXT_MAX_FILES as usize {
|
|
return Err(Error::new(EMFILE));
|
|
}
|
|
|
|
self.validate_free_slots(handles)?;
|
|
|
|
let max_index = handles
|
|
.iter()
|
|
.map(|&h| Self::strip_tags(h))
|
|
.max()
|
|
.unwrap_or(0);
|
|
let old_len = self.table.len();
|
|
let new_len = if old_len <= max_index {
|
|
max_index + 1
|
|
} else {
|
|
old_len
|
|
};
|
|
|
|
self.with_transaction(old_len, |this| {
|
|
if old_len <= max_index {
|
|
FdTbl::sync_size(sync_fd, new_len, syscall::UPPER_FDTBL_TAG)?;
|
|
this.resize(max_index + 1);
|
|
}
|
|
|
|
for (entry, &index) in entries.into_iter().zip(handles) {
|
|
this.table[Self::strip_tags(index)] = entry;
|
|
}
|
|
|
|
this.len += count as u32;
|
|
|
|
this.rebuild_free_list();
|
|
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
pub fn remove(&mut self, handle: usize) -> Option<FdTblEntry> {
|
|
if handle >= self.table.len() || matches!(self.table[handle], FdTblEntry::Vacant { .. }) {
|
|
return None;
|
|
}
|
|
|
|
let old_entry = core::mem::replace(
|
|
&mut self.table[handle],
|
|
FdTblEntry::Vacant {
|
|
next_vacant_idx: self.first_vacant_idx,
|
|
},
|
|
);
|
|
self.first_vacant_idx = handle as u32;
|
|
self.len -= 1;
|
|
|
|
Some(old_entry)
|
|
}
|
|
|
|
pub fn bulk_remove(&mut self, handles: &[usize]) -> Option<Vec<FdTblEntry>> {
|
|
self.validate_handles(handles).ok()?;
|
|
|
|
let files = handles
|
|
.iter()
|
|
.map(|&i| self.remove(i).expect("fd should exist"))
|
|
.collect();
|
|
|
|
Some(files)
|
|
}
|
|
|
|
fn rebuild_free_list(&mut self) {
|
|
let mut next_vacant = FdTbl::CONTEXT_MAX_FILES;
|
|
for i in (0..self.table.len()).rev() {
|
|
if let FdTblEntry::Vacant { next_vacant_idx } = &mut self.table[i] {
|
|
*next_vacant_idx = next_vacant;
|
|
next_vacant = i as u32;
|
|
}
|
|
}
|
|
self.first_vacant_idx = next_vacant;
|
|
}
|
|
}
|
|
|
|
pub struct FdTblIter<'a> {
|
|
fdtbl: &'a FdTbl,
|
|
stage: u8,
|
|
cursor: usize,
|
|
}
|
|
|
|
impl<'a> FdTblIter<'a> {
|
|
fn new(fdtbl: &'a FdTbl) -> Self {
|
|
Self {
|
|
fdtbl,
|
|
stage: 0,
|
|
cursor: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for FdTblIter<'a> {
|
|
type Item = (usize, usize);
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
loop {
|
|
match self.stage {
|
|
0 => {
|
|
let table = &self.fdtbl.posix_fdtbl.table;
|
|
if self.cursor < table.len() {
|
|
let idx = self.cursor;
|
|
self.cursor += 1;
|
|
|
|
let internal_flags = table[idx];
|
|
if internal_flags.contains(FdFlags::OCCUPIED) {
|
|
let mut raw_flags = 0;
|
|
if internal_flags.contains(FdFlags::CLOEXEC) {
|
|
raw_flags |= O_CLOEXEC;
|
|
}
|
|
/*
|
|
if internal_flags.contains(FdFlags::CLOFORK) {
|
|
raw_flags |= syscall::O_CLOFORK;
|
|
}
|
|
*/
|
|
return Some((idx, raw_flags));
|
|
}
|
|
} else {
|
|
self.stage = 1;
|
|
self.cursor = 0;
|
|
}
|
|
}
|
|
1 => {
|
|
let table = &self.fdtbl.upper_fdtbl.table;
|
|
if self.cursor < table.len() {
|
|
let idx = self.cursor;
|
|
self.cursor += 1;
|
|
|
|
if let FdTblEntry::Occupied { flag } = table[idx] {
|
|
let raw_flags = flag as usize;
|
|
|
|
let full_fd = idx | syscall::UPPER_FDTBL_TAG;
|
|
return Some((full_fd, raw_flags));
|
|
}
|
|
} else {
|
|
self.stage = 2;
|
|
}
|
|
}
|
|
_ => return None,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FdTbl {
|
|
pub fn iter(&self) -> FdTblIter<'_> {
|
|
FdTblIter::new(self)
|
|
}
|
|
}
|
|
|
|
impl<'a> IntoIterator for &'a FdTbl {
|
|
type Item = (usize, usize);
|
|
type IntoIter = FdTblIter<'a>;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
self.iter()
|
|
}
|
|
}
|