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:
parent
3d1a7d90c7
commit
0b1941b313
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue