Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions kernel/src/arch_impl/aarch64/syscall_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,20 @@ fn dispatch_syscall_enum(
SyscallNumber::EpollCtl => result_to_u64(crate::syscall::epoll::sys_epoll_ctl(arg1 as i32, arg2 as i32, arg3 as i32, arg4)),
SyscallNumber::EpollWait => result_to_u64(crate::syscall::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, 0, 0)),
SyscallNumber::EpollPwait => result_to_u64(crate::syscall::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, arg5, arg6)),
// Identity syscalls
SyscallNumber::Getuid => result_to_u64(crate::syscall::handlers::sys_getuid()),
SyscallNumber::Geteuid => result_to_u64(crate::syscall::handlers::sys_geteuid()),
SyscallNumber::Getgid => result_to_u64(crate::syscall::handlers::sys_getgid()),
SyscallNumber::Getegid => result_to_u64(crate::syscall::handlers::sys_getegid()),
SyscallNumber::Setuid => result_to_u64(crate::syscall::handlers::sys_setuid(arg1 as u32)),
SyscallNumber::Setgid => result_to_u64(crate::syscall::handlers::sys_setgid(arg1 as u32)),
// File creation mask
SyscallNumber::Umask => result_to_u64(crate::syscall::handlers::sys_umask(arg1 as u32)),
// Timestamps
SyscallNumber::Utimensat => result_to_u64(crate::syscall::fs::sys_utimensat(arg1 as i32, arg2, arg3, arg4 as u32)),
// Positional I/O
SyscallNumber::Pread64 => result_to_u64(crate::syscall::handlers::sys_pread64(arg1 as i32, arg2, arg3, arg4 as i64)),
SyscallNumber::Pwrite64 => result_to_u64(crate::syscall::handlers::sys_pwrite64(arg1 as i32, arg2, arg3, arg4 as i64)),
}
}

Expand Down
1 change: 1 addition & 0 deletions kernel/src/boot/test_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub const TEST_BINARIES: &[&str] = &[
"env_musl_test",
"uname_musl_test",
"rlimit_musl_test",
"identity_musl_test",
// Fork / CoW tests
"fork_memory_test",
"fork_state_test",
Expand Down
10 changes: 10 additions & 0 deletions kernel/src/fs/ext2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,16 @@ impl Ext2Fs {
Ok(data.len())
}

/// Write a modified inode back to disk
///
/// # Arguments
/// * `inode_num` - The inode number to write
/// * `inode` - The modified inode data
pub fn write_inode(&mut self, inode_num: u32, inode: &Ext2Inode) -> Result<(), &'static str> {
inode.write_to(self.device.as_ref(), inode_num, &self.superblock, &self.block_groups)
.map_err(|_| "Failed to write inode")
}

/// Create a new file in the filesystem
///
/// # Arguments
Expand Down
8 changes: 6 additions & 2 deletions kernel/src/process/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,8 +532,12 @@ pub fn copy_process_state(
child_process.sid = parent_process.sid;
}

// 5. Copy umask (when per-process umask is implemented)
// TODO: child_process.umask = parent_process.umask;
// 5. Copy uid/gid/euid/egid/umask
child_process.uid = parent_process.uid;
child_process.gid = parent_process.gid;
child_process.euid = parent_process.euid;
child_process.egid = parent_process.egid;
child_process.umask = parent_process.umask;

