fix(libkernel): report a stable file identity from stat and fstat on Windows

st_ino was always 0 and st_dev differed between stat (drive number) and
fstat (0), so the usual a.st_dev == b.st_dev && a.st_ino == b.st_ino
identity check gave wrong answers. Read the volume serial, the file index
and the link count from the file handle, which works the same for path and
descriptor lookups.

Fixes #2296
This commit is contained in:
Hussein Kadhim 2026-10-10 05:06:05 +03:00 committed by Adrià Franch
parent 3d1a7d90c7
commit 0b1941b313
2 changed files with 39 additions and 3 deletions

View File

@ -2,6 +2,7 @@
#include "prx/libkernel/File/include/DirectoryDescriptor.hpp"
#include "prx/libc/include/General.hpp"
#include <algorithm>
#include <stdexcept>
#include <string>
@ -12,28 +13,36 @@
#include <sys/types.h>
struct NativeStat : __stat64 {
FILE_BASIC_INFO times{};
BY_HANDLE_FILE_INFORMATION identity{};
bool hasTimes = false;
bool hasIdentity = false;
};
static int ReadTimes(HANDLE handle, NativeStat* st) {
static int ReadHandleInfo(HANDLE handle, NativeStat* st) {
if (GetFileType(handle) != FILE_TYPE_DISK) return 0;
if (!GetFileInformationByHandleEx(handle, FileBasicInfo, &st->times, sizeof(st->times)))
return File::WindowsFileTime::Failure(GetLastError());
st->hasTimes = true;
if (!GetFileInformationByHandle(handle, &st->identity)) return File::WindowsFileTime::Failure(GetLastError());
st->hasIdentity = true;
return 0;
}
static std::uint32_t FoldFileIndex(const BY_HANDLE_FILE_INFORMATION& identity) {
const std::uint32_t folded = identity.nFileIndexLow ^ (identity.nFileIndexHigh * 0x9E3779B1u);
return folded == 0 ? 1 : folded;
}
static int DoStat(const std::filesystem::path& p, NativeStat* st) {
if (_wstat64(p.c_str(), st) != 0) return -1;
const auto handle = CreateFileW(p.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (handle == INVALID_HANDLE_VALUE) return File::WindowsFileTime::Failure(GetLastError());
const int result = ReadTimes(handle, st);
const int result = ReadHandleInfo(handle, st);
CloseHandle(handle);
return result;
}
static int DoFstat(int fd, NativeStat* st) {
if (const auto directory = File::DirectoryDescriptorPath(fd)) return DoStat(*directory, st);
if (_fstat64(fd, st) != 0) return -1;
return ReadTimes(reinterpret_cast<HANDLE>(::_get_osfhandle(fd)), st);
return ReadHandleInfo(reinterpret_cast<HANDLE>(::_get_osfhandle(fd)), st);
}
static int DoLstat(const std::filesystem::path& p, NativeStat* st) {
std::error_code error;
@ -81,6 +90,11 @@ static void CopyNativeStat(const NativeStat& st, FileStat* sb) {
sb->st_ctim = File::WindowsFileTime::Decode(st.times.ChangeTime.QuadPart);
sb->st_birthtim = File::WindowsFileTime::Decode(st.times.CreationTime.QuadPart);
}
if (st.hasIdentity) {
sb->st_dev = st.identity.dwVolumeSerialNumber;
sb->st_ino = FoldFileIndex(st.identity);
sb->st_nlink = static_cast<std::uint16_t>(std::min<DWORD>(st.identity.nNumberOfLinks, 0xffff));
}
#else
sb->st_dev = static_cast<std::uint32_t>(st.st_dev);
sb->st_ino = static_cast<std::uint32_t>(st.st_ino);

View File

@ -37,6 +37,7 @@ int APS5_VABI mkdir_nid_postfix(const char*, unsigned short);
int APS5_VABI sceKernelOpen(const char*, int, unsigned short);
int APS5_VABI sceKernelClose(int);
int APS5_VABI sceKernelStat(const char*, FileStat*);
int APS5_VABI sceKernelFstat(int, FileStat*);
int APS5_VABI sceKernelUnlink(const char*);
int APS5_VABI sceKernelRmdir(const char*);
int* APS5_VABI __error_nid_postfix();
@ -211,6 +212,27 @@ int main() {
Require(lstat_nid_postfix((root / "present.txt" / "child").string().c_str(), &linkStatus) == -1 && *__error_nid_postfix() == 20);
Require(unlink_nid_postfix(link.string().c_str()) == 0 && unlink_nid_postfix(dangling.string().c_str()) == 0);
#endif
FileStat identity{};
FileStat byDescriptor{};
const int identityDescriptor = open_nid_postfix(presentName.c_str(), 0, 0);
Require(identityDescriptor >= 0 && stat_nid_postfix(presentName.c_str(), &identity) == 0);
Require(sceKernelFstat(identityDescriptor, &byDescriptor) == 0 && close_nid_postfix(identityDescriptor) == 0);
Require(identity.st_ino != 0 && byDescriptor.st_dev == identity.st_dev && byDescriptor.st_ino == identity.st_ino);
const auto sibling = root / "sibling.txt";
{ std::ofstream stream(sibling); stream << "other"; }
FileStat siblingStatus{};
Require(stat_nid_postfix(sibling.string().c_str(), &siblingStatus) == 0);
Require(siblingStatus.st_dev == identity.st_dev && siblingStatus.st_ino != identity.st_ino);
Require(unlink_nid_postfix(sibling.string().c_str()) == 0);
const auto hardLink = root / "hardlink.txt";
std::error_code linkError;
std::filesystem::create_hard_link(present, hardLink, linkError);
if (!linkError) {
FileStat linked{};
Require(stat_nid_postfix(hardLink.string().c_str(), &linked) == 0);
Require(linked.st_dev == identity.st_dev && linked.st_ino == identity.st_ino && linked.st_nlink == 2);
Require(unlink_nid_postfix(hardLink.string().c_str()) == 0);
}
const int opened = open_nid_postfix(presentName.c_str(), 0, 0);
Require(opened >= 0 && close_nid_postfix(opened) == 0);
const int reopened = _open_nid_postfix(presentName.c_str(), 0);