use alloc::{format, string::ToString, sync::Arc, vec::Vec};
use core::{
ffi::{c_char, c_int},
mem::size_of,
ops::DerefMut,
};
use ax_fs_ng::vfs::{FS_CONTEXT, FileBackend, MountNamespace, OpenOptions, OpenResult};
use ax_memory_addr::PAGE_SIZE_4K;
use axfs_ng_vfs::{DirEntry, FileNode, Location, NodeType, Reference, VfsError};
use bitflags::bitflags;
use linux_raw_sys::general::*;
use crate::{
StarryError, StarryResult,
file::{
Directory, FD_TABLE, File, FileDescriptor, FileLike, MountTableFile, NsFd, Pipe,
add_file_like, close_file_like, get_file_like, memfd::Memfd, with_fs,
},
mm::{VmMutPtr, VmPtr, vm_load, vm_load_path_string},
pseudofs::{Device, dev::tty},
sync::RawSpinRwLock,
task::{
TgidNumber, TidNumber, current_pid_view, get_user_process_data_by_number,
get_user_task_by_number,
},
};
fn flags_to_options(flags: c_int, mode: __kernel_mode_t, (uid, gid): (u32, u32)) -> OpenOptions {
let flags = flags as u32;
let mut options = OpenOptions::new();
options.mode(mode).user(uid, gid);
match flags & 0b11 {
O_RDONLY => options.read(true),
O_WRONLY => options.write(true),
_ => options.read(true).write(true),
};
if flags & O_APPEND != 0 {
options.append(true);
}
if flags & O_TRUNC != 0 {
options.truncate(true);
}
if flags & O_CREAT != 0 {
options.create(true);
}
if flags & O_EXCL != 0 && flags & O_CREAT != 0 {
options.create_new(true);
}
if flags & O_DIRECTORY != 0 {
options.directory(true);
}
if flags & O_NOFOLLOW != 0 {
options.no_follow(true);
}
if flags & O_DIRECT != 0 {
options.direct(true);
}
if flags & O_PATH != 0 {
options.path(true);
options.read(true).write(false);
options
.create(false)
.create_new(false)
.truncate(false)
.append(false);
}
options
}
fn add_to_fd(
current: &crate::task::UserTaskRef,
result: OpenResult,
flags: u32,
mount_table_namespace: Option<Arc<MountNamespace>>,
) -> StarryResult<i32> {
if flags & O_PATH == 0
&& flags & O_NONBLOCK != 0
&& flags & 0b11 == O_WRONLY
&& let OpenResult::File(ref f) = result
&& let Ok(meta) = f.location().metadata()
&& meta.node_type == NodeType::Fifo
{
return Err(StarryError::NoSuchDeviceOrAddress);
}
let f: Arc<dyn FileLike> = match result {
OpenResult::File(mut file) => {
if flags & O_PATH != 0 {
return add_file_like(Arc::new(File::new(file, flags)), flags & O_CLOEXEC != 0);
}
if let Ok(device) = file.location().entry().downcast::<Device>() {
let inner = device.inner().as_any();
if crate::pseudofs::usbfs::is_usbfs_device(inner) {
let wrapped = crate::pseudofs::usbfs::open_usbfs_file(inner, file, flags)?;
if flags & O_NONBLOCK != 0 {
wrapped.set_nonblocking(true)?;
}
return add_file_like(wrapped, flags & O_CLOEXEC != 0);
}
#[cfg(feature = "rknpu")]
if crate::pseudofs::dev::card1::is_card1_device(inner) {
let wrapped = crate::pseudofs::dev::card1::open_card1_file(file, flags)?;
if flags & O_NONBLOCK != 0 {
wrapped.set_nonblocking(true)?;
}
return add_file_like(wrapped, flags & O_CLOEXEC != 0);
}
#[cfg(feature = "rga")]
if crate::pseudofs::dev::rga::is_rga_device(inner) {
let wrapped = crate::pseudofs::dev::rga::open_rga_file(file, flags)?;
if flags & O_NONBLOCK != 0 {
wrapped.set_nonblocking(true)?;
}
return add_file_like(wrapped, flags & O_CLOEXEC != 0);
}
if let Some(ptmx) = inner.downcast_ref::<tty::Ptmx>() {
let (master, pty_number) = ptmx.create_pty()?;
let pts = ax_fs_ng::vfs::current_fs_context()
.lock()
.resolve("/dev/pts")?;
let entry = DirEntry::new_file(
FileNode::new(master),
NodeType::CharacterDevice,
Reference::new(Some(pts.entry().clone()), pty_number.to_string()),
);
let loc = Location::new(file.location().mountpoint().clone(), entry);
file = ax_fs_ng::vfs::File::new(FileBackend::Direct(loc), file.flags());
} else if inner.is::<tty::CurrentTty>() {
let term = current
.as_thread()
.proc_data
.proc
.group()
.session()
.terminal()
.ok_or(StarryError::NotFound)?;
let target = tty::terminal_device(term.as_ref()).ok_or_else(|| {
warn!("unknown controlling terminal type for /dev/tty");
StarryError::BadState
})?;
let loc = match target {
tty::TerminalDevice::Location(location) => location,
tty::TerminalDevice::Path(path) => {
ax_fs_ng::vfs::current_fs_context().lock().resolve(&path)?
}
};
file = ax_fs_ng::vfs::File::new(FileBackend::Direct(loc), file.flags());
}
}
if let Ok(device) = file.location().entry().downcast::<Device>() {
device.inner().open(flags & O_EXCL != 0)?;
}
let file = Arc::new(File::new(file, flags));
if let Some(namespace) = mount_table_namespace {
MountTableFile::new(file, &namespace)
} else {
file
}
}
OpenResult::Dir(dir) => Arc::new(Directory::new(dir, flags)),
};
if flags & O_NONBLOCK != 0 {
f.set_nonblocking(true)?;
}
add_file_like(f, flags & O_CLOEXEC != 0)
}
fn mount_table_namespace(
current: &crate::task::UserTaskRef,
result: &OpenResult,
) -> Option<Arc<MountNamespace>> {
let OpenResult::File(file) = result else {
return None;
};
let path = file.location().absolute_path().ok()?.to_string();
let components: Vec<_> = path.trim_start_matches('/').split('/').collect();
let tid = match components.as_slice() {
["proc", "mountinfo" | "mounts"] | ["proc", "self", "mountinfo" | "mounts"] => {
TidNumber::from(
current_pid_view()
.visible_process_number(¤t.as_thread().proc_data.identity())?
.pid_number(),
)
}
["proc", pid, "mountinfo" | "mounts"] => {
TidNumber::try_from(pid.parse::<u32>().ok()?).ok()?
}
["proc", _, "task", tid, "mountinfo" | "mounts"] => {
TidNumber::try_from(tid.parse::<u32>().ok()?).ok()?
}
_ => return None,
};
let task = get_user_task_by_number(tid).ok()?;
let fs_context = task.as_thread().clone_scope_item(&FS_CONTEXT)?;
Some(fs_context.lock().mount_namespace().clone())
}
fn self_fd_number(path: &str) -> Option<c_int> {
["/proc/self/fd/", "/dev/fd/"]
.into_iter()
.find_map(|prefix| path.strip_prefix(prefix))?
.parse()
.ok()
}
fn try_reopen_self_pipe(path: &str, flags: u32) -> Option<StarryResult<isize>> {
let fd = self_fd_number(path)?;
let file = match get_file_like(fd) {
Ok(file) => file,
Err(_) => return Some(Err(StarryError::NotFound)),
};
let pipe = file.downcast_ref::<Pipe>()?;
let requested_access = flags & O_ACCMODE;
let expected_access = if pipe.is_read() { O_RDONLY } else { O_WRONLY };
if requested_access != expected_access {
return Some(Err(StarryError::PermissionDenied));
}
let pipe = Arc::new(pipe.reopen(flags & O_NONBLOCK != 0));
Some(add_file_like(pipe, flags & O_CLOEXEC != 0).map(|fd| fd as isize))
}
fn try_reopen_self_regular_file(
current: &crate::task::UserTaskRef,
path: &str,
flags: u32,
) -> Option<StarryResult<isize>> {
if flags & O_NOFOLLOW != 0 {
return None;
}
let fd = self_fd_number(path)?;
let file_like = match get_file_like(fd) {
Ok(file) => file,
Err(_) => return Some(Err(StarryError::NotFound)),
};
let file = file_like.downcast_ref::<File>()?;
let location = file.inner().location();
if location.node_type() != NodeType::RegularFile {
return None;
}
let cred = current.as_thread().cred();
let options = flags_to_options(flags as i32, 0, (cred.fsuid, cred.fsgid));
Some(
options
.open_loc(location.clone())
.map_err(StarryError::from)
.and_then(|result| add_to_fd(current, result, flags, None))
.map(|fd| fd as isize),
)
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::AnyBitPattern)]
pub struct OpenHow {
flags: u64,
mode: u64,
resolve: u64,
}
const OPENAT2_VALID_RESOLVE: u64 = (RESOLVE_NO_XDEV
| RESOLVE_NO_MAGICLINKS
| RESOLVE_NO_SYMLINKS
| RESOLVE_BENEATH
| RESOLVE_IN_ROOT
| RESOLVE_CACHED) as u64;
const OPENAT2_VALID_FLAGS: u64 = (O_ACCMODE
| O_CREAT
| O_EXCL
| O_NOCTTY
| O_TRUNC
| O_APPEND
| O_NONBLOCK
| O_DSYNC
| O_DIRECT
| O_LARGEFILE
| O_DIRECTORY
| O_NOFOLLOW
| O_NOATIME
| O_CLOEXEC
| O_SYNC
| O_PATH
| O_TMPFILE) as u64;
fn openat2_check_extra_bytes(
current: &crate::task::UserTaskRef,
how: *const OpenHow,
size: usize,
) -> crate::StarryResult<()> {
let base_size = size_of::<OpenHow>();
if size <= base_size {
return Ok(());
}
let extra = vm_load(
current,
unsafe { (how as *const u8).add(base_size) },
size - base_size,
)?;
if extra.iter().any(|byte| *byte != 0) {
return Err(StarryError::ArgumentListTooLong);
}
Ok(())
}
fn try_open_nsfd(
current: &crate::task::UserTaskRef,
path: &str,
flags: u32,
) -> Option<crate::StarryResult<i32>> {
if !path.starts_with("/proc/") {
return None;
}
let rest = path.strip_prefix("/proc/")?;
let (pid_str, ns_type_str) = rest.split_once("/ns/")?;
if pid_str.is_empty() || ns_type_str.is_empty() {
return None;
}
if ns_type_str.contains('/') {
return None;
}
let tgid = if pid_str == "self" {
current_pid_view().visible_process_number(¤t.as_thread().proc_data.identity())?
} else {
TgidNumber::try_from(pid_str.parse::<u32>().ok()?).ok()?
};
let proc_data = match get_user_process_data_by_number(tgid) {
Ok(p) => p,
Err(_) => return Some(Err(StarryError::NotFound)),
};
let mnt_fs_ns = if ns_type_str == "mnt" {
let task = match get_user_task_by_number(TidNumber::from(tgid.pid_number())) {
Ok(task) => task,
Err(_) => return Some(Err(StarryError::NotFound)),
};
let Some(fs_context) = task.as_thread().clone_scope_item(&FS_CONTEXT) else {
return Some(Err(StarryError::NotFound));
};
Some(fs_context.lock().mount_namespace().clone())
} else {
None
};
let nsproxy = proc_data.namespace_snapshot();
let nsfd: NsFd = match ns_type_str {
"uts" => NsFd::Uts(nsproxy.uts_ns.clone()),
"ipc" => NsFd::Ipc(nsproxy.ipc_ns.clone()),
"mnt" => NsFd::Mnt {
ns: nsproxy.mnt_ns.clone(),
fs_ns: mnt_fs_ns.unwrap(),
},
"pid" => NsFd::Pid(proc_data.identity().active_namespace()),
"net" => NsFd::Net(nsproxy.net_ns.clone()),
"user" => NsFd::User(nsproxy.user_ns.clone()),
"cgroup" => NsFd::Cgroup(nsproxy.cgroup_ns.clone()),
_ => return Some(Err(StarryError::NotFound)),
};
drop(nsproxy);
let fd = nsfd.add_to_fd_table(flags & O_CLOEXEC != 0);
Some(fd)
}
ax_tracepoint::define_event_trace!(
sys_enter_openat,
TP_kops(crate::tracepoint::KernelTraceAux),
TP_system(syscalls),
TP_PROTO(dfd: i32, path: *const u8, o_flags: u32, mode: u32),
TP_STRUCT__entry {
dfd: i32,
o_flags: u32,
path: u64,
mode: u32,
},
TP_fast_assign {
dfd: dfd,
path: path as u64,
o_flags: o_flags,
mode: mode,
},
TP_ident(__entry),
TP_printk({
format!(
"dfd: {}, path: {:#x}, o_flags: {:?}, mode: {:?}",
__entry.dfd, __entry.path, __entry.o_flags, __entry.mode
)
})
);
pub fn sys_openat(
current: &crate::task::UserTaskRef,
dirfd: c_int,
path: *const c_char,
flags: i32,
mode: __kernel_mode_t,
) -> StarryResult<isize> {
trace_sys_enter_openat(dirfd, path as _, flags as _, mode);
let curr = current;
let thread = curr.as_thread();
let path = vm_load_path_string(current, path)?;
debug!("sys_openat <= {dirfd} {path:?} {flags:#o} {mode:#o}");
let uflags = flags as u32;
if path.is_empty() {
return Err(StarryError::NotFound);
}
if uflags & O_CREAT != 0 && uflags & O_DIRECTORY != 0 && uflags & O_PATH == 0 {
return Err(StarryError::InvalidInput);
}
if uflags & O_TMPFILE == O_TMPFILE && uflags & 0b11 == O_RDONLY && uflags & O_PATH == 0 {
return Err(StarryError::InvalidInput);
}
if let Some(result) = try_reopen_self_pipe(&path, uflags) {
return result;
}
if let Some(result) = try_reopen_self_regular_file(current, &path, uflags) {
return result;
}
let dirfd = if path.starts_with('/') {
AT_FDCWD as _
} else {
dirfd
};
let mode = mode & !thread.proc_data.umask();
if let Some(result) = try_open_nsfd(current, &path, uflags) {
return result.map(|fd| fd as isize);
}
let cred = thread.cred();
let options = flags_to_options(flags, mode, (cred.fsuid, cred.fsgid));
let should_notify_create = uflags & O_CREAT != 0
&& uflags & O_PATH == 0
&& with_fs(dirfd, |fs| match fs.resolve_no_follow(&path) {
Ok(_) => Ok(false),
Err(VfsError::NotFound) => Ok(true),
Err(err) => Err(err.into()),
})?;
let result = with_fs(dirfd, |fs| Ok(options.open(fs, path)?))?;
let mount_table_namespace = mount_table_namespace(current, &result);
let fd = add_to_fd(current, result, flags as _, mount_table_namespace)?;
if should_notify_create {
let file = get_file_like(fd)?;
crate::file::inotify::notify_create_path(file.path().as_ref(), false);
}
Ok(fd as isize)
}
pub fn sys_openat2(
current: &crate::task::UserTaskRef,
dirfd: c_int,
path: *const c_char,
how: *const OpenHow,
size: usize,
) -> StarryResult<isize> {
let base_size = size_of::<OpenHow>();
if size < base_size {
return Err(StarryError::InvalidInput);
}
if size > PAGE_SIZE_4K {
return Err(StarryError::ArgumentListTooLong);
}
let how_value = how.vm_read(current)?;
openat2_check_extra_bytes(current, how, size)?;
if how_value.flags & !OPENAT2_VALID_FLAGS != 0 {
return Err(StarryError::InvalidInput);
}
if how_value.mode & !0o7777 != 0 {
return Err(StarryError::InvalidInput);
}
if how_value.mode != 0 && how_value.flags & ((O_CREAT | O_TMPFILE) as u64) == 0 {
return Err(StarryError::InvalidInput);
}
if how_value.resolve & !OPENAT2_VALID_RESOLVE != 0 {
return Err(StarryError::InvalidInput);
}
const NIX_RESTORE_RESOLVE: u64 = (RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS) as u64;
if how_value.resolve != 0 && how_value.resolve != NIX_RESTORE_RESOLVE {
return Err(StarryError::OperationNotSupported);
}
let flags: i32 = how_value
.flags
.try_into()
.map_err(|_| StarryError::InvalidInput)?;
let mode: __kernel_mode_t = how_value
.mode
.try_into()
.map_err(|_| StarryError::InvalidInput)?;
let uflags = flags as u32;
if uflags & O_CREAT != 0 && uflags & O_DIRECTORY != 0 && uflags & O_PATH == 0 {
return Err(StarryError::InvalidInput);
}
if uflags & O_TMPFILE == O_TMPFILE && uflags & 0b11 == O_RDONLY && uflags & O_PATH == 0 {
return Err(StarryError::InvalidInput);
}
if how_value.resolve == 0 {
return sys_openat(current, dirfd, path, flags, mode);
}
let path = vm_load_path_string(current, path)?;
if path.is_empty() {
return Err(StarryError::NotFound);
}
if path.starts_with('/') {
return Err(StarryError::CrossesDevices);
}
let curr = current;
let thread = curr.as_thread();
let mode = mode & !thread.proc_data.umask();
let cred = thread.cred();
let mut options = flags_to_options(flags, mode, (cred.fsuid, cred.fsgid));
let result = with_fs(dirfd, |fs| {
let (parent, name) = fs.resolve_parent_beneath_no_symlinks(path.as_ref())?;
match parent.lookup_no_follow(name.as_ref()) {
Ok(location) if location.node_type() == NodeType::Symlink => {
return Err(StarryError::FilesystemLoop);
}
Err(VfsError::NotFound) | Ok(_) => {}
Err(error) => return Err(error.into()),
}
options.no_follow(true);
Ok(options.open(&fs.with_current_dir(parent)?, name.as_ref())?)
})?;
let mount_table_namespace = mount_table_namespace(current, &result);
add_to_fd(current, result, flags as u32, mount_table_namespace).map(|fd| fd as isize)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_open(
current: &crate::task::UserTaskRef,
path: *const c_char,
flags: i32,
mode: __kernel_mode_t,
) -> crate::StarryResult<isize> {
sys_openat(current, AT_FDCWD as _, path, flags, mode)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_creat(
current: &crate::task::UserTaskRef,
path: *const c_char,
mode: __kernel_mode_t,
) -> crate::StarryResult<isize> {
sys_openat(
current,
AT_FDCWD as _,
path,
(O_CREAT | O_WRONLY | O_TRUNC) as _,
mode,
)
}
pub fn sys_close(fd: c_int) -> StarryResult<isize> {
debug!("sys_close <= {fd}");
close_file_like(fd)?;
Ok(0)
}
bitflags! {
#[derive(Debug, Clone, Copy)]
struct CloseRangeFlags: u32 {
const UNSHARE = 1 << 1;
const CLOEXEC = 1 << 2;
}
}
pub fn sys_close_range(
current: &crate::task::UserTaskRef,
first: u32,
last: u32,
flags: u32,
) -> crate::StarryResult<isize> {
if last < first {
return Err(StarryError::InvalidInput);
}
let flags = CloseRangeFlags::from_bits(flags).ok_or(StarryError::InvalidInput)?;
debug!("sys_close_range <= fds: [{first}, {last}], flags: {flags:?}");
if flags.contains(CloseRangeFlags::UNSHARE) {
let curr = current;
let new_files = Arc::new(RawSpinRwLock::new(
crate::file::current_fd_table().read().clone(),
));
curr.as_thread().with_current_scope_mut(|scope| {
*FD_TABLE.scope_mut(scope).deref_mut() = crate::file::new_file_table_scope(new_files);
});
}
let cloexec = flags.contains(CloseRangeFlags::CLOEXEC);
let current_fd_table = crate::file::current_fd_table();
let mut fd_table = current_fd_table.write();
let mut closing = alloc::vec::Vec::new();
if let Some(max_index) = fd_table.last_id() {
for fd in first..=last.min(max_index as u32) {
if cloexec {
let _ = fd_table.set_cloexec(fd as _, true);
} else if let Some(f) = fd_table.remove(fd as _) {
closing.push(f);
}
}
}
drop(fd_table);
for f in closing {
crate::file::release_locks_on_close(f);
}
Ok(0)
}
fn dup_fd(old_fd: c_int, cloexec: bool) -> StarryResult<isize> {
let f = get_file_like(old_fd)?;
let new_fd = add_file_like(f, cloexec)?;
Ok(new_fd as _)
}
fn dup_fd_min(
current: &crate::task::UserTaskRef,
old_fd: c_int,
min_fd: c_int,
cloexec: bool,
) -> crate::StarryResult<isize> {
if min_fd < 0 {
return Err(StarryError::InvalidInput);
}
let f = get_file_like(old_fd)?;
let max_nofile = current.as_thread().proc_data.rlimit_current(RLIMIT_NOFILE) as i32;
let current_fd_table = crate::file::current_fd_table();
let mut fd_table = current_fd_table.write();
for candidate in min_fd..max_nofile {
let entry = FileDescriptor {
inner: f.clone(),
cloexec,
};
if fd_table.add_at(candidate as _, entry).is_ok() {
return Ok(candidate as isize);
}
}
Err(StarryError::TooManyOpenFiles)
}
pub fn sys_dup(old_fd: c_int) -> StarryResult<isize> {
debug!("sys_dup <= {old_fd}");
dup_fd(old_fd, false)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_dup2(old_fd: c_int, new_fd: c_int) -> StarryResult<isize> {
if old_fd == new_fd {
get_file_like(new_fd)?;
return Ok(new_fd as _);
}
sys_dup3(old_fd, new_fd, 0)
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Dup3Flags: c_int {
const O_CLOEXEC = O_CLOEXEC as _; }
}
pub fn sys_dup3(old_fd: c_int, new_fd: c_int, flags: c_int) -> StarryResult<isize> {
let flags = Dup3Flags::from_bits(flags).ok_or(StarryError::InvalidInput)?;
debug!("sys_dup3 <= old_fd: {old_fd}, new_fd: {new_fd}, flags: {flags:?}");
if old_fd == new_fd {
return Err(StarryError::InvalidInput);
}
let current_fd_table = crate::file::current_fd_table();
let mut fd_table = current_fd_table.write();
let mut f = fd_table
.get(old_fd as _)
.cloned()
.ok_or(StarryError::BadFileDescriptor)?;
f.cloexec = flags.contains(Dup3Flags::O_CLOEXEC);
if fd_table.is_reserved(new_fd as _) {
return Err(StarryError::ResourceBusy);
}
let prev = fd_table.remove(new_fd as _);
fd_table
.add_at(new_fd as _, f)
.map_err(|_| StarryError::BadFileDescriptor)?;
drop(fd_table);
if let Some(prev) = prev {
crate::file::release_locks_on_close(prev);
}
Ok(new_fd as _)
}
pub fn sys_fcntl(
current: &crate::task::UserTaskRef,
fd: c_int,
cmd: c_int,
arg: usize,
) -> crate::StarryResult<isize> {
debug!("sys_fcntl <= fd: {fd} cmd: {cmd} arg: {arg}");
if let Some(r) = super::lock::dispatch_fcntl(current, fd, cmd, arg) {
return r;
}
match cmd as u32 {
F_DUPFD => dup_fd_min(current, fd, arg as _, false),
F_DUPFD_CLOEXEC => dup_fd_min(current, fd, arg as _, true),
F_SETFL => {
let f = get_file_like(fd)?;
let async_mode = arg & (FASYNC as usize) != 0;
let async_mode_changed = async_mode != f.async_mode();
if async_mode_changed && !f.supports_async_mode() {
return Err(StarryError::NotATty);
}
f.set_nonblocking(arg & (O_NONBLOCK as usize) > 0)?;
f.set_append(arg & (O_APPEND as usize) > 0)?;
if async_mode_changed {
f.set_async_mode(async_mode)?;
}
Ok(0)
}
F_GETFL => {
let f = get_file_like(fd)?;
let mut ret = f.open_flags() & !O_APPEND;
if f.nonblocking() {
ret |= O_NONBLOCK;
}
if f.append() {
ret |= O_APPEND;
}
if f.async_mode() {
ret |= FASYNC;
}
Ok(ret as _)
}
F_GETFD => {
let cloexec = crate::file::current_fd_table()
.read()
.get(fd as _)
.ok_or(StarryError::BadFileDescriptor)?
.cloexec;
Ok(if cloexec { FD_CLOEXEC as _ } else { 0 })
}
F_SETFD => {
let cloexec = arg & FD_CLOEXEC as usize != 0;
crate::file::current_fd_table()
.write()
.set_cloexec(fd as _, cloexec)?;
Ok(0)
}
F_SETOWN => {
let f = get_file_like(fd)?;
f.set_owner(arg as i32)?;
Ok(0)
}
F_GETOWN => {
let f = get_file_like(fd)?;
Ok(f.owner()? as _)
}
F_GETPIPE_SZ => {
let pipe = Pipe::from_fd(fd)?;
Ok(pipe.capacity() as _)
}
F_SETPIPE_SZ => {
let pipe = Pipe::from_fd(fd)?;
set_pipe_size(&pipe, arg)
}
F_GET_SEALS => {
let memfd = Memfd::from_fd(fd)?;
Ok(memfd.get_seals() as _)
}
F_ADD_SEALS => {
let memfd = Memfd::from_fd(fd)?;
memfd.add_seals(arg as u32)?;
Ok(0)
}
1035 | 1037 => {
(arg as *mut u64).vm_write(current, 0u64)?;
Ok(0)
}
1036 | 1038 => {
let hint = (arg as *const u64).vm_read(current)?;
if hint > 5 {
return Err(StarryError::InvalidInput);
}
Ok(0)
}
10 | 11 | 15 | 16 => Ok(0),
_ => {
warn!("unsupported fcntl parameters: cmd: {cmd}");
Err(StarryError::InvalidInput)
}
}
}
fn set_pipe_size(pipe: &Pipe, size: usize) -> StarryResult<isize> {
pipe.resize(size)?;
Ok(pipe.capacity() as _)
}
pub fn sys_flock(
current: &crate::task::UserTaskRef,
fd: c_int,
operation: c_int,
) -> crate::StarryResult<isize> {
debug!("flock <= fd: {fd}, operation: {operation}");
super::lock::flock_op(current, fd, operation)
}
#[cfg(all(test, axtest))]
fn fcntl_setpipe_size_returns_capacity_for_test() -> bool {
let (read_end, _write_end) = Pipe::new();
matches!(set_pipe_size(&read_end, 4097), Ok(8192))
}
#[cfg(all(test, axtest))]
fn pipe_size_rounding_and_rejection_rules_hold_for_test() -> bool {
let (read_end, _write_end) = Pipe::new();
matches!(set_pipe_size(&read_end, 1), Ok(4096))
&& matches!(set_pipe_size(&read_end, 8192), Ok(8192))
&& matches!(set_pipe_size(&read_end, 4097), Ok(8192))
&& matches!(
set_pipe_size(&read_end, 1024 * 1024),
Ok(capacity) if capacity == 1024 * 1024
)
&& set_pipe_size(&read_end, 1024 * 1024 + 1).is_err()
&& matches!(set_pipe_size(&read_end, 0), Ok(4096))
}
#[cfg(all(test, axtest))]
mod tests {
#[cfg(axtest)]
#[axtest::axtest]
fn fcntl_setpipe_size_returns_capacity() {
assert!(super::fcntl_setpipe_size_returns_capacity_for_test());
}
#[cfg(axtest)]
#[axtest::axtest]
fn pipe_size_rounding_and_rejection_rules_hold() {
assert!(super::pipe_size_rounding_and_rejection_rules_hold_for_test());
}
}