// 6. Current working directory: inherited from parent in fork_internal()
// (before copy_process_state is called)
Expand Down
18 changes: 18 additions & 0 deletions kernel/src/process/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ pub struct Process {
/// a controlling terminal. Initially set to pid on process creation.
pub sid: ProcessId,

/// Real user ID
pub uid: u32,
/// Real group ID
pub gid: u32,
/// Effective user ID
pub euid: u32,
/// Effective group ID
pub egid: u32,
/// File creation mask (umask)
pub umask: u32,

/// Current working directory (absolute path)
pub cwd: String,

Expand Down Expand Up @@ -196,6 +207,13 @@ impl Process {
pgid: id,
// By default, a process's sid equals its pid (process is its own session leader)
sid: id,
// Single-user OS: everything runs as root (uid=0, gid=0)
uid: 0,
gid: 0,
euid: 0,
egid: 0,
// Standard default umask: owner rwx, group/other rx
umask: 0o022,
// Default working directory is root
cwd: String::from("/"),
name,
Expand Down
14 changes: 14 additions & 0 deletions kernel/src/syscall/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,19 @@ pub fn dispatch_syscall(
SyscallNumber::EpollCtl => super::epoll::sys_epoll_ctl(arg1 as i32, arg2 as i32, arg3 as i32, arg4),
SyscallNumber::EpollWait => super::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, 0, 0),
SyscallNumber::EpollPwait => super::epoll::sys_epoll_pwait(arg1 as i32, arg2, arg3 as i32, arg4 as i32, arg5, arg6),
// Identity syscalls
SyscallNumber::Getuid => handlers::sys_getuid(),
SyscallNumber::Geteuid => handlers::sys_geteuid(),
SyscallNumber::Getgid => handlers::sys_getgid(),
SyscallNumber::Getegid => handlers::sys_getegid(),
SyscallNumber::Setuid => handlers::sys_setuid(arg1 as u32),
SyscallNumber::Setgid => handlers::sys_setgid(arg1 as u32),
// File creation mask
SyscallNumber::Umask => handlers::sys_umask(arg1 as u32),
// Timestamps
SyscallNumber::Utimensat => super::fs::sys_utimensat(arg1 as i32, arg2, arg3, arg4 as u32),
// Positional I/O
SyscallNumber::Pread64 => handlers::sys_pread64(arg1 as i32, arg2, arg3, arg4 as i64),
SyscallNumber::Pwrite64 => handlers::sys_pwrite64(arg1 as i32, arg2, arg3, arg4 as i64),
}
}
3 changes: 3 additions & 0 deletions kernel/src/syscall/errno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ pub const ENOTTY: i32 = 25;
/// No space left on device
pub const ENOSPC: i32 = 28;

/// Illegal seek (not a seekable fd)
pub const ESPIPE: i32 = 29;

/// Broken pipe
pub const EPIPE: i32 = 32;

Expand Down
212 changes: 212 additions & 0 deletions kernel/src/syscall/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3470,3 +3470,215 @@ pub fn sys_readlinkat(dirfd: i32, pathname: u64, buf: u64, bufsiz: u64) -> Sysca
}
sys_readlink(pathname, buf, bufsiz)
}

// =============================================================================
// utimensat - Update file timestamps
// =============================================================================

/// Special timespec value: set timestamp to current time
const UTIME_NOW: i64 = 0x3FFFFFFF;
/// Special timespec value: leave timestamp unchanged
const UTIME_OMIT: i64 = 0x3FFFFFFE;
/// AT_SYMLINK_NOFOLLOW flag
#[allow(dead_code)]
const AT_SYMLINK_NOFOLLOW: u32 = 0x100;

/// Timespec layout for utimensat (matches Linux ABI)
#[repr(C)]
#[derive(Copy, Clone)]
struct UtimeTimespec {
tv_sec: i64,
tv_nsec: i64,
}

