use alloc::{ffi::CString, vec, vec::Vec};
use core::{
ffi::{c_char, c_int},
mem::offset_of,
time::Duration,
};
use ax_errno::{AxError, AxResult};
use ax_fs::{FS_CONTEXT, FsContext};
use ax_hal::time::wall_time;
use ax_task::current;
use axfs_ng_vfs::{DeviceId, MetadataUpdate, NodePermission, NodeType, path::Path};
use linux_raw_sys::{
general::*,
ioctl::{BLKGETSIZE64, BLKRAGET, BLKSSZGET, FIONBIO, TIOCGWINSZ},
};
use starry_vm::{VmPtr, vm_write_slice};
use crate::{
file::{Directory, FileLike, get_file_like, resolve_at, with_fs},
mm::vm_load_string,
task::AsThread,
time::TimeValueLike,
};
pub fn sys_ioctl(fd: i32, cmd: u32, arg: usize) -> AxResult<isize> {
debug!("sys_ioctl <= fd: {fd}, cmd: {cmd}, arg: {arg}");
let f = get_file_like(fd)?;
if cmd == FIONBIO {
let val: i32 = (arg as *const i32).vm_read()?;
f.set_nonblocking(val != 0)?;
return Ok(0);
}
f.ioctl(cmd, arg)
.map(|result| result as isize)
.inspect_err(|err| {
if *err == AxError::NotATty {
if matches!(cmd, TIOCGWINSZ | BLKGETSIZE64 | BLKRAGET | BLKSSZGET) {
return;
}
warn!("Unsupported ioctl command: {cmd} for fd: {fd}");
}
})
}
#[ddebug::named]
pub fn sys_chdir(path: *const c_char) -> AxResult<isize> {
let path = vm_load_string(path)?;
debug_fn!("sys_chdir <= path: {path}");
let mut fs = FS_CONTEXT.lock();
let entry = fs.resolve(path)?;
fs.set_current_dir(entry)?;
Ok(0)
}
pub fn sys_fchdir(dirfd: i32) -> AxResult<isize> {
debug!("sys_fchdir <= dirfd: {dirfd}");
let entry = with_fs(dirfd, |fs| Ok(fs.current_dir().clone()))?;
FS_CONTEXT.lock().set_current_dir(entry)?;
Ok(0)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_mkdir(path: *const c_char, mode: u32) -> AxResult<isize> {
sys_mkdirat(AT_FDCWD, path, mode)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_mknod(path: *const c_char, mode: u32, dev: u64) -> AxResult<isize> {
sys_mknodat(AT_FDCWD, path, mode, dev)
}
pub fn sys_chroot(path: *const c_char) -> AxResult<isize> {
let path = vm_load_string(path)?;
debug!("sys_chroot <= path: {path}");
let mut fs = FS_CONTEXT.lock();
let loc = fs.resolve(path)?;
if loc.node_type() != NodeType::Directory {
return Err(AxError::NotADirectory);
}
*fs = FsContext::new(loc);
Ok(0)
}
pub fn sys_mkdirat(dirfd: i32, path: *const c_char, mode: u32) -> AxResult<isize> {
let path = vm_load_string(path)?;
debug!("sys_mkdirat <= dirfd: {dirfd}, path: {path}, mode: {mode}");
let mode = mode & !current().as_thread().proc_data.umask();
let mode = NodePermission::from_bits_truncate(mode as u16);
with_fs(dirfd, |fs| match fs.create_dir(&path, mode) {
Ok(_) => Ok(0),
Err(AxError::InvalidInput) if !path.is_empty() && fs.resolve_no_follow(&path).is_ok() => {
Err(AxError::AlreadyExists)
}
Err(err) => Err(err),
})
}
pub fn sys_mknodat(dirfd: i32, path: *const c_char, mode: u32, dev: u64) -> Result<isize, AxError> {
let path = vm_load_string(path)?;
debug!(
"sys_mknodat <= dirfd: {}, path: {:?}, mode: {}, dev: {}",
dirfd, path, mode, dev
);
let ftype = mode & S_IFMT;
let mut perm = mode & !S_IFMT;
perm &= !current().as_thread().proc_data.umask();
let node_type = match ftype {
0 | S_IFREG => NodeType::RegularFile,
S_IFCHR => NodeType::CharacterDevice,
S_IFBLK => NodeType::BlockDevice,
S_IFIFO => NodeType::Fifo,
S_IFSOCK => NodeType::Socket,
S_IFDIR => return Err(AxError::OperationNotPermitted),
_ => return Err(AxError::InvalidInput),
};
let res = with_fs(dirfd, |fs| {
let (dir, name) = fs.resolve_nonexistent(Path::new(&path))?;
let loc = dir.create(
name,
node_type,
NodePermission::from_bits_truncate(perm as u16),
)?;
if matches!(node_type, NodeType::CharacterDevice | NodeType::BlockDevice) {
loc.update_metadata(MetadataUpdate {
rdev: Some(DeviceId(dev)),
..Default::default()
})?;
}
Ok(0)
})?;
Ok(res)
}
struct DirBuffer {
buf: Vec<u8>,
offset: usize,
}
impl DirBuffer {
fn new(len: usize) -> Self {
Self {
buf: vec![0; len],
offset: 0,
}
}
fn remaining_space(&self) -> usize {
self.buf.len().saturating_sub(self.offset)
}
fn write_entry(&mut self, d_ino: u64, d_off: i64, d_type: NodeType, name: &[u8]) -> bool {
const NAME_OFFSET: usize = offset_of!(linux_dirent64, d_name);
let len = NAME_OFFSET + name.len() + 1;
let len = len.next_multiple_of(align_of::<linux_dirent64>());
if self.remaining_space() < len {
return false;
}
unsafe {
let entry_ptr = self.buf.as_mut_ptr().add(self.offset);
entry_ptr.cast::<linux_dirent64>().write(linux_dirent64 {
d_ino,
d_off,
d_reclen: len as _,
d_type: d_type as _,
d_name: Default::default(),
});
let name_ptr = entry_ptr.add(NAME_OFFSET);
name_ptr.copy_from_nonoverlapping(name.as_ptr(), name.len());
name_ptr.add(name.len()).write(0);
}
self.offset += len;
true
}
}
pub fn sys_getdents64(fd: i32, buf: *mut u8, len: usize) -> AxResult<isize> {
debug!("sys_getdents64 <= fd: {fd}, buf: {buf:?}, len: {len}");
let mut buffer = DirBuffer::new(len);
let dir = Directory::from_fd(fd)?;
let mut dir_offset = dir.offset.lock();
let mut has_remaining = false;
dir.inner()
.read_dir(*dir_offset, &mut |name: &str, ino, node_type, offset| {
has_remaining = true;
if !buffer.write_entry(ino, offset as _, node_type, name.as_bytes()) {
return false;
}
*dir_offset = offset;
true
})?;
if has_remaining && buffer.offset == 0 {
return Err(AxError::InvalidInput);
}
vm_write_slice(buf, &buffer.buf)?;
Ok(buffer.offset as _)
}
pub fn sys_linkat(
old_dirfd: c_int,
old_path: *const c_char,
new_dirfd: c_int,
new_path: *const c_char,
flags: u32,
) -> AxResult<isize> {
const LINKAT_VALID_FLAGS: u32 = AT_SYMLINK_FOLLOW | AT_EMPTY_PATH;
if flags & !LINKAT_VALID_FLAGS != 0 {
return Err(AxError::InvalidInput);
}
let old_path = old_path.nullable().map(vm_load_string).transpose()?;
let new_path = vm_load_string(new_path)?;
debug!(
"sys_linkat <= old_dirfd: {old_dirfd}, old_path: {old_path:?}, new_dirfd: {new_dirfd}, \
new_path: {new_path}, flags: {flags}"
);
let resolve_flags = if flags & AT_SYMLINK_FOLLOW != 0 {
flags & AT_EMPTY_PATH
} else {
(flags & AT_EMPTY_PATH) | AT_SYMLINK_NOFOLLOW
};
let old = resolve_at(old_dirfd, old_path.as_deref(), resolve_flags)?
.into_file()
.ok_or(AxError::BadFileDescriptor)?;
if old.is_dir() {
return Err(AxError::OperationNotPermitted);
}
let (new_dir, new_name) =
with_fs(new_dirfd, |fs| fs.resolve_nonexistent(Path::new(&new_path)))?;
new_dir.link(new_name, &old)?;
Ok(0)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_link(old_path: *const c_char, new_path: *const c_char) -> AxResult<isize> {
sys_linkat(AT_FDCWD, old_path, AT_FDCWD, new_path, 0)
}
pub fn sys_unlinkat(dirfd: i32, path: *const c_char, flags: usize) -> AxResult<isize> {
let path = vm_load_string(path)?;
debug!("sys_unlinkat <= dirfd: {dirfd}, path: {path:?}, flags: {flags}");
if flags & !(AT_REMOVEDIR as usize) != 0 {
return Err(AxError::InvalidInput);
}
with_fs(dirfd, |fs| {
if flags & AT_REMOVEDIR as usize != 0 {
fs.remove_dir(path)?;
} else {
fs.remove_file(path)?;
}
Ok(0)
})
}
#[cfg(target_arch = "x86_64")]
pub fn sys_rmdir(path: *const c_char) -> AxResult<isize> {
sys_unlinkat(AT_FDCWD, path, AT_REMOVEDIR as _)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_unlink(path: *const c_char) -> AxResult<isize> {
sys_unlinkat(AT_FDCWD, path, 0)
}
pub fn sys_getcwd(buf: *mut u8, size: isize) -> AxResult<isize> {
let size: usize = size.try_into().map_err(|_| AxError::BadAddress)?;
let cwd = FS_CONTEXT.lock().current_dir().absolute_path()?;
debug!("sys_getcwd => cwd: {cwd}");
let cwd = CString::new(cwd.as_str()).map_err(|_| AxError::InvalidInput)?;
let cwd = cwd.as_bytes_with_nul();
if cwd.len() <= size {
vm_write_slice(buf, cwd)?;
Ok(cwd.len() as _)
} else {
Err(AxError::OutOfRange)
}
}
#[cfg(target_arch = "x86_64")]
pub fn sys_symlink(target: *const c_char, linkpath: *const c_char) -> AxResult<isize> {
sys_symlinkat(target, AT_FDCWD, linkpath)
}
pub fn sys_symlinkat(
target: *const c_char,
new_dirfd: i32,
linkpath: *const c_char,
) -> AxResult<isize> {
let target = vm_load_string(target)?;
let linkpath = vm_load_string(linkpath)?;
debug!("sys_symlinkat <= target: {target:?}, new_dirfd: {new_dirfd}, linkpath: {linkpath:?}");
with_fs(new_dirfd, |fs| {
fs.symlink(target, linkpath)?;
Ok(0)
})
}
#[cfg(target_arch = "x86_64")]
pub fn sys_readlink(path: *const c_char, buf: *mut u8, size: usize) -> AxResult<isize> {
sys_readlinkat(AT_FDCWD, path, buf, size)
}
pub fn sys_readlinkat(
dirfd: i32,
path: *const c_char,
buf: *mut u8,
size: usize,
) -> AxResult<isize> {
if size == 0 {
return Err(AxError::InvalidInput);
}
let path = vm_load_string(path)?;
debug!("sys_readlinkat <= dirfd: {dirfd}, path: {path:?}");
with_fs(dirfd, |fs| {
let entry = fs.resolve_no_follow(path)?;
let link = entry.read_link()?;
let read = size.min(link.len());
vm_write_slice(buf, &link.as_bytes()[..read])?;
Ok(read as isize)
})
}
#[cfg(target_arch = "x86_64")]
pub fn sys_chown(path: *const c_char, uid: i32, gid: i32) -> AxResult<isize> {
sys_fchownat(AT_FDCWD, path, uid, gid, 0)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_lchown(path: *const c_char, uid: i32, gid: i32) -> AxResult<isize> {
use linux_raw_sys::general::AT_SYMLINK_NOFOLLOW;
sys_fchownat(AT_FDCWD, path, uid, gid, AT_SYMLINK_NOFOLLOW)
}
pub fn sys_fchown(fd: i32, uid: i32, gid: i32) -> AxResult<isize> {
sys_fchownat(fd, core::ptr::null(), uid, gid, AT_EMPTY_PATH)
}
pub fn sys_fchownat(
dirfd: i32,
path: *const c_char,
uid: i32,
gid: i32,
flags: u32,
) -> AxResult<isize> {
const FCHOWNAT_VALID_FLAGS: u32 = AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW;
if flags & !FCHOWNAT_VALID_FLAGS != 0 {
return Err(AxError::InvalidInput);
}
let path = path.nullable().map(vm_load_string).transpose()?;
let loc = resolve_at(dirfd, path.as_deref(), flags)?
.into_file()
.ok_or(AxError::BadFileDescriptor)?;
let meta = loc.metadata()?;
let cred = current().as_thread().cred();
let changing_owner = uid != -1 && uid as u32 != meta.uid;
let changing_group = gid != -1 && gid as u32 != meta.gid;
if changing_owner && !cred.has_cap_chown() {
return Err(AxError::OperationNotPermitted);
}
if changing_group && !cred.has_cap_chown() {
if cred.fsuid != meta.uid {
return Err(AxError::OperationNotPermitted);
}
if !cred.in_group(gid as u32) {
return Err(AxError::OperationNotPermitted);
}
}
let mut mode = meta.mode;
let is_dir = meta.node_type == NodeType::Directory;
if !is_dir {
mode.remove(NodePermission::SET_UID);
if mode.contains(NodePermission::GROUP_EXEC) {
mode.remove(NodePermission::SET_GID);
}
}
let uid = if uid == -1 { meta.uid } else { uid as _ };
let gid = if gid == -1 { meta.gid } else { gid as _ };
loc.update_metadata(MetadataUpdate {
owner: Some((uid, gid)),
mode: Some(mode),
..Default::default()
})?;
Ok(0)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_chmod(path: *const c_char, mode: u32) -> AxResult<isize> {
sys_fchmodat(AT_FDCWD, path, mode, 0)
}
pub fn sys_fchmod(fd: i32, mode: u32) -> AxResult<isize> {
sys_fchmodat(fd, core::ptr::null(), mode, AT_EMPTY_PATH)
}
pub fn sys_fchmodat(dirfd: i32, path: *const c_char, mode: u32, flags: u32) -> AxResult<isize> {
const FCHMODAT_VALID_FLAGS: u32 = AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW;
if flags & !FCHMODAT_VALID_FLAGS != 0 {
return Err(AxError::InvalidInput);
}
let path = path.nullable().map(vm_load_string).transpose()?;
let loc = resolve_at(dirfd, path.as_deref(), flags)?
.into_file()
.ok_or(AxError::BadFileDescriptor)?;
let cred = current().as_thread().cred();
if !cred.has_cap_fowner() {
let meta = loc.metadata()?;
if cred.fsuid != meta.uid {
return Err(AxError::OperationNotPermitted);
}
}
loc.update_metadata(MetadataUpdate {
mode: Some(NodePermission::from_bits_truncate(mode as u16)),
..Default::default()
})?;
Ok(0)
}
fn update_times(
dirfd: i32,
path: *const c_char,
atime: Option<Duration>,
mtime: Option<Duration>,
flags: u32,
) -> AxResult<()> {
let path = path.nullable().map(vm_load_string).transpose()?;
resolve_at(dirfd, path.as_deref(), flags)?
.into_file()
.ok_or(AxError::BadFileDescriptor)?
.update_metadata(MetadataUpdate {
atime,
mtime,
..Default::default()
})?;
Ok(())
}
#[cfg(target_arch = "x86_64")]
#[allow(non_camel_case_types)]
#[repr(C)]
pub struct utimbuf {
actime: linux_raw_sys::general::__kernel_old_time_t,
modtime: linux_raw_sys::general::__kernel_old_time_t,
}
#[cfg(target_arch = "x86_64")]
pub fn sys_utime(path: *const c_char, times: *const utimbuf) -> AxResult<isize> {
let (atime, mtime) = if let Some(times) = times.nullable() {
let times = unsafe { times.vm_read_uninit()?.assume_init() };
(
Duration::from_secs(times.actime as _),
Duration::from_secs(times.modtime as _),
)
} else {
let time = wall_time();
(time, time)
};
update_times(AT_FDCWD, path, Some(atime), Some(mtime), 0)?;
Ok(0)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_utimes(
path: *const c_char,
times: *const [linux_raw_sys::general::timeval; 2],
) -> AxResult<isize> {
let (atime, mtime) = if let Some(times) = times.nullable() {
let [atime, mtime] = unsafe { times.vm_read_uninit()?.assume_init() };
(atime.try_into_time_value()?, mtime.try_into_time_value()?)
} else {
let time = wall_time();
(time, time)
};
update_times(AT_FDCWD, path, Some(atime), Some(mtime), 0)?;
Ok(0)
}
pub fn sys_utimensat(
dirfd: i32,
path: *const c_char,
times: *const [timespec; 2],
mut flags: u32,
) -> AxResult<isize> {
if path.is_null() {
flags |= AT_EMPTY_PATH;
}
fn utime_to_duration(time: ×pec) -> Option<AxResult<Duration>> {
match time.tv_nsec {
val if val == UTIME_OMIT as _ => None,
val if val == UTIME_NOW as _ => Some(Ok(wall_time())),
_ => Some(time.try_into_time_value()),
}
}
let (atime, mtime) = if let Some(times) = times.nullable() {
let [atime, mtime] = unsafe { times.vm_read_uninit()?.assume_init() };
(
utime_to_duration(&atime).transpose()?,
utime_to_duration(&mtime).transpose()?,
)
} else {
let time = wall_time();
(Some(time), Some(time))
};
if atime.is_none() && mtime.is_none() {
return Ok(0);
}
update_times(dirfd, path, atime, mtime, flags)?;
Ok(0)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_rename(old_path: *const c_char, new_path: *const c_char) -> AxResult<isize> {
sys_renameat(AT_FDCWD, old_path, AT_FDCWD, new_path)
}
#[cfg(not(target_arch = "riscv64"))]
pub fn sys_renameat(
old_dirfd: i32,
old_path: *const c_char,
new_dirfd: i32,
new_path: *const c_char,
) -> AxResult<isize> {
sys_renameat2(old_dirfd, old_path, new_dirfd, new_path, 0)
}
pub fn sys_renameat2(
old_dirfd: i32,
old_path: *const c_char,
new_dirfd: i32,
new_path: *const c_char,
flags: u32,
) -> AxResult<isize> {
const RENAMEAT2_SUPPORTED_FLAGS: u32 = RENAME_NOREPLACE;
if flags & !RENAMEAT2_SUPPORTED_FLAGS != 0 {
return Err(AxError::InvalidInput);
}
let old_path = vm_load_string(old_path)?;
let new_path = vm_load_string(new_path)?;
debug!(
"sys_renameat2 <= old_dirfd: {old_dirfd}, old_path: {old_path:?}, new_dirfd: {new_dirfd}, \
new_path: {new_path}, flags: {flags}"
);
let (old_dir, old_name) = with_fs(old_dirfd, |fs| fs.resolve_parent(Path::new(&old_path)))?;
let (new_dir, new_name) = with_fs(new_dirfd, |fs| fs.resolve_parent(Path::new(&new_path)))?;
if flags & RENAME_NOREPLACE != 0 {
old_dir.lookup_no_follow(&old_name)?;
match new_dir.lookup_no_follow(&new_name) {
Ok(_) => return Err(AxError::AlreadyExists),
Err(AxError::NotFound) => {}
Err(err) => return Err(err),
}
}
old_dir.rename(&old_name, &new_dir, &new_name)?;
Ok(0)
}
pub fn sys_sync() -> AxResult<isize> {
debug!("sys_sync");
FS_CONTEXT.lock().root_dir().sync(false)?;
Ok(0)
}
pub fn sys_syncfs(fd: c_int) -> AxResult<isize> {
debug!("sys_syncfs <= fd: {fd}");
let f = crate::file::File::from_fd(fd)?;
f.inner().location().filesystem().flush()?;
Ok(0)
}