262 lines
7.9 KiB
Rust
262 lines
7.9 KiB
Rust
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
use core::time::Duration;
|
|
|
|
use ostd::mm::VmIo;
|
|
|
|
use super::{SyscallReturn, constants::MAX_FILENAME_LEN};
|
|
use crate::{
|
|
fs,
|
|
fs::{
|
|
file_table::FileDesc,
|
|
fs_resolver::{AT_FDCWD, FsPath},
|
|
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;
|
|
}
|
|
}
|