/// utimensat(dirfd, pathname, times, flags) - Update file timestamps
///
/// If pathname is NULL and dirfd is a valid fd: operate on that fd (futimens behavior).
/// If times is NULL: set atime and mtime to current time.
/// Otherwise: read two Timespec structs from userspace for atime and mtime.
/// UTIME_NOW (0x3FFFFFFF): use current time for that field.
/// UTIME_OMIT (0x3FFFFFFE): don't change that timestamp.
pub fn sys_utimensat(dirfd: i32, path_ptr: u64, times_ptr: u64, flags: u32) -> SyscallResult {
use crate::fs::ext2;

let now = crate::time::current_unix_time() as u32;

// Determine what atime/mtime to set
let (set_atime, set_mtime) = if times_ptr == 0 {
// NULL times = set both to current time
(Some(now), Some(now))
} else {
// Read two Timespec structs from userspace
let times: [UtimeTimespec; 2] = match super::userptr::copy_from_user(times_ptr as *const [UtimeTimespec; 2]) {
Ok(t) => t,
Err(e) => return SyscallResult::Err(e),
};

let atime = if times[0].tv_nsec == UTIME_NOW {
Some(now)
} else if times[0].tv_nsec == UTIME_OMIT {
None
} else {
Some(times[0].tv_sec as u32)
};

let mtime = if times[1].tv_nsec == UTIME_NOW {
Some(now)
} else if times[1].tv_nsec == UTIME_OMIT {
None
} else {
Some(times[1].tv_sec as u32)
};

(atime, mtime)
};

// If both are OMIT, nothing to do
if set_atime.is_none() && set_mtime.is_none() {
return SyscallResult::Ok(0);
}

// Determine the target inode
if path_ptr == 0 {
// futimens behavior: operate on dirfd
if dirfd < 0 {
return SyscallResult::Err(super::errno::EBADF as u64);
}

let thread_id = match crate::task::scheduler::current_thread_id() {
Some(id) => id,
None => return SyscallResult::Err(super::errno::EBADF as u64),
};

let fd_info = crate::arch_without_interrupts(|| {
let manager_guard = crate::process::manager();
if let Some(ref manager) = *manager_guard {
if let Some((_pid, process)) = manager.find_process_by_thread(thread_id) {
if let Some(fd_entry) = process.fd_table.get(dirfd) {
if let FdKind::RegularFile(file_ref) = &fd_entry.kind {
let file = file_ref.lock();
return Some((file.inode_num as u32, file.mount_id));
}
}
}
}
None
});

let (inode_num, mount_id) = match fd_info {
Some((ino, mid)) => (ino, mid),
None => return SyscallResult::Err(super::errno::EBADF as u64),
};

return update_inode_timestamps(inode_num, mount_id, set_atime, set_mtime);
}

// Path-based: resolve path
let path = match super::userptr::copy_cstr_from_user(path_ptr) {
Ok(s) => s,
Err(e) => return SyscallResult::Err(e as u64),
};

// Handle AT_FDCWD
if dirfd != AT_FDCWD && !path.starts_with('/') {
return SyscallResult::Err(super::errno::ENOSYS as u64);
}

let full_path = if path.starts_with('/') {
path.clone()
} else {
let cwd = get_current_cwd().unwrap_or_else(|| alloc::string::String::from("/"));
if cwd.ends_with('/') {
alloc::format!("{}{}", cwd, path)
} else {
alloc::format!("{}/{}", cwd, path)
}
};

let is_home = ext2::is_home_path(&full_path);
let fs_path = if is_home { ext2::strip_home_prefix(&full_path) } else { &full_path };
let no_follow = (flags & AT_SYMLINK_NOFOLLOW) != 0;

// Resolve path first (read lock), then update timestamps (write lock)
let (inode_num, mount_id) = if is_home {
let fs_guard = ext2::home_fs_read();
let fs = match fs_guard.as_ref() {
Some(f) => f,
None => return SyscallResult::Err(super::errno::ENOENT as u64),
};
let ino = if no_follow {
fs.resolve_path_no_follow(fs_path)
} else {
fs.resolve_path(fs_path)
};
match ino {
Ok(n) => (n, fs.mount_id),
Err(_) => return SyscallResult::Err(super::errno::ENOENT as u64),
}
} else {
let fs_guard = ext2::root_fs_read();
let fs = match fs_guard.as_ref() {
Some(f) => f,
None => return SyscallResult::Err(super::errno::ENOENT as u64),
};
let ino = if no_follow {
fs.resolve_path_no_follow(fs_path)
} else {
fs.resolve_path(fs_path)
};
match ino {
Ok(n) => (n, fs.mount_id),
Err(_) => return SyscallResult::Err(super::errno::ENOENT as u64),
}
};

update_inode_timestamps(inode_num, mount_id, set_atime, set_mtime)
}

