//! Filesystem syscalls use core::{mem::size_of, num::NonZeroUsize}; use alloc::{string::String, sync::Arc, vec::Vec}; use redox_path::RedoxPath; use spin::RwLock; use crate::{ context::{ self, file::{FileDescription, FileDescriptor, InternalFlags}, memory::{AddrSpace, GenericFlusher, Grant, PageSpan, TlbShootdownActions}, }, paging::{Page, VirtualAddress, PAGE_SIZE}, scheme::{self, FileHandle, KernelScheme, OpenResult, StrOrBytes}, sync::CleanLockToken, syscall::{data::Stat, error::*, flag::*}, }; use super::usercopy::{UserSlice, UserSliceRo, UserSliceRw, UserSliceWo}; pub fn file_op_generic( fd: FileHandle, token: &mut CleanLockToken, op: impl FnOnce(&dyn KernelScheme, usize, &mut CleanLockToken) -> Result, ) -> Result { file_op_generic_ext(fd, token, |s, _, desc, token| op(s, desc.number, token)) } pub fn file_op_generic_ext( fd: FileHandle, token: &mut CleanLockToken, op: impl FnOnce( &dyn KernelScheme, Arc>, FileDescription, &mut CleanLockToken, ) -> Result, ) -> Result { let (file, desc) = { let file = context::current() .read(token.token()) .get_file(fd) .ok_or(Error::new(EBADF))?; let desc = *file.description.read(); (file, desc) }; let scheme = scheme::get_scheme(token.token(), desc.scheme)?; op(&*scheme, file.description, desc, token) } pub fn copy_path_to_buf(raw_path: UserSliceRo, max_len: usize) -> Result { let mut path_buf = vec![0_u8; max_len]; if raw_path.len() > path_buf.len() { return Err(Error::new(ENAMETOOLONG)); } let path_len = raw_path.copy_common_bytes_to_slice(&mut path_buf)?; path_buf.truncate(path_len); String::from_utf8(path_buf).map_err(|_| Error::new(EINVAL)) //core::str::from_utf8(&path_buf[..path_len]).map_err(|_| Error::new(EINVAL)) } // TODO: Define elsewhere const PATH_MAX: usize = PAGE_SIZE; pub fn openat( fh: FileHandle, raw_path: UserSliceRo, flags: usize, fcntl_flags: u32, euid: u32, egid: u32, token: &mut CleanLockToken, ) -> Result { let path_buf = copy_path_to_buf(raw_path, PATH_MAX)?; let pipe = context::current() .read(token.token()) .get_file(fh) .ok_or(Error::new(EBADF))?; let description = pipe.description.read(); let caller_ctx = context::current() .read(token.token()) .caller_ctx() .filter_uid_gid(euid, egid); let new_description = { let scheme = scheme::get_scheme(token.token(), description.scheme)?; let res = scheme.kopenat( description.number, StrOrBytes::from_str(&path_buf), flags, fcntl_flags, caller_ctx, token, ); match res? { OpenResult::SchemeLocal(number, internal_flags) => { Arc::new(RwLock::new(FileDescription { offset: 0, internal_flags, scheme: description.scheme, number, flags: (flags & !O_CLOEXEC) as u32, })) } OpenResult::External(desc) => desc, } }; context::current() .read(token.token()) .add_file(FileDescriptor { description: new_description, cloexec: flags as usize & O_CLOEXEC == O_CLOEXEC, }) .ok_or(Error::new(EMFILE)) } /// Unlinkat syscall pub fn unlinkat( fh: FileHandle, raw_path: UserSliceRo, flags: usize, euid: u32, egid: u32, token: &mut CleanLockToken, ) -> Result<()> { let path_buf = copy_path_to_buf(raw_path, PATH_MAX)?; let pipe = context::current() .read(token.token()) .get_file(fh) .ok_or(Error::new(EBADF))?; let description = pipe.description.read(); let scheme = scheme::get_scheme(token.token(), description.scheme)?; let caller_ctx = context::current() .read(token.token()) .caller_ctx() .filter_uid_gid(euid, egid); /* let mut path_buf = BorrowedHtBuf::head()?; let path = path_buf.use_for_string(raw_path)?; */ scheme.unlinkat(description.number, &path_buf, flags, caller_ctx, token) } /// Close syscall pub fn close(fd: FileHandle, token: &mut CleanLockToken) -> Result<()> { let file = { let context_lock = context::current(); let context = context_lock.read(token.token()); context.remove_file(fd).ok_or(Error::new(EBADF))? }; file.close(token) } fn duplicate_file( fd: FileHandle, user_buf: UserSliceRo, cloexec: bool, token: &mut CleanLockToken, ) -> Result { let (caller_ctx, file) = { let context_lock = context::current(); let context = context_lock.read(token.token()); ( context.caller_ctx(), context.get_file(fd).ok_or(Error::new(EBADF))?, ) }; if user_buf.is_empty() { Ok(FileDescriptor { description: Arc::clone(&file.description), cloexec, }) } else { let description = { *file.description.read() }; let new_description = { let scheme = scheme::get_scheme(token.token(), description.scheme)?; match scheme.kdup(description.number, user_buf, caller_ctx, token)? { OpenResult::SchemeLocal(number, internal_flags) => { Arc::new(RwLock::new(FileDescription { offset: 0, internal_flags, scheme: description.scheme, number, flags: description.flags, })) } OpenResult::External(desc) => desc, } }; Ok(FileDescriptor { description: new_description, cloexec, }) } } /// Duplicate file descriptor pub fn dup(fd: FileHandle, buf: UserSliceRo, token: &mut CleanLockToken) -> Result { let new_file = duplicate_file(fd, buf, false, token)?; context::current() .read(token.token()) .add_file(new_file) .ok_or(Error::new(EMFILE)) } /// Duplicate file descriptor, replacing another pub fn dup2( fd: FileHandle, new_fd: FileHandle, buf: UserSliceRo, token: &mut CleanLockToken, ) -> Result { if fd == new_fd { Ok(new_fd) } else { let _ = close(new_fd, token); let new_file = duplicate_file(fd, buf, false, token)?; let context_ref = context::current(); let context = context_ref.read(token.token()); context .insert_file(new_fd, new_file) .ok_or(Error::new(EMFILE)) } } pub fn call( fd: FileHandle, payload: UserSliceRw, flags: CallFlags, metadata: UserSliceRo, token: &mut CleanLockToken, ) -> Result { let mut meta = [0_u64; 3]; // TODO: bytemuck/plain let copied = metadata.copy_common_bytes_to_slice(unsafe { core::slice::from_raw_parts_mut(meta.as_mut_ptr().cast(), meta.len() * 8) })?; match flags { f if f.contains(CallFlags::WRITE | CallFlags::FD) => { call_fdwrite(fd, payload, flags, &meta[..copied / 8], token) } f if f.contains(CallFlags::READ | CallFlags::FD) => { call_fdread(fd, payload, flags, &meta[..copied / 8], token) } _ => call_normal(fd, payload, flags, &meta[..copied / 8], token), } } fn call_normal( fd: FileHandle, payload: UserSliceRw, flags: CallFlags, metadata: &[u64], token: &mut CleanLockToken, ) -> Result { let file = (match ( context::current().read(token.token()), flags.contains(CallFlags::CONSUME), ) { (ctxt, true) => ctxt.remove_file(fd), (ctxt, false) => ctxt.get_file(fd), }) .ok_or(Error::new(EBADF))?; let (scheme_id, number) = { let desc = file.description.read(); (desc.scheme, desc.number) }; let scheme = scheme::get_scheme(token.token(), scheme_id)?; if flags.contains(CallFlags::STD_FS) { scheme.kstdfscall(number, payload, flags, metadata, token) } else { scheme.kcall(number, payload, flags, metadata, token) } } fn call_fdwrite( fd: FileHandle, payload: UserSliceRw, flags: CallFlags, metadata: &[u64], token: &mut CleanLockToken, ) -> Result { let payload_chunks = payload.in_exact_chunks(size_of::()); let fds = payload_chunks .map(|chunk| { let fd = chunk.read_usize()?; Ok(FileHandle::from(fd)) }) .collect::>>()?; let len = fds.len(); fdwrite_inner(fd, fds, flags, 0, metadata, token)?; Ok(len) } fn fdwrite_inner( socket: FileHandle, target_fds: Vec, flags: CallFlags, arg: u64, metadata: &[u64], token: &mut CleanLockToken, ) -> Result { // TODO: Ensure deadlocks can't happen let (scheme, number, descs_to_send) = { let (scheme, number) = { let current_lock = context::current(); let current = current_lock.read(token.token()); let file_descriptor = current.get_file(socket).ok_or(Error::new(EBADF))?; let desc = &file_descriptor.description.read(); (desc.scheme, desc.number) }; let scheme = scheme::get_scheme(token.token(), scheme)?; let current_lock = context::current(); let current = current_lock.read(token.token()); ( scheme, number, if flags.contains(CallFlags::FD_CLONE) { current.bulk_get_files(&target_fds) } else { current.bulk_remove_files(&target_fds) }? .into_iter() .map(|f| f.description) .collect(), ) }; // Inform the scheme whether there are still references to the file description to be sent, // either in the current file table or in other file tables, regardless of whether EXCLUSIVE is // requested. let flags_to_scheme = if flags.contains(CallFlags::FD_EXCLUSIVE) { for desc in &descs_to_send { if Arc::strong_count(desc) > 1 { return Err(Error::new(EBUSY)); } } CallFlags::FD_EXCLUSIVE } else { CallFlags::empty() }; scheme.kfdwrite(number, descs_to_send, flags_to_scheme, arg, metadata, token) } fn call_fdread( fd: FileHandle, payload: UserSliceRw, flags: CallFlags, metadata: &[u64], token: &mut CleanLockToken, ) -> Result { let (scheme, number) = { let (scheme, number) = { let current_lock = context::current(); let current = current_lock.read(token.token()); let file_descriptor = current.get_file(fd).ok_or(Error::new(EBADF))?; let desc = file_descriptor.description.read(); (desc.scheme, desc.number) }; let scheme = scheme::get_scheme(token.token(), scheme)?; (scheme, number) }; scheme.kfdread(number, payload, flags, metadata, token) } pub fn sendfd( socket: FileHandle, fd: FileHandle, flags_raw: usize, arg: u64, token: &mut CleanLockToken, ) -> Result { let sendfd_flags = SendFdFlags::from_bits(flags_raw).ok_or(Error::new(EINVAL))?; let mut call_flags = CallFlags::FD | CallFlags::WRITE; if sendfd_flags.contains(SendFdFlags::CLONE) { call_flags |= CallFlags::FD_CLONE; } if sendfd_flags.contains(SendFdFlags::EXCLUSIVE) { call_flags |= CallFlags::FD_EXCLUSIVE; } fdwrite_inner(socket, Vec::from([fd]), call_flags, arg, &[], token) } /// File descriptor controls pub fn fcntl(fd: FileHandle, cmd: usize, arg: usize, token: &mut CleanLockToken) -> Result { let file = context::current() .read(token.token()) .get_file(fd) .ok_or(Error::new(EBADF))?; let description = file.description.read(); if cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC { // Not in match because 'files' cannot be locked let new_file = duplicate_file(fd, UserSlice::empty(), cmd == F_DUPFD_CLOEXEC, token)?; let context_lock = context::current(); let context = context_lock.read(token.token()); return context .add_file_min(new_file, arg) .ok_or(Error::new(EMFILE)) .map(FileHandle::into); } // Communicate fcntl with scheme if cmd != F_GETFD && cmd != F_SETFD { let scheme = scheme::get_scheme(token.token(), description.scheme)?; scheme.fcntl(description.number, cmd, arg, token)?; }; // Perform kernel operation if scheme agrees { let context_lock = context::current(); let context = context_lock.read(token.token()); let mut files = context.files.write(); match *files.get_mut(fd.get()).ok_or(Error::new(EBADF))? { Some(ref mut file) => match cmd { F_GETFD => { if file.cloexec { Ok(O_CLOEXEC) } else { Ok(0) } } F_SETFD => { file.cloexec = arg & O_CLOEXEC == O_CLOEXEC; Ok(0) } F_GETFL => Ok(description.flags as usize), F_SETFL => { let new_flags = (description.flags & O_ACCMODE as u32) | (arg as u32 & !O_ACCMODE as u32); drop(description); file.description.write().flags = new_flags; Ok(0) } _ => Err(Error::new(EINVAL)), }, None => Err(Error::new(EBADF)), } } } pub fn flink(fd: FileHandle, raw_path: UserSliceRo, token: &mut CleanLockToken) -> Result<()> { let caller_ctx = context::current().read(token.token()).caller_ctx(); let file = context::current() .read(token.token()) .get_file(fd) .ok_or(Error::new(EBADF))?; /* let mut path_buf = BorrowedHtBuf::head()?; let path = path_buf.use_for_string(raw_path)?; */ let path_buf = copy_path_to_buf(raw_path, PATH_MAX)?; let path = RedoxPath::from_absolute(&path_buf).ok_or(Error::new(EINVAL))?; let (_, reference) = path.as_parts().ok_or(Error::new(EINVAL))?; let description = file.description.read(); let scheme = scheme::get_scheme(token.token(), description.scheme)?; // TODO: Check EXDEV. /* if scheme_id != description.scheme { return Err(Error::new(EXDEV)); } */ scheme.flink(description.number, reference.as_ref(), caller_ctx, token) } pub fn frename(fd: FileHandle, raw_path: UserSliceRo, token: &mut CleanLockToken) -> Result<()> { let caller_ctx = context::current().read(token.token()).caller_ctx(); let file = context::current() .read(token.token()) .get_file(fd) .ok_or(Error::new(EBADF))?; /* let mut path_buf = BorrowedHtBuf::head()?; let path = path_buf.use_for_string(raw_path)?; */ let path_buf = copy_path_to_buf(raw_path, PATH_MAX)?; let path = RedoxPath::from_absolute(&path_buf).ok_or(Error::new(EINVAL))?; let (_, reference) = path.as_parts().ok_or(Error::new(EINVAL))?; let description = file.description.read(); let scheme = scheme::get_scheme(token.token(), description.scheme)?; // TODO: Check EXDEV. /* if scheme_id != description.scheme { return Err(Error::new(EXDEV)); } */ scheme.frename(description.number, reference.as_ref(), caller_ctx, token) } /// File status pub fn fstat(fd: FileHandle, user_buf: UserSliceWo, token: &mut CleanLockToken) -> Result<()> { file_op_generic_ext(fd, token, |scheme, _, desc, token| { scheme.kfstat(desc.number, user_buf, token)?; // TODO: Ensure only the kernel can access the stat when st_dev is set, or use another API // for retrieving the scheme ID from a file descriptor. // TODO: Less hacky method. let st_dev = desc .scheme .get() .try_into() .map_err(|_| Error::new(EOVERFLOW))?; user_buf .advance(core::mem::offset_of!(Stat, st_dev)) .and_then(|b| b.limit(8)) .ok_or(Error::new(EIO))? .copy_from_slice(&u64::to_ne_bytes(st_dev))?; Ok(()) }) } pub fn funmap(virtual_address: usize, length: usize, token: &mut CleanLockToken) -> Result { // Partial lengths in funmap are allowed according to POSIX, but not particularly meaningful; // since the memory needs to SIGSEGV if later read, the entire page needs to disappear. // // Thus, while (temporarily) allowing unaligned lengths for compatibility, aligning the length // should be done by libc. let length_aligned = length.next_multiple_of(PAGE_SIZE); if length != length_aligned { warn!( "funmap passed length {:#x} instead of {:#x}", length, length_aligned ); } let addr_space = Arc::clone(context::current().read(token.token()).addr_space()?); let span = PageSpan::validate_nonempty(VirtualAddress::new(virtual_address), length_aligned) .ok_or(Error::new(EINVAL))?; let unpin = false; let notify = addr_space.munmap(span, unpin)?; for map in notify { let _ = map.unmap(token); } Ok(0) } pub fn mremap( old_address: usize, old_size: usize, new_address: usize, new_size: usize, flags: usize, token: &mut CleanLockToken, ) -> Result { if !old_address.is_multiple_of(PAGE_SIZE) || !old_size.is_multiple_of(PAGE_SIZE) || !new_address.is_multiple_of(PAGE_SIZE) || !new_size.is_multiple_of(PAGE_SIZE) { return Err(Error::new(EINVAL)); } if old_size == 0 || new_size == 0 { return Err(Error::new(EINVAL)); } let old_base = Page::containing_address(VirtualAddress::new(old_address)); let new_base = Page::containing_address(VirtualAddress::new(new_address)); let mremap_flags = MremapFlags::from_bits_truncate(flags); let prot_flags = MapFlags::from_bits_truncate(flags) & (MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::PROT_EXEC); let map_flags = if mremap_flags.contains(MremapFlags::FIXED_REPLACE) { MapFlags::MAP_FIXED } else if mremap_flags.contains(MremapFlags::FIXED) { MapFlags::MAP_FIXED_NOREPLACE } else { MapFlags::empty() } | prot_flags; let addr_space = AddrSpace::current()?; let src_span = PageSpan::new(old_base, old_size.div_ceil(PAGE_SIZE)); let new_page_count = new_size.div_ceil(PAGE_SIZE); let fixed = map_flags.contains(MapFlags::MAP_FIXED) || map_flags.contains(MapFlags::MAP_FIXED_NOREPLACE); let requested_dst_base = (new_address != 0 || fixed).then_some(new_base); if mremap_flags.contains(MremapFlags::KEEP_OLD) { // TODO: This is a hack! Find a better interface for replacing this, perhaps a capability // for non-CoW-borrowed i.e. owned frames, that can be inserted into address spaces. if new_page_count != 1 { return Err(Error::new(EOPNOTSUPP)); } let raii_frame = addr_space.borrow_frame_enforce_rw_allocated(src_span.base, token)?; let base = addr_space.acquire_write().mmap( &addr_space, requested_dst_base, NonZeroUsize::new(1).unwrap(), map_flags, &mut Vec::new(), |page, page_flags, mapper, flusher| { let frame = raii_frame.take(); // XXX: add_ref(RefKind::Shared) is internally done by borrow_frame_enforce_rw_allocated(src_span.base). // The page does not get unref-ed as we call take() on the `raii_frame`. unsafe { mapper .map_phys(page.start_address(), frame.base(), page_flags) .ok_or(Error::new(ENOMEM))? .ignore(); flusher.queue(frame, None, TlbShootdownActions::NEW_MAPPING); } Ok(Grant::allocated_one_page_nomap(page, page_flags)) }, )?; Ok(base.start_address().data()) } else { let base = addr_space.r#move( None, src_span, requested_dst_base, new_page_count, map_flags, &mut Vec::new(), )?; Ok(base.start_address().data()) } } pub fn lseek(fd: FileHandle, pos: i64, whence: usize, token: &mut CleanLockToken) -> Result { let (fsize, desc) = file_op_generic_ext(fd, token, |scheme, desc_arc, desc, token| { Ok(if whence == SEEK_END { (Some(scheme.fsize(desc.number, token)?), desc_arc) } else { (None, desc_arc) }) })?; let mut guard = desc.write(); let new_pos = match whence { SEEK_SET => pos, SEEK_CUR => pos .checked_add_unsigned(guard.offset) .ok_or(Error::new(EOVERFLOW))?, SEEK_END => pos .checked_add_unsigned(fsize.unwrap()) .ok_or(Error::new(EOVERFLOW))?, _ => return Err(Error::new(EINVAL)), }; guard.offset = new_pos.try_into().map_err(|_| Error::new(EINVAL))?; Ok(guard.offset as usize) } pub fn sys_read(fd: FileHandle, buf: UserSliceWo, token: &mut CleanLockToken) -> Result { let (bytes_read, desc_arc, desc) = file_op_generic_ext(fd, token, |scheme, desc_arc, desc, token| { let offset = if desc.internal_flags.contains(InternalFlags::POSITIONED) { desc.offset } else { u64::MAX }; Ok(( scheme.kreadoff(desc.number, buf, offset, desc.flags, desc.flags, token)?, desc_arc, desc, )) })?; if desc.internal_flags.contains(InternalFlags::POSITIONED) { let offset = &mut desc_arc.write().offset; *offset = offset.saturating_add(bytes_read as u64) } Ok(bytes_read) } pub fn sys_write(fd: FileHandle, buf: UserSliceRo, token: &mut CleanLockToken) -> Result { let (bytes_written, desc_arc, desc) = file_op_generic_ext(fd, token, |scheme, desc_arc, desc, token| { let offset = if desc.internal_flags.contains(InternalFlags::POSITIONED) { desc.offset } else { u64::MAX }; Ok(( scheme.kwriteoff(desc.number, buf, offset, desc.flags, desc.flags, token)?, desc_arc, desc, )) })?; if desc.internal_flags.contains(InternalFlags::POSITIONED) { let offset = &mut desc_arc.write().offset; *offset = offset.saturating_add(bytes_written as u64) } Ok(bytes_written) }