Files
FEX-Emu--FEX/Source/Tests/LinuxSyscalls/FileManagement.cpp
T
Ryan Houdek 35cf7703b1 Thunks: Support direct thunk config in configuration files
Previously in order to enable thunks, we needed an independent
description file of which thunks to be enabled. This is nice for quickly
testing out new games by setting `FEX_THUNKCONFIG` environment variable.

For users that just want to enable thunks this is an unwieldy
indirection that doesn't make much sense at a glance.

Previously this meant you needed two files as an example:
```
  ryanh@ubuntu-linux-20-04-desktop:~/.fex-emu$ cat thunks.json
  {
    "ThunksDB": {
      "GL": 1,
      "Vulkan": 1
    }
  }
  ryanh@ubuntu-linux-20-04-desktop:~/.fex-emu$ cat AppConfig/EnderLiliesSteam-Linux-Shipping.json
  {
    "Config": {
      "ThunkConfig":"~\/.fex-emu\/thunks.json"
    }
  }
```

Instead of this unwieldy redirection just support `ThunksDB` json
directly in the AppConfig.

```
  ryanh@ubuntu-linux-20-04-desktop:~/.fex-emu$ cat AppConfig/EnderLiliesSteam-Linux-Shipping.json
  {
    "Config": {
      <...>
    },
    "ThunksDB": {
      "GL": 1,
      "Vulkan": 1
    }
  }
```

As can be seen this makes this significantly easier for new users
getting in to thunks. Depending on which path to enable thunks the user
is more comfortable with, they can still enable them using the
`ThunkConfig` option or embedding directly in the application
configuration.

Additionally this removes the older non-ThunksDB path to loading thunks.
All users of it have moved on to using ThunksDB.
2022-08-20 10:02:13 -07:00

737 lines
22 KiB
C++