/// Helper: update an inode's atime/mtime on the ext2 filesystem
fn update_inode_timestamps(
inode_num: u32,
mount_id: usize,
set_atime: Option<u32>,
set_mtime: Option<u32>,
) -> SyscallResult {
use crate::fs::ext2;

let is_home = ext2::home_mount_id().map_or(false, |id| id == mount_id);

let do_update = |fs: &mut ext2::Ext2Fs| -> SyscallResult {
let mut inode = match fs.read_inode(inode_num) {
Ok(i) => i,
Err(_) => return SyscallResult::Err(super::errno::EIO as u64),
};

if let Some(atime) = set_atime {
inode.i_atime = atime;
}
if let Some(mtime) = set_mtime {
inode.i_mtime = mtime;
}
// Always update ctime when timestamps change
inode.i_ctime = crate::time::current_unix_time() as u32;

match fs.write_inode(inode_num, &inode) {
Ok(()) => SyscallResult::Ok(0),
Err(_) => SyscallResult::Err(super::errno::EIO as u64),
}
};

if is_home {
let mut fs_guard = ext2::home_fs_write();
match fs_guard.as_mut() {
Some(fs) => do_update(fs),
None => SyscallResult::Err(super::errno::EIO as u64),
}
} else {
let mut fs_guard = ext2::root_fs_write();
match fs_guard.as_mut() {
Some(fs) => do_update(fs),
None => SyscallResult::Err(super::errno::EIO as u64),
}
}
}

14 changes: 14 additions & 0 deletions kernel/src/syscall/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,20 @@ pub extern "C" fn rust_syscall_handler(frame: &mut SyscallFrame) {
Some(SyscallNumber::EpollCtl) => super::epoll::sys_epoll_ctl(args.0 as i32, args.1 as i32, args.2 as i32, args.3),
Some(SyscallNumber::EpollWait) => super::epoll::sys_epoll_pwait(args.0 as i32, args.1, args.2 as i32, args.3 as i32, 0, 0),
Some(SyscallNumber::EpollPwait) => super::epoll::sys_epoll_pwait(args.0 as i32, args.1, args.2 as i32, args.3 as i32, args.4, args.5),
// Identity syscalls
Some(SyscallNumber::Getuid) => super::handlers::sys_getuid(),
Some(SyscallNumber::Geteuid) => super::handlers::sys_geteuid(),
Some(SyscallNumber::Getgid) => super::handlers::sys_getgid(),
Some(SyscallNumber::Getegid) => super::handlers::sys_getegid(),
Some(SyscallNumber::Setuid) => super::handlers::sys_setuid(args.0 as u32),
Some(SyscallNumber::Setgid) => super::handlers::sys_setgid(args.0 as u32),
// File creation mask
Some(SyscallNumber::Umask) => super::handlers::sys_umask(args.0 as u32),
// Timestamps
Some(SyscallNumber::Utimensat) => super::fs::sys_utimensat(args.0 as i32, args.1, args.2, args.3 as u32),
// Positional I/O
Some(SyscallNumber::Pread64) => super::handlers::sys_pread64(args.0 as i32, args.1, args.2, args.3 as i64),
Some(SyscallNumber::Pwrite64) => super::handlers::sys_pwrite64(args.0 as i32, args.1, args.2, args.3 as i64),
None => {
log::warn!("Unknown syscall number: {} - returning ENOSYS", syscall_num);
SyscallResult::Err(super::ErrorCode::NoSys as u64)
Expand Down
Loading
Loading