Reconstruct utime-like syscalls and fix filetime implementations

This commit is contained in:
Fabing Li
2024-06-13 14:53:09 +08:00
committed by Tate, Hongliang Tian
parent 3de8a9330a
commit 5edc110f9d
21 changed files with 328 additions and 88 deletions

View File

@ -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 | ✅ |

View File

@ -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<Arc<dyn Inode>> {
Err(Error::new(Errno::EPERM))
}

View File

@ -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<usize> {
Ok(0)
}

View File

@ -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<usize> {
self.device.read(buf)
}

View File

@ -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<Uid> {
Ok(Uid::new(
self.inner.read().fs().mount_option().fs_uid as u32,

View File

@ -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 _
}

View File

@ -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<RawInode> for InodeDesc {
impl InodeDesc {
pub fn new(type_: FileType, perm: FilePerm) -> Dirty<Self> {
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,

View File

@ -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<dyn Inode>;
pub fn is_root_of_mount(&self) -> bool;

View File

@ -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,

View File

@ -81,6 +81,8 @@ impl<D: DirOps + 'static> Inode for ProcDir<D> {
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<dyn FileSystem>;
fn resize(&self, _new_size: usize) -> Result<()> {

View File

@ -50,6 +50,8 @@ impl<F: FileOps + 'static> Inode for ProcFile<F> {
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<dyn FileSystem>;
fn resize(&self, _new_size: usize) -> Result<()> {

View File

@ -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<InodeMode> {
Ok(self.metadata.read().mode)
}

View File

@ -47,6 +47,8 @@ impl<S: SymOps + 'static> Inode for ProcSym<S> {
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<dyn FileSystem>;
fn resize(&self, _new_size: usize) -> Result<()> {

View File

@ -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
}

View File

@ -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<Vmo<Full>> {
None
}

View File

@ -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]);

View File

@ -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<SyscallReturn> {
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::<timespec_t>(timespecs_addr)?;
timespecs_addr += core::mem::size_of::<timespec_t>();
let mutime = read_val_from_user::<timespec_t>(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)
};
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
"utimensat: dirfd: {}, pathname_ptr: {:#x}, timespecs_ptr: {:#x}, flags: {:#x}",
dirfd, pathname_ptr, timespecs_ptr, flags
);
if atime.is_none() && mtime.is_none() {
let times = if timespecs_ptr != 0 {
let (autime, mutime) = read_time_from_user::<timespec_t>(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<SyscallReturn> {
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<SyscallReturn> {
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<SyscallReturn> {
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>(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<Dentry>, times: Option<TimeSpecPair>) -> Result<SyscallReturn> {
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<TimeSpecPair>,
flags: u32,
) -> Result<SyscallReturn> {
let flags = UtimensFlags::from_bits(flags)
.ok_or(Error::with_message(Errno::EINVAL, "invalid flags"))?;
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));
vfs_utimes(&dentry, times)
}
if let Some(time) = mtime {
dentry.set_mtime(Duration::from(time));
// 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<SyscallReturn> {
let times = if timeval_ptr != 0 {
let (autime, mutime) = read_time_from_user::<timeval_t>(timeval_ptr)?;
if autime.usec >= 1000000 || autime.usec < 0 || mutime.usec >= 1000000 || mutime.usec < 0 {
return_errno_with_message!(Errno::EINVAL, "Invalid time");
}
Ok(SyscallReturn::Return(0))
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<T: Pod>(time_ptr: Vaddr) -> Result<(T, T)> {
let mut time_addr = time_ptr;
let autime = read_val_from_user::<T>(time_addr)?;
time_addr += core::mem::size_of::<T>();
let mutime = read_val_from_user::<T>(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;

View File

@ -43,6 +43,15 @@ impl From<Duration> for timespec_t {
}
}
impl From<timeval_t> 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<timespec_t> for Duration {
fn from(timespec: timespec_t) -> Self {
Duration::new(timespec.sec as u64, timespec.nsec as u32)

View File

@ -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)))

View File

@ -0,0 +1,8 @@
UtimesTest.OnFile
UtimesTest.OnDir
FutimesatTest.OnAbsPath
FutimesatTest.OnRelPath
UtimensatTest.OnAbsPath
UtimensatTest.OnRelPath
UtimeTest.ZeroAtimeandMtime
Utimensat.NullPath