//! Filesystem syscalls use core::num::NonZeroUsize; use alloc::{string::{String, ToString}, sync::Arc, vec::Vec}; use redox_path::RedoxPath; use crate::{ context::{ self, file::{FileDescription, FileDescriptor, InternalFlags, LockedFileDescription}, memory::{AddrSpace, GenericFlusher, Grant, PageSpan, TlbShootdownActions}, }, memory::{Page, VirtualAddress, PAGE_SIZE}, scheme::{self, FileHandle, KernelScheme, OpenResult, StrOrBytes}, sync::{CleanLockToken, RwLock}, 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 current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); let file = context.get_file(fd, &mut token).ok_or(Error::new(EBADF))?; let desc = *file.description.read(token.token()); (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 (scheme_id, number) = { let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); let name = context.name; let pipe = context.get_file(fh, &mut token).ok_or_else(|| { let ft = context.files.read(token.token()); let raw = fh.get(); let stripped = raw & !syscall::UPPER_FDTBL_TAG; let is_upper = raw & syscall::UPPER_FDTBL_TAG != 0; let upper_len = ft.upper_fdtbl.len(); let upper_at_idx = if is_upper && stripped < ft.upper_fdtbl.len() { ft.upper_fdtbl[stripped].is_some() } else { false }; let upper_present: String = ft.upper_fdtbl.iter().enumerate() .filter(|(_, f)| f.is_some()) .map(|(i, _)| i.to_string()) .collect::>().join(","); let posix_present: String = ft.posix_fdtbl.iter().enumerate() .filter(|(_, f)| f.is_some()) .map(|(i, _)| i.to_string()) .collect::>().join(","); info!( "openat: EBADF fh={:#x} ctx='{}' is_upper={} idx={} upper_len={} upper_at_idx={} upper_present=[{}] posix_present=[{}]", raw, name, is_upper, stripped, upper_len, upper_at_idx, upper_present, posix_present ); Error::new(EBADF) })?; let desc = pipe.description.read(token.token()); info!("openat: fh={:#x} scheme_id={} number={} path='{}' ctx='{}'", fh.get(), desc.scheme.get(), desc.number, path_buf, name); (desc.scheme, desc.number) }; 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(), scheme_id)?; let res = scheme.kopenat( number, StrOrBytes::from_str(&path_buf), flags, fcntl_flags, caller_ctx, token, ); match res? { OpenResult::SchemeLocal(number, internal_flags) => { FileDescriptor { description: Arc::new(RwLock::new(FileDescription { offset: 0, internal_flags, scheme: scheme_id, number, flags: (flags & !O_CLOEXEC) as u32, })), cloexec: flags & O_CLOEXEC == O_CLOEXEC, } } OpenResult::External(desc) => FileDescriptor { description: desc, cloexec: flags & O_CLOEXEC == O_CLOEXEC, }, } }; let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); context .add_file( new_description, &mut token, ) .ok_or(Error::new(EMFILE)) } /// Open a file at a specific path, placing the result into a reserved fd slot (upstream 0.9.0). pub fn openat_into( fh: FileHandle, raw_path: UserSliceRo, flags: usize, fcntl_flags: u32, new_fd: FileHandle, token: &mut CleanLockToken, ) -> Result { let path_buf = copy_path_to_buf(raw_path, PATH_MAX)?; let (scheme_id, number) = { let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); let pipe = context.get_file(fh, &mut token).ok_or(Error::new(EBADF))?; let desc = pipe.description.read(token.token()); (desc.scheme, desc.number) }; let caller_ctx = context::current().read(token.token()).caller_ctx(); let new_description = { let scheme = scheme::get_scheme(token.token(), scheme_id)?; let res = scheme.kopenat(number, StrOrBytes::from_str(&path_buf), flags, fcntl_flags, caller_ctx, token)?; match res { OpenResult::SchemeLocal(number, internal_flags) => { FileDescriptor { description: Arc::new(RwLock::new(FileDescription { offset: 0, internal_flags, scheme: scheme_id, number, flags: (flags & !O_CLOEXEC) as u32, })), cloexec: flags & O_CLOEXEC == O_CLOEXEC, } } OpenResult::External(desc) => FileDescriptor { description: desc, cloexec: flags & O_CLOEXEC == O_CLOEXEC, }, } }; let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); context.insert_file(new_fd, new_description, &mut token).ok_or(Error::new(EEXIST)) } /// 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 (number, scheme_id) = { let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); let pipe = context.get_file(fh, &mut token).ok_or(Error::new(EBADF))?; let desc = pipe.description.read(token.token()); (desc.number, desc.scheme) }; let scheme = scheme::get_scheme(token.token(), scheme_id)?; 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(number, &path_buf, flags, caller_ctx, token) } /// Close syscall pub fn close(fd: FileHandle, token: &mut CleanLockToken) -> Result<()> { let file = { let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); context .remove_file(fd, &mut token) .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 current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); ( context.caller_ctx(), context.get_file(fd, &mut token).ok_or(Error::new(EBADF))?, ) }; if user_buf.is_empty() { Ok(FileDescriptor { description: Arc::clone(&file.description), cloexec, }) } else { let description = { *file.description.read(token.token()) }; 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)?; let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); context .add_file(new_file, &mut token) .ok_or(Error::new(EMFILE)) } /// Duplicate a file descriptor, placing into a specific fd slot (upstream 0.9.0). pub fn dup_into( fd: FileHandle, new_fd: FileHandle, buf: UserSliceRo, token: &mut CleanLockToken, ) -> Result { let new_file = duplicate_file(fd, buf, false, token)?; let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); context.insert_file(new_fd, new_file, &mut token).ok_or(Error::new(EEXIST)) } /// 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 current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); context .insert_file(new_fd, new_file, &mut token) .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 = { let current_lock = context::current(); let mut current = current_lock.read(token.token()); match (current.token_split(), flags.contains(CallFlags::CONSUME)) { ((ctxt, mut token), true) => ctxt.remove_file(fd, &mut token), ((ctxt, mut token), false) => ctxt.get_file(fd, &mut token), } } .ok_or(Error::new(EBADF))?; let (scheme_id, number) = { let desc = file.description.read(token.token()); (desc.scheme, desc.number) }; let scheme = scheme::get_scheme(token.token(), scheme_id)?; if flags.contains(CallFlags::STD_FS) { scheme.translate_std_fs_call(number, file.description, 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 mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); let file_descriptor = context .get_file(socket, &mut token) .ok_or(Error::new(EBADF))?; let desc = &file_descriptor.description.read(token.token()); (desc.scheme, desc.number) }; let scheme = scheme::get_scheme(token.token(), scheme)?; let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); ( scheme, number, if flags.contains(CallFlags::FD_CLONE) { context.bulk_get_files(&target_fds, &mut token) } else { context.bulk_remove_files(&target_fds, &mut token) }? .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 mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); let file_descriptor = context.get_file(fd, &mut token).ok_or(Error::new(EBADF))?; let desc = file_descriptor.description.read(token.token()); (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 = { let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); context.get_file(fd, &mut token) } .ok_or(Error::new(EBADF))?; let (scheme_id, number, flags) = { let desc = file.description.write(token.token()); (desc.scheme, desc.number, desc.flags) }; 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 current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); return context .add_file_min(new_file, arg, &mut token) .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(), scheme_id)?; scheme.fcntl(number, cmd, arg, token)?; }; // Perform kernel operation if scheme agrees { let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); let mut files = context.files.write(token.token()); let (files, mut token) = files.token_split(); 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(flags as usize), F_SETFL => { let new_flags = (flags & O_ACCMODE as u32) | (arg as u32 & !O_ACCMODE as u32); file.description.write(token.token()).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, file) = { let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); ( context.caller_ctx(), context.get_file(fd, &mut token).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 (number, scheme_id) = { let desc = file.description.read(token.token()); (desc.number, desc.scheme) }; let scheme = scheme::get_scheme(token.token(), scheme_id)?; // TODO: Check EXDEV. /* if scheme_id != description.scheme { return Err(Error::new(EXDEV)); } */ scheme.flink(number, reference.as_ref(), caller_ctx, token) } pub fn frename(fd: FileHandle, raw_path: UserSliceRo, token: &mut CleanLockToken) -> Result<()> { let (caller_ctx, file) = { let current_lock = context::current(); let mut current = current_lock.read(token.token()); let (context, mut token) = current.token_split(); ( context.caller_ctx(), context.get_file(fd, &mut token).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 (number, scheme_id) = { let desc = file.description.read(token.token()); (desc.number, desc.scheme) }; let scheme = scheme::get_scheme(token.token(), scheme_id)?; // TODO: Check EXDEV. /* if scheme_id != description.scheme { return Err(Error::new(EXDEV)); } */ scheme.frename(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, token)?; 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 mut token = token.token(); let base = addr_space.acquire_write(token.downgrade()).mmap( &addr_space, requested_dst_base, NonZeroUsize::new(1).expect("value specified is not zero"), map_flags, None, |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, None, token.downgrade(), )?; 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(token.token()); 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.expect("fsize not None as whence is SEEK_END")) .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 { 0 }; 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(token.token()).offset; *offset = offset.saturating_add(bytes_read as u64) } // I/O accounting: increment rchar and syscr on current context. // Cross-referenced with Linux 7.1 mm/filemap.c:filemap_get_pages // and task_io_account_read(). let current = context::current(); let mut guard = current.write(token.token()); guard.io_rchar = guard.io_rchar.saturating_add(bytes_read as u64); guard.io_syscr = guard.io_syscr.saturating_add(1); guard.io_read_bytes = guard.io_read_bytes.saturating_add(bytes_read as u64); Ok(bytes_read) } pub fn sys_write(fd: FileHandle, buf: UserSliceRo, token: &mut CleanLockToken) -> Result { let 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 { 0 }; 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(token.token()).offset; *offset = offset.saturating_add(bytes_written as u64) } // I/O accounting: increment wchar and syscw on current context. let current = context::current(); let mut guard = current.write(token.token()); guard.io_wchar = guard.io_wchar.saturating_add(bytes_written as u64); guard.io_syscw = guard.io_syscw.saturating_add(1); guard.io_write_bytes = guard.io_write_bytes.saturating_add(bytes_written as u64); Ok(bytes_written) })(); if result.is_err() && fd.get() <= 10 { let name = context::current().read(token.token()).name; info!("sys_write fd={} ctx='{}' err={:?}", fd.get(), name, result.as_ref().err()); } result }