use alloc::{ boxed::Box, sync::{Arc, Weak}, vec::Vec, }; use core::{ mem, mem::size_of, num::NonZeroUsize, sync::atomic::{AtomicBool, Ordering}, }; use slab::Slab; use spin::{Mutex, RwLock}; use syscall::{ schemev2::{Cqe, CqeOpcode, Opcode, Sqe, SqeFlags}, CallFlags, FmoveFdFlags, FobtainFdFlags, MunmapFlags, RecvFdFlags, SchemeSocketCall, SendFdFlags, MAP_FIXED_NOREPLACE, }; use crate::{ context::{ self, context::HardBlockedReason, file::{FileDescription, FileDescriptor, InternalFlags}, memory::{ AddrSpace, AddrSpaceWrapper, BorrowedFmapSource, Grant, GrantFileRef, MmapMode, PageSpan, DANGLING, }, BorrowedHtBuf, ContextLock, PreemptGuard, Status, }, event, memory::Frame, paging::{Page, VirtualAddress, PAGE_SIZE}, scheme::SchemeId, sync::{CleanLockToken, WaitQueue}, syscall::{ data::Map, error::*, flag::{EventFlags, MapFlags, EVENT_READ, O_NONBLOCK, PROT_READ}, usercopy::{UserSlice, UserSliceRo, UserSliceRw, UserSliceWo}, }, }; use super::{CallerCtx, FileHandle, KernelScheme, OpenResult}; pub struct UserInner { root_id: SchemeId, handle_id: usize, pub name: Box, pub scheme_id: SchemeId, supports_on_close: bool, context: Weak, todo: WaitQueue, // TODO: custom packed radix tree data structure states: Mutex>, unmounting: AtomicBool, } enum State { Waiting { context: Weak, fds: Option>>>, callee_responsible: PageSpan, canceling: bool, }, Responded(Response), Fmap(Weak), Placeholder, } #[derive(Debug)] pub enum Response { Regular(usize, u8), Fd(Arc>), MultipleFds(Option>>>), } const ONE: NonZeroUsize = match NonZeroUsize::new(1) { Some(one) => one, None => unreachable!(), }; enum ParsedCqe { TriggerFevent { number: usize, flags: EventFlags, }, RegularResponse { tag: u32, code: usize, extra0: u8, }, ResponseWithFd { tag: u32, fd: usize, }, ResponseWithMultipleFds { tag: u32, num_fds: usize, }, ObtainFd { tag: u32, flags: FobtainFdFlags, dst_fd_or_ptr: usize, }, ProvideMmap { tag: u32, offset: u64, base_addr: VirtualAddress, page_count: usize, }, } impl ParsedCqe { fn parse_cqe(cqe: &Cqe) -> Result { Ok( 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, extra0: cqe.extra_raw[0], }, CqeOpcode::RespondWithFd => Self::ResponseWithFd { 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))?, }, CqeOpcode::ObtainFd => Self::ObtainFd { tag: cqe.tag, flags: FobtainFdFlags::from_bits(cqe.extra() as usize) .ok_or(Error::new(EINVAL))?, dst_fd_or_ptr: cqe.result as usize, }, }, ) } } impl UserInner { pub fn new( root_id: SchemeId, scheme_id: SchemeId, new_close: bool, handle_id: usize, name: Box, _flags: usize, context: Weak, ) -> UserInner { UserInner { root_id, handle_id, name, supports_on_close: new_close, scheme_id, context, todo: WaitQueue::new(), unmounting: AtomicBool::new(false), states: Mutex::new(Slab::with_capacity(32)), } } pub fn unmount(&self, token: &mut CleanLockToken) -> Result<()> { // First, block new requests and prepare to return EOF self.unmounting.store(true, Ordering::SeqCst); // Wake up any blocked scheme handler unsafe { self.todo.condition.notify_signal(token) }; // Tell the scheme handler to read event::trigger(self.root_id, self.handle_id, EVENT_READ); //TODO: wait for all todo and done to be processed? Ok(()) } fn next_id(&self) -> Result { let idx = { let mut states = self.states.lock(); states.insert(State::Placeholder) }; // TODO: implement blocking? u32::try_from(idx).map_err(|_| Error::new(EAGAIN)) } pub fn call( &self, opcode: Opcode, args: impl Args, caller_responsible: &mut PageSpan, token: &mut CleanLockToken, ) -> Result { let ctx = { context::current().read(token.token()).caller_ctx() }; match self.call_extended(ctx, None, opcode, args, caller_responsible, token)? { 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, fds: Option>>>, opcode: Opcode, args: impl Args, caller_responsible: &mut PageSpan, token: &mut CleanLockToken, ) -> Result { let next_id = self.next_id()?; self.call_extended_inner( fds, Sqe { opcode: opcode as u8, sqe_flags: SqeFlags::empty(), _rsvd: 0, tag: next_id, caller: ctx.pid as u64, args: { let mut a = args.args(); a[5] = uid_gid_hack_merge([ctx.uid, ctx.gid]); a }, }, caller_responsible, token, ) } fn call_extended_inner( &self, fds: Option>>>, sqe: Sqe, caller_responsible: &mut PageSpan, token: &mut CleanLockToken, ) -> Result { if self.unmounting.load(Ordering::SeqCst) { return Err(Error::new(ENODEV)); } { // Disable preemption to avoid context switches between setting the // process state and sending the scheme request. The process is made // runnable again when the scheme response is received. Hence, we // need to ensure that the following operations are atomic as // otherwise the process will be blocked forever. let current_context = context::current(); let mut preempt = PreemptGuard::new(¤t_context, token); let token = preempt.token(); current_context .write(token.token()) .block("UserInner::call"); { let mut states = self.states.lock(); states[sqe.tag as usize] = State::Waiting { context: Arc::downgrade(¤t_context), fds, canceling: false, // This is the part that the scheme handler will deallocate when responding. It // starts as empty, so the caller can unmap it (optimal for TLB), but is populated // the caller is interrupted by SIGKILL. callee_responsible: PageSpan::empty(), }; } self.todo.send(sqe, token); event::trigger(self.root_id, self.handle_id, EVENT_READ); } loop { context::switch(token); { let mut eintr_if_sigkill = |callee_responsible: &mut PageSpan| { // If SIGKILL was found without waiting for scheme, EINTR directly. In that // case, data loss doesn't matter. if context::current().read(token.token()).being_sigkilled { // Callee must deallocate memory, rather than the caller. This is less optimal // for TLB, but we don't really have any other choice. The scheme must be able // to access the borrowed memory until it has responded to the request. *callee_responsible = core::mem::replace(caller_responsible, PageSpan::empty()); Err(Error::new(EINTR)) } else { Ok(()) } }; let mut states = self.states.lock(); match states.get_mut(sqe.tag as usize) { // invalid state None => return Err(Error::new(EBADFD)), Some(o) => match mem::replace(o, State::Placeholder) { // signal wakeup while awaiting cancelation State::Waiting { canceling: true, mut callee_responsible, context, fds, } => { let maybe_eintr = eintr_if_sigkill(&mut callee_responsible); *o = State::Waiting { canceling: true, callee_responsible, context, fds, }; maybe_eintr?; context::current() .write(token.token()) .block("UserInner::call (woken up after cancelation request)"); // We do not want to drop the lock before blocking // as if we get preempted in between we might miss a // wakeup. drop(states); } // spurious wakeup State::Waiting { canceling: false, fds, context, mut callee_responsible, } => { let maybe_eintr = eintr_if_sigkill(&mut callee_responsible); let current_context = context::current(); *o = State::Waiting { // Currently we treat all spurious wakeups to have the same behavior // as signals (i.e., we send a cancellation request). It is not something // that should happen, but it certainly can happen, for example if a context // is awoken through its thread handle without setting any sig bits, or if the // caller clears its own sig bits. If it actually is a signal, then it is the // intended behavior. canceling: true, fds, context, callee_responsible, }; maybe_eintr?; // We do not want to preempt between sending the // cancellation and blocking again where we might // miss a wakeup. let mut preempt = PreemptGuard::new(¤t_context, token); let token = preempt.token(); self.todo.send( Sqe { opcode: Opcode::Cancel as u8, sqe_flags: SqeFlags::ONEWAY, tag: sqe.tag, ..Default::default() }, token, ); event::trigger(self.root_id, self.handle_id, EVENT_READ); // 1. If cancellation was requested and arrived // before the scheme processed the request, an // acknowledgement will be sent back after the // cancellation is processed and we will be woken up // again. State will be State::Responded then. // // 2. If cancellation was requested but the scheme // already processed the request, we will receive // the actual response next and woken up again. // State will be State::Responded then. context::current() .write(token.token()) .block("UserInner::call (spurious wakeup)"); drop(states); } // invalid state old_state @ (State::Placeholder | State::Fmap(_)) => { *o = old_state; return Err(Error::new(EBADFD)); } State::Responded(response) => { states.remove(sqe.tag as usize); return Ok(response); } }, } } } } /// Map a readable structure to the scheme's userspace and return the /// pointer #[must_use = "copying back to head/tail buffers can fail"] pub fn capture_user( &self, buf: UserSlice, token: &mut CleanLockToken, ) -> Result> { UserInner::capture_inner(&self.context, buf, token) } pub fn copy_and_capture_tail( &self, buf: &[u8], token: &mut CleanLockToken, ) -> Result> { let dst_addr_space = { Arc::clone( self.context .upgrade() .ok_or(Error::new(ENODEV))? .read(token.token()) .addr_space()?, ) }; let mut tail = BorrowedHtBuf::tail(token)?; let tail_frame = tail.frame(); if buf.len() > tail.buf().len() { return Err(Error::new(EINVAL)); } tail.buf_mut()[..buf.len()].copy_from_slice(buf); let is_pinned = true; let dst_page = { dst_addr_space.acquire_write().mmap_anywhere( &dst_addr_space, ONE, PROT_READ, |dst_page, flags, mapper, flusher| { Grant::allocated_shared_one_page( tail_frame, dst_page, flags, mapper, flusher, is_pinned, ) }, )? }; let base = dst_page.start_address().data(); let len = buf.len(); Ok(CaptureGuard { base, len, destroyed: false, head: CopyInfo { src: Some(tail), dst: None, }, tail: CopyInfo { src: None, dst: None, }, span: { let (first_page, page_count, _offset) = page_range_containing(base, len); PageSpan::new(first_page, page_count) }, addrsp: Some(dst_addr_space), }) } // TODO: Use an address space Arc over a context Arc. While contexts which share address spaces // still can access borrowed scheme pages, it would both be cleaner and would handle the case // where the initial context is closed. /// Capture a buffer owned by userspace, mapping it contiguously onto scheme memory. // TODO: Hypothetical accept_head_leak, accept_tail_leak options might be useful for // libc-controlled buffer pools. fn capture_inner( context_weak: &Weak, user_buf: UserSlice, token: &mut CleanLockToken, ) -> Result> { let mut map_flags = MapFlags::empty(); map_flags.set(MapFlags::PROT_READ, READ); map_flags.set(MapFlags::PROT_WRITE, WRITE); if user_buf.is_empty() { // NOTE: Rather than returning NULL, we return a dummy dangling address, that is // happens to be non-canonical on x86. This relieves scheme handlers from having to // check the length before e.g. creating nonnull Rust references (when an empty length // still requires a nonnull but possibly dangling pointer, and this has in practice // made nulld errorneously confuse an empty Some("") with None (invalid UTF-8), due to // enum layout optimization, as the pointer was null and not dangling). A good choice // is thus to simply set the most-significant bit to be compatible with all alignments. return Ok(CaptureGuard { destroyed: false, base: DANGLING, len: 0, head: CopyInfo { src: None, dst: None, }, tail: CopyInfo { src: None, dst: None, }, span: PageSpan::empty(), addrsp: None, }); } let cur_space_lock = AddrSpace::current()?; let dst_space_lock = { Arc::clone( context_weak .upgrade() .ok_or(Error::new(ESRCH))? .read(token.token()) .addr_space()?, ) }; if Arc::ptr_eq(&dst_space_lock, &cur_space_lock) { // Same address space, no need to remap anything! return Ok(CaptureGuard { destroyed: false, base: user_buf.addr(), len: user_buf.len(), head: CopyInfo { src: None, dst: None, }, tail: CopyInfo { src: None, dst: None, }, span: PageSpan::empty(), addrsp: Some(dst_space_lock), }); } let (src_page, page_count, offset) = page_range_containing(user_buf.addr(), user_buf.len()); let align_offset = if offset == 0 { 0 } else { PAGE_SIZE - offset }; let (head_part_of_buf, middle_tail_part_of_buf) = user_buf .split_at(core::cmp::min(align_offset, user_buf.len())) .expect("split must succeed"); let mut dst_space = dst_space_lock.acquire_write(); let free_span = dst_space .grants .find_free(dst_space.mmap_min, page_count) .ok_or(Error::new(ENOMEM))?; let head = if !head_part_of_buf.is_empty() { // FIXME: Signal context can probably recursively use head/tail. let mut array = BorrowedHtBuf::head(token)?; let frame = array.frame(); let len = core::cmp::min(PAGE_SIZE - offset, user_buf.len()); if READ { array.buf_mut()[..offset].fill(0_u8); array.buf_mut()[offset + len..].fill(0_u8); let slice = &mut array.buf_mut()[offset..][..len]; let head_part_of_buf = user_buf.limit(len).expect("always smaller than max len"); head_part_of_buf .reinterpret_unchecked::() .copy_to_slice(slice)?; } else { array.buf_mut().fill(0_u8); } dst_space.mmap( &dst_space_lock, Some(free_span.base), ONE, map_flags | MAP_FIXED_NOREPLACE, &mut Vec::new(), move |dst_page, page_flags, mapper, flusher| { let is_pinned = true; Grant::allocated_shared_one_page( frame, dst_page, page_flags, mapper, flusher, is_pinned, ) }, )?; let head = CopyInfo { src: Some(array), dst: WRITE.then_some(head_part_of_buf.reinterpret_unchecked()), }; head } else { CopyInfo { src: None, dst: None, } }; let (first_middle_dst_page, first_middle_src_page) = if !head_part_of_buf.is_empty() { (free_span.base.next(), src_page.next()) } else { (free_span.base, src_page) }; let middle_page_count = middle_tail_part_of_buf.len() / PAGE_SIZE; let tail_size = middle_tail_part_of_buf.len() % PAGE_SIZE; let (_middle_part_of_buf, tail_part_of_buf) = middle_tail_part_of_buf .split_at(middle_page_count * PAGE_SIZE) .expect("split must succeed"); if let Some(middle_page_count) = NonZeroUsize::new(middle_page_count) { dst_space.mmap( &dst_space_lock, Some(first_middle_dst_page), middle_page_count, map_flags | MAP_FIXED_NOREPLACE, &mut Vec::new(), move |dst_page, _, mapper, flusher| { let eager = true; // It doesn't make sense to allow a context, that has borrowed non-RAM physical // memory, to DIRECTLY do scheme calls onto that memory. // // (TODO: Maybe there are some niche use cases for that, possibly PCI transfer // BARs, but it doesn't make sense yet.) let allow_phys = false; // Deny any attempts by the scheme, to unmap these temporary pages. The only way to // unmap them is to respond to the scheme socket. let is_pinned_userscheme_borrow = true; Grant::borrow( Arc::clone(&cur_space_lock), &mut cur_space_lock.acquire_write(), first_middle_src_page, dst_page, middle_page_count.get(), map_flags, mapper, flusher, eager, allow_phys, is_pinned_userscheme_borrow, ) }, )?; } let tail = if !tail_part_of_buf.is_empty() { let tail_dst_page = first_middle_dst_page.next_by(middle_page_count); // FIXME: Signal context can probably recursively use head/tail. let mut array = BorrowedHtBuf::tail(token)?; let frame = array.frame(); if READ { let (to_copy, to_zero) = array.buf_mut().split_at_mut(tail_size); to_zero.fill(0_u8); // FIXME: remove reinterpret_unchecked tail_part_of_buf .reinterpret_unchecked::() .copy_to_slice(to_copy)?; } else { array.buf_mut().fill(0_u8); } dst_space.mmap( &dst_space_lock, Some(tail_dst_page), ONE, map_flags | MAP_FIXED_NOREPLACE, &mut Vec::new(), move |dst_page, page_flags, mapper, flusher| { let is_pinned = true; Grant::allocated_shared_one_page( frame, dst_page, page_flags, mapper, flusher, is_pinned, ) }, )?; CopyInfo { src: Some(array), dst: WRITE.then_some(tail_part_of_buf.reinterpret_unchecked()), } } else { CopyInfo { src: None, dst: None, } }; drop(dst_space); let base = free_span.base.start_address().data() + offset; Ok(CaptureGuard { destroyed: false, base, len: user_buf.len(), head, tail, span: { let (first_page, page_count, _offset) = page_range_containing(base, user_buf.len()); PageSpan::new(first_page, page_count) }, addrsp: Some(dst_space_lock), }) } pub fn read(&self, buf: UserSliceWo, flags: u32, token: &mut CleanLockToken) -> Result { // If O_NONBLOCK is used, do not block let nonblock = flags & O_NONBLOCK as u32 != 0; // If unmounting, do not block so that EOF can be returned immediately let block = !(nonblock || self.unmounting.load(Ordering::SeqCst)); match self .todo .receive_into_user(buf, block, "UserInner::read (v2)", token) { // If we received requests, return them to the scheme handler Ok(byte_count) => Ok(byte_count), // If there were no requests and we were unmounting, return EOF Err(Error { errno: EAGAIN }) if self.unmounting.load(Ordering::SeqCst) => Ok(0), // If there were no requests and O_NONBLOCK was used (EAGAIN), or some other error // occurred, return that. Err(error) => Err(error), } } pub fn write(&self, buf: UserSliceRo, token: &mut CleanLockToken) -> Result { let mut bytes_read = 0; for chunk in buf.in_exact_chunks(size_of::()) { match ParsedCqe::parse_cqe(&unsafe { chunk.read_exact::()? }) .and_then(|p| self.handle_parsed(&p, token)) { Ok(()) => bytes_read += size_of::(), Err(_) if bytes_read > 0 => break, Err(error) => return Err(error), } } Ok(bytes_read) } pub fn request_fmap( &self, id: usize, _offset: u64, required_page_count: usize, flags: MapFlags, token: &mut CleanLockToken, ) -> Result<()> { info!("REQUEST FMAP"); let tag = self.next_id()?; { let mut states = self.states.lock(); states[tag as usize] = State::Fmap(Arc::downgrade(&context::current())); } self.todo.send( Sqe { opcode: Opcode::RequestMmap as u8, sqe_flags: SqeFlags::empty(), _rsvd: 0, tag, args: [ id as u64, flags.bits() as u64, required_page_count as u64, 0, 0, uid_gid_hack_merge(current_uid_gid(token)), ], caller: { context::current().read(token.token()).pid as u64 }, }, token, ); event::trigger(self.root_id, self.handle_id, EVENT_READ); Ok(()) } fn handle_parsed(&self, cqe: &ParsedCqe, token: &mut CleanLockToken) -> Result<()> { match *cqe { ParsedCqe::RegularResponse { tag, code, extra0 } => { self.respond(tag, Response::Regular(code, extra0), token)? } ParsedCqe::ResponseWithFd { tag, fd } => self.respond( tag, Response::Fd({ context::current() .read(token.token()) .remove_file(FileHandle::from(fd)) .ok_or(Error::new(EINVAL))? .description }), token, )?, ParsedCqe::ResponseWithMultipleFds { tag, num_fds: _ } => { self.respond(tag, Response::MultipleFds(None), token)?; } ParsedCqe::ObtainFd { tag, flags, dst_fd_or_ptr, } => { let description = { match self .states .lock() .get_mut(tag as usize) .ok_or(Error::new(EINVAL))? { &mut State::Waiting { ref mut fds, .. } => { fds.take().ok_or(Error::new(ENOENT))?.remove(0) } _ => return Err(Error::new(ENOENT)), } }; // FIXME: Description can leak if there is no additional file table space. if flags.contains(FobtainFdFlags::MANUAL_FD) { context::current().read(token.token()).insert_file( FileHandle::from(dst_fd_or_ptr), FileDescriptor { description, cloexec: true, }, ); } else { let fd = context::current() .read(token.token()) .add_file(FileDescriptor { description, cloexec: true, }) .ok_or(Error::new(EMFILE))?; UserSlice::wo(dst_fd_or_ptr, size_of::())?.write_usize(fd.get())?; } } ParsedCqe::ProvideMmap { tag, offset, base_addr, page_count, } => { info!( "PROVIDE_MAP {:x} {:x} {:?} {:x}", tag, offset, base_addr, page_count ); if offset % PAGE_SIZE as u64 != 0 { return Err(Error::new(EINVAL)); } if base_addr.data() % PAGE_SIZE != 0 { return Err(Error::new(EINVAL)); } if page_count != 1 { return Err(Error::new(EINVAL)); } let context = { let mut states = self.states.lock(); match states.get_mut(tag as usize) { Some(o) => match mem::replace(o, State::Placeholder) { // invalid state State::Placeholder => { return Err(Error::new(EBADFD)); } // invalid kernel to scheme call old_state @ (State::Waiting { .. } | State::Responded(_)) => { *o = old_state; return Err(Error::new(EINVAL)); } State::Fmap(context) => { states.remove(tag as usize); context } }, None => return Err(Error::new(EINVAL)), } }; let context = context.upgrade().ok_or(Error::new(ESRCH))?; let (frame, _) = AddrSpace::current()? .acquire_read() .table .utable .translate(base_addr) .ok_or(Error::new(EFAULT))?; { let mut context = context.write(token.token()); if let Status::HardBlocked { reason: HardBlockedReason::AwaitingMmap { .. }, } = context.status { context.status = Status::Runnable } context.fmap_ret = Some(Frame::containing(frame)); } } ParsedCqe::TriggerFevent { number, flags } => { event::trigger(self.scheme_id, number, flags) } } Ok(()) } fn respond(&self, tag: u32, mut response: Response, token: &mut CleanLockToken) -> Result<()> { let to_close: Vec; { let mut states = self.states.lock(); match states.get_mut(tag as usize) { Some(o) => match mem::replace(o, State::Placeholder) { // invalid state State::Placeholder => return Err(Error::new(EBADFD)), // invalid scheme to kernel call old_state @ (State::Responded(_) | State::Fmap(_)) => { *o = old_state; return Err(Error::new(EINVAL)); } State::Waiting { context, mut fds, canceling, callee_responsible, } => { // Convert ECANCELED to EINTR if a request was being canceled (currently always // due to signals). if let Response::Regular(ref mut code, _) = response && canceling && *code == Error::mux(Err(Error::new(ECANCELED))) { *code = Error::mux(Err(Error::new(EINTR))); } // TODO: Require ECANCELED? if let Response::Regular(ref mut code, _) = response && !canceling && *code == Error::mux(Err(Error::new(EINTR))) { // EINTR is valid after cancelation has been requested, but not otherwise. // This is because the userspace signal trampoline will be invoked after a // syscall returns EINTR. *code = Error::mux(Err(Error::new(EIO))); } 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(); match context.upgrade() { Some(context) => { *o = State::Responded(response); context.write(token.token()).unblock(); } _ => { states.remove(tag as usize); } } let unpin = true; AddrSpace::current()?.munmap(callee_responsible, unpin)?; } }, // invalid state None => return Err(Error::new(EBADFD)), } } for fd in to_close { let _ = fd.try_close(token); } Ok(()) } pub fn fevent(&self, flags: EventFlags) -> Result { // TODO: Should the root scheme also suppress events if `flags` does not contain // `EVENT_READ`? Ok(if self.todo.is_currently_empty() { EventFlags::empty() } else { EventFlags::EVENT_READ.intersection(flags) }) } pub fn fsync(&self) -> Result<()> { Ok(()) } fn fmap_inner( &self, dst_addr_space: Arc, file: usize, map: &Map, token: &mut CleanLockToken, ) -> Result { let unaligned_size = map.size; if unaligned_size == 0 { return Err(Error::new(EINVAL)); } let page_count = unaligned_size.div_ceil(PAGE_SIZE); if map.address % PAGE_SIZE != 0 { return Err(Error::new(EINVAL)); }; let fixed = map.flags.contains(MapFlags::MAP_FIXED) || map.flags.contains(MapFlags::MAP_FIXED_NOREPLACE); let dst_base = (map.address != 0 || fixed) .then_some(Page::containing_address(VirtualAddress::new(map.address))); if map.offset % PAGE_SIZE != 0 { return Err(Error::new(EINVAL)); } let src_address_space = { Arc::clone( self.context .upgrade() .ok_or(Error::new(ENODEV))? .read(token.token()) .addr_space()?, ) }; if Arc::ptr_eq(&src_address_space, &dst_addr_space) { return Err(Error::new(EBUSY)); } let (pid, desc) = { let context_lock = context::current(); let context = context_lock.read(token.token()); let desc = context.files.read().find_by_scheme(self.scheme_id, file)?; (context.pid, desc.description) }; let response = self.call_extended_inner( None, Sqe { opcode: Opcode::MmapPrep as u8, sqe_flags: SqeFlags::empty(), _rsvd: 0, tag: self.next_id()?, args: [ file as u64, unaligned_size as u64, map.flags.bits() as u64, map.offset as u64, 0, uid_gid_hack_merge(current_uid_gid(token)), ], caller: pid as u64, }, &mut PageSpan::empty(), token, )?; // TODO: I've previously tested that this works, but because the scheme trait all of // Redox's schemes currently rely on doesn't allow one-way messages, there's no current // code using it. //let mapping_is_lazy = map.flags.contains(MapFlags::MAP_LAZY); let mapping_is_lazy = false; let base_page_opt = match response { Response::Regular(code, _) => (!mapping_is_lazy).then_some(Error::demux(code)?), Response::Fd(_) => { debug!("Scheme incorrectly returned an fd for fmap."); return Err(Error::new(EIO)); } Response::MultipleFds(_) => return Err(Error::new(EIO)), }; let file_ref = GrantFileRef { description: desc, base_offset: map.offset, }; let src = match base_page_opt { Some(base_addr) => Some({ if base_addr % PAGE_SIZE != 0 { return Err(Error::new(EINVAL)); } let addr_space_lock = &src_address_space; BorrowedFmapSource { src_base: Page::containing_address(VirtualAddress::new(base_addr)), addr_space_lock, addr_space_guard: addr_space_lock.acquire_write(), mode: if map.flags.contains(MapFlags::MAP_SHARED) { MmapMode::Shared } else { MmapMode::Cow }, } }), None => None, }; let page_count_nz = NonZeroUsize::new(page_count).expect("already validated map.size != 0"); let mut notify_files = Vec::new(); let dst_base = { dst_addr_space.acquire_write().mmap( &dst_addr_space, dst_base, page_count_nz, map.flags, &mut notify_files, |dst_base, flags, mapper, flusher| { Grant::borrow_fmap( PageSpan::new(dst_base, page_count), flags, file_ref, src, &dst_addr_space, mapper, flusher, token, ) }, )? }; for map in notify_files { let _ = map.unmap(token); } Ok(dst_base.start_address().data()) } pub fn call_fdwrite( &self, descs: Vec>>, flags: CallFlags, _arg: u64, metadata: &[u64], ) -> Result { 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>>, request_id: usize, _flags: FmoveFdFlags, ) -> Result { let num_fds = descs.len(); match self .states .lock() .get_mut(request_id) .ok_or(Error::new(EINVAL))? { &mut 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], token: &mut CleanLockToken, ) -> Result { if metadata.is_empty() { return Err(Error::new(EINVAL)); } 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, token) } _ => Err(Error::new(EINVAL)), } } fn handle_obtainfd( &self, payload: UserSliceRw, request_id: usize, flags: FobtainFdFlags, token: &mut CleanLockToken, ) -> Result { let descriptions = match self .states .lock() .get_mut(request_id) .ok_or(Error::new(EINVAL))? { &mut 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, token)? } else { Self::bulk_add_fds(descriptions, payload, token)? }; Ok(num_fds) } fn bulk_add_fds( descriptions: Vec>>, payload: UserSliceRw, token: &mut CleanLockToken, ) -> Result { let cnt = descriptions.len(); if payload.len() != cnt * size_of::() { return Err(Error::new(EINVAL)); } if descriptions.is_empty() { return Ok(0); } let current_lock = context::current(); let current = current_lock.write(token.token()); let files: Vec = 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::()); 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>>, payload: UserSliceRw, token: &mut CleanLockToken, ) -> Result { let cnt = descriptions.len(); if payload.len() != cnt * size_of::() { 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::()) .next() .ok_or(Error::new(EINVAL))? .read_usize()?; let current_lock = context::current(); let current = current_lock.write(token.token()); if first_fd == usize::MAX { let files = files_iter.collect::>(); let handles = current .bulk_insert_files_upper(files) .ok_or(Error::new(EMFILE))?; let payload_chunks = payload.in_exact_chunks(size_of::()); 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 = payload .usizes() .map(|res| res.map(|i| FileHandle::from(i | syscall::UPPER_FDTBL_TAG))) .collect::>()?; let files = files_iter.collect::>(); current.bulk_insert_files_upper_manual(files, &handles)?; Ok(handles.len()) } } } pub struct CaptureGuard { destroyed: bool, base: usize, len: usize, span: PageSpan, head: CopyInfo, tail: CopyInfo, addrsp: Option>, } impl CaptureGuard { fn base(&self) -> usize { self.base } fn len(&self) -> usize { self.len } fn span(&mut self) -> &mut PageSpan { &mut self.span } } struct CopyInfo { src: Option, // TODO dst: Option>, } impl CaptureGuard { fn release_inner(&mut self) -> Result<()> { if self.destroyed { return Ok(()); } self.destroyed = true; if self.base == DANGLING { return Ok(()); } // TODO: Encode src and dst better using const generics. if let CopyInfo { src: Some(ref src), dst: Some(ref mut dst), } = self.head { dst.copy_from_slice(&src.buf()[self.base % PAGE_SIZE..][..dst.len()])?; } if let CopyInfo { src: Some(ref src), dst: Some(ref mut dst), } = self.tail { dst.copy_from_slice(&src.buf()[..dst.len()])?; } let unpin = true; if let Some(ref addrsp) = self.addrsp && !self.span.is_empty() { addrsp.munmap(self.span, unpin)?; } Ok(()) } pub fn release(mut self) -> Result<()> { self.release_inner() } } impl Drop for CaptureGuard { fn drop(&mut self) { let _ = self.release_inner(); } } /// base..base+size => page..page+page_count*PAGE_SIZE, offset fn page_range_containing(base: usize, size: usize) -> (Page, usize, usize) { let first_page = Page::containing_address(VirtualAddress::new(base)); let offset = base - first_page.start_address().data(); (first_page, (size + offset).div_ceil(PAGE_SIZE), offset) } /// `UserInner` has to be wrapped #[derive(Clone)] pub struct UserScheme { pub(crate) inner: Weak, } impl UserScheme { pub fn new(inner: Weak) -> UserScheme { UserScheme { inner } } } impl KernelScheme for UserScheme { fn kopen( &self, path: &str, flags: usize, ctx: CallerCtx, token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.copy_and_capture_tail(path.as_bytes(), token)?; match inner.call_extended( ctx, None, Opcode::Open, [address.base(), address.len(), flags], address.span(), token, )? { Response::Regular(code, fl) => Ok({ let _ = Error::demux(code)?; OpenResult::SchemeLocal( code, InternalFlags::from_extra0(fl).ok_or(Error::new(EINVAL))?, ) }), Response::Fd(desc) => Ok(OpenResult::External(desc)), Response::MultipleFds(_) => Err(Error::new(EIO)), } } fn kopenat( &self, file: usize, path: super::StrOrBytes, flags: usize, fcntl_flags: u32, ctx: CallerCtx, token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.copy_and_capture_tail(path.as_bytes(), token)?; let result = inner.call_extended( ctx, None, Opcode::OpenAt, [file, address.base(), address.len(), flags, fcntl_flags as _], address.span(), token, ); address.release()?; match result? { Response::Regular(code, fl) => Ok({ let fd = Error::demux(code)?; OpenResult::SchemeLocal( fd, InternalFlags::from_extra0(fl).ok_or(Error::new(EINVAL))?, ) }), Response::Fd(desc) => Ok(OpenResult::External(desc)), Response::MultipleFds(_) => Err(Error::new(EIO)), } } fn rmdir(&self, path: &str, _ctx: CallerCtx, token: &mut CleanLockToken) -> Result<()> { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.copy_and_capture_tail(path.as_bytes(), token)?; inner.call( Opcode::Rmdir, [address.base(), address.len()], address.span(), token, )?; Ok(()) } fn unlink(&self, path: &str, _ctx: CallerCtx, token: &mut CleanLockToken) -> Result<()> { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.copy_and_capture_tail(path.as_bytes(), token)?; inner.call( Opcode::Unlink, [address.base(), address.len()], address.span(), token, )?; Ok(()) } fn fsize(&self, file: usize, token: &mut CleanLockToken) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; inner .call(Opcode::Fsize, [file], &mut PageSpan::empty(), token) .map(|o| o as u64) } fn fchmod(&self, file: usize, mode: u16, token: &mut CleanLockToken) -> Result<()> { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; inner.call( Opcode::Fchmod, [file, mode as usize], &mut PageSpan::empty(), token, )?; Ok(()) } fn fchown(&self, file: usize, uid: u32, gid: u32, token: &mut CleanLockToken) -> Result<()> { { let ctx = context::current(); let cx = &ctx.read(token.token()); if cx.euid != 0 && (uid != cx.euid || gid != cx.egid) { return Err(Error::new(EPERM)); } } let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; inner.call( Opcode::Fchown, [file, uid as usize, gid as usize], &mut PageSpan::empty(), token, )?; Ok(()) } fn fcntl( &self, file: usize, cmd: usize, arg: usize, token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; inner.call( Opcode::Fcntl, [file, cmd, arg], &mut PageSpan::empty(), token, ) } fn fevent( &self, file: usize, flags: EventFlags, token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; inner .call( Opcode::Fevent, [file, flags.bits()], &mut PageSpan::empty(), token, ) .map(EventFlags::from_bits_truncate) } fn flink( &self, file: usize, path: &str, _ctx: CallerCtx, token: &mut CleanLockToken, ) -> Result<()> { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.copy_and_capture_tail(path.as_bytes(), token)?; inner.call( Opcode::Flink, [file, address.base(), address.len()], address.span(), token, )?; Ok(()) } fn frename( &self, file: usize, path: &str, _ctx: CallerCtx, token: &mut CleanLockToken, ) -> Result<()> { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.copy_and_capture_tail(path.as_bytes(), token)?; inner.call( Opcode::Frename, [file, address.base(), address.len()], address.span(), token, )?; Ok(()) } fn fsync(&self, file: usize, token: &mut CleanLockToken) -> Result<()> { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; inner.call(Opcode::Fsync, [file], &mut PageSpan::empty(), token)?; Ok(()) } fn ftruncate(&self, file: usize, len: usize, token: &mut CleanLockToken) -> Result<()> { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; inner.call( Opcode::Ftruncate, [file, len], &mut PageSpan::empty(), token, )?; Ok(()) } fn close(&self, id: usize, token: &mut CleanLockToken) -> Result<()> { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; if !inner.supports_on_close { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; inner.call(Opcode::Close, [id], &mut PageSpan::empty(), token)?; return Ok(()); } inner.todo.send( Sqe { opcode: Opcode::CloseMsg as u8, sqe_flags: SqeFlags::empty(), _rsvd: 0, tag: 0, args: [id as u64, 0, 0, 0, 0, 0], caller: 0, // TODO? }, token, ); event::trigger(inner.root_id, inner.handle_id, EVENT_READ); Ok(()) } fn kdup( &self, file: usize, buf: UserSliceRo, ctx: CallerCtx, token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.capture_user(buf, token)?; let result = inner.call_extended( ctx, None, Opcode::Dup, [file, address.base(), address.len()], address.span(), token, ); address.release()?; match result? { Response::Regular(code, fl) => Ok({ let fd = Error::demux(code)?; OpenResult::SchemeLocal( fd, InternalFlags::from_extra0(fl).ok_or(Error::new(EINVAL))?, ) }), Response::Fd(desc) => Ok(OpenResult::External(desc)), Response::MultipleFds(_) => Err(Error::new(EIO)), } } fn kfpath(&self, file: usize, buf: UserSliceWo, token: &mut CleanLockToken) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.capture_user(buf, token)?; let result = inner.call( Opcode::Fpath, [file, address.base(), address.len()], address.span(), token, ); address.release()?; result } fn kreadoff( &self, file: usize, buf: UserSliceWo, offset: u64, call_flags: u32, _stored_flags: u32, token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.capture_user(buf, token)?; let result = inner.call( Opcode::Read, [ file as u64, address.base() as u64, address.len() as u64, offset, u64::from(call_flags), ], address.span(), token, ); address.release()?; result } fn kwriteoff( &self, file: usize, buf: UserSliceRo, offset: u64, call_flags: u32, _stored_flags: u32, token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.capture_user(buf, token)?; let result = inner.call( Opcode::Write, [ file as u64, address.base() as u64, address.len() as u64, offset, u64::from(call_flags), ], address.span(), token, ); address.release()?; result } fn kfutimens( &self, file: usize, buf: UserSliceRo, token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.capture_user(buf, token)?; let result = inner.call( Opcode::Futimens, [file, address.base(), address.len()], address.span(), token, ); address.release()?; result } fn getdents( &self, file: usize, buf: UserSliceWo, header_size: u16, opaque_id_start: u64, token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.capture_user(buf, token)?; // TODO: Support passing the 16-byte record_len of the last dent, to make it possible to // iterate backwards without first interating forward? The last entry will contain the // opaque id to pass to the next getdents. Since this field is small, this would fit in the // extra_raw field of `Cqe`s. let result = inner.call( Opcode::Getdents, [ file, address.base(), address.len(), header_size.into(), opaque_id_start as usize, ], address.span(), token, ); address.release()?; result } fn kfstat(&self, file: usize, stat: UserSliceWo, token: &mut CleanLockToken) -> Result<()> { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.capture_user(stat, token)?; let result = inner.call( Opcode::Fstat, [file, address.base(), address.len()], address.span(), token, ); address.release()?; result.map(|_| ()) } fn kfstatvfs(&self, file: usize, stat: UserSliceWo, token: &mut CleanLockToken) -> Result<()> { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.capture_user(stat, token)?; let result = inner.call( Opcode::Fstatvfs, [file, address.base(), address.len()], address.span(), token, ); address.release()?; result.map(|_| ()) } fn kfmap( &self, file: usize, addr_space: &Arc, map: &Map, _consume: bool, token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; inner.fmap_inner(Arc::clone(addr_space), file, map, token) } fn kfunmap( &self, number: usize, offset: usize, size: usize, flags: MunmapFlags, token: &mut CleanLockToken, ) -> Result<()> { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let ctx = { context::current().read(token.token()).caller_ctx() }; let res = inner.call_extended( ctx, None, Opcode::Munmap, [number, size, flags.bits(), offset], &mut PageSpan::empty(), token, )?; match res { Response::Regular(_, _) => Ok(()), Response::Fd(_) => Err(Error::new(EIO)), Response::MultipleFds(_) => Err(Error::new(EIO)), } } fn kcall( &self, id: usize, payload: UserSliceRw, _flags: CallFlags, metadata: &[u64], token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; let mut address = inner.capture_user(payload, token)?; let ctx = { context::current().read(token.token()).caller_ctx() }; let mut sqe = Sqe { opcode: Opcode::Call as u8, sqe_flags: SqeFlags::empty(), _rsvd: 0, tag: inner.next_id()?, caller: ctx.pid as u64, args: [ id as u64, address.base() as u64, address.len() as u64, 0, 0, 0, ], }; { let dst = &mut sqe.args[3..]; let len = dst.len().min(metadata.len()); dst[..len].copy_from_slice(&metadata[..len]); } let res = inner.call_extended_inner(None, sqe, address.span(), token)?; 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>>, flags: CallFlags, arg: u64, _metadata: &[u64], token: &mut CleanLockToken, ) -> Result { 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(token.token()).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(), token, )?; 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], token: &mut CleanLockToken, ) -> Result { let inner = self.inner.upgrade().ok_or(Error::new(ENODEV))?; if payload.len() % mem::size_of::() != 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(token.token()).caller_ctx() }; let len = payload.len() / mem::size_of::(); let res = inner.call_extended( ctx, None, Opcode::Recvfd, [id, recvfd_flags.bits(), len], &mut PageSpan::empty(), token, )?; 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, token)? } else { UserInner::bulk_add_fds(descriptions, payload, token)? } } else { 0 }; Ok(num_fds) } } pub trait Args: Copy { fn args(self) -> [u64; 6]; } impl Args for [u64; N] { fn args(self) -> [u64; 6] { assert!(self.len() <= N); core::array::from_fn(|i| self.get(i).copied().unwrap_or(0)) } } impl Args for [usize; N] { fn args(self) -> [u64; 6] { self.map(|s| s as u64).args() } } // TODO: Find a better way to do authentication. No scheme call currently uses arg 5 but this will // likely change. Ideally this mechanism would also allow the scheme to query the supplementary // group list. fn uid_gid_hack_merge([uid, gid]: [u32; 2]) -> u64 { u64::from(uid) | (u64::from(gid) << 32) } fn current_uid_gid(token: &mut CleanLockToken) -> [u32; 2] { let ctx = context::current(); let p = &ctx.read(token.token()); [p.euid, p.egid] }