Files
AnyPS5/core/libs/tests/GuestRecordLockOwners.cpp
Alexej Szpevák 9ece094515 test(libkernel): cover F_GETLK against another process's record locks
The fcntl tests only ever held locks in the guest's own process, where FreeBSD's lf_blocks never reports a conflict, so the host lock path that answers F_GETLK was not exercised. A forked process now holds a read lock, a write lock and a write lock to the end of the file through the host. F_GETLK with F_UNLCK reports only the write locks, F_RDLCK the same and F_WRLCK the read lock, each with the owner's pid and SEEK_SET; F_SETLK over the read lock fails with EAGAIN, and nothing is reported once the owner exits.
2026-10-17 12:13:54 +02:00

149 lines
5.0 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/File/include/FileFlags.hpp"
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <string>
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
extern "C" {
int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode);
int APS5_VABI sceKernelClose(int d);
int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...);
int* APS5_VABI __error_nid_postfix();
}
namespace {
void Require(bool value, int line) {
if (!value) {
std::fprintf(stderr, "record lock owner check failed at line %d, errno %d\n", line, *__error_nid_postfix());
std::exit(1);
}
}
#define Check(value) Require((value), __LINE__)
constexpr int GetLock = 11;
constexpr int SetLock = 12;
constexpr std::int16_t ReadLock = 1;
constexpr std::int16_t Unlock = 2;
constexpr std::int16_t WriteLock = 3;
constexpr std::int16_t FromStart = 0;
constexpr int GuestEagain = 35;
struct GuestLock {
std::int64_t start;
std::int64_t length;
std::int32_t pid;
std::int16_t type;
std::int16_t whence;
std::int32_t system;
};
static_assert(sizeof(GuestLock) == 32);
const char* const FileName = "owners.bin";
pid_t HoldLocks(int ready[2], int release[2]) {
const pid_t child = ::fork();
Check(child >= 0);
if (child != 0) {
::close(ready[1]);
::close(release[0]);
return child;
}
::close(ready[0]);
::close(release[1]);
const int fd = ::open(FileName, O_RDWR);
struct flock read{};
read.l_type = F_RDLCK;
read.l_whence = SEEK_SET;
read.l_start = 0;
read.l_len = 10;
struct flock write = read;
write.l_type = F_WRLCK;
write.l_start = 20;
write.l_len = 10;
struct flock tail = write;
tail.l_start = 50;
tail.l_len = 0;
const bool held = fd >= 0 && ::fcntl(fd, F_SETLK, &read) == 0 && ::fcntl(fd, F_SETLK, &write) == 0 && ::fcntl(fd, F_SETLK, &tail) == 0;
const char state = held ? 'y' : 'n';
if (::write(ready[1], &state, 1) != 1) ::_exit(2);
char done = 0;
if (::read(release[0], &done, 1) != 1) ::_exit(3);
::_exit(held ? 0 : 1);
}
GuestLock Query(int fd, GuestLock request) {
Check(fcntl_nid_postfix(fd, GetLock, &request) == 0);
return request;
}
bool Unchanged(const GuestLock& reply, const GuestLock& asked) {
return reply.type == Unlock && reply.start == asked.start && reply.length == asked.length && reply.pid == asked.pid &&
reply.whence == asked.whence && reply.system == asked.system;
}
bool Reports(const GuestLock& reply, std::int16_t type, std::int64_t start, std::int64_t length, pid_t owner) {
return reply.type == type && reply.start == start && reply.length == length && reply.pid == owner && reply.whence == FromStart;
}
void OtherOwner() {
int ready[2];
int release[2];
Check(::pipe(ready) == 0 && ::pipe(release) == 0);
const pid_t child = HoldLocks(ready, release);
char state = 0;
Check(::read(ready[0], &state, 1) == 1 && state == 'y');
const int fd = sceKernelOpen(FileName, SCE_KERNEL_O_RDWR, 0);
Check(fd >= 0);
const GuestLock readRange{0, 10, 7, Unlock, FromStart, 3};
Check(Unchanged(Query(fd, readRange), readRange));
Check(Reports(Query(fd, {0, 0, 0, Unlock, FromStart, 0}), WriteLock, 20, 10, child));
Check(Reports(Query(fd, {25, 1, 0, Unlock, FromStart, 0}), WriteLock, 20, 10, child));
Check(Reports(Query(fd, {60, 5, 0, Unlock, FromStart, 0}), WriteLock, 50, 0, child));
const GuestLock between{10, 10, 0, Unlock, FromStart, 0};
Check(Unchanged(Query(fd, between), between));
const GuestLock sharedRead{0, 15, 0, ReadLock, FromStart, 0};
Check(Unchanged(Query(fd, sharedRead), sharedRead));
Check(Reports(Query(fd, {0, 15, 0, WriteLock, FromStart, 0}), ReadLock, 0, 10, child));
Check(Reports(Query(fd, {22, 2, 0, ReadLock, FromStart, 0}), WriteLock, 20, 10, child));
GuestLock blocked{5, 1, 0, WriteLock, FromStart, 0};
*__error_nid_postfix() = 0;
Check(fcntl_nid_postfix(fd, SetLock, &blocked) == -1 && *__error_nid_postfix() == GuestEagain);
const char done = 'd';
Check(::write(release[1], &done, 1) == 1);
int status = 0;
Check(::waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0);
const GuestLock afterExit{0, 0, 0, Unlock, FromStart, 0};
Check(Unchanged(Query(fd, afterExit), afterExit));
Check(sceKernelClose(fd) == 0);
}
}
int main() {
const auto root = std::filesystem::temp_directory_path() /
("anyps5-record-lock-owners-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
std::filesystem::create_directories(root);
std::filesystem::current_path(root);
{ std::ofstream file(FileName, std::ios::binary); file << std::string(100, 'x'); }
OtherOwner();
std::error_code error;
std::filesystem::current_path(std::filesystem::temp_directory_path(), error);
std::filesystem::remove_all(root, error);
return 0;
}