Merge pull request #4622 from jagrat04/feat/libkernel-lutimes
feat(libkernel): implement lutimes
This commit is contained in:
commit
fc1f063f51
|
|
@ -125,9 +125,9 @@ static int SetTimes(HANDLE handle, const KernelTimeval* times) {
|
|||
}
|
||||
return SetFileTime(handle, nullptr, &access, &modified) ? 0 : File::WindowsFileTime::Failure(GetLastError());
|
||||
}
|
||||
static int NativeUtimes(const std::filesystem::path& path, const KernelTimeval* times) {
|
||||
static int NativeUtimes(const std::filesystem::path& path, const KernelTimeval* times, bool followLink = true) {
|
||||
const auto handle = CreateFileW(path.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | (followLink ? 0 : FILE_FLAG_OPEN_REPARSE_POINT), nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE) return File::WindowsFileTime::Failure(GetLastError());
|
||||
const int result = SetTimes(handle, times);
|
||||
CloseHandle(handle);
|
||||
|
|
@ -256,11 +256,11 @@ static int NativeFtruncate(int descriptor, std::int64_t length) {
|
|||
static int NativeTruncate(const std::filesystem::path& path, std::int64_t length) {
|
||||
return ::truncate(path.c_str(), static_cast<off_t>(length)) == 0 ? 0 : errno;
|
||||
}
|
||||
static int NativeUtimes(const std::filesystem::path& path, const KernelTimeval* times) {
|
||||
if (times == nullptr) return ::utimes(path.c_str(), nullptr);
|
||||
static int NativeUtimes(const std::filesystem::path& path, const KernelTimeval* times, bool followLink = true) {
|
||||
if (times == nullptr) return followLink ? ::utimes(path.c_str(), nullptr) : ::lutimes(path.c_str(), nullptr);
|
||||
struct timeval values[2]{{static_cast<time_t>(times[0].tv_sec), static_cast<suseconds_t>(times[0].tv_usec)},
|
||||
{static_cast<time_t>(times[1].tv_sec), static_cast<suseconds_t>(times[1].tv_usec)}};
|
||||
return ::utimes(path.c_str(), values);
|
||||
return followLink ? ::utimes(path.c_str(), values) : ::lutimes(path.c_str(), values);
|
||||
}
|
||||
static int NativeFutimes(int descriptor, const KernelTimeval* times) {
|
||||
if (times == nullptr) return ::futimes(descriptor, nullptr);
|
||||
|
|
@ -1214,6 +1214,19 @@ int APS5_VABI utimes_nid_postfix(const char* path, const KernelTimeval* times) {
|
|||
return PosixResult(sceKernelUtimes_nid_postfix(path, times));
|
||||
}
|
||||
|
||||
int APS5_VABI lutimes_nid_postfix(const char* path, const KernelTimeval* times) {
|
||||
if (const int error = PathError(path)) return PosixFailure(error);
|
||||
if (times != nullptr) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (times[i].tv_usec < 0 || times[i].tv_usec >= 1000000) return PosixFailure(GUEST_EINVAL);
|
||||
}
|
||||
}
|
||||
std::filesystem::path native;
|
||||
if (const int error = ResolvePathError_nid_no_patch(path, native)) return PosixFailure(error);
|
||||
if (NativeUtimes(native, times, false) != 0) return PosixResult(SceErrorFromErrno(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelFutimes(int d, const KernelTimeval* times) {
|
||||
if (GuestSockets::IsOpen(d)) return SceErrorFromErrno(GUEST_EINVAL);
|
||||
if (times != nullptr) {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@
|
|||
|
||||
extern "C" {
|
||||
int APS5_VABI utimes_nid_postfix(const char*, const KernelTimeval*);
|
||||
int APS5_VABI lutimes_nid_postfix(const char*, const KernelTimeval*);
|
||||
int APS5_VABI futimes_nid_postfix(int, const KernelTimeval*);
|
||||
int APS5_VABI sceKernelUtimes_nid_postfix(const char*, const KernelTimeval*);
|
||||
int APS5_VABI stat_nid_postfix(const char*, FileStat*);
|
||||
int APS5_VABI lstat_nid_postfix(const char*, FileStat*);
|
||||
int APS5_VABI sceKernelFstat(int, FileStat*);
|
||||
std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*);
|
||||
int APS5_VABI open_nid_postfix(const char*, int, int);
|
||||
|
|
@ -72,6 +74,8 @@ int main() {
|
|||
CheckTimes(path, fd, second);
|
||||
Require(sceKernelUtimes_nid_postfix(path.c_str(), first) == 0, "sceKernelUtimes fractional timestamp");
|
||||
CheckTimes(path, fd, first);
|
||||
Require(lutimes_nid_postfix(path.c_str(), second) == 0, "lutimes fractional timestamp");
|
||||
CheckTimes(path, fd, second);
|
||||
|
||||
for (int index = 0; index < 2; ++index) {
|
||||
for (const auto micros : {-1LL, 1000000LL}) {
|
||||
|
|
@ -80,7 +84,8 @@ int main() {
|
|||
Require(utimes_nid_postfix(path.c_str(), invalid) == -1 && *__error_nid_postfix() == 22, "utimes rejects invalid microseconds");
|
||||
Require(futimes_nid_postfix(fd, invalid) == -1 && *__error_nid_postfix() == 22, "futimes rejects invalid microseconds");
|
||||
Require(sceKernelUtimes_nid_postfix(path.c_str(), invalid) == static_cast<int>(0x80020016u), "sceKernelUtimes rejects invalid microseconds");
|
||||
CheckTimes(path, fd, first);
|
||||
Require(lutimes_nid_postfix(path.c_str(), invalid) == -1 && *__error_nid_postfix() == 22, "lutimes rejects invalid microseconds");
|
||||
CheckTimes(path, fd, second);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,6 +142,35 @@ int main() {
|
|||
Require(close_nid_postfix(directoryFd) == 0, "close directory descriptor");
|
||||
|
||||
Require(utimes_nid_postfix((root / "missing").string().c_str(), first) == -1 && *__error_nid_postfix() == 2, "missing path reports ENOENT");
|
||||
Require(lutimes_nid_postfix(nullptr, first) == -1 && *__error_nid_postfix() == 14, "lutimes rejects null path");
|
||||
Require(lutimes_nid_postfix("", first) == -1 && *__error_nid_postfix() == 2, "lutimes rejects empty path");
|
||||
Require(lutimes_nid_postfix((root / "missing").string().c_str(), first) == -1 && *__error_nid_postfix() == 2, "lutimes missing path reports ENOENT");
|
||||
Require(utimes_nid_postfix(path.c_str(), second) == 0, "utimes target to second");
|
||||
std::error_code symlinkError;
|
||||
const auto linkPath = (root / "link.txt").string();
|
||||
std::filesystem::create_symlink(path, linkPath, symlinkError);
|
||||
if (!symlinkError) {
|
||||
Require(lutimes_nid_postfix(linkPath.c_str(), first) == 0, "lutimes modifies symlink");
|
||||
FileStat targetStatus{};
|
||||
Require(stat_nid_postfix(path.c_str(), &targetStatus) == 0 && Same(targetStatus.st_mtim, second[1]), "target file unchanged");
|
||||
#ifdef _WIN32
|
||||
const HANDLE handle = CreateFileW(std::filesystem::path(linkPath).c_str(), FILE_READ_ATTRIBUTES,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
Require(handle != INVALID_HANDLE_VALUE, "open symlink");
|
||||
FILETIME modified{};
|
||||
Require(GetFileTime(handle, nullptr, nullptr, &modified), "get symlink time");
|
||||
CloseHandle(handle);
|
||||
const auto ticks = [](FILETIME time) {
|
||||
return (static_cast<std::uint64_t>(time.dwHighDateTime) << 32) | time.dwLowDateTime;
|
||||
};
|
||||
Require(ticks(modified) == static_cast<std::uint64_t>(first[1].tv_sec + 11644473600LL) * 10000000 + first[1].tv_usec * 10,
|
||||
"symlink mtime updated");
|
||||
#else
|
||||
FileStat linkStatus{};
|
||||
Require(lstat_nid_postfix(linkPath.c_str(), &linkStatus) == 0 && Same(linkStatus.st_mtim, first[1]), "symlink mtime updated");
|
||||
#endif
|
||||
}
|
||||
int pipe[2];
|
||||
Require(pipe_nid_postfix(pipe) == 0, "create pipe");
|
||||
FileStat pipeStatus{};
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@
|
|||
- [EVFILT_VNODE, sceKernelAddFileEvent and sceKernelDeleteFileEvent](../../../core/libs/prx/libkernel/Equeue/src/FileEvent.cpp) (libkernel) watch the host file or directory behind the descriptor and turn host changes into FreeBSD 11 vnode hints, which [`filt_vfsvnode`](https://github.com/freebsd/freebsd-src/blob/34bd34750187bae02a0086402c78c031c59dd8b3/sys/kern/vfs_subr.c#L5032) adds whole when they share a bit with the requested `fflags`: a write that grows a file is `NOTE_WRITE | NOTE_EXTEND`, another write `NOTE_WRITE`, a shrinking truncate or an attribute change `NOTE_ATTRIB`, a higher link count `NOTE_LINK`, a lower one or a pending delete `NOTE_DELETE`, a new path `NOTE_RENAME`; in a directory, an entry created, removed or renamed is `NOTE_WRITE`, with `NOTE_LINK` for a subdirectory, and on Linux an entry moved from or to another directory adds `NOTE_EXTEND`; inotify queues the two halves of a rename one after the other, so a watcher woken by the first can read it alone, and a half read without the other is classified only after a `getdents64` on the watched directory, which takes the directory lock that `rename` holds until both halves are queued ([`do_renameat2`](https://github.com/torvalds/linux/blob/v6.8/fs/namei.c#L4975) around `vfs_rename` and its `fsnotify_move`, [`iterate_dir`](https://github.com/torvalds/linux/blob/v6.8/fs/readdir.c#L103)), an ordering taken from the kernel source, not from inotify(7), which promises none; a directory registered with `NOTE_EXTEND` on Windows is accepted, as `vfs_kqfilter` does not check it, but never receives it, because the directory handle cannot tell a move between directories from a removal plus an addition. FreeBSD's hints depend on the filesystem and the console's filesystems are not known: a growing `ftruncate` reports `NOTE_WRITE | NOTE_EXTEND` where `vop_setattr_post` reports `NOTE_ATTRIB`, and on Windows `utimes` on a file reports `NOTE_WRITE`. Windows uses `FindFirstChangeNotification` on the parent directory (and on a watched directory itself) and compares `GetFileInformationByHandleEx` snapshots at least every 50 ms, because a write through an open handle is reported only when the handle is flushed or closed, so file hints can arrive 50 ms late; Linux uses inotify on the file and is not run. `NOTE_REVOKE`, `NOTE_OPEN`, `NOTE_CLOSE`, `NOTE_CLOSE_WRITE`, `NOTE_READ`, unknown bits and descriptors other than files, directories, pipes and sockets throw. `sceKernelAddFileEvent(eq, fd, watch, udata)` and `sceKernelDeleteFileEvent(eq, fd)` take the [OpenOrbis](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/0a1aaf9d/include/orbis/libkernel.h#L35) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0/src/SharpProspero/Interop/Kernel/KernelEqueue.cs#L164) signatures; `watch` is taken as the `fflags` and the event is added with `EV_CLEAR`, neither confirmed on a title
|
||||
- [sem_timedwait](../../../core/libs/prx/libkernel/Pthread/Posix/Sem.cpp) (libkernel) consumes an available count before checking the timeout and waits indefinitely for a null timeout, following FreeBSD 11's `sem_timedwait`. Console behavior for null and invalid timeouts when a count is available is unverified.
|
||||
- [tcsendbreak, tcdrain, tcflush, tcflow, tcgetpgrp, tcsetpgrp](../../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel) follow FreeBSD 11.4's [`termios.c`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/termios.c): `tcflush` answers `EINVAL` for a queue other than `TCIFLUSH`, `TCOFLUSH` or `TCIOFLUSH` and `tcflow` for an action outside `TCOOFF`..`TCION` before the descriptor is looked at, and every other call fails as its terminal ioctl does on a descriptor that is not a terminal, with the errors `tcgetattr` gives, so `tcsendbreak` never reaches its 0.4 s pause. FreeBSD's `pipe_ioctl` answers `TIOCGPGRP` and `TIOCSPGRP` on a pipe or FIFO with the descriptor's SIGIO owner, which is not modelled, so `tcgetpgrp` and `tcsetpgrp` throw on a host pipe or FIFO above descriptor 2; descriptors 0 to 2 are taken as non-terminals that are not pipes, whatever the host attaches to them, so a pipe the guest creates on a freed descriptor 0 to 2 gets `ENOTTY` there. The PS5 `libkernel` is assumed to use this FreeBSD code, and `tcdrain` is not a cancellation point here
|
||||
- [Windows file timestamps](../../../core/libs/prx/libkernel/File/include/WindowsFileTime.hpp) use the host filesystem's timestamp range and resolution. `utimes` and `futimes` reject timestamps outside nonnegative signed 64-bit Windows file-time ticks with `EINVAL`, including exactly 1601-01-01 00:00:00 UTC, whose zero value means "leave unchanged" to `SetFileTime`. Guest directory descriptors still resolve through their recorded path. Filesystems with coarser timestamp resolution cannot retain every guest microsecond
|
||||
- [Windows file timestamps](../../../core/libs/prx/libkernel/File/include/WindowsFileTime.hpp) use the host filesystem's timestamp range and resolution. `utimes`, `futimes`, and `lutimes` reject timestamps outside nonnegative signed 64-bit Windows file-time ticks with `EINVAL`, including exactly 1601-01-01 00:00:00 UTC, whose zero value means "leave unchanged" to `SetFileTime`. Guest directory descriptors still resolve through their recorded path. Filesystems with coarser timestamp resolution cannot retain every guest microsecond
|
||||
- [Windows file identity](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) - `stat` and `fstat` report the volume serial number as `st_dev` and the 64-bit file index folded into the guest's 32-bit `st_ino` (never 0), so two different files can in principle share an `st_ino`; the fold is not taken from a reference. `st_nlink` is clamped to 65535, the width of the guest field. The call keeps the `_wstat64` values when `GetFileInformationByHandle` fails
|
||||
- [setitimer](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) follows FreeBSD's `sys_setitimer` and `kern_setitimer`: `EINVAL` for timers other than `ITIMER_REAL`, `ITIMER_VIRTUAL` and `ITIMER_PROF`, and for a value, or the interval of a non-zero value, with a negative field, `tv_usec` of 1000000 or more, or more than `INT32_MAX / 2` seconds; a null value reads the timer as `getitimer` does, with `EFAULT` when the output is null too. `ITIMER_REAL` shares the timer thread of `alarm`, which clears its interval: a zero value disarms it, the previous value is returned, and each expiry re-arms it by `it_interval`. An expiry delivers nothing: with `SIG_IGN` it is dropped, with the default disposition the process exits with status 142, and with a handler, or with `SIGALRM` blocked, it throws, since guest `SIGALRM` handlers are never run and signals are never left pending. The value is not raised to `tick / 16` as `itimerfix` does, and an interval below 1 ms throws, since the console's `hz`, and so that rounding, is unknown (1 ms covers any `hz` of 63 or more). `ITIMER_VIRTUAL` and `ITIMER_PROF` throw when armed, as process CPU time is not accounted, and read and disarm as zero. PPSA99420 imports it and fails to load without it
|
||||
- On Linux, [direct memory](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) lives in one sparse memfd at file offset = physical address, so the [GPU imports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) can share one udmabuf buffer object per 64 MiB of it. Released direct memory is not punched out of the file, because a live import of its chunk pins the pages and the GPU would keep reading the old ones: its pages stay committed (the process keeps the high-water mark of the direct memory it used) and are zeroed when the range is allocated again.
|
||||
|
|
|
|||
Loading…
Reference in New Issue