From 5edc110f9de4db9d05d3fcc3faf78cceaa15cfe1 Mon Sep 17 00:00:00 2001 From: Fabing Li Date: Thu, 13 Jun 2024 14:53:09 +0800 Subject: [PATCH] Reconstruct utime-like syscalls and fix filetime implementations --- docs/src/kernel/linux-compatibility.md | 6 +- kernel/aster-nix/src/fs/devpts/mod.rs | 8 + kernel/aster-nix/src/fs/devpts/ptmx.rs | 8 + kernel/aster-nix/src/fs/devpts/slave.rs | 8 + kernel/aster-nix/src/fs/exfat/inode.rs | 8 + .../src/fs/ext2/impl_for_vfs/inode.rs | 8 + kernel/aster-nix/src/fs/ext2/inode.rs | 15 +- kernel/aster-nix/src/fs/path/dentry.rs | 4 + kernel/aster-nix/src/fs/pipe.rs | 15 +- .../aster-nix/src/fs/procfs/template/dir.rs | 2 + .../aster-nix/src/fs/procfs/template/file.rs | 2 + .../aster-nix/src/fs/procfs/template/mod.rs | 8 + .../aster-nix/src/fs/procfs/template/sym.rs | 2 + kernel/aster-nix/src/fs/ramfs/fs.rs | 23 +- kernel/aster-nix/src/fs/utils/inode.rs | 40 +-- kernel/aster-nix/src/syscall/arch/x86.rs | 5 +- kernel/aster-nix/src/syscall/utimens.rs | 236 ++++++++++++++---- kernel/aster-nix/src/time/mod.rs | 9 + regression/syscall_test/Makefile | 1 + .../syscall_test/blocklists.exfat/utimes_test | 8 + .../syscall_test/blocklists/utimes_test | 0 21 files changed, 328 insertions(+), 88 deletions(-) create mode 100644 regression/syscall_test/blocklists.exfat/utimes_test create mode 100644 regression/syscall_test/blocklists/utimes_test diff --git a/docs/src/kernel/linux-compatibility.md b/docs/src/kernel/linux-compatibility.md index f65df7e2a..6ba38a9c0 100644 --- a/docs/src/kernel/linux-compatibility.md +++ b/docs/src/kernel/linux-compatibility.md @@ -152,7 +152,7 @@ provided by Linux on x86-64 architecture. | 129 | rt_sigqueueinfo | ❌ | | 130 | rt_sigsuspend | ✅ | | 131 | sigaltstack | ✅ | -| 132 | utime | ❌ | +| 132 | utime | ✅ | | 133 | mknod | ❌ | | 134 | uselib | ❌ | | 135 | personality | ❌ | @@ -255,7 +255,7 @@ provided by Linux on x86-64 architecture. | 232 | epoll_wait | ✅ | | 233 | epoll_ctl | ✅ | | 234 | tgkill | ✅ | -| 235 | utimes | ❌ | +| 235 | utimes | ✅ | | 236 | vserver | ❌ | | 237 | mbind | ❌ | | 238 | set_mempolicy | ❌ | @@ -281,7 +281,7 @@ provided by Linux on x86-64 architecture. | 258 | mkdirat | ✅ | | 259 | mknodat | ❌ | | 260 | fchownat | ✅ | -| 261 | futimesat | ❌ | +| 261 | futimesat | ✅ | | 262 | newfstatat | ✅ | | 263 | unlinkat | ✅ | | 264 | renameat | ✅ | diff --git a/kernel/aster-nix/src/fs/devpts/mod.rs b/kernel/aster-nix/src/fs/devpts/mod.rs index 28af7752b..1af688aab 100644 --- a/kernel/aster-nix/src/fs/devpts/mod.rs +++ b/kernel/aster-nix/src/fs/devpts/mod.rs @@ -210,6 +210,14 @@ impl Inode for RootInode { self.metadata.write().mtime = time; } + fn ctime(&self) -> Duration { + self.metadata.read().ctime + } + + fn set_ctime(&self, time: Duration) { + self.metadata.write().ctime = time; + } + fn create(&self, name: &str, type_: InodeType, mode: InodeMode) -> Result> { Err(Error::new(Errno::EPERM)) } diff --git a/kernel/aster-nix/src/fs/devpts/ptmx.rs b/kernel/aster-nix/src/fs/devpts/ptmx.rs index 5565261d9..5df4ea1db 100644 --- a/kernel/aster-nix/src/fs/devpts/ptmx.rs +++ b/kernel/aster-nix/src/fs/devpts/ptmx.rs @@ -124,6 +124,14 @@ impl Inode for Ptmx { self.metadata.write().mtime = time; } + fn ctime(&self) -> Duration { + self.metadata.read().ctime + } + + fn set_ctime(&self, time: Duration) { + self.metadata.write().ctime = time; + } + fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result { Ok(0) } diff --git a/kernel/aster-nix/src/fs/devpts/slave.rs b/kernel/aster-nix/src/fs/devpts/slave.rs index 1fce26a6d..51722db46 100644 --- a/kernel/aster-nix/src/fs/devpts/slave.rs +++ b/kernel/aster-nix/src/fs/devpts/slave.rs @@ -105,6 +105,14 @@ impl Inode for PtySlaveInode { self.metadata.write().mtime = time; } + fn ctime(&self) -> Duration { + self.metadata.read().ctime + } + + fn set_ctime(&self, time: Duration) { + self.metadata.write().ctime = time; + } + fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result { self.device.read(buf) } diff --git a/kernel/aster-nix/src/fs/exfat/inode.rs b/kernel/aster-nix/src/fs/exfat/inode.rs index 88b0ee0fd..f4a96f66b 100644 --- a/kernel/aster-nix/src/fs/exfat/inode.rs +++ b/kernel/aster-nix/src/fs/exfat/inode.rs @@ -1175,6 +1175,14 @@ impl Inode for ExfatInode { self.inner.write().mtime = DosTimestamp::from_duration(time).unwrap_or_default(); } + fn ctime(&self) -> Duration { + self.inner.read().ctime.as_duration().unwrap_or_default() + } + + fn set_ctime(&self, time: Duration) { + self.inner.write().ctime = DosTimestamp::from_duration(time).unwrap_or_default(); + } + fn owner(&self) -> Result { Ok(Uid::new( self.inner.read().fs().mount_option().fs_uid as u32, diff --git a/kernel/aster-nix/src/fs/ext2/impl_for_vfs/inode.rs b/kernel/aster-nix/src/fs/ext2/impl_for_vfs/inode.rs index 31cbd6ff2..63cfe6660 100644 --- a/kernel/aster-nix/src/fs/ext2/impl_for_vfs/inode.rs +++ b/kernel/aster-nix/src/fs/ext2/impl_for_vfs/inode.rs @@ -61,6 +61,14 @@ impl Inode for Ext2Inode { self.set_mtime(time) } + fn ctime(&self) -> Duration { + self.ctime() + } + + fn set_ctime(&self, time: Duration) { + self.set_ctime(time) + } + fn ino(&self) -> u64 { self.ino() as _ } diff --git a/kernel/aster-nix/src/fs/ext2/inode.rs b/kernel/aster-nix/src/fs/ext2/inode.rs index 1397f7a76..fc6688bb4 100644 --- a/kernel/aster-nix/src/fs/ext2/inode.rs +++ b/kernel/aster-nix/src/fs/ext2/inode.rs @@ -15,6 +15,7 @@ use super::{ indirect_block_cache::{IndirectBlock, IndirectBlockCache}, prelude::*, }; +use crate::time::clocks::RealTimeCoarseClock; /// Max length of file name. pub const MAX_FNAME_LEN: usize = 255; @@ -649,6 +650,7 @@ impl Inode { pub fn set_gid(&self, gid: u32); pub fn set_atime(&self, time: Duration); pub fn set_mtime(&self, time: Duration); + pub fn set_ctime(&self, time: Duration); } impl Debug for Inode { @@ -728,6 +730,7 @@ impl Inner { pub fn mtime(&self) -> Duration; pub fn set_mtime(&mut self, time: Duration); pub fn ctime(&self) -> Duration; + pub fn set_ctime(&mut self, time: Duration); pub fn set_device_id(&mut self, device_id: u64); pub fn device_id(&self) -> u64; pub fn sync_metadata(&self) -> Result<()>; @@ -1653,6 +1656,11 @@ impl InodeImpl { self.0.read().desc.ctime } + pub fn set_ctime(&self, time: Duration) { + let mut inner = self.0.write(); + inner.desc.ctime = time; + } + pub fn read_block_sync(&self, bid: Ext2Bid, block: &Frame) -> Result<()> { self.0.read().read_block_sync(bid, block) } @@ -1829,15 +1837,16 @@ impl TryFrom for InodeDesc { impl InodeDesc { pub fn new(type_: FileType, perm: FilePerm) -> Dirty { + let now = RealTimeCoarseClock::get().read_time(); Dirty::new_dirty(Self { type_, perm, uid: 0, gid: 0, size: 0, - atime: Duration::ZERO, - ctime: Duration::ZERO, - mtime: Duration::ZERO, + atime: now, + ctime: now, + mtime: now, dtime: Duration::ZERO, hard_links: 1, blocks_count: 0, diff --git a/kernel/aster-nix/src/fs/path/dentry.rs b/kernel/aster-nix/src/fs/path/dentry.rs index ad03ec6e4..05b3c8c99 100644 --- a/kernel/aster-nix/src/fs/path/dentry.rs +++ b/kernel/aster-nix/src/fs/path/dentry.rs @@ -314,6 +314,8 @@ impl Dentry_ { pub fn set_atime(&self, time: Duration); pub fn mtime(&self) -> Duration; pub fn set_mtime(&self, time: Duration); + pub fn ctime(&self) -> Duration; + pub fn set_ctime(&self, time: Duration); } impl Debug for Dentry_ { @@ -693,6 +695,8 @@ impl Dentry { pub fn set_atime(&self, time: Duration); pub fn mtime(&self) -> Duration; pub fn set_mtime(&self, time: Duration); + pub fn ctime(&self) -> Duration; + pub fn set_ctime(&self, time: Duration); pub fn key(&self) -> DentryKey; pub fn inode(&self) -> &Arc; pub fn is_root_of_mount(&self) -> bool; diff --git a/kernel/aster-nix/src/fs/pipe.rs b/kernel/aster-nix/src/fs/pipe.rs index baa29b309..a6c018b1c 100644 --- a/kernel/aster-nix/src/fs/pipe.rs +++ b/kernel/aster-nix/src/fs/pipe.rs @@ -10,6 +10,7 @@ use crate::{ events::{IoEvents, Observer}, prelude::*, process::{signal::Poller, Gid, Uid}, + time::clocks::RealTimeCoarseClock, }; pub struct PipeReader { @@ -44,15 +45,16 @@ impl FileLike for PipeReader { } fn metadata(&self) -> Metadata { + let now = RealTimeCoarseClock::get().read_time(); Metadata { dev: 0, ino: 0, size: 0, blk_size: 0, blocks: 0, - atime: Default::default(), - mtime: Default::default(), - ctime: Default::default(), + atime: now, + mtime: now, + ctime: now, type_: InodeType::NamedPipe, mode: InodeMode::from_bits_truncate(0o400), nlinks: 1, @@ -110,15 +112,16 @@ impl FileLike for PipeWriter { } fn metadata(&self) -> Metadata { + let now = RealTimeCoarseClock::get().read_time(); Metadata { dev: 0, ino: 0, size: 0, blk_size: 0, blocks: 0, - atime: Default::default(), - mtime: Default::default(), - ctime: Default::default(), + atime: now, + mtime: now, + ctime: now, type_: InodeType::NamedPipe, mode: InodeMode::from_bits_truncate(0o200), nlinks: 1, diff --git a/kernel/aster-nix/src/fs/procfs/template/dir.rs b/kernel/aster-nix/src/fs/procfs/template/dir.rs index 0624d5449..1ceca98ae 100644 --- a/kernel/aster-nix/src/fs/procfs/template/dir.rs +++ b/kernel/aster-nix/src/fs/procfs/template/dir.rs @@ -81,6 +81,8 @@ impl Inode for ProcDir { fn set_atime(&self, time: Duration); fn mtime(&self) -> Duration; fn set_mtime(&self, time: Duration); + fn ctime(&self) -> Duration; + fn set_ctime(&self, time: Duration); fn fs(&self) -> Arc; fn resize(&self, _new_size: usize) -> Result<()> { diff --git a/kernel/aster-nix/src/fs/procfs/template/file.rs b/kernel/aster-nix/src/fs/procfs/template/file.rs index 70f2493ff..7b7648fa4 100644 --- a/kernel/aster-nix/src/fs/procfs/template/file.rs +++ b/kernel/aster-nix/src/fs/procfs/template/file.rs @@ -50,6 +50,8 @@ impl Inode for ProcFile { fn set_atime(&self, time: Duration); fn mtime(&self) -> Duration; fn set_mtime(&self, time: Duration); + fn ctime(&self) -> Duration; + fn set_ctime(&self, time: Duration); fn fs(&self) -> Arc; fn resize(&self, _new_size: usize) -> Result<()> { diff --git a/kernel/aster-nix/src/fs/procfs/template/mod.rs b/kernel/aster-nix/src/fs/procfs/template/mod.rs index e8ad51de2..79aae3481 100644 --- a/kernel/aster-nix/src/fs/procfs/template/mod.rs +++ b/kernel/aster-nix/src/fs/procfs/template/mod.rs @@ -71,6 +71,14 @@ impl Common { self.metadata.write().mtime = time; } + pub fn ctime(&self) -> Duration { + self.metadata.read().ctime + } + + pub fn set_ctime(&self, time: Duration) { + self.metadata.write().ctime = time; + } + pub fn mode(&self) -> Result { Ok(self.metadata.read().mode) } diff --git a/kernel/aster-nix/src/fs/procfs/template/sym.rs b/kernel/aster-nix/src/fs/procfs/template/sym.rs index 7232f342a..1f43a6a7b 100644 --- a/kernel/aster-nix/src/fs/procfs/template/sym.rs +++ b/kernel/aster-nix/src/fs/procfs/template/sym.rs @@ -47,6 +47,8 @@ impl Inode for ProcSym { fn set_atime(&self, time: Duration); fn mtime(&self) -> Duration; fn set_mtime(&self, time: Duration); + fn ctime(&self) -> Duration; + fn set_ctime(&self, time: Duration); fn fs(&self) -> Arc; fn resize(&self, _new_size: usize) -> Result<()> { diff --git a/kernel/aster-nix/src/fs/ramfs/fs.rs b/kernel/aster-nix/src/fs/ramfs/fs.rs index e428fa534..af4027dbf 100644 --- a/kernel/aster-nix/src/fs/ramfs/fs.rs +++ b/kernel/aster-nix/src/fs/ramfs/fs.rs @@ -25,6 +25,7 @@ use crate::{ }, prelude::*, process::{signal::Poller, Gid, Uid}, + time::clocks::RealTimeCoarseClock, vm::vmo::Vmo, }; @@ -188,12 +189,13 @@ struct InodeMeta { impl InodeMeta { pub fn new(mode: InodeMode, uid: Uid, gid: Gid) -> Self { + let now = RealTimeCoarseClock::get().read_time(); Self { size: 0, blocks: 0, - atime: Default::default(), - mtime: Default::default(), - ctime: Default::default(), + atime: now, + mtime: now, + ctime: now, mode, nlinks: 1, uid, @@ -202,12 +204,13 @@ impl InodeMeta { } pub fn new_dir(mode: InodeMode, uid: Uid, gid: Gid) -> Self { + let now = RealTimeCoarseClock::get().read_time(); Self { size: 2, blocks: 1, - atime: Default::default(), - mtime: Default::default(), - ctime: Default::default(), + atime: now, + mtime: now, + ctime: now, mode, nlinks: 2, uid, @@ -574,6 +577,14 @@ impl Inode for RamInode { self.node.write().metadata.mtime = time; } + fn ctime(&self) -> Duration { + self.node.read().metadata.ctime + } + + fn set_ctime(&self, time: Duration) { + self.node.write().metadata.ctime = time; + } + fn ino(&self) -> u64 { self.ino } diff --git a/kernel/aster-nix/src/fs/utils/inode.rs b/kernel/aster-nix/src/fs/utils/inode.rs index d5ffde2d2..e2ce9205f 100644 --- a/kernel/aster-nix/src/fs/utils/inode.rs +++ b/kernel/aster-nix/src/fs/utils/inode.rs @@ -13,6 +13,7 @@ use crate::{ fs::device::{Device, DeviceType}, prelude::*, process::{signal::Poller, Gid, Uid}, + time::clocks::RealTimeCoarseClock, vm::vmo::Vmo, }; @@ -137,15 +138,16 @@ pub struct Metadata { impl Metadata { pub fn new_dir(ino: u64, mode: InodeMode, blk_size: usize) -> Self { + let now = RealTimeCoarseClock::get().read_time(); Self { dev: 0, ino, size: 2, blk_size, blocks: 1, - atime: Default::default(), - mtime: Default::default(), - ctime: Default::default(), + atime: now, + mtime: now, + ctime: now, type_: InodeType::Dir, mode, nlinks: 2, @@ -156,15 +158,16 @@ impl Metadata { } pub fn new_file(ino: u64, mode: InodeMode, blk_size: usize) -> Self { + let now = RealTimeCoarseClock::get().read_time(); Self { dev: 0, ino, size: 0, blk_size, blocks: 0, - atime: Default::default(), - mtime: Default::default(), - ctime: Default::default(), + atime: now, + mtime: now, + ctime: now, type_: InodeType::File, mode, nlinks: 1, @@ -175,15 +178,16 @@ impl Metadata { } pub fn new_symlink(ino: u64, mode: InodeMode, blk_size: usize) -> Self { + let now = RealTimeCoarseClock::get().read_time(); Self { dev: 0, ino, size: 0, blk_size, blocks: 0, - atime: Default::default(), - mtime: Default::default(), - ctime: Default::default(), + atime: now, + mtime: now, + ctime: now, type_: InodeType::SymLink, mode, nlinks: 1, @@ -193,15 +197,16 @@ impl Metadata { } } pub fn new_device(ino: u64, mode: InodeMode, blk_size: usize, device: &dyn Device) -> Self { + let now = RealTimeCoarseClock::get().read_time(); Self { dev: 0, ino, size: 0, blk_size, blocks: 0, - atime: Default::default(), - mtime: Default::default(), - ctime: Default::default(), + atime: now, + mtime: now, + ctime: now, type_: InodeType::from(device.type_()), mode, nlinks: 1, @@ -212,15 +217,16 @@ impl Metadata { } pub fn new_socket(ino: u64, mode: InodeMode, blk_size: usize) -> Metadata { + let now = RealTimeCoarseClock::get().read_time(); Self { dev: 0, ino, size: 0, blk_size, blocks: 0, - atime: Default::default(), - mtime: Default::default(), - ctime: Default::default(), + atime: now, + mtime: now, + ctime: now, type_: InodeType::Socket, mode, nlinks: 1, @@ -262,6 +268,10 @@ pub trait Inode: Any + Sync + Send { fn set_mtime(&self, time: Duration); + fn ctime(&self) -> Duration; + + fn set_ctime(&self, time: Duration); + fn page_cache(&self) -> Option> { None } diff --git a/kernel/aster-nix/src/syscall/arch/x86.rs b/kernel/aster-nix/src/syscall/arch/x86.rs index 0a034a3f6..7bb7a289b 100644 --- a/kernel/aster-nix/src/syscall/arch/x86.rs +++ b/kernel/aster-nix/src/syscall/arch/x86.rs @@ -117,7 +117,7 @@ use crate::syscall::{ umount::sys_umount, uname::sys_uname, unlink::{sys_unlink, sys_unlinkat}, - utimens::sys_utimensat, + utimens::{sys_futimesat, sys_utime, sys_utimensat, sys_utimes}, wait4::sys_wait4, waitid::sys_waitid, write::sys_write, @@ -230,6 +230,7 @@ impl_syscall_nums_and_dispatch_fn! { SYS_RT_SIGPENDING = 127 => sys_rt_sigpending(args[..2]); SYS_RT_SIGSUSPEND = 130 => sys_rt_sigsuspend(args[..2]); SYS_SIGALTSTACK = 131 => sys_sigaltstack(args[..2]); + SYS_UTIME = 132 => sys_utime(args[..2]); SYS_STATFS = 137 => sys_statfs(args[..2]); SYS_FSTATFS = 138 => sys_fstatfs(args[..2]); SYS_GET_PRIORITY = 140 => sys_get_priority(args[..2]); @@ -257,10 +258,12 @@ impl_syscall_nums_and_dispatch_fn! { SYS_EPOLL_WAIT = 232 => sys_epoll_wait(args[..4]); SYS_EPOLL_CTL = 233 => sys_epoll_ctl(args[..4]); SYS_TGKILL = 234 => sys_tgkill(args[..3]); + SYS_UTIMES = 235 => sys_utimes(args[..2]); SYS_WAITID = 247 => sys_waitid(args[..5]); SYS_OPENAT = 257 => sys_openat(args[..4]); SYS_MKDIRAT = 258 => sys_mkdirat(args[..3]); SYS_FCHOWNAT = 260 => sys_fchownat(args[..5]); + SYS_FUTIMESAT = 261 => sys_futimesat(args[..3]); SYS_FSTATAT = 262 => sys_fstatat(args[..4]); SYS_UNLINKAT = 263 => sys_unlinkat(args[..3]); SYS_RENAMEAT = 264 => sys_renameat(args[..4]); diff --git a/kernel/aster-nix/src/syscall/utimens.rs b/kernel/aster-nix/src/syscall/utimens.rs index 87b8f1c23..ec2e30ff2 100644 --- a/kernel/aster-nix/src/syscall/utimens.rs +++ b/kernel/aster-nix/src/syscall/utimens.rs @@ -2,69 +2,168 @@ use core::time::Duration; -use super::SyscallReturn; +use super::{constants::MAX_FILENAME_LEN, SyscallReturn}; use crate::{ - fs::{file_table::FileDesc, fs_resolver::FsPath}, + fs::{ + file_table::FileDesc, + fs_resolver::{FsPath, AT_FDCWD}, + path::Dentry, + }, prelude::*, - syscall::constants::MAX_FILENAME_LEN, - time::timespec_t, + time::{clocks::RealTimeCoarseClock, timespec_t, timeval_t}, util::{read_cstring_from_user, read_val_from_user}, }; +/// The 'sys_utimensat' system call sets the access and modification times of a file. +/// The times are defined by an array of two timespec structures, where times[0] represents the access time, +/// and times[1] represents the modification time. +/// The `flags` argument is a bit mask that can include the following values: +/// - `AT_SYMLINK_NOFOLLOW`: If set, the file is not dereferenced if it is a symbolic link. pub fn sys_utimensat( dirfd: FileDesc, - path_addr: Vaddr, + pathname_ptr: Vaddr, timespecs_ptr: Vaddr, flags: u32, ) -> Result { - let path = read_cstring_from_user(path_addr, MAX_FILENAME_LEN)?; - let (atime, mtime) = { - let (autime, mutime) = if timespecs_ptr == 0 { - (timespec_t::utime_now(), timespec_t::utime_now()) - } else { - let mut timespecs_addr = timespecs_ptr; - let autime = read_val_from_user::(timespecs_addr)?; - timespecs_addr += core::mem::size_of::(); - let mutime = read_val_from_user::(timespecs_addr)?; - (autime, mutime) - }; - - // TODO: Get current time - let current_time: timespec_t = Default::default(); - - let atime = if autime.is_utime_omit() { - None - } else if autime.is_utime_now() { - Some(current_time) - } else { - Some(autime) - }; - let mtime = if mutime.is_utime_omit() { - None - } else if mutime.is_utime_now() { - Some(current_time) - } else { - Some(mutime) - }; - (atime, mtime) + debug!( + "utimensat: dirfd: {}, pathname_ptr: {:#x}, timespecs_ptr: {:#x}, flags: {:#x}", + dirfd, pathname_ptr, timespecs_ptr, flags + ); + let times = if timespecs_ptr != 0 { + let (autime, mutime) = read_time_from_user::(timespecs_ptr)?; + if autime.is_utime_omit() && mutime.is_utime_omit() { + return Ok(SyscallReturn::Return(0)); + } + Some(TimeSpecPair { + atime: autime, + mtime: mutime, + }) + } else { + None }; + do_utimes(dirfd, pathname_ptr, times, flags) +} + +/// The 'sys_futimesat' system call sets the access and modification times of a file. +/// Unlike 'sys_utimensat', it receives time values in the form of timeval structures, +/// and it does not support the 'flags' argument. +pub fn sys_futimesat( + dirfd: FileDesc, + pathname_ptr: Vaddr, + timeval_ptr: Vaddr, +) -> Result { + debug!( + "futimesat: dirfd: {}, pathname_ptr: {:#x}, timeval_ptr: {:#x}", + dirfd, pathname_ptr, timeval_ptr + ); + do_futimesat(dirfd, pathname_ptr, timeval_ptr) +} + +/// The 'sys_utimes' system call sets the access and modification times of a file. +/// It receives time values in the form of timeval structures like 'sys_futimesat', +/// but it uses the current working directory as the base directory. +pub fn sys_utimes(pathname_ptr: Vaddr, timeval_ptr: Vaddr) -> Result { + debug!( + "utimes: pathname_ptr: {:#x}, timeval_ptr: {:#x}", + pathname_ptr, timeval_ptr + ); + do_futimesat(AT_FDCWD, pathname_ptr, timeval_ptr) +} + +/// The 'sys_utime' system call is similar to 'sys_utimes' but uses the older 'utimbuf' structure to specify times. +pub fn sys_utime(pathname_ptr: Vaddr, utimbuf_ptr: Vaddr) -> Result { + debug!( + "utime: pathname_ptr: {:#x}, utimbuf_ptr: {:#x}", + pathname_ptr, utimbuf_ptr + ); + let times = if utimbuf_ptr != 0 { + let utimbuf = read_val_from_user::(utimbuf_ptr)?; + let atime = timespec_t { + sec: utimbuf.actime, + nsec: 0, + }; + let mtime = timespec_t { + sec: utimbuf.modtime, + nsec: 0, + }; + Some(TimeSpecPair { atime, mtime }) + } else { + None + }; + do_utimes(AT_FDCWD, pathname_ptr, times, 0) +} + +// Structure to hold access and modification times +#[derive(Debug)] +struct TimeSpecPair { + atime: timespec_t, + mtime: timespec_t, +} + +/// This struct is corresponding to the `utimbuf` struct in Linux. +#[repr(C)] +#[derive(Debug, Default, Copy, Clone, Pod)] +struct Utimbuf { + actime: i64, + modtime: i64, +} + +fn vfs_utimes(dentry: &Arc, times: Option) -> Result { + let (atime, mtime) = match times { + Some(times) => { + if !times.atime.is_valid() || !times.mtime.is_valid() { + return_errno_with_message!(Errno::EINVAL, "invalid time") + } + let now = RealTimeCoarseClock::get().read_time(); + let atime = if times.atime.is_utime_omit() { + dentry.atime() + } else if times.atime.is_utime_now() { + now + } else { + Duration::from(times.atime) + }; + let mtime = if times.mtime.is_utime_omit() { + dentry.mtime() + } else if times.mtime.is_utime_now() { + now + } else { + Duration::from(times.mtime) + }; + (atime, mtime) + } + None => { + let now = RealTimeCoarseClock::get().read_time(); + (now, now) + } + }; + + // Update times + dentry.set_atime(atime); + dentry.set_mtime(mtime); + + Ok(SyscallReturn::Return(0)) +} + +// Common function to handle updating file times, supporting both fd and path based operations +fn do_utimes( + dirfd: FileDesc, + pathname_ptr: Vaddr, + times: Option, + flags: u32, +) -> Result { let flags = UtimensFlags::from_bits(flags) .ok_or(Error::with_message(Errno::EINVAL, "invalid flags"))?; - debug!( - "dirfd = {}, path = {:?}, atime = {:?}, mtime = {:?}, flags = {:?}", - dirfd, path, atime, mtime, flags - ); - if atime.is_none() && mtime.is_none() { - return Ok(SyscallReturn::Return(0)); - } + let pathname = if pathname_ptr == 0 { + String::new() + } else { + let cstring = read_cstring_from_user(pathname_ptr, MAX_FILENAME_LEN)?; + cstring.to_string_lossy().into_owned() + }; let current = current!(); let dentry = { - let path = path.to_string_lossy(); - if path.is_empty() { - return_errno_with_message!(Errno::ENOENT, "path is empty"); - } - let fs_path = FsPath::new(dirfd, path.as_ref())?; + // Determine the file system path and the corresponding entry + let fs_path = FsPath::new(dirfd, pathname.as_ref())?; let fs = current.fs().read(); if flags.contains(UtimensFlags::AT_SYMLINK_NOFOLLOW) { fs.lookup_no_follow(&fs_path)? @@ -72,13 +171,35 @@ pub fn sys_utimensat( fs.lookup(&fs_path)? } }; - if let Some(time) = atime { - dentry.set_atime(Duration::from(time)); - } - if let Some(time) = mtime { - dentry.set_mtime(Duration::from(time)); - } - Ok(SyscallReturn::Return(0)) + + vfs_utimes(&dentry, times) +} + +// Sets the access and modification times for a file, +// specified by a pathname relative to the directory file descriptor `dirfd`. +fn do_futimesat(dirfd: FileDesc, pathname_ptr: Vaddr, timeval_ptr: Vaddr) -> Result { + let times = if timeval_ptr != 0 { + let (autime, mutime) = read_time_from_user::(timeval_ptr)?; + if autime.usec >= 1000000 || autime.usec < 0 || mutime.usec >= 1000000 || mutime.usec < 0 { + return_errno_with_message!(Errno::EINVAL, "Invalid time"); + } + let (autime, mutime) = (timespec_t::from(autime), timespec_t::from(mutime)); + Some(TimeSpecPair { + atime: autime, + mtime: mutime, + }) + } else { + None + }; + do_utimes(dirfd, pathname_ptr, times, 0) +} + +fn read_time_from_user(time_ptr: Vaddr) -> Result<(T, T)> { + let mut time_addr = time_ptr; + let autime = read_val_from_user::(time_addr)?; + time_addr += core::mem::size_of::(); + let mutime = read_val_from_user::(time_addr)?; + Ok((autime, mutime)) } trait UtimeExt { @@ -87,6 +208,7 @@ trait UtimeExt { fn utime_omit() -> Self; fn is_utime_now(&self) -> bool; fn is_utime_omit(&self) -> bool; + fn is_valid(&self) -> bool; } impl UtimeExt for timespec_t { @@ -111,6 +233,12 @@ impl UtimeExt for timespec_t { fn is_utime_omit(&self) -> bool { self.nsec == UTIME_OMIT } + + fn is_valid(&self) -> bool { + self.nsec == UTIME_OMIT + || self.nsec == UTIME_NOW + || (self.nsec >= 0 && self.nsec <= 999_999_999) + } } const UTIME_NOW: i64 = (1i64 << 30) - 1i64; diff --git a/kernel/aster-nix/src/time/mod.rs b/kernel/aster-nix/src/time/mod.rs index f106f0777..e6c5d410d 100644 --- a/kernel/aster-nix/src/time/mod.rs +++ b/kernel/aster-nix/src/time/mod.rs @@ -43,6 +43,15 @@ impl From for timespec_t { } } +impl From for timespec_t { + fn from(timeval: timeval_t) -> timespec_t { + let sec = timeval.sec; + let nsec = timeval.usec * 1000; + debug_assert!(sec >= 0); // nsec >= 0 always holds + timespec_t { sec, nsec } + } +} + impl From for Duration { fn from(timespec: timespec_t) -> Self { Duration::new(timespec.sec as u64, timespec.nsec as u32) diff --git a/regression/syscall_test/Makefile b/regression/syscall_test/Makefile index b96af384a..424d5c23d 100644 --- a/regression/syscall_test/Makefile +++ b/regression/syscall_test/Makefile @@ -37,6 +37,7 @@ TESTS ?= \ unlink_test \ vdso_clock_gettime_test \ write_test \ + utimes_test \ # The end of the list MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) diff --git a/regression/syscall_test/blocklists.exfat/utimes_test b/regression/syscall_test/blocklists.exfat/utimes_test new file mode 100644 index 000000000..86e3e23d8 --- /dev/null +++ b/regression/syscall_test/blocklists.exfat/utimes_test @@ -0,0 +1,8 @@ +UtimesTest.OnFile +UtimesTest.OnDir +FutimesatTest.OnAbsPath +FutimesatTest.OnRelPath +UtimensatTest.OnAbsPath +UtimensatTest.OnRelPath +UtimeTest.ZeroAtimeandMtime +Utimensat.NullPath \ No newline at end of file diff --git a/regression/syscall_test/blocklists/utimes_test b/regression/syscall_test/blocklists/utimes_test new file mode 100644 index 000000000..e69de29bb