Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/libs/prx/libkernel/File/include/DescriptorControl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ bool IoctlRequestMalformed(std::uint64_t request);
int Ioctl(int fd, std::uint64_t request, void* argument);
int DuplicateFrom(int fd, int start);
void RememberDescriptor(int fd, int openFlags);
void RememberPipe(int fd);
void ShareDescriptor(int from, int to);
void ForgetDescriptor(int fd);
bool Appends(int fd);
Expand Down
19 changes: 16 additions & 3 deletions core/libs/prx/libkernel/File/src/DescriptorControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ enum class Kind { File, Directory, Pipe, Socket, Device, RandomDevice, ArchivedF
struct Entry {
bool closeOnExec = false;
std::shared_ptr<int> status;
bool pipe = false;
};

std::mutex g_mutex;
Expand Down Expand Up @@ -362,6 +363,12 @@ int CurrentStatus(int fd) {
return *Materialize(fd).status;
}

bool CreatedByPipe(int fd) {
std::lock_guard lock(g_mutex);
const auto found = g_entries.find(fd);
return found != g_entries.end() && found->second.pipe;
}

void SetCloseOnExec(int fd, bool value) {
std::lock_guard lock(g_mutex);
Materialize(fd).closeOnExec = value;
Expand Down Expand Up @@ -482,7 +489,7 @@ int Fcntl(int fd, int command, std::intptr_t argument) {
return 0;
case GetStatusFlags: {
if (!Classify(fd, "fcntl")) return Fail(GuestEbadf);
const int access = AccessMode(fd, "fcntl");
const int access = CreatedByPipe(fd) ? ReadWrite : AccessMode(fd, "fcntl");
return access | CurrentStatus(fd);
}
case SetStatusFlags: {
Expand Down Expand Up @@ -561,11 +568,17 @@ void RememberDescriptor(int fd, int openFlags) {
#endif
}

void RememberPipe(int fd) {
RememberDescriptor(fd, 0);
std::lock_guard lock(g_mutex);
g_entries[fd].pipe = true;
}

void ShareDescriptor(int from, int to) {
File::ShareArchivedFile(from, to);
std::lock_guard lock(g_mutex);
auto status = Materialize(from).status;
g_entries[to] = Entry{false, std::move(status)};
const auto& source = Materialize(from);
g_entries[to] = Entry{false, source.status, source.pipe};
#ifdef _WIN32
File::SharePositionedTransfer(from, to);
GuestFileLockShare_nid_no_patch(from, to);
Expand Down
4 changes: 2 additions & 2 deletions core/libs/prx/libkernel/File/src/Stdio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -595,8 +595,8 @@ extern "C" int APS5_VABI pipe_nid_postfix(int* descriptors) {
const int result = ::pipe(native);
#endif
if (result != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
File::RememberDescriptor(native[0], 0);
File::RememberDescriptor(native[1], 0);
File::RememberPipe(native[0]);
File::RememberPipe(native[1]);
std::memcpy(descriptors, native, sizeof(native));
return 0;
}
Expand Down
7 changes: 4 additions & 3 deletions core/libs/tests/GuestDup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ static void DuplicatesShareStatusFlags() {
constexpr int SetDescriptorFlags = 2;
constexpr int GetFlags = 3;
constexpr int SetFlags = 4;
constexpr int ReadWrite = 2;
constexpr int NonBlocking = 4;
constexpr int GuestEagain = 35;
int pipe[2];
Expand All @@ -201,14 +202,14 @@ static void DuplicatesShareStatusFlags() {
Require(duplicate >= 0);
Require(fcntl_nid_postfix(duplicate, GetDescriptorFlags) == 0);
Require(fcntl_nid_postfix(duplicate, SetFlags, NonBlocking) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == NonBlocking);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadWrite | NonBlocking));
Require(dup2_nid_postfix(pipe[0], other[0]) == other[0]);
Require(fcntl_nid_postfix(other[0], GetDescriptorFlags) == 0);
Require(fcntl_nid_postfix(other[0], GetFlags) == NonBlocking);
Require(fcntl_nid_postfix(other[0], GetFlags) == (ReadWrite | NonBlocking));
char buffer[4] = {};
RequireFailure(static_cast<int>(read_nid_postfix(other[0], buffer, sizeof(buffer))), GuestEagain);
Require(fcntl_nid_postfix(pipe[0], SetFlags, 0) == 0);
Require(fcntl_nid_postfix(duplicate, GetFlags) == 0);
Require(fcntl_nid_postfix(duplicate, GetFlags) == ReadWrite);
Require(fcntl_nid_postfix(pipe[0], GetDescriptorFlags) == 1);
for (const int descriptor : {pipe[0], pipe[1], other[0], other[1], duplicate}) Require(close_nid_postfix(descriptor) == 0);
}
Expand Down
12 changes: 7 additions & 5 deletions core/libs/tests/GuestKernelFcntl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,10 @@ static void RecordLocksBelongToTheProcess() {
static void PipeFlagsRoundTrip() {
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == ReadOnly);
Require(fcntl_nid_postfix(pipe[1], GetFlags) == WriteOnly);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == ReadWrite);
Require(fcntl_nid_postfix(pipe[1], GetFlags) == ReadWrite);
const int duplicate = fcntl_nid_postfix(pipe[1], DuplicateCommand, 20);
Require(duplicate >= 20 && fcntl_nid_postfix(duplicate, GetFlags) == ReadWrite && close_nid_postfix(duplicate) == 0);
Require(fcntl_nid_postfix(pipe[1], SetDescriptorFlags, 1) == 0);
Require(fcntl_nid_postfix(pipe[1], GetDescriptorFlags) == 1);
Require(fcntl_nid_postfix(pipe[0], GetDescriptorFlags) == 0);
Expand All @@ -436,7 +438,7 @@ static void PipeFlagsRoundTrip() {
Require(fcntl_nid_postfix(pipe[1], GetDescriptorFlags) == 0);

Require(fcntl_nid_postfix(pipe[0], SetFlags, fcntl_nid_postfix(pipe[0], GetFlags) | NonBlocking) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadOnly | NonBlocking));
Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadWrite | NonBlocking));
char buffer[8] = {};
RequireFailure(static_cast<int>(read_nid_postfix(pipe[0], buffer, sizeof(buffer))), GuestEagain);
Require(write_nid_postfix(pipe[1], "abc", 3) == 3);
Expand All @@ -448,10 +450,10 @@ static void PipeFlagsRoundTrip() {

int off = 0;
Require(ioctl_nid_postfix(pipe[0], NonblockingRequest, &off) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == ReadOnly);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == ReadWrite);
int on = 1;
Require(ioctl_nid_postfix(pipe[0], NonblockingRequest, &on) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadOnly | NonBlocking));
Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadWrite | NonBlocking));

