diff --git a/core/libs/prx/libkernel/File/src/NativeStat.cpp b/core/libs/prx/libkernel/File/src/NativeStat.cpp index a6fb3304a..a08c971fe 100644 --- a/core/libs/prx/libkernel/File/src/NativeStat.cpp +++ b/core/libs/prx/libkernel/File/src/NativeStat.cpp @@ -2,6 +2,7 @@ #include "prx/libkernel/File/include/DirectoryDescriptor.hpp" #include "prx/libc/include/General.hpp" +#include #include #include @@ -12,28 +13,36 @@ #include 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(::_get_osfhandle(fd)), st); + return ReadHandleInfo(reinterpret_cast(::_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::min(st.identity.nNumberOfLinks, 0xffff)); + } #else sb->st_dev = static_cast(st.st_dev); sb->st_ino = static_cast(st.st_ino); diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index 8def58d3f..bd0f598b7 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -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);