// SPDX-License-Identifier: MPL-2.0 use super::SyscallReturn; use crate::{ fs::{ file_handle::FileLike, file_table::{get_file_fast, FdFlags, FileDesc, WithFileTable}, utils::{ FileRange, RangeLockItem, RangeLockItemBuilder, RangeLockType, StatusFlags, OFFSET_MAX, }, }, prelude::*, process::{process_table, Pid}, }; pub fn sys_fcntl(fd: FileDesc, cmd: i32, arg: u64, ctx: &Context) -> Result { let fcntl_cmd = FcntlCmd::try_from(cmd)?; debug!("fd = {}, cmd = {:?}, arg = {}", fd, fcntl_cmd, arg); match fcntl_cmd { FcntlCmd::F_DUPFD => handle_dupfd(fd, arg, FdFlags::empty(), ctx), FcntlCmd::F_DUPFD_CLOEXEC => handle_dupfd(fd, arg, FdFlags::CLOEXEC, ctx), FcntlCmd::F_GETFD => handle_getfd(fd, ctx), FcntlCmd::F_SETFD => handle_setfd(fd, arg, ctx), FcntlCmd::F_GETFL => handle_getfl(fd, ctx), FcntlCmd::F_SETFL => handle_setfl(fd, arg, ctx), FcntlCmd::F_GETLK => handle_getlk(fd, arg, ctx), FcntlCmd::F_SETLK => handle_setlk(fd, arg, true, ctx), FcntlCmd::F_SETLKW => handle_setlk(fd, arg, false, ctx).map_err(|err| match err.error() { Errno::EINTR => Error::new(Errno::ERESTARTSYS), _ => err, }), FcntlCmd::F_GETOWN => handle_getown(fd, ctx), FcntlCmd::F_SETOWN => handle_setown(fd, arg, ctx), } } fn handle_dupfd(fd: FileDesc, arg: u64, flags: FdFlags, ctx: &Context) -> Result { let file_table = ctx.thread_local.file_table().borrow(); let new_fd = file_table.write().dup(fd, arg as FileDesc, flags)?; Ok(SyscallReturn::Return(new_fd as _)) } fn handle_getfd(fd: FileDesc, ctx: &Context) -> Result { let mut file_table = ctx.thread_local.file_table().borrow_mut(); file_table.read_with(|inner| { let fd_flags = inner.get_entry(fd)?.flags(); Ok(SyscallReturn::Return(fd_flags.bits() as _)) }) } fn handle_setfd(fd: FileDesc, arg: u64, ctx: &Context) -> Result { let flags = if arg > u64::from(u8::MAX) { return_errno_with_message!(Errno::EINVAL, "invalid fd flags"); } else { FdFlags::from_bits(arg as u8).ok_or(Error::with_message(Errno::EINVAL, "invalid flags"))? }; let mut file_table = ctx.thread_local.file_table().borrow_mut(); file_table.read_with(|inner| { inner.get_entry(fd)?.set_flags(flags); Ok(SyscallReturn::Return(0)) }) } fn handle_getfl(fd: FileDesc, ctx: &Context) -> Result { let mut file_table = ctx.thread_local.file_table().borrow_mut(); let file = get_file_fast!(&mut file_table, fd); let status_flags = file.status_flags(); let access_mode = file.access_mode(); Ok(SyscallReturn::Return( (status_flags.bits() | access_mode as u32) as _, )) } fn handle_setfl(fd: FileDesc, arg: u64, ctx: &Context) -> Result { let mut file_table = ctx.thread_local.file_table().borrow_mut(); let file = get_file_fast!(&mut file_table, fd); let valid_flags_mask = StatusFlags::O_APPEND | StatusFlags::O_ASYNC | StatusFlags::O_DIRECT | StatusFlags::O_NOATIME | StatusFlags::O_NONBLOCK; let mut status_flags = file.status_flags(); status_flags.remove(valid_flags_mask); status_flags.insert(StatusFlags::from_bits_truncate(arg as _) & valid_flags_mask); file.set_status_flags(status_flags)?; Ok(SyscallReturn::Return(0)) } fn handle_getlk(fd: FileDesc, arg: u64, ctx: &Context) -> Result { let mut file_table = ctx.thread_local.file_table().borrow_mut(); let file = get_file_fast!(&mut file_table, fd); let lock_mut_ptr = arg as Vaddr; let mut lock_mut_c = ctx.user_space().read_val::(lock_mut_ptr)?; let lock_type = RangeLockType::try_from(lock_mut_c.l_type)?; if lock_type == RangeLockType::Unlock { return_errno_with_message!(Errno::EINVAL, "invalid flock type for getlk"); } let mut lock = RangeLockItemBuilder::new() .type_(lock_type) .range(from_c_flock_and_file(&lock_mut_c, &**file)?) .build()?; let inode_file = file.as_inode_or_err()?; lock = inode_file.test_range_lock(lock)?; lock_mut_c.copy_from_range_lock(&lock); ctx.user_space().write_val(lock_mut_ptr, &lock_mut_c)?; Ok(SyscallReturn::Return(0)) } fn handle_setlk( fd: FileDesc, arg: u64, is_nonblocking: bool, ctx: &Context, ) -> Result { let mut file_table = ctx.thread_local.file_table().borrow_mut(); let file = get_file_fast!(&mut file_table, fd); let lock_mut_ptr = arg as Vaddr; let lock_mut_c = ctx.user_space().read_val::(lock_mut_ptr)?; let lock_type = RangeLockType::try_from(lock_mut_c.l_type)?; let lock = RangeLockItemBuilder::new() .type_(lock_type) .range(from_c_flock_and_file(&lock_mut_c, &**file)?) .build()?; let inode_file = file.as_inode_or_err()?; inode_file.set_range_lock(&lock, is_nonblocking)?; Ok(SyscallReturn::Return(0)) } fn handle_getown(fd: FileDesc, ctx: &Context) -> Result { let mut file_table = ctx.thread_local.file_table().borrow_mut(); file_table.read_with(|inner| { let pid = inner.get_entry(fd)?.owner().unwrap_or(0); Ok(SyscallReturn::Return(pid as _)) }) } fn handle_setown(fd: FileDesc, arg: u64, ctx: &Context) -> Result { // A process ID is specified as a positive value; a process group ID is specified as a negative value. let abs_arg = (arg as i32).unsigned_abs(); if abs_arg > i32::MAX as u32 { return_errno_with_message!(Errno::EINVAL, "process (group) id overflowed"); } let pid = Pid::try_from(abs_arg) .map_err(|_| Error::with_message(Errno::EINVAL, "invalid process (group) id"))?; let owner_process = if pid == 0 { None } else { Some(process_table::get_process(pid).ok_or(Error::with_message( Errno::ESRCH, "cannot set_owner with an invalid pid", ))?) }; let file_table = ctx.thread_local.file_table().borrow(); let mut file_table_locked = file_table.write(); let file_entry = file_table_locked.get_entry_mut(fd)?; file_entry.set_owner(owner_process.as_ref())?; Ok(SyscallReturn::Return(0)) } #[repr(i32)] #[derive(Debug, Clone, Copy, TryFromInt)] #[expect(non_camel_case_types)] enum FcntlCmd { F_DUPFD = 0, F_GETFD = 1, F_SETFD = 2, F_GETFL = 3, F_SETFL = 4, F_GETLK = 5, F_SETLK = 6, F_SETLKW = 7, F_SETOWN = 8, F_GETOWN = 9, F_DUPFD_CLOEXEC = 1030, } #[expect(non_camel_case_types)] pub type off_t = i64; #[expect(non_camel_case_types)] #[derive(Debug, Copy, Clone, TryFromInt)] #[repr(u16)] pub enum RangeLockWhence { SEEK_SET = 0, SEEK_CUR = 1, SEEK_END = 2, } /// C struct for a file range lock in Libc #[repr(C)] #[derive(Debug, Copy, Clone, Pod)] pub struct c_flock { /// Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK pub l_type: u16, /// Where `l_start' is relative to pub l_whence: u16, /// Offset where the lock begins pub l_start: off_t, /// Size of the locked area, 0 means until EOF pub l_len: off_t, /// Process holding the lock pub l_pid: Pid, } impl c_flock { pub fn copy_from_range_lock(&mut self, lock: &RangeLockItem) { self.l_type = lock.type_() as u16; if RangeLockType::Unlock != lock.type_() { self.l_whence = RangeLockWhence::SEEK_SET as u16; self.l_start = lock.start() as off_t; self.l_len = if lock.end() == OFFSET_MAX { 0 } else { lock.range().len() as off_t }; self.l_pid = lock.owner(); } } } /// Create the file range through C flock and opened file reference fn from_c_flock_and_file(lock: &c_flock, file: &dyn FileLike) -> Result { let start = { let whence = RangeLockWhence::try_from(lock.l_whence)?; match whence { RangeLockWhence::SEEK_SET => lock.l_start, RangeLockWhence::SEEK_CUR => (file.as_inode_or_err()?.offset() as off_t) .checked_add(lock.l_start) .ok_or(Error::with_message(Errno::EOVERFLOW, "start overflow"))?, RangeLockWhence::SEEK_END => (file.metadata().size as off_t) .checked_add(lock.l_start) .ok_or(Error::with_message(Errno::EOVERFLOW, "start overflow"))?, } }; let (start, end) = match lock.l_len { len if len > 0 => { let end = start .checked_add(len) .ok_or(Error::with_message(Errno::EOVERFLOW, "end overflow"))?; (start as usize, end as usize) } 0 => (start as usize, OFFSET_MAX), len if len < 0 => { let end = start; let new_start = start + len; if new_start < 0 { return Err(Error::with_message(Errno::EINVAL, "invalid len")); } (new_start as usize, end as usize) } _ => unreachable!(), }; FileRange::new(start, end) }