Require(fcntl_nid_postfix(pipe[1], SetFlags, NonBlocking) == 0);
char block[1024] = {};
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/debt/libkernel.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@
- [kill, killpg](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) follow FreeBSD's `kern_kill` and `killpg1` for the only guest process: `EINVAL` for signals outside 0 to 128 and for negative `killpg` process groups, signal 0 only checks that the target exists, and its own pid, 0 and minus its own pid (the guest is taken to lead its own process group) signal it through `raise`, so they share `raise`'s limits above. Any other pid, and -1, which skips the caller, report `ESRCH`, so `EPERM` is never reported
- [sceKernelFcntl](../../../core/libs/prx/libkernel/File/src/Open.cpp) (libkernel) runs the POSIX `fcntl` below and returns its guest errno as `SCE_KERNEL_ERROR_*`, the form the other `sceKernel` file exports use. The return values and error codes are unverified on a console
- [Creating through a trailing slash](../../../core/libs/prx/libkernel/File/src/Open.cpp) (libkernel) - `open` and `sceKernelOpen` with `O_CREAT` on a missing name followed by a slash (`new/`) fail with `EINVAL` on Windows and with the host's `EISDIR` on Linux, where FreeBSD 11.4's `lookup()` (sys/kern/vfs_lookup.c) fails with `ENOENT` because only a directory may be created through a trailing slash
- [fcntl](../../../core/libs/prx/libkernel/File/src/DescriptorControl.cpp) (libkernel) follows FreeBSD 11.4's [`kern_fcntl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L479-L799) and [`kern_dup`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L812) on file, directory, pipe and character device descriptors; a socket keeps the libSceNet path (`F_DUPFD`, `F_DUP2FD`, `F_DUPFD_CLOEXEC`, `F_DUP2FD_CLOEXEC`, `F_GETFL`, `F_SETFL` with `O_NONBLOCK`, `F_GETFD` and `F_SETFD`, `EINVAL` for every other command, see the next entry). On Linux and macOS a host socket that is not a guest socket, such as a standard descriptor a service manager connected to its journal, takes the flag handling of a pipe end and `FIONREAD` asks the host for its queued bytes (for a datagram socket Linux counts only the next datagram, where FreeBSD's [`soo_ioctl`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/sys_socket.c#L202-L205) counts every buffered byte); its record locks and read-ahead hints fail with `EBADF`, as FreeBSD fails them on a descriptor without a vnode, and `O_ASYNC` on it throws. The close-on-exec flag and the status flags live in a guest table, the status flags shared by `dup`, `dup2` and `F_DUPFD` as in an open file description; a descriptor the table has not seen, such as 0 to 2, starts from the host's flags, since the console's are not known. `F_GETFL` takes the access mode from the host (the granted access of the handle on Windows), so pipe ends report `O_RDONLY` and `O_WRONLY` where FreeBSD's bidirectional pipes report `O_RDWR`. `F_DUPFD` and `F_DUPFD_CLOEXEC` return the lowest free host descriptor at or above the start, so the bound is the host table (`EMFILE` on Windows, `EINVAL` past `RLIMIT_NOFILE` on Linux), not the console's unknown `RLIMIT_NOFILE` and `kern.maxfilesperproc`. `O_NONBLOCK` switches a host pipe (`PIPE_NOWAIT` on Windows, where an empty read and a full write then report `EAGAIN`) and is only recorded on files, directories and the random device, as their reads never block; on a Windows console or other character device it throws. Changing `O_APPEND` on a Windows file, changing `O_FSYNC` on a file, `O_ASYNC` on a pipe or character device (`SIGIO` is not delivered), `F_GETOWN`, `F_SETOWN` and `F_SETLK_REMOTE` throw; `O_DIRECT`, `F_RDAHEAD` and `F_READAHEAD` are recorded as the hints they are. Record locks (`F_GETLK`, `F_SETLK`, `F_SETLKW` and their forms without `l_sysid`) use the host's POSIX locks on Linux and macOS. `F_GETLK` with `F_UNLCK` asks the host with `F_RDLCK`: as in [`lf_blocks`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L866) only another owner's overlapping write lock conflicts with either request. On Windows they follow [`kern_fcntl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L607-L752) and [`lf_advlockasync`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L433-L487) for the one guest process, which owns every lock: `EOVERFLOW` for an `SEEK_CUR` start past `OFF_MAX`, then `EBADF` for `F_RDLCK` without read access or `F_WRLCK` without write access and `EINVAL` for another type, then `EINVAL` and `EOVERFLOW` for the range; as [`lf_blocks`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L866) never lets locks of one owner conflict, whichever descriptor or thread asks, `F_SETLK` and `F_SETLKW` then succeed without waiting and `F_GETLK` reports `F_UNLCK`, so no lock list is kept and merging, splitting and the release on close have nothing to change. Nothing is held on the host, since Windows byte-range locks are mandatory and would fail the guest's own reads and writes through other descriptors, so other host processes neither see these locks nor are seen; a lock from the end of a directory throws. The return values and error codes are unverified on a console
- [fcntl](../../../core/libs/prx/libkernel/File/src/DescriptorControl.cpp) (libkernel) follows FreeBSD 11.4's [`kern_fcntl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L479-L799) and [`kern_dup`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L812) on file, directory, pipe and character device descriptors; a socket keeps the libSceNet path (`F_DUPFD`, `F_DUP2FD`, `F_DUPFD_CLOEXEC`, `F_DUP2FD_CLOEXEC`, `F_GETFL`, `F_SETFL` with `O_NONBLOCK`, `F_GETFD` and `F_SETFD`, `EINVAL` for every other command, see the next entry). On Linux and macOS a host socket that is not a guest socket, such as a standard descriptor a service manager connected to its journal, takes the flag handling of a pipe end and `FIONREAD` asks the host for its queued bytes (for a datagram socket Linux counts only the next datagram, where FreeBSD's [`soo_ioctl`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/sys_socket.c#L202-L205) counts every buffered byte); its record locks and read-ahead hints fail with `EBADF`, as FreeBSD fails them on a descriptor without a vnode, and `O_ASYNC` on it throws. The close-on-exec flag and the status flags live in a guest table, the status flags shared by `dup`, `dup2` and `F_DUPFD` as in an open file description; a descriptor the table has not seen, such as 0 to 2, starts from the host's flags, since the console's are not known. `F_DUPFD` and `F_DUPFD_CLOEXEC` return the lowest free host descriptor at or above the start, so the bound is the host table (`EMFILE` on Windows, `EINVAL` past `RLIMIT_NOFILE` on Linux), not the console's unknown `RLIMIT_NOFILE` and `kern.maxfilesperproc`. `O_NONBLOCK` switches a host pipe (`PIPE_NOWAIT` on Windows, where an empty read and a full write then report `EAGAIN`) and is only recorded on files, directories and the random device, as their reads never block; on a Windows console or other character device it throws. Changing `O_APPEND` on a Windows file, changing `O_FSYNC` on a file, `O_ASYNC` on a pipe or character device (`SIGIO` is not delivered), `F_GETOWN`, `F_SETOWN` and `F_SETLK_REMOTE` throw; `O_DIRECT`, `F_RDAHEAD` and `F_READAHEAD` are recorded as the hints they are. Record locks (`F_GETLK`, `F_SETLK`, `F_SETLKW` and their forms without `l_sysid`) use the host's POSIX locks on Linux and macOS. `F_GETLK` with `F_UNLCK` asks the host with `F_RDLCK`: as in [`lf_blocks`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L866) only another owner's overlapping write lock conflicts with either request. On Windows they follow [`kern_fcntl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L607-L752) and [`lf_advlockasync`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L433-L487) for the one guest process, which owns every lock: `EOVERFLOW` for an `SEEK_CUR` start past `OFF_MAX`, then `EBADF` for `F_RDLCK` without read access or `F_WRLCK` without write access and `EINVAL` for another type, then `EINVAL` and `EOVERFLOW` for the range; as [`lf_blocks`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_lockf.c#L866) never lets locks of one owner conflict, whichever descriptor or thread asks, `F_SETLK` and `F_SETLKW` then succeed without waiting and `F_GETLK` reports `F_UNLCK`, so no lock list is kept and merging, splitting and the release on close have nothing to change. Nothing is held on the host, since Windows byte-range locks are mandatory and would fail the guest's own reads and writes through other descriptors, so other host processes neither see these locks nor are seen; a lock from the end of a directory throws. The return values and error codes are unverified on a console

Check warning on line 231 in docs/dev/debt/libkernel.md

View workflow job for this annotation

GitHub Actions / conventions

debt-length

Technical debt entry over 1000 characters: state the gap first (what differs from the console, what is unverified, what throws) and keep only the behaviour it needs: 3951 characters
- [fcntl on a guest socket](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) answers `F_DUPFD`, `F_DUP2FD`, `F_DUPFD_CLOEXEC`, `F_DUP2FD_CLOEXEC`, `F_GETFD`, `F_SETFD`, `F_GETFL` and `F_SETFL` and fails every other command with `EINVAL`; FreeBSD 11.4's `kern_fcntl` also handles `F_GETOWN` and `F_SETOWN` on a socket. `FD_CLOEXEC` is only recorded (there is no `exec`): `dup`, `dup2`, `F_DUPFD`, `F_DUP2FD` onto another descriptor and an accepted socket start without it, `F_DUP2FD` onto the same descriptor keeps it, and `SOCK_CLOEXEC`, `F_DUPFD_CLOEXEC`, `F_DUP2FD_CLOEXEC` and `F_SETFD` set it. None of this was measured on a console.
- [ioctl, _ioctl](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) handle only `FIONBIO` and `FIONREAD` on a socket, and any other request there fails with `ENOTTY`. On the other descriptors they follow FreeBSD's [`kern_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_generic.c#L758), [`vn_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_vnops.c#L1488) and [`pipe_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_pipe.c#L1346): `FIONBIO` sets `O_NONBLOCK` as `fcntl` does, `FIOCLEX` and `FIONCLEX` set and clear the close-on-exec flag `F_GETFD` reports, and `FIONREAD` reports the size past the offset of a file, the queued bytes of a pipe's read end, 0 on its write end and `ENOTTY` on the random device, while it throws on a directory or another character device. Every termios request (group `t`, `TIOCGWINSZ` included) fails with `ENOTTY`, since those descriptors are taken as not a terminal, as in `tcgetattr`, and any other well-formed request throws. An input request with length 0 is not rejected as malformed: [`sys_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_generic.c#L703-L718) rejects it only in a kernel built without `COMPAT_FREEBSD4`, `COMPAT_FREEBSD5` and `COMPAT_43`, and which of these the PS5 kernel has is unknown. Stremio Plus (PPSA74126) imports `_ioctl` for its statically linked `tc*` functions, which send `TIOCGETA`, `TIOCSETA`, `TIOCSPGRP` and the other termios requests
- [ioctl, _ioctl](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) do not pass a `sceNetSocket` socket to libSceNet as `fcntl` does. On Linux its number duplicates the host socket, so `FIONBIO` switches the host `O_NONBLOCK` but not the library's `SO_NBIO` state; on Windows it is a `NUL` placeholder, so `FIONREAD` and enabling `FIONBIO` throw as on a character device. `sceNetIoctl` handles both requests on such a socket.
Expand Down
Loading