Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6a2d162
fix(libkernel): build the file, module and rtld calls on macOS hosts
krishalivaibhav Oct 23, 2026
886c65b
fix(libkernel): find a directory descriptor's path through F_GETPATH …
krishalivaibhav Oct 24, 2026
f7444ca
fix(libkernel): check module info eh_frame against the Mach-O section…
krishalivaibhav Oct 24, 2026
9721499
fix(libkernel): open long-path stat prefixes with O_SEARCH on macOS
iamthehimansh Oct 25, 2026
a159765
fix(libkernel): guard getdirentries guest outputs on macOS
iamthehimansh Oct 25, 2026
85a6a46
fix(libkernel): return null for an address outside every image on macOS
krishalivaibhav Oct 26, 2026
bfe18c9
fix(libkernel): report named shm_open and shm_unlink as not implement…
krishalivaibhav Oct 26, 2026
a326681
docs(libkernel): record the macOS module info fields left at 0
krishalivaibhav Oct 26, 2026
5a0b260
fix(libkernel): treat only unnamed FIFOs as pipes on macOS
krishalivaibhav Oct 26, 2026
070e1e6
fix(libkernel): check the descriptor before an empty vector read or w…
krishalivaibhav Oct 26, 2026
2ae8689
fix(libkernel): match macOS shared memory only in the temporary direc…
krishalivaibhav Oct 26, 2026
01f740f
docs(libkernel): record macOS getdirentries and module scope
krishalivaibhav Oct 26, 2026
be79850
fix(libkernel): unregister a closed module under its load bias on macOS
krishalivaibhav Oct 27, 2026
f4bcaa5
fix(libkernel): read archived directories through getdirentries on macOS
krishalivaibhav Oct 27, 2026
e6a53be
refactor(libkernel): resolve the macOS temporary directory once for s…
krishalivaibhav Oct 27, 2026
61bc44a
docs(libkernel): record the macOS default scope fallback
krishalivaibhav Oct 27, 2026
c2c8036
fix(libkernel): keep hidden modules out of main-program lookups on macOS
krishalivaibhav Oct 27, 2026
9b47e4c
feat(libkernel): report eh_frame_hdr for relinked images on macOS
krishalivaibhav Oct 27, 2026
63911cc
fix(libkernel): drop leading slashes after a long-path split on macOS
krishalivaibhav Oct 27, 2026
2f75dd5
docs(libkernel): update the macOS module scope, directory entry and m…
krishalivaibhav Oct 27, 2026
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
24 changes: 20 additions & 4 deletions core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,28 @@
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef __APPLE__
#include <sys/param.h>
#endif
#endif

