fix(libkernel): return errors from file operation failures

On Windows, sceKernelPread and sceKernelPwrite called the POSIX pread and
pwrite wrappers, which throw on every failure, so a bad descriptor, a
negative offset or a write to a read-only descriptor aborted the process
instead of returning an error as the Linux build does. ftruncate and
sceKernelFtruncate threw on any host failure on both platforms.

Give the Windows sceKernelPread and sceKernelPwrite the same checks and
SceErrorFromErrno returns as the Linux ones, and accept a zero-length
transfer with a null buffer. Report a positioned transfer on a handle
opened without the needed access as EBADF instead of EIO. Return EINVAL
from ftruncate for a negative length and for a descriptor opened without
write access, as FreeBSD does, and pass other host errors to the guest.

Fixes #2423
This commit is contained in:
Doston Abdullayev 2026-10-10 09:29:09 +05:00 committed by Dean Galvin
parent f6af6d11d3
commit 1fd723f21f
2 changed files with 46 additions and 7 deletions

View File

@ -138,7 +138,7 @@ std::int64_t NativePositioned_nid_no_patch(int descriptor, void* buf, std::size_
}
if (!ok) {
if (!write && error == ERROR_HANDLE_EOF) return 0;
errno = EIO;
errno = error == ERROR_ACCESS_DENIED ? EBADF : EIO;
return -1;
}
return done;
@ -313,23 +313,24 @@ int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int d, FileStat* sb) {
int APS5_VABI ftruncate_nid_postfix(int d, int64_t length) {
if (length < 0) {
APS5_INVALID_ARG_EX;
return PosixFailure(GUEST_EINVAL);
}
#ifdef _WIN32
int error = NativeFtruncate(d, length);
if (error != 0) {
throw std::runtime_error(std::string(__func__) + ": ftruncate failed, fd=" + std::to_string(d) + ", error=" + std::to_string(error));
if (error == EACCES) error = GUEST_EINVAL;
return PosixFailure(SceErrorFromErrno(error) & 0xffff);
}
#else
if (NativeFtruncate(d, length) != 0) {
throw std::runtime_error(std::string(__func__) + ": ftruncate failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno));
return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
}
#endif
return 0;
}
int APS5_VABI sceKernelFtruncate(int d, int64_t length) {
return ftruncate_nid_postfix(d, length);
return ftruncate_nid_postfix(d, length) == 0 ? 0 : SceErrorFromErrno(*__error_nid_postfix());
}
int64_t APS5_VABI lseek_nid_postfix(int d, int64_t offset, int whence) {
@ -577,11 +578,21 @@ static bool OpenIovecs(const KernelIovec* iov, int iovcnt, std::deque<GuestArena
#ifdef _WIN32
int64_t APS5_VABI sceKernelPread(int d, void* buf, size_t nbytes, int64_t offset) {
return pread_nid_postfix(d, buf, nbytes, offset);
if (buf == nullptr && nbytes != 0) return SceErrorFromErrno(GUEST_EFAULT);
if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL);
const GuestArena::HostWrite destination(buf, nbytes);
if (!destination.Open()) return SceErrorFromErrno(GUEST_EFAULT);
char empty = 0;
const auto result = NativePread(d, nbytes == 0 && buf == nullptr ? &empty : buf, nbytes, offset);
return result < 0 ? SceErrorFromErrno(errno) : result;
}
int64_t APS5_VABI sceKernelPwrite(int d, const void* buf, size_t nbytes, int64_t offset) {
return pwrite_nid_disambig1_nid_postfix(d, buf, nbytes, offset);
if (buf == nullptr && nbytes != 0) return SceErrorFromErrno(GUEST_EFAULT);
if (offset < 0) return SceErrorFromErrno(GUEST_EINVAL);
char empty = 0;
const auto result = NativePwrite(d, nbytes == 0 && buf == nullptr ? &empty : buf, nbytes, offset);
return result < 0 ? SceErrorFromErrno(errno) : result;
}
static std::int64_t TransferIovecs(int d, const KernelIovec* iov, int iovcnt, const std::int64_t* offset, bool write) {

View File

@ -36,6 +36,9 @@ std::int64_t APS5_VABI sceKernelReadv(int, const GuestIovec*, int);
std::int64_t APS5_VABI sceKernelWritev(int, const GuestIovec*, int);
std::int64_t APS5_VABI sceKernelPreadv(int, const GuestIovec*, int, std::int64_t);
std::int64_t APS5_VABI sceKernelPwritev(int, const GuestIovec*, int, std::int64_t);
std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t);
std::int64_t APS5_VABI sceKernelPwrite(int, const void*, std::size_t, std::int64_t);
int APS5_VABI sceKernelFtruncate(int, std::int64_t);
}
static void Check(bool value, int line) {
@ -120,11 +123,36 @@ int main() {
Require(sceKernelPwritev(file, writes, 2, -1) == ErrorEinval);
Require(Contents(path) == "01ABCDE7ABCDE");
char byte = 0;
Require(sceKernelPread(file, &byte, 1, 2) == 1 && byte == 'A');
Require(sceKernelPread(file, nullptr, 0, 0) == 0);
Require(sceKernelPwrite(file, nullptr, 0, 0) == 0);
Require(sceKernelPread(file, nullptr, 1, 0) == ErrorEfault);
Require(sceKernelPwrite(file, nullptr, 1, 0) == ErrorEfault);
Require(sceKernelPread(file, &byte, 1, -1) == ErrorEinval);
Require(sceKernelPwrite(file, &byte, 1, -1) == ErrorEinval);
Require(sceKernelFtruncate(file, -1) == ErrorEinval);
const int readOnly = sceKernelOpen(path.string().c_str(), SCE_KERNEL_O_RDONLY, 0);
Require(readOnly >= 0);
Require(sceKernelPwrite(readOnly, "X", 1, 0) == ErrorEbadf);
Require(sceKernelFtruncate(readOnly, 0) == ErrorEinval);
Require(sceKernelClose(readOnly) == 0);
const int writeOnly = sceKernelOpen(path.string().c_str(), SCE_KERNEL_O_WRONLY, 0);
Require(writeOnly >= 0);
Require(sceKernelPread(writeOnly, &byte, 1, 0) == ErrorEbadf);
Require(sceKernelClose(writeOnly) == 0);
Require(Contents(path) == "01ABCDE7ABCDE");
Require(sceKernelFtruncate(file, 5) == 0);
Require(Contents(path) == "01ABC");
Require(sceKernelClose(file) == 0);
Require(sceKernelReadv(file, reads, 2) == ErrorEbadf);
Require(sceKernelWritev(file, writes, 2) == ErrorEbadf);
Require(sceKernelPreadv(file, reads, 2, 0) == ErrorEbadf);
Require(sceKernelPwritev(file, writes, 2, 0) == ErrorEbadf);
Require(sceKernelPread(file, &byte, 1, 0) == ErrorEbadf);
Require(sceKernelPwrite(file, &byte, 1, 0) == ErrorEbadf);
Require(sceKernelFtruncate(file, 0) == ErrorEbadf);
int ends[2] = {};
Require(MakePipe(ends) == 0);