/*
$info$
tags: LinuxSyscalls|common
desc: Rootfs overlay logic
$end_info$
*/
#include "Common/FDUtils.h"
#include "FEXCore/Config/Config.h"
#include "Tests/LinuxSyscalls/FileManagement.h"
#include "Tests/LinuxSyscalls/EmulatedFiles/EmulatedFiles.h"
#include "Tests/LinuxSyscalls/Syscalls.h"
#include "Tests/LinuxSyscalls/x64/Syscalls.h"
#include <FEXCore/Common/Paths.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXHeaderUtils/ScopedSignalMask.h>
#include <FEXHeaderUtils/Syscalls.h>
#include <algorithm>
#include <errno.h>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <fstream>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <syscall.h>
#include <system_error>
#include <unistd.h>
#include <utility>
#include <vector>
#include <tiny-json.h>
namespace JSON {
struct JsonAllocator {
jsonPool_t PoolObject;
std::unique_ptr<std::list<json_t>> json_objects;
};
static_assert(offsetof(JsonAllocator, PoolObject) == 0, "This needs to be at offset zero");
json_t* PoolInit(jsonPool_t* Pool) {
JsonAllocator* alloc = reinterpret_cast<JsonAllocator*>(Pool);
alloc->json_objects = std::make_unique<std::list<json_t>>();
return &*alloc->json_objects->emplace(alloc->json_objects->end());
}
json_t* PoolAlloc(jsonPool_t* Pool) {
JsonAllocator* alloc = reinterpret_cast<JsonAllocator*>(Pool);
return &*alloc->json_objects->emplace(alloc->json_objects->end());
}
}
namespace FEXCore::Context {
struct Context;
}
namespace FEX::HLE {
struct open_how;
static bool LoadFile(std::vector<char> &Data, const std::string &Filename) {
std::fstream File(Filename, std::ios::in);
if (!File.is_open()) {
return false;
}
if (!File.seekg(0, std::fstream::end)) {
LogMan::Msg::DFmt("Couldn't load configuration file: Seek end");
return false;
}
auto FileSize = File.tellg();
if (File.fail()) {
LogMan::Msg::DFmt("Couldn't load configuration file: tellg");
return false;
}
if (!File.seekg(0, std::fstream::beg)) {
LogMan::Msg::DFmt("Couldn't load configuration file: Seek beginning");
return false;
}
if (FileSize <= 0) {
LogMan::Msg::DFmt("FileSize less than or equal to zero specified");
return false;
}
Data.resize(FileSize);
if (!File.read(Data.data(), FileSize)) {
// Probably means permissions aren't set. Just early exit
return false;
}
return true;
}
void FileManager::LoadThunkDatabase(bool Global) {
auto ThunkDBPath = FEXCore::Config::GetConfigDirectory(Global) + "ThunksDB.json";
std::vector<char> FileData;
if (LoadFile(FileData, ThunkDBPath)) {
FileData.push_back(0);
JSON::JsonAllocator Pool {
.PoolObject = {
.init = JSON::PoolInit,
.alloc = JSON::PoolAlloc,
},
};
json_t const *json = json_createWithPool(&FileData.at(0), &Pool.PoolObject);
json_t const* DB = json_getProperty( json, "DB" );
if ( !DB || JSON_OBJ != json_getType( DB ) ) {
return;
}
std::string_view HomeDirectory = FEXCore::Paths::GetHomeDirectory();
for( json_t const* Library = json_getChild( DB ); Library != nullptr; Library = json_getSibling( Library )) {
// Get the user defined name for the library
const char* LibraryName = json_getName(Library);
auto DBObject = ThunkDB.insert_or_assign(LibraryName, ThunkDBObject{}).first;
// Walk the libraries items to get the data
for (json_t const* LibraryItem = json_getChild(Library); LibraryItem != nullptr; LibraryItem = json_getSibling(LibraryItem)) {
const char* ItemName = json_getName(LibraryItem);
if (strcmp(ItemName, "Library") == 0) {
// "Library": "libGL-guest.so"
DBObject->second.LibraryName = json_getValue(LibraryItem);
}
else if (strcmp(ItemName, "Depends") == 0) {
jsonType_t PropertyType = json_getType(LibraryItem);
if (PropertyType == JSON_TEXT) {
DBObject->second.Depends.insert(json_getValue(LibraryItem));
}
else if (PropertyType == JSON_ARRAY) {
for (json_t const* Depend = json_getChild(LibraryItem); Depend != nullptr; Depend = json_getSibling(Depend)) {
DBObject->second.Depends.insert(json_getValue(Depend));
}
}
}
else if (strcmp(ItemName, "Overlay") == 0) {
auto AddWithReplacement = [DBObject, HomeDirectory](json_t const* Value) {
constexpr static std::array<std::string_view, 4> LibPrefixes = {
"/usr/lib",
"/usr/local/lib",
"/lib",
"/usr/lib/pressure-vessel/overrides/lib",
};
auto FindAndReplacePrefixes = [DBObject](std::string_view String, std::string_view Prefix, auto NewPrefixes) -> bool {
auto it = String.find(Prefix);
if (it != String.npos) {
size_t SizeOfOldPrefix = Prefix.size();
for (auto& prefix : NewPrefixes) {
std::string Replacement {String};
Replacement.replace(it, SizeOfOldPrefix, prefix);
DBObject->second.Overlays.emplace_back(std::move(Replacement));
}
return true;
}
else {
return false;
}
};
auto FindAndReplaceSingleNewPrefix = [DBObject](std::string_view String, std::string_view Prefix, auto NewPrefix) -> bool {
auto it = String.find(Prefix);
if (it != String.npos) {
size_t SizeOfOldPrefix = Prefix.size();
std::string Replacement {String};
Replacement.replace(it, SizeOfOldPrefix, NewPrefix);
DBObject->second.Overlays.emplace_back(std::move(Replacement));
return true;
}
else {
return false;
}
};
std::string NonModifiedLibraryItem {static_cast<const char*>(json_getValue(Value))};
// Prefixes are mutually exclusive currently.
// Walk through each individual library item and attempt to change prefixes before inserting this in to our overlay system.
// Attempt to replace @PREFIX_LIB@ first
bool Inserted = FindAndReplacePrefixes(NonModifiedLibraryItem, "@PREFIX_LIB@", LibPrefixes);
// Attempt to replace @HOME@ second
if (!Inserted) {
Inserted = FindAndReplaceSingleNewPrefix(NonModifiedLibraryItem, "@HOME@", HomeDirectory);
}
// Failing to replace prefixes, insert the item unmodifed
if (!Inserted) {
DBObject->second.Overlays.emplace_back(NonModifiedLibraryItem);
}
};
jsonType_t PropertyType = json_getType(LibraryItem);
if (PropertyType == JSON_TEXT) {
AddWithReplacement(LibraryItem);
}
else if (PropertyType == JSON_ARRAY) {
for (json_t const* Overlay = json_getChild(LibraryItem); Overlay != nullptr; Overlay = json_getSibling(Overlay)) {
AddWithReplacement(Overlay);
}
}
}
}
}
}
}
FileManager::FileManager(FEXCore::Context::Context *ctx)
: EmuFD {ctx} {
bool LoadedThunkDatabase{};
auto ThunkConfigFile = ThunkConfig();
auto ThunkGuestPath = std::filesystem::path(ThunkGuestLibs());
auto LoadThunksDB = [this, ThunkGuestPath](bool *LoadedThunkDatabase, json_t const* ThunksDB) {
// If a thunks DB property exists then we pull in data from the thunks database
// Load the initial thunks database
if (LoadedThunkDatabase) {
LoadThunkDatabase(true);
LoadThunkDatabase(false);
*LoadedThunkDatabase = true;
}
// Now load this property
for (json_t const* Item = json_getChild(ThunksDB); Item != nullptr; Item = json_getSibling(Item)) {
const char *LibraryName = json_getName(Item);
int64_t LibraryEnabled = json_getInteger(Item);
if (LibraryEnabled != 0) {
// If the library is enabled then find it in the DB
// Enable the overlay and all the dependencies in one go
auto DBObject = ThunkDB.find(LibraryName);
if (DBObject != ThunkDB.end() &&
DBObject->second.Enabled == false) {
auto ThunkPath = ThunkGuestPath / DBObject->second.LibraryName;
if (std::filesystem::exists(ThunkPath)) {
for (auto Overlay : DBObject->second.Overlays) {
// Direct full path in guest RootFS to our overlay file
ThunkOverlays.emplace(Overlay, ThunkPath);
}
}
DBObject->second.Enabled = true;
// Now walk the dependencies and set them up as well
// Make sure to enable each one as we go to remove circular dependencies
std::function<void(std::unordered_set<std::string> &Depends)> InsertDependencies
= [this, &ThunkGuestPath, &InsertDependencies](std::unordered_set<std::string> &Depends) -> void {
for (auto &Depend : Depends) {
auto DBDepend = ThunkDB.find(Depend);
if (DBDepend != ThunkDB.end() &&
DBDepend->second.Enabled == false) {
auto ThunkPath = ThunkGuestPath / DBDepend->second.LibraryName;
if (std::filesystem::exists(ThunkPath)) {
for (auto Overlay : DBDepend->second.Overlays) {
// Direct full path in guest RootFS to our overlay file
ThunkOverlays.emplace(Overlay, ThunkPath);
}
}
// Enabled, now walk this dependencies
DBDepend->second.Enabled = true;
InsertDependencies(DBDepend->second.Depends);
}
}
};
InsertDependencies(DBObject->second.Depends);
}
}
}
};
// We try to load ThunksDB from {FEX global config, FEX user config, AppConfig Global, AppConfig Local, Defined ThunksConfig option}
// This doesn't support the classic thunks interface.
std::vector<std::string> ConfigPaths {
FEXCore::Config::GetConfigFileLocation(true),
FEXCore::Config::GetConfigFileLocation(false),
FEXCore::Config::GetApplicationConfig(AppConfigName(), true),
FEXCore::Config::GetApplicationConfig(AppConfigName(), false),
ThunkConfigFile,
};
for (const auto &Path : ConfigPaths) {
std::vector<char> FileData;
if (LoadFile(FileData, Path)) {
JSON::JsonAllocator Pool {
.PoolObject = {
.init = JSON::PoolInit,
.alloc = JSON::PoolAlloc,
},
};
json_t const *json = json_createWithPool(&FileData.at(0), &Pool.PoolObject);
json_t const* ThunksDB = json_getProperty( json, "ThunksDB" );
if (ThunksDB) {
LoadThunksDB(&LoadedThunkDatabase, ThunksDB);
}
}
}
// Now clear the thunk database since we're loaded
ThunkDB.clear();
if (false) {
// Useful for debugging
if (ThunkOverlays.size()) {
LogMan::Msg::IFmt("Thunk Overlays:");
for (const auto& [Overlay, ThunkPath] : ThunkOverlays) {
LogMan::Msg::IFmt("\t{} -> {}", Overlay, ThunkPath);
}
}
}
UpdatePID(::getpid());
}
FileManager::~FileManager() {
}
std::string FileManager::GetEmulatedPath(const char *pathname, bool FollowSymlink) {
auto RootFSPath = LDPath();
if (!pathname || // If no pathname
pathname[0] != '/' || // If relative
strcmp(pathname, "/") == 0) { // If we are getting root
return {};
}
auto thunkOverlay = ThunkOverlays.find(pathname);
if (thunkOverlay != ThunkOverlays.end()) {
return thunkOverlay->second;
}
if (RootFSPath.empty()) { // If RootFS doesn't exist
return {};
}
std::string Path = RootFSPath + pathname;
if (FollowSymlink) {
std::error_code ec;
while(std::filesystem::is_symlink(Path, ec)) {
auto SymlinkTarget = std::filesystem::read_symlink(Path);
if (SymlinkTarget.is_absolute()) {
Path = RootFSPath + SymlinkTarget.string();
}
else {
break;
}
}
}
return Path;
}
std::optional<std::string> FileManager::GetSelf(const char *Pathname) {
if (!Pathname) {
return std::nullopt;
}
char PidSelfPath[50];
snprintf(PidSelfPath, 50, "/proc/%i/exe", CurrentPID);
if (strcmp(Pathname, "/proc/self/exe") == 0 ||
strcmp(Pathname, "/proc/thread-self/exe") == 0 ||
strcmp(Pathname, PidSelfPath) == 0) {
return Filename();
}
return Pathname;
}
uint64_t FileManager::Open(const char *pathname, [[maybe_unused]] int flags, [[maybe_unused]] uint32_t mode) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
int fd = -1;
fd = EmuFD.OpenAt(AT_FDCWD, SelfPath, flags, mode);
if (fd == -1) {
auto Path = GetEmulatedPath(SelfPath, true);
if (!Path.empty()) {
fd = ::open(Path.c_str(), flags, mode);
}
if (fd == -1) {
fd = ::open(SelfPath, flags, mode);
}
}
if (fd != -1) {
FHU::ScopedSignalMaskWithMutex lk(FDLock);
FDToNameMap.insert_or_assign(fd, SelfPath);
}
return fd;
}
uint64_t FileManager::Close(int fd) {
{
FHU::ScopedSignalMaskWithMutex lk(FDLock);
FDToNameMap.erase(fd);
}
return ::close(fd);
}
uint64_t FileManager::CloseRange(unsigned int first, unsigned int last, unsigned int flags) {
#ifndef CLOSE_RANGE_CLOEXEC
#define CLOSE_RANGE_CLOEXEC (1U << 2)
#endif
if (!(flags & CLOSE_RANGE_CLOEXEC)) {
// If the flag was set then it doesn't actually close the FDs
// Just sets the flag on a range
FHU::ScopedSignalMaskWithMutex lk(FDLock);
auto Lower = FDToNameMap.lower_bound(first);
auto Upper = FDToNameMap.upper_bound(last);
// We remove from first to last inclusive
FDToNameMap.erase(Lower, Upper);
}
return ::syscall(SYSCALL_DEF(close_range), first, last, flags);
}
uint64_t FileManager::Stat(const char *pathname, void *buf) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
// Stat follows symlinks
auto Path = GetEmulatedPath(SelfPath, true);
if (!Path.empty()) {
uint64_t Result = ::stat(Path.c_str(), reinterpret_cast<struct stat*>(buf));
if (Result != -1)
return Result;
}
return ::stat(SelfPath, reinterpret_cast<struct stat*>(buf));
}
uint64_t FileManager::Lstat(const char *pathname, void *buf) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
// lstat does not follow symlinks
auto Path = GetEmulatedPath(SelfPath, false);
if (!Path.empty()) {
uint64_t Result = ::lstat(Path.c_str(), reinterpret_cast<struct stat*>(buf));
if (Result != -1)
return Result;
}
return ::lstat(pathname, reinterpret_cast<struct stat*>(buf));
}
uint64_t FileManager::Access(const char *pathname, [[maybe_unused]] int mode) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
// Access follows symlinks
auto Path = GetEmulatedPath(SelfPath, true);
if (!Path.empty()) {
uint64_t Result = ::access(Path.c_str(), mode);
if (Result != -1)
return Result;
}
return ::access(SelfPath, mode);
}
uint64_t FileManager::FAccessat(int dirfd, const char *pathname, int mode) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
auto Path = GetEmulatedPath(SelfPath);
if (!Path.empty()) {
uint64_t Result = ::syscall(SYS_faccessat, dirfd, Path.c_str(), mode);
if (Result != -1)
return Result;
}
return ::syscall(SYS_faccessat, dirfd, SelfPath, mode);
}
uint64_t FileManager::FAccessat2(int dirfd, const char *pathname, int mode, int flags) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
auto Path = GetEmulatedPath(SelfPath, (flags & AT_SYMLINK_NOFOLLOW) == 0);
if (!Path.empty()) {
uint64_t Result = ::syscall(SYSCALL_DEF(faccessat2), dirfd, Path.c_str(), mode, flags);
if (Result != -1)
return Result;
}
return ::syscall(SYSCALL_DEF(faccessat2), dirfd, SelfPath, mode, flags);
}
uint64_t FileManager::Readlink(const char *pathname, char *buf, size_t bufsiz) {
// calculate the non-self link to exe
// Some executables do getpid, stat("/proc/$pid/exe")
char PidSelfPath[50];
snprintf(PidSelfPath, 50, "/proc/%i/exe", CurrentPID);
if (strcmp(pathname, "/proc/self/exe") == 0 ||
strcmp(pathname, "/proc/thread-self/exe") == 0 ||
strcmp(pathname, PidSelfPath) == 0) {
auto App = Filename();
strncpy(buf, App.c_str(), bufsiz);
return std::min(bufsiz, App.size());
}
auto Path = GetEmulatedPath(pathname);
if (!Path.empty()) {
uint64_t Result = ::readlink(Path.c_str(), buf, bufsiz);
if (Result != -1)
return Result;
if (Result == -1 &&
errno == EINVAL) {
// This means that the file wasn't a symlink
// This is expected behaviour
return -errno;
}
}
return ::readlink(pathname, buf, bufsiz);
}
uint64_t FileManager::Chmod(const char *pathname, mode_t mode) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
auto Path = GetEmulatedPath(SelfPath);
if (!Path.empty()) {
uint64_t Result = ::chmod(Path.c_str(), mode);
if (Result != -1)
return Result;
}
return ::chmod(SelfPath, mode);
}
uint64_t FileManager::Readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz) {
// calculate the non-self link to exe
// Some executables do getpid, stat("/proc/$pid/exe")
// Can't use `GetSelf` directly here since readlink{at,} returns EINVAL if it isn't a symlink
// Self is always a symlink and isn't expected to fail
std::string Path{};
if (((pathname && pathname[0] != '/') || // If pathname exists then it must not be absolute
!pathname) &&
dirfd != AT_FDCWD) {
// Passed in a dirfd that isn't magic FDCWD
// We need to get the path from the fd now
Path = FEX::get_fdpath(dirfd).value_or("");
if (pathname) {
if (!Path.empty()) {
// If the path returned empty then we don't need a separator
Path += "/";
}
Path += pathname;
}
}
else {
if (!pathname || strlen(pathname) == 0) {
return -1;
}
else if (pathname) {
Path = pathname;
}
}
char PidSelfPath[50];
snprintf(PidSelfPath, 50, "/proc/%i/exe", CurrentPID);
if (Path == "/proc/self/exe" ||
Path == "/proc/thread-self/exe" ||
Path == PidSelfPath) {
auto App = Filename();
strncpy(buf, App.c_str(), bufsiz);
return std::min(bufsiz, App.size());
}
Path = GetEmulatedPath(pathname);
if (!Path.empty()) {
uint64_t Result = ::readlinkat(dirfd, Path.c_str(), buf, bufsiz);
if (Result != -1)
return Result;
if (Result == -1 &&
errno == EINVAL) {
// This means that the file wasn't a symlink
// This is expected behaviour
return -errno;
}
}
return ::readlinkat(dirfd, pathname, buf, bufsiz);
}
uint64_t FileManager::Openat([[maybe_unused]] int dirfs, const char *pathname, int flags, uint32_t mode) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
int32_t fd = -1;
fd = EmuFD.OpenAt(dirfs, SelfPath, flags, mode);
if (fd == -1) {
auto Path = GetEmulatedPath(SelfPath, true);
if (!Path.empty()) {
fd = ::openat(dirfs, Path.c_str(), flags, mode);
}
if (fd == -1)
fd = ::openat(dirfs, SelfPath, flags, mode);
}
if (fd != -1) {
FHU::ScopedSignalMaskWithMutex lk(FDLock);
FDToNameMap.insert_or_assign(fd, SelfPath);
}
return fd;
}
uint64_t FileManager::Openat2(int dirfs, const char *pathname, FEX::HLE::open_how *how, size_t usize) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
int32_t fd = -1;
fd = EmuFD.OpenAt(dirfs, SelfPath, how->flags, how->mode);
if (fd == -1) {
auto Path = GetEmulatedPath(SelfPath, true);
if (!Path.empty()) {
fd = ::syscall(SYSCALL_DEF(openat2), dirfs, Path.c_str(), how, usize);
}
if (fd == -1)
fd = ::syscall(SYSCALL_DEF(openat2), dirfs, SelfPath, how, usize);
}
if (fd != -1) {
FHU::ScopedSignalMaskWithMutex lk(FDLock);
FDToNameMap.insert_or_assign(fd, SelfPath);
}
return fd;
}
uint64_t FileManager::Statx(int dirfd, const char *pathname, int flags, uint32_t mask, struct statx *statxbuf) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
auto Path = GetEmulatedPath(SelfPath, (flags & AT_SYMLINK_NOFOLLOW) == 0);
if (!Path.empty()) {
uint64_t Result = FHU::Syscalls::statx(dirfd, Path.c_str(), flags, mask, statxbuf);
if (Result != -1)
return Result;
}
return FHU::Syscalls::statx(dirfd, SelfPath, flags, mask, statxbuf);
}
uint64_t FileManager::Mknod(const char *pathname, mode_t mode, dev_t dev) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
auto Path = GetEmulatedPath(SelfPath);
if (!Path.empty()) {
uint64_t Result = ::mknod(Path.c_str(), mode, dev);
if (Result != -1)
return Result;
}
return ::mknod(SelfPath, mode, dev);
}
uint64_t FileManager::Statfs(const char *path, void *buf) {
auto Path = GetEmulatedPath(path);
if (!Path.empty()) {
uint64_t Result = ::statfs(Path.c_str(), reinterpret_cast<struct statfs*>(buf));
if (Result != -1)
return Result;
}
return ::statfs(path, reinterpret_cast<struct statfs*>(buf));
}
uint64_t FileManager::NewFSStatAt(int dirfd, const char *pathname, struct stat *buf, int flag) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
auto Path = GetEmulatedPath(SelfPath, (flag & AT_SYMLINK_NOFOLLOW) == 0);
if (!Path.empty()) {
uint64_t Result = ::fstatat(dirfd, Path.c_str(), buf, flag);
if (Result != -1) {
return Result;
}
}
return ::fstatat(dirfd, SelfPath, buf, flag);
}
uint64_t FileManager::NewFSStatAt64(int dirfd, const char *pathname, struct stat64 *buf, int flag) {
auto NewPath = GetSelf(pathname);
const char *SelfPath = NewPath ? NewPath->c_str() : nullptr;
auto Path = GetEmulatedPath(SelfPath, (flag & AT_SYMLINK_NOFOLLOW) == 0);
if (!Path.empty()) {
uint64_t Result = ::fstatat64(dirfd, Path.c_str(), buf, flag);
if (Result != -1) {
return Result;
}
}
return ::fstatat64(dirfd, SelfPath, buf, flag);
}
std::string *FileManager::FindFDName(int fd) {
FHU::ScopedSignalMaskWithMutex lk(FDLock);
auto it = FDToNameMap.find(fd);
if (it == FDToNameMap.end()) {
return nullptr;
}
return &it->second;
}
}