namespace {

#ifndef _WIN32
std::filesystem::path HostDescriptorTarget(int fd, std::error_code& error) {
#ifdef __APPLE__
char buffer[MAXPATHLEN];
if (::fcntl(fd, F_GETPATH, buffer) != 0) {
error = std::error_code(errno, std::generic_category());
return {};
}
return std::filesystem::path(buffer);
#else
return std::filesystem::read_symlink(std::filesystem::path("/proc/self/fd") / std::to_string(fd), error);
#endif
}
#endif

constexpr std::uint8_t GuestUnknownType = 0;
constexpr std::uint8_t GuestDirectoryType = 4;
constexpr std::uint8_t GuestRegularType = 8;
Expand Down Expand Up @@ -301,8 +319,7 @@ std::optional<std::filesystem::path> DirectoryDescriptorPath(int fd) {
#else
if (fd < 0) return std::nullopt;
std::error_code error;
const auto link = std::filesystem::path("/proc/self/fd") / std::to_string(fd);
const auto target = std::filesystem::read_symlink(link, error);
const auto target = HostDescriptorTarget(fd, error);
if (error) return std::nullopt;
return target;
#endif
Expand All @@ -312,8 +329,7 @@ std::optional<std::filesystem::path> DirectoryDescriptorPath(int fd) {
#else
if (fd < 0) return std::nullopt;
std::error_code error;
const auto link = std::filesystem::path("/proc/self/fd") / std::to_string(fd);
const auto target = std::filesystem::read_symlink(link, error);
const auto target = HostDescriptorTarget(fd, error);
if (error) return std::nullopt;
if (!std::filesystem::is_directory(target, error)) return std::nullopt;
return target;
Expand Down
52 changes: 52 additions & 0 deletions core/libs/prx/libkernel/File/src/NativeStat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,49 @@ static int DoLstat(const std::filesystem::path& p, NativeStat* st) {
st->isSymbolicLink = true;
return 0;
}
#elif defined(__APPLE__)
#include <fcntl.h>
#include <limits.h>
#include <sys/stat.h>
#include <unistd.h>
using NativeStat = struct stat;
static int StatAt(const std::filesystem::path& p, NativeStat* st, int flags) {
std::string rest = p.native();
int directory = AT_FDCWD;
while (rest.size() >= PATH_MAX) {
const std::size_t split = rest.rfind('/', PATH_MAX - 1);
if (split == std::string::npos) {
if (directory != AT_FDCWD) ::close(directory);
errno = ENAMETOOLONG;
return -1;
}
const std::string prefix = split == 0 ? std::string("/") : rest.substr(0, split);
const int next = ::openat(directory, prefix.c_str(), O_SEARCH | O_CLOEXEC);
const int error = errno;
if (directory != AT_FDCWD) ::close(directory);
if (next < 0) {
errno = error;
return -1;
}
directory = next;
rest.erase(0, split + 1);
rest.erase(0, rest.find_first_not_of('/'));
}
const int result = ::fstatat(directory, rest.empty() ? "." : rest.c_str(), st, flags);
const int error = errno;
if (directory != AT_FDCWD) ::close(directory);
errno = error;
return result;
}
static int DoStat(const std::filesystem::path& p, NativeStat* st) {
return StatAt(p, st, 0);
}
static int DoFstat(int fd, NativeStat* st) {
return ::fstat(fd, st);
}
static int DoLstat(const std::filesystem::path& p, NativeStat* st) {
return StatAt(p, st, AT_SYMLINK_NOFOLLOW);
}
#else
#include <sys/stat.h>
using NativeStat = struct stat;
Expand Down Expand Up @@ -129,12 +172,21 @@ static void CopyNativeStat(const NativeStat& st, FileStat* sb) {
sb->st_rdev = static_cast<std::uint32_t>(st.st_rdev);
sb->st_blksize = static_cast<std::uint32_t>(st.st_blksize);
sb->st_blocks = static_cast<std::int64_t>(st.st_blocks);
#if defined(__APPLE__)
sb->st_atim.tv_sec = static_cast<std::int64_t>(st.st_atimespec.tv_sec);
sb->st_atim.tv_nsec = static_cast<std::int64_t>(st.st_atimespec.tv_nsec);
sb->st_mtim.tv_sec = static_cast<std::int64_t>(st.st_mtimespec.tv_sec);
sb->st_mtim.tv_nsec = static_cast<std::int64_t>(st.st_mtimespec.tv_nsec);
sb->st_ctim.tv_sec = static_cast<std::int64_t>(st.st_ctimespec.tv_sec);
sb->st_ctim.tv_nsec = static_cast<std::int64_t>(st.st_ctimespec.tv_nsec);
#else
sb->st_atim.tv_sec = static_cast<std::int64_t>(st.st_atim.tv_sec);
sb->st_atim.tv_nsec = static_cast<std::int64_t>(st.st_atim.tv_nsec);
sb->st_mtim.tv_sec = static_cast<std::int64_t>(st.st_mtim.tv_sec);
sb->st_mtim.tv_nsec = static_cast<std::int64_t>(st.st_mtim.tv_nsec);
sb->st_ctim.tv_sec = static_cast<std::int64_t>(st.st_ctim.tv_sec);
sb->st_ctim.tv_nsec = static_cast<std::int64_t>(st.st_ctim.tv_nsec);
#endif
#if defined(__APPLE__)
sb->st_birthtim.tv_sec = static_cast<std::int64_t>(st.st_birthtimespec.tv_sec);
sb->st_birthtim.tv_nsec = static_cast<std::int64_t>(st.st_birthtimespec.tv_nsec);
Expand Down
29 changes: 29 additions & 0 deletions core/libs/prx/libkernel/File/src/SharedMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handle
#include <sys/uio.h>
#include <unistd.h>
#include <cstdio>
#ifdef __APPLE__
#include <filesystem>
#include <sys/param.h>
#endif
#endif

namespace {
Expand Down Expand Up @@ -118,6 +122,20 @@ bool IsHostSharedMemoryDescriptor(int descriptor) {
return std::all_of(value.begin(), value.end(), [](wchar_t character) { return character >= L'0' && character <= L'9'; });
};
return decimal(suffix.substr(0, separator)) && decimal(suffix.substr(separator + 1));
#elif defined(__APPLE__)
struct stat status{};
if (::fstat(descriptor, &status) != 0 || !S_ISREG(status.st_mode) || status.st_nlink != 0) return false;
char objectPath[MAXPATHLEN];
if (::fcntl(descriptor, F_GETPATH, objectPath) != 0) return false;
const std::string_view path(objectPath);
const auto slash = path.rfind('/');
if (slash == std::string_view::npos || !path.substr(slash + 1).starts_with("anyps5-shm-")) return false;
static const std::string directory = [] {
std::error_code error;
auto canonical = std::filesystem::canonical(std::filesystem::temp_directory_path(error), error);
return error ? std::string{} : canonical.string();
}();
return !directory.empty() && std::string_view(objectPath, slash) == directory;
#else
(void)descriptor;
return false;
Expand Down Expand Up @@ -601,6 +619,9 @@ int OpenNamedFile(const char* path, int flags, int mode) {
int validationError = 0;
const auto hostName = NamedHostName(path, validationError);
if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid);
#ifdef __APPLE__
NotImplemented_nid_no_patch("shm_open");
#endif
HostShmLock namespaceLock;
if (namespaceLock.Error() != 0) {
errno = namespaceLock.Error();
Expand Down Expand Up @@ -656,6 +677,11 @@ int CreateAnonymousFile() {
}
errno = EEXIST;
return -1;
#elif defined(__APPLE__)
auto path = (std::filesystem::temp_directory_path() / "anyps5-shm-XXXXXX").string();
const int descriptor = mkostemp(path.data(), O_CLOEXEC);
if (descriptor >= 0) ::unlink(path.c_str());
return descriptor;
#else
const int descriptor = memfd_create("anyps5 shm_open", MFD_CLOEXEC);
return descriptor;
Expand Down Expand Up @@ -1019,6 +1045,9 @@ extern "C" int APS5_VABI shm_unlink_nid_postfix(const char* path) {
int validationError = 0;
const auto hostName = NamedHostName(path, validationError);
if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid);
#ifdef __APPLE__
NotImplemented_nid_no_patch("shm_unlink");
#endif
HostShmLock namespaceLock;
if (namespaceLock.Error() != 0) {
errno = namespaceLock.Error();
Expand Down
125 changes: 125 additions & 0 deletions core/libs/prx/libkernel/File/src/Stdio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,9 @@ static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nb
#include <dirent.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#ifdef __APPLE__
extern "C" ssize_t __getdirentries64(int fd, void* buffer, size_t bytes, off_t* base);
#endif
static int NativeRmdir(const std::filesystem::path& path) {
return ::rmdir(path.c_str());
}
Expand Down Expand Up @@ -594,26 +597,46 @@ static bool NativeIsPipe(int descriptor) {
}
#else
#include <sys/statvfs.h>
#ifdef __APPLE__
#include <sys/mount.h>
#else
#include <sys/vfs.h>
#endif
static bool NativeIsPipe(int descriptor) {
#ifdef __APPLE__
struct stat file{};
struct statfs kind{};
return ::fstat(descriptor, &file) == 0 && S_ISFIFO(file.st_mode) && ::fstatfs(descriptor, &kind) != 0;
#else
constexpr long PipeFilesystem = 0x50495045;
struct stat file{};
struct statfs kind{};
return ::fstat(descriptor, &file) == 0 && S_ISFIFO(file.st_mode) && ::fstatfs(descriptor, &kind) == 0
&& static_cast<long>(kind.f_type) == PipeFilesystem;
#endif
}

static int NativeFstatfs(int descriptor, KernelStatfs* fs) {
struct stat file{};
if (::fstat(descriptor, &file) != 0) return SceErrorFromErrno(errno) & 0xffff;
#ifdef __APPLE__
if (S_ISSOCK(file.st_mode)) return GUEST_EINVAL;
if (NativeIsPipe(descriptor)) return GUEST_EINVAL;
#else
if (S_ISSOCK(file.st_mode) && (::fcntl(descriptor, F_GETFL) & O_PATH) == 0) return GUEST_EINVAL;
if (NativeIsPipe(descriptor)) return GUEST_EINVAL;
#endif
if (S_ISCHR(file.st_mode)) return DeviceFilesystem(fs);
struct statvfs host{};
if (::fstatvfs(descriptor, &host) != 0) return SceErrorFromErrno(errno) & 0xffff;
if (host.f_flag & ST_RDONLY) fs->f_flags |= KERNEL_MNT_RDONLY;
if (host.f_flag & ST_NOSUID) fs->f_flags |= KERNEL_MNT_NOSUID;
#ifdef __APPLE__
struct statfs mount{};
if (::fstatfs(descriptor, &mount) == 0 && (mount.f_flags & MNT_NOEXEC)) fs->f_flags |= KERNEL_MNT_NOEXEC;
#else
if (host.f_flag & ST_NOEXEC) fs->f_flags |= KERNEL_MNT_NOEXEC;
#endif
fs->f_bsize = host.f_frsize != 0 ? host.f_frsize : host.f_bsize;
fs->f_iosize = host.f_bsize;
fs->f_blocks = host.f_blocks;
Expand Down Expand Up @@ -1196,6 +1219,8 @@ int APS5_VABI sceKernelFdatasync(int fd) {
File::RefuseArchivedFile(fd, "fdatasync");
#ifdef _WIN32
if (NativeSync(fd) != 0) return SceErrorFromErrno(errno);
#elif defined(__APPLE__)
if (::fsync(fd) != 0) return SceErrorFromErrno(errno);
#else
if (::fdatasync(fd) != 0) return SceErrorFromErrno(errno);
#endif
Expand Down Expand Up @@ -1249,6 +1274,85 @@ static bool InsideApp0(int fd) {
return inside;
}

#ifdef __APPLE__
static unsigned char GuestEntryType(int fd, const struct dirent& entry, bool followLinks) {
if (entry.d_type != DT_LNK && entry.d_type != DT_UNKNOWN) return entry.d_type;
if (entry.d_type == DT_LNK && !followLinks) return DT_LNK;
const int savedErrno = errno;
struct stat target{};
const bool found = ::fstatat(fd, entry.d_name, &target, followLinks ? 0 : AT_SYMLINK_NOFOLLOW) == 0;
errno = savedErrno;
if (!found) return entry.d_type;
if (S_ISDIR(target.st_mode)) return DT_DIR;
if (S_ISREG(target.st_mode)) return DT_REG;
if (S_ISLNK(target.st_mode)) return DT_LNK;
return entry.d_type;
}

int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* basep) {
if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL);
File::RefuseArchivedFile(fd, "getdirentries");
constexpr std::size_t GuestHeaderBytes = 8;
constexpr std::size_t GuestMaxName = 255;
if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT);
if (nbytes <= 0) return SceErrorFromErrno(GUEST_EINVAL);
const GuestArena::HostWrite destination(buf, static_cast<std::size_t>(nbytes));
const GuestArena::HostWrite baseDestination(basep, basep == nullptr ? 0 : sizeof(*basep));
if (!destination.Open() || !baseDestination.Open()) return SceErrorFromErrno(GUEST_EFAULT);
if (File::IsDirectoryDescriptor(fd)) return File::ReadDirectoryDescriptor(fd, buf, nbytes, basep);
struct stat status{};
if (::fstat(fd, &status) != 0) return SceErrorFromErrno(errno);
if (!S_ISDIR(status.st_mode)) return SceErrorFromErrno(GUEST_EINVAL);
const off_t base = ::lseek(fd, 0, SEEK_CUR);
if (base < 0) return SceErrorFromErrno(errno);
if (basep != nullptr) *basep = static_cast<int64_t>(base);
if (::lseek(fd, 0, SEEK_SET) < 0) return SceErrorFromErrno(errno);
std::vector<char> listing;
for (;;) {
constexpr std::size_t Chunk = 64 * 1024;
const auto start = listing.size();
listing.resize(start + Chunk);
off_t position = 0;
const auto read = ::__getdirentries64(fd, listing.data() + start, Chunk, &position);
if (read < 0) {
const int error = errno;
::lseek(fd, base, SEEK_SET);
return SceErrorFromErrno(error);
}
listing.resize(start + static_cast<std::size_t>(read));
if (read == 0) break;
}
const bool followLinks = InsideApp0(fd);
std::size_t written = 0;
off_t index = 0;
for (std::size_t offset = 0; offset < listing.size(); ++index) {
const auto* entry = reinterpret_cast<const struct dirent*>(listing.data() + offset);
if (entry->d_reclen == 0) break;
offset += entry->d_reclen;
if (index < base) continue;
const auto nameLength = std::strlen(entry->d_name);
const auto record = (GuestHeaderBytes + nameLength + 1 + 3) & ~std::size_t{3};
if (nameLength > GuestMaxName || written + record > static_cast<std::size_t>(nbytes)) {
if (::lseek(fd, index, SEEK_SET) < 0) return SceErrorFromErrno(errno);
if (written != 0) return static_cast<int>(written);
return nameLength > GuestMaxName ? static_cast<int>(0x80020000u | GUEST_ENAMETOOLONG) : SceErrorFromErrno(GUEST_EINVAL);
}
char* out = buf + written;
std::memset(out, 0, record);
const auto fileNumber = static_cast<std::uint32_t>(entry->d_ino);
const auto recordLength = static_cast<std::uint16_t>(record);
const auto nameBytes = static_cast<std::uint8_t>(nameLength);
std::memcpy(out, &fileNumber, sizeof(fileNumber));
std::memcpy(out + 4, &recordLength, sizeof(recordLength));
out[6] = static_cast<char>(GuestEntryType(fd, *entry, followLinks));
out[7] = static_cast<char>(nameBytes);
std::memcpy(out + GuestHeaderBytes, entry->d_name, nameLength);
written += record;
}
if (::lseek(fd, index, SEEK_SET) < 0) return SceErrorFromErrno(errno);
return static_cast<int>(written);
}
#else
static unsigned char GuestEntryType(int fd, const struct dirent64& entry, bool followLinks) {
if (entry.d_type != DT_LNK && entry.d_type != DT_UNKNOWN) return entry.d_type;
if (entry.d_type == DT_LNK && !followLinks) return DT_LNK;
Expand Down Expand Up @@ -1314,6 +1418,7 @@ int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* bas
if (::lseek(fd, resume, SEEK_SET) < 0) return SceErrorFromErrno(errno);
return static_cast<int>(written);
}
#endif

int APS5_VABI sceKernelGetdents(int fd, char* buf, int nbytes) {
return sceKernelGetdirentries(fd, buf, nbytes, nullptr);
Expand Down Expand Up @@ -1546,6 +1651,16 @@ int64_t APS5_VABI sceKernelPwrite(int d, const void* buf, size_t nbytes, int64_t
return result < 0 ? SceErrorFromErrno(errno) : result;
}

#ifdef __APPLE__
static std::int64_t EmptyIovecsResult(int d, bool write, bool positioned) {
const int flags = ::fcntl(d, F_GETFL);
if (flags < 0) return SceErrorFromErrno(errno);
if ((flags & O_ACCMODE) == (write ? O_RDONLY : O_WRONLY)) return SceErrorFromErrno(EBADF);
if (positioned && ::lseek(d, 0, SEEK_CUR) < 0) return SceErrorFromErrno(errno);
return 0;
}
#endif

int64_t APS5_VABI sceKernelReadv(int d, const KernelIovec* guestIov, int iovcnt) {
if (File::IsDirectoryDescriptor(d)) return SceErrorFromErrno(GUEST_EISDIR);
std::vector<KernelIovec> vectors;
Expand All @@ -1559,6 +1674,9 @@ int64_t APS5_VABI sceKernelReadv(int d, const KernelIovec* guestIov, int iovcnt)
}
if (File::IsRandomDevice(d)) return ReadRandomIovecs(d, iov, iovcnt);
if (const auto archived = File::ReadArchivedIovecs(d, iov, iovcnt, nullptr)) return *archived;
#ifdef __APPLE__
if (iovcnt == 0) return EmptyIovecsResult(d, false, false);
#endif
const auto result = static_cast<std::int64_t>(::readv(d, NativeIovecs(iov), iovcnt));
return result < 0 ? SceErrorFromErrno(errno) : result;
}
Expand All @@ -1576,6 +1694,9 @@ int64_t APS5_VABI sceKernelWritev(int d, const KernelIovec* guestIov, int iovcnt
PrepareWriteIovecs(iov, iovcnt);
const auto sharedResult = File::WritevSharedMemory_nid_no_patch(d, NativeIovecs(iov), iovcnt);
if (sharedResult != -2) return sharedResult < 0 ? SceErrorFromErrno(errno) : sharedResult;
#ifdef __APPLE__
if (iovcnt == 0) return EmptyIovecsResult(d, true, false);
#endif
const auto result = static_cast<std::int64_t>(::writev(d, NativeIovecs(iov), iovcnt));
return result < 0 ? SceErrorFromErrno(errno) : result;
}
Expand All @@ -1591,6 +1712,9 @@ int64_t APS5_VABI sceKernelPreadv(int d, const KernelIovec* guestIov, int iovcnt
if (!OpenIovecs(iov, iovcnt, destinations)) return SceErrorFromErrno(GUEST_EFAULT);
if (File::IsRandomDevice(d)) return ReadRandomIovecs(d, iov, iovcnt);
if (const auto archived = File::ReadArchivedIovecs(d, iov, iovcnt, &offset)) return *archived;
#ifdef __APPLE__
if (iovcnt == 0) return EmptyIovecsResult(d, false, true);
#endif
const auto result = static_cast<std::int64_t>(::preadv(d, NativeIovecs(iov), iovcnt, static_cast<off_t>(offset)));
return result < 0 ? SceErrorFromErrno(errno) : result;
}
Expand All @@ -1607,6 +1731,7 @@ int64_t APS5_VABI sceKernelPwritev(int d, const KernelIovec* guestIov, int iovcn
const auto sharedResult = File::PwritevSharedMemory_nid_no_patch(d, NativeIovecs(iov), iovcnt, offset);
if (sharedResult != -2) return sharedResult < 0 ? SceErrorFromErrno(errno) : sharedResult;
#ifdef __APPLE__
if (iovcnt == 0) return EmptyIovecsResult(d, true, true);
if (const auto appended = File::WritevAtEnd(d, NativeIovecs(iov), iovcnt); appended != -2) return appended < 0 ? SceErrorFromErrno(errno) : appended;
#endif
const auto result = static_cast<std::int64_t>(::pwritev(d, NativeIovecs(iov), iovcnt, static_cast<off_t>(offset)));
Expand Down
Loading
Loading