Merge pull request #5264 from Sonicadvance1/66

FEXLinuxTests: Adds execveat with mfd_cloexec test and related fixes.
This commit is contained in:
LC authored and GitHub committed 2026-01-28 15:57:53 -05:00
commit e3dcb4b0dc
5 files changed
+129 -13

No files matched your search

+61 -11
View File
@@ -7,6 +7,7 @@
#include <FEXCore/fextl/fmt.h>
#include <FEXCore/fextl/map.h>
#include <FEXCore/fextl/string.h>
#include <FEXCore/Utils/Allocator.h>
#include <FEXCore/Utils/FileLoading.h>
#include <FEXHeaderUtils/Filesystem.h>
#include <FEXHeaderUtils/SymlinkChecks.h>
@@ -22,6 +23,9 @@
#include <utility>
#include <tiny-json.h>
#include <range/v3/view/split.hpp>
#include <range/v3/view/transform.hpp>
namespace FEX::Config {
namespace JSON {
static void LoadJSonConfig(const fextl::string& Config, std::function<void(const char* Name, const char* ConfigSring)> Func) {
@@ -494,20 +498,66 @@ void LoadConfig(fextl::string ProgramName, char** const envp, const PortableInfo
}
#ifndef _WIN32
const char* FindUserHomeThroughUID() {
auto passwd = getpwuid(geteuid());
if (passwd) {
return passwd->pw_dir;
fextl::string FindUserHomeThroughUID() {
// `getpwuid` allocates memory, parse `/etc/passwd` manually.
// Format is trivial: `<name>:<password hash>:<uid>:<gid>:<comment>:<home>:<shell>`
fextl::vector<char> Data;
if (!FEXCore::FileLoading::LoadFile(Data, "/etc/passwd")) {
return {};
}
return nullptr;
auto to_string_view = [](auto rng) {
return std::string_view(&*rng.begin(), ranges::distance(rng));
};
const auto uid = geteuid();
for (const auto entry : ranges::views::split(Data, '\n') | ranges::views::transform(to_string_view)) {
const auto elements = ranges::views::split(entry, ':') | ranges::views::transform(to_string_view);
// Reject bad entries.
if (std::distance(elements.begin(), elements.end()) != 7) {
continue;
}
auto iter = elements.begin();
++iter; // name
++iter; // password hash
++iter; // comment
// uid
const auto uid_s = *iter;
++iter;
++iter; // gid
// home
const auto home_s = *iter;
++iter;
++iter; // shell
uint64_t element_uid;
auto Results = std::from_chars(uid_s.begin(), uid_s.end(), element_uid, 10);
// Error parsing.
if (Results.ptr == uid_s.begin()) {
continue;
}
if (element_uid == uid) {
return fextl::string(home_s);
}
}
return {};
}
const char* GetHomeDirectory() {
fextl::string GetHomeDirectory() {
const char* HomeDir = getenv("HOME");
// Try to get home directory from uid
if (!HomeDir || !FHU::Filesystem::Exists(HomeDir)) {
HomeDir = FindUserHomeThroughUID();
auto UIDHome = FindUserHomeThroughUID();
if (!UIDHome.empty() && FHU::Filesystem::Exists(UIDHome)) {
return UIDHome;
}
}
// try the PWD
@@ -523,7 +573,7 @@ const char* GetHomeDirectory() {
return HomeDir;
}
#else
const char* GetHomeDirectory() {
fextl::string GetHomeDirectory() {
const char* HomeDir = getenv("WINEHOMEDIR");
if (HomeDir) {
// Skip over the \??\ prefix in the NT path since we want a DOS path
@@ -560,7 +610,7 @@ fextl::string GetDataDirectory(bool Global, const PortableInformation& PortableI
return GLOBAL_DATA_DIRECTORY;
}
const char* HomeDir = GetHomeDirectory();
auto HomeDir = GetHomeDirectory();
const char* DataXDG = getenv("XDG_DATA_HOME");
const fextl::string LegacyDir = fextl::string {HomeDir} + "/.fex-emu/";
@@ -607,7 +657,7 @@ fextl::string GetConfigDirectory(bool Global, const PortableInformation& Portabl
return GLOBAL_DATA_DIRECTORY;
}
const char* HomeDir = GetHomeDirectory();
auto HomeDir = GetHomeDirectory();
const char* ConfigXDG = getenv("XDG_CONFIG_HOME");
const fextl::string LegacyDir = fextl::string {HomeDir} + "/.fex-emu/";
@@ -644,7 +694,7 @@ fextl::string GetCacheDirectory() {
}
#endif
const char* HomeDir = GetHomeDirectory();
auto HomeDir = GetHomeDirectory();
const char* CacheXDG = getenv("XDG_CACHE_HOME");
return (CacheXDG ? fextl::string {CacheXDG} : (fextl::string {HomeDir} + "/.cache")) + "/fex-emu/";
#else
+1 -1
View File
@@ -57,7 +57,7 @@ ApplicationNames GetApplicationNames(const fextl::vector<fextl::string>& Args, b
*/
void LoadConfig(fextl::string ProgramName = {}, char** const envp = nullptr, const PortableInformation& PortableInfo = {});
const char* GetHomeDirectory();
fextl::string GetHomeDirectory();
fextl::string GetDataDirectory(const PortableInformation& PortableInfo);
fextl::string GetConfigDirectory(bool Global, const PortableInformation& PortableInfo);
@@ -106,7 +106,7 @@ void FileManager::LoadThunkDatabase(fextl::unordered_map<fextl::string, ThunkDBO
return;
}
std::string_view HomeDirectory = FEX::Config::GetHomeDirectory();
auto HomeDirectory = FEX::Config::GetHomeDirectory();
for (const json_t* Library = json_getChild(DB); Library != nullptr; Library = json_getSibling(Library)) {
// Get the user defined name for the library
+4
View File
@@ -110,6 +110,10 @@ These all consume a `std::string` as their first argument. Use the equivalent fu
### `fmt::format`
Use `fextl::fmt::` instead
### `getpwuid` and `getpwuid_r`
Allocates memory for parsing passwd and other files. One would assume `getpwuid_r` would use the buffer passed in, but nope glibc nss_database_get
allocates memory.
### APIs that FEX doesn't have a replacement for
Don't use any of these APIs in FEXLoader/FEXInterpreter. Shoutout to
[this](https://stackoverflow.com/questions/43056338/standard-library-facilities-which-allocate-but-dont-use-an-allocator) StackOverflow post for this
@@ -0,0 +1,62 @@
#include <catch2/catch_test_macros.hpp>
#include <cstdio>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
static std::vector<char> LoadFile(const char* Path) {
int fd = open(Path, O_RDONLY);
REQUIRE(fd != -1);
struct stat st {};
REQUIRE(fstat(fd, &st) != -1);
std::vector<char> Result {};
Result.resize(st.st_size);
size_t DidRead {};
do {
auto Read = read(fd, Result.data() + DidRead, Result.size() - DidRead);
if (Read == -1) {
if (errno == EINTR || errno == EAGAIN) {
continue;
}
REQUIRE(errno != 0);
}
DidRead += Read;
} while (DidRead != st.st_size);
return Result;
}
TEST_CASE("execveat - memfd - MFD_CLOEXEC") {
auto MapsFile = LoadFile("/usr/bin/true");
REQUIRE(MapsFile.size() != 0);
int fd = memfd_create("Anonymous", MFD_CLOEXEC | MFD_ALLOW_SEALING);
REQUIRE(fd != -1);
size_t Written {};
do {
auto Wrote = write(fd, MapsFile.data() + Written, MapsFile.size() - Written);
if (Wrote == -1) {
if (errno == EINTR || errno == EAGAIN) {
continue;
}
REQUIRE(errno != 0);
}
Written += Wrote;
} while (Written != MapsFile.size());
const char* argv[] = {"tmp", nullptr};
auto Res = ::syscall(SYS_execveat, fd, "", argv, nullptr, AT_EMPTY_PATH);
// Will only get here if execveat fails.
close(fd);
REQUIRE(Res == 0);
}