diff --git a/Kernel/API/POSIX/sys/stat.h b/Kernel/API/POSIX/sys/stat.h index 31df0e079a..5c3a0b52f4 100644 --- a/Kernel/API/POSIX/sys/stat.h +++ b/Kernel/API/POSIX/sys/stat.h @@ -70,6 +70,9 @@ struct stat { #define st_mtime st_mtim.tv_sec #define st_ctime st_ctim.tv_sec +#define UTIME_OMIT -1 +#define UTIME_NOW -2 + #ifdef __cplusplus } #endif diff --git a/Kernel/API/Syscall.h b/Kernel/API/Syscall.h index a05f7be98d..7347dbfe7d 100644 --- a/Kernel/API/Syscall.h +++ b/Kernel/API/Syscall.h @@ -185,6 +185,7 @@ enum class NeedsBigProcessLock { S(unlink, NeedsBigProcessLock::No) \ S(unveil, NeedsBigProcessLock::Yes) \ S(utime, NeedsBigProcessLock::Yes) \ + S(utimensat, NeedsBigProcessLock::Yes) \ S(waitid, NeedsBigProcessLock::Yes) \ S(write, NeedsBigProcessLock::Yes) \ S(writev, NeedsBigProcessLock::Yes) \ @@ -416,6 +417,13 @@ struct SC_unveil_params { StringArgument permissions; }; +struct SC_utimensat_params { + int dirfd; + StringArgument path; + struct timespec const* times; + int flag; +}; + struct SC_waitid_params { int idtype; int id; diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 2135076d59..5bd7c07cf8 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -281,6 +281,7 @@ set(KERNEL_SOURCES Syscalls/unlink.cpp Syscalls/unveil.cpp Syscalls/utime.cpp + Syscalls/utimensat.cpp Syscalls/waitid.cpp Syscalls/inode_watcher.cpp Syscalls/write.cpp diff --git a/Kernel/FileSystem/Ext2FileSystem.cpp b/Kernel/FileSystem/Ext2FileSystem.cpp index b016a2403c..533a43e85b 100644 --- a/Kernel/FileSystem/Ext2FileSystem.cpp +++ b/Kernel/FileSystem/Ext2FileSystem.cpp @@ -1545,6 +1545,8 @@ ErrorOr Ext2FSInode::set_atime(time_t t) MutexLocker locker(m_inode_lock); if (fs().is_readonly()) return EROFS; + if (t > INT32_MAX) + return EINVAL; m_raw_inode.i_atime = t; set_metadata_dirty(true); return {}; diff --git a/Kernel/FileSystem/VirtualFileSystem.cpp b/Kernel/FileSystem/VirtualFileSystem.cpp index bea549d0c6..1c29b4f4bd 100644 --- a/Kernel/FileSystem/VirtualFileSystem.cpp +++ b/Kernel/FileSystem/VirtualFileSystem.cpp @@ -196,6 +196,25 @@ ErrorOr VirtualFileSystem::utime(StringView path, Custody& base, time_t at return {}; } +ErrorOr VirtualFileSystem::utimensat(StringView path, Custody& base, timespec const& atime, timespec const& mtime, int options) +{ + auto custody = TRY(resolve_path(path, base, nullptr, options)); + auto& inode = custody->inode(); + auto& current_process = Process::current(); + if (!current_process.is_superuser() && inode.metadata().uid != current_process.euid()) + return EACCES; + if (custody->is_readonly()) + return EROFS; + + // NOTE: A standard ext2 inode cannot store nanosecond timestamps. + if (atime.tv_nsec != UTIME_OMIT) + TRY(inode.set_atime(atime.tv_sec)); + if (mtime.tv_nsec != UTIME_OMIT) + TRY(inode.set_mtime(mtime.tv_sec)); + + return {}; +} + ErrorOr VirtualFileSystem::lookup_metadata(StringView path, Custody& base, int options) { auto custody = TRY(resolve_path(path, base, nullptr, options)); diff --git a/Kernel/FileSystem/VirtualFileSystem.h b/Kernel/FileSystem/VirtualFileSystem.h index 169439e4c9..1ee402299e 100644 --- a/Kernel/FileSystem/VirtualFileSystem.h +++ b/Kernel/FileSystem/VirtualFileSystem.h @@ -64,6 +64,7 @@ public: ErrorOr access(StringView path, int mode, Custody& base); ErrorOr lookup_metadata(StringView path, Custody& base, int options = 0); ErrorOr utime(StringView path, Custody& base, time_t atime, time_t mtime); + ErrorOr utimensat(StringView path, Custody& base, timespec const& atime, timespec const& mtime, int options = 0); ErrorOr rename(StringView oldpath, StringView newpath, Custody& base); ErrorOr mknod(StringView path, mode_t, dev_t, Custody& base); ErrorOr> open_directory(StringView path, Custody& base); diff --git a/Kernel/Process.h b/Kernel/Process.h index fb460e08d1..3302f90a8f 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -353,6 +353,7 @@ public: ErrorOr sys$mkdir(Userspace pathname, size_t path_length, mode_t mode); ErrorOr sys$times(Userspace); ErrorOr sys$utime(Userspace pathname, size_t path_length, Userspace); + ErrorOr sys$utimensat(Userspace); ErrorOr sys$link(Userspace); ErrorOr sys$unlink(int dirfd, Userspace pathname, size_t path_length, int flags); ErrorOr sys$symlink(Userspace); diff --git a/Kernel/Syscalls/utimensat.cpp b/Kernel/Syscalls/utimensat.cpp new file mode 100644 index 0000000000..4a6ca189a3 --- /dev/null +++ b/Kernel/Syscalls/utimensat.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, Ariel Don + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Kernel { + +ErrorOr Process::sys$utimensat(Userspace user_params) +{ + VERIFY_PROCESS_BIG_LOCK_ACQUIRED(this); + TRY(require_promise(Pledge::fattr)); + + auto params = TRY(copy_typed_from_user(user_params)); + auto now = kgettimeofday().to_truncated_seconds(); + + timespec times[2]; + if (params.times) { + TRY(copy_from_user(times, params.times, sizeof(times))); + if (times[0].tv_nsec == UTIME_NOW) + times[0].tv_sec = now; + if (times[1].tv_nsec == UTIME_NOW) + times[1].tv_sec = now; + } else { + // According to POSIX, both access and modification times are set to + // the current time given a nullptr. + times[0].tv_sec = now; + times[0].tv_nsec = UTIME_NOW; + times[1].tv_sec = now; + times[1].tv_nsec = UTIME_NOW; + } + + int dirfd = params.dirfd; + auto path = TRY(get_syscall_path_argument(params.path)); + + RefPtr base; + if (dirfd == AT_FDCWD) { + base = current_directory(); + } else { + auto base_description = TRY(open_file_description(dirfd)); + if (!KLexicalPath::is_absolute(path->view()) && !base_description->is_directory()) + return ENOTDIR; + if (!base_description->custody()) + return EINVAL; + base = base_description->custody(); + } + + auto& atime = times[0]; + auto& mtime = times[1]; + int follow_symlink = params.flag & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW_NOERROR : 0; + TRY(VirtualFileSystem::the().utimensat(path->view(), *base, atime, mtime, follow_symlink)); + return 0; +} + +} diff --git a/Userland/Libraries/LibC/fcntl.cpp b/Userland/Libraries/LibC/fcntl.cpp index 9ce5039f18..4a421178d0 100644 --- a/Userland/Libraries/LibC/fcntl.cpp +++ b/Userland/Libraries/LibC/fcntl.cpp @@ -11,6 +11,7 @@ #include #include #include +#include extern "C" { @@ -102,4 +103,50 @@ int posix_fadvise(int fd, off_t offset, off_t len, int advice) (void)advice; return 0; } + +// https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimensat.html +int utimensat(int dirfd, char const* path, struct timespec const times[2], int flag) +{ + if (!path) { + errno = EFAULT; + return -1; + } + + size_t path_length = strlen(path); + if (path_length > INT32_MAX) { + errno = EINVAL; + return -1; + } + + // POSIX allows AT_SYMLINK_NOFOLLOW flag or no flags. + if (flag & ~AT_SYMLINK_NOFOLLOW) { + errno = EINVAL; + return -1; + } + + // Return early without error since both changes are to be omitted. + if (times && times[0].tv_nsec == UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT) + return 0; + + // According to POSIX, when times is a nullptr, it's equivalent to setting + // both last access time and last modification time to the current time. + // Setting the times argument to nullptr if it matches this case prevents + // the need to copy it in the kernel. + if (times && times[0].tv_nsec == UTIME_NOW && times[1].tv_nsec == UTIME_NOW) + times = nullptr; + + if (times) { + for (int i = 0; i < 2; ++i) { + if ((times[i].tv_nsec != UTIME_NOW && times[i].tv_nsec != UTIME_OMIT) + && (times[i].tv_nsec < 0 || times[i].tv_nsec >= 1'000'000'000L)) { + errno = EINVAL; + return -1; + } + } + } + + Syscall::SC_utimensat_params params { dirfd, { path, path_length }, times, flag }; + int rc = syscall(SC_utimensat, ¶ms); + __RETURN_WITH_ERRNO(rc, rc, -1); +} } diff --git a/Userland/Libraries/LibC/fcntl.h b/Userland/Libraries/LibC/fcntl.h index 8681676945..3704d6a7a7 100644 --- a/Userland/Libraries/LibC/fcntl.h +++ b/Userland/Libraries/LibC/fcntl.h @@ -8,6 +8,7 @@ #pragma once #include +#include __BEGIN_DECLS @@ -29,4 +30,6 @@ int inode_watcher_remove_watch(int fd, int wd); int posix_fadvise(int fd, off_t offset, off_t len, int advice); +int utimensat(int dirfd, char const* path, struct timespec const times[2], int flag); + __END_DECLS