asterinas/kernel/src/syscall/utimens.rs

262 lines
7.9 KiB
Rust

// SPDX-License-Identifier: MPL-2.0
use core::time::Duration;
use ostd::mm::VmIo;
use super::{constants::MAX_FILENAME_LEN, SyscallReturn};
use crate::{
fs,
fs::{
file_table::FileDesc,
fs_resolver::{FsPath, AT_FDCWD},
path::Path,
},
prelude::*,
time::{clocks::RealTimeCoarseClock, timespec_t, timeval_t},
};
/// 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,
pathname_ptr: Vaddr,
timespecs_ptr: Vaddr,
flags: u32,
ctx: &Context,
) -> Result<SyscallReturn> {
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::<timespec_t>(timespecs_ptr, ctx)?;
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, ctx)
}
/// 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,
ctx: &Context,
) -> Result<SyscallReturn> {
debug!(
"futimesat: dirfd: {}, pathname_ptr: {:#x}, timeval_ptr: {:#x}",
dirfd, pathname_ptr, timeval_ptr
);
do_futimesat(dirfd, pathname_ptr, timeval_ptr, ctx)
}
/// 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, ctx: &Context) -> Result<SyscallReturn> {
debug!(
"utimes: pathname_ptr: {:#x}, timeval_ptr: {:#x}",
pathname_ptr, timeval_ptr
);
do_futimesat(AT_FDCWD, pathname_ptr, timeval_ptr, ctx)
}
/// 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, ctx: &Context) -> Result<SyscallReturn> {
debug!(
"utime: pathname_ptr: {:#x}, utimbuf_ptr: {:#x}",
pathname_ptr, utimbuf_ptr
);
let times = if utimbuf_ptr != 0 {
let utimbuf = ctx.user_space().read_val::<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, ctx)
}
// 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(path: &Path, times: Option<TimeSpecPair>) -> Result<SyscallReturn> {
let (atime, mtime, ctime) = 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() {
path.atime()
} else if times.atime.is_utime_now() {
now
} else {
Duration::try_from(times.atime)?
};
let mtime = if times.mtime.is_utime_omit() {
path.mtime()
} else if times.mtime.is_utime_now() {
now
} else {
Duration::try_from(times.mtime)?
};
(atime, mtime, now)
}
None => {
let now = RealTimeCoarseClock::get().read_time();
(now, now, now)
}
};
// Update times
path.set_atime(atime);
path.set_mtime(mtime);
path.set_ctime(ctime);
fs::notify::on_attr_change(path);
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,
ctx: &Context,
) -> Result<SyscallReturn> {
let flags = UtimensFlags::from_bits(flags)
.ok_or_else(|| Error::with_message(Errno::EINVAL, "invalid flags"))?;
// Unlike other system calls, `utimesat` has special handling for the NULL path string.
// Reference: <https://elixir.bootlin.com/linux/v6.17.1/source/fs/utimes.c#L138-L139>
let pathname = if dirfd != AT_FDCWD && pathname_ptr == 0 {
None
} else {
let pathname = ctx
.user_space()
.read_cstring(pathname_ptr, MAX_FILENAME_LEN)?;
Some(pathname.to_string_lossy().into_owned())
};
let path = {
let fs_path = if let Some(pathname) = pathname.as_ref() {
FsPath::from_fd_and_path(dirfd, pathname)?
} else {
FsPath::from_fd(dirfd)?
};
let fs_ref = ctx.thread_local.borrow_fs();
let fs = fs_ref.resolver().read();
if flags.contains(UtimensFlags::AT_SYMLINK_NOFOLLOW) {
fs.lookup_no_follow(&fs_path)?
} else {
fs.lookup(&fs_path)?
}
};
vfs_utimes(&path, 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,
ctx: &Context,
) -> Result<SyscallReturn> {
let times = if timeval_ptr != 0 {
let (autime, mutime) = read_time_from_user::<timeval_t>(timeval_ptr, ctx)?;
if autime.usec >= 1000000
|| autime.usec < 0
|| autime.sec < 0
|| mutime.usec >= 1000000
|| mutime.usec < 0
|| mutime.sec < 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, ctx)
}
fn read_time_from_user<T: Pod>(time_ptr: Vaddr, ctx: &Context) -> Result<(T, T)> {
let mut time_addr = time_ptr;
let user_space = ctx.user_space();
let autime = user_space.read_val::<T>(time_addr)?;
time_addr += size_of::<T>();
let mutime = user_space.read_val::<T>(time_addr)?;
Ok((autime, mutime))
}
trait UtimeExt {
fn is_utime_now(&self) -> bool;
fn is_utime_omit(&self) -> bool;
fn is_valid(&self) -> bool;
}
impl UtimeExt for timespec_t {
fn is_utime_now(&self) -> bool {
self.nsec == UTIME_NOW
}
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;
const UTIME_OMIT: i64 = (1i64 << 30) - 2i64;
bitflags::bitflags! {
struct UtimensFlags: u32 {
const AT_SYMLINK_NOFOLLOW = 0x100;
}
}