From 91bbb92c50f2c67f12c34a9f73e99d5f7db65bbe Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Wed, 16 Nov 2022 21:29:47 -0800 Subject: [PATCH 1/3] FEXServer: More Systemd fixes Two changes here. - Make the mount path follow server temp folder requirements. - Will be mounted in `/tmp/` or `$XDG_RUNTIME_DIR/` now - Switch the FEXServer socket to an "abstract" AF_UNIX socket. - If the socket is in `/tmp/` then systemd will put the service in a private `/tmp` folder that only exists for the service. - If the socket is in `$XDG_RUNTIME_DIR` then pressure-vessel can't chroot anymore since they make their own runtime directory. - If it is in `$HOME/.fex-emu/` then it breaks usage where the filesystem is a mount that doesn't support AF_UNIX like sshfs. The only reasonable thing to do is to switch over to `abstract` sockets which will work in all cases. Tested with pressure-vessel and systemd and now it works in all situations. --- Source/Common/FEXServerClient.cpp | 47 +++++++++++++++++--------- Source/Common/FEXServerClient.h | 3 +- Source/Tools/FEXServer/ProcessPipe.cpp | 15 ++++---- Source/Tools/FEXServer/SquashFS.cpp | 2 +- 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/Source/Common/FEXServerClient.cpp b/Source/Common/FEXServerClient.cpp index 24b7693e2f..903ea6c316 100644 --- a/Source/Common/FEXServerClient.cpp +++ b/Source/Common/FEXServerClient.cpp @@ -100,21 +100,29 @@ namespace FEXServerClient { return GetServerLockFolder() + "RootFS.lock"; } - std::string GetServerSocketFile() { - FEX_CONFIG_OPT(ServerSocketPath, SERVERSOCKETPATH); - if (ServerSocketPath().empty()) { - auto TmpDir = std::filesystem::temp_directory_path().string(); - - auto XDGRuntimeEnv = getenv("XDG_RUNTIME_DIR"); - if (XDGRuntimeEnv) { - // If the XDG runtime directory works then use that instead. - TmpDir = XDGRuntimeEnv; - } + std::string GetServerTempFolder() { + // We need a server temporary folder that has the following requirements. + // - Can't be `/tmp/` + // - systemd services use `PrivateTmp` feature to gives services their own tmp. + // - Can't be `$HOME/.fex-emu/` + // - Can be mounted with a filesystem (sshfs) which can't handle mount points inside it. + auto Folder = std::filesystem::temp_directory_path().string(); + auto XDGRuntimeEnv = getenv("XDG_RUNTIME_DIR"); + if (XDGRuntimeEnv) { + // If the XDG runtime directory works then use that instead. + Folder = XDGRuntimeEnv; + } - return fmt::format("{}/{}.FEXServer.socket", TmpDir, ::geteuid()); + if (FEXCore::Config::FindContainer() == "pressure-vessel") { + // In pressure-vessel the server location changes. + Folder = "/run/host/" + Folder; } - return ServerSocketPath; + return Folder; + } + + std::string GetServerSocketName() { + return fmt::format("{}.FEXServer.Socket", ::geteuid()); } int GetServerFD() { @@ -122,7 +130,7 @@ namespace FEXServerClient { } int ConnectToServer() { - auto ServerSocketFile = GetServerSocketFile(); + auto ServerSocketName = GetServerSocketName(); // Create the initial unix socket int SocketFD = socket(AF_UNIX, SOCK_STREAM, 0); @@ -131,11 +139,18 @@ namespace FEXServerClient { return -1; } + // AF_UNIX has a special feature for named socket paths. + // If the name of the socket begins with `\0` then it is an "abstract" socket address. + // The entirety of the name is used as a path to a socket that doesn't have any filesystem backing. struct sockaddr_un addr{}; addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, ServerSocketFile.data(), std::min(ServerSocketFile.size(), sizeof(addr.sun_path))); + // + 1 for null character initializer. + size_t SizeOfSocketString = std::min(ServerSocketName.size(), sizeof(addr.sun_path) - 2); + strncpy(addr.sun_path + 1, ServerSocketName.data(), SizeOfSocketString); + // Include final null character. + size_t SizeOfAddr = sizeof(addr.sun_family) + SizeOfSocketString + 1; - if (connect(SocketFD, reinterpret_cast(&addr), sizeof(addr)) == -1) { + if (connect(SocketFD, reinterpret_cast(&addr), SizeOfAddr) == -1) { LogMan::Msg::EFmt("Couldn't connect to FEXServer socket {} {} {}", ServerSocketFile, errno, strerror(errno)); close(SocketFD); return -1; @@ -231,7 +246,7 @@ namespace FEXServerClient { if (ServerFD == -1) { // Still couldn't connect to the socket. - LogMan::Msg::EFmt("Couldn't connect to FEXServer socket {} after launching the process", GetServerSocketFile()); + LogMan::Msg::EFmt("Couldn't connect to FEXServer socket {} after launching the process", GetServerSocketName()); } } } diff --git a/Source/Common/FEXServerClient.h b/Source/Common/FEXServerClient.h index 21a4d9d040..e6d317597b 100644 --- a/Source/Common/FEXServerClient.h +++ b/Source/Common/FEXServerClient.h @@ -50,7 +50,8 @@ namespace FEXServerClient { std::string GetServerLockFolder(); std::string GetServerLockFile(); std::string GetServerRootFSLockFile(); - std::string GetServerSocketFile(); + std::string GetServerTempFolder(); + std::string GetServerSocketName(); int GetServerFD(); bool SetupClient(char *InterpreterPath); diff --git a/Source/Tools/FEXServer/ProcessPipe.cpp b/Source/Tools/FEXServer/ProcessPipe.cpp index e2a26200d5..7dbcb86519 100644 --- a/Source/Tools/FEXServer/ProcessPipe.cpp +++ b/Source/Tools/FEXServer/ProcessPipe.cpp @@ -179,12 +179,7 @@ namespace ProcessPipe { } bool InitializeServerSocket() { - auto ServerSocketFile = FEXServerClient::GetServerSocketFile(); - - // Unlink the socket file if it exists - // We are being asked to create a daemon, not error check - // We don't care if this failed or not - unlink(ServerSocketFile.c_str()); + auto ServerSocketName = FEXServerClient::GetServerSocketName(); // Create the initial unix socket ServerSocketFD = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); @@ -195,10 +190,14 @@ namespace ProcessPipe { struct sockaddr_un addr{}; addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, ServerSocketFile.data(), std::min(ServerSocketFile.size(), sizeof(addr.sun_path))); + // + 1 for null character initializer. + size_t SizeOfSocketString = std::min(ServerSocketName.size(), sizeof(addr.sun_path) - 2); + strncpy(addr.sun_path + 1, ServerSocketName.data(), SizeOfSocketString); + // Include final null character. + size_t SizeOfAddr = sizeof(addr.sun_family) + SizeOfSocketString + 1; // Bind the socket to the path - int Result = bind(ServerSocketFD, reinterpret_cast(&addr), sizeof(addr)); + int Result = bind(ServerSocketFD, reinterpret_cast(&addr), SizeOfAddr); if (Result == -1) { LogMan::Msg::EFmt("Couldn't bind AF_UNIX socket '{}': {} {}\n", addr.sun_path, errno, strerror(errno)); close(ServerSocketFD); diff --git a/Source/Tools/FEXServer/SquashFS.cpp b/Source/Tools/FEXServer/SquashFS.cpp index 2fbab468b0..a8c2e53ba2 100644 --- a/Source/Tools/FEXServer/SquashFS.cpp +++ b/Source/Tools/FEXServer/SquashFS.cpp @@ -106,7 +106,7 @@ namespace SquashFS { bool MountRootFSImagePath(std::string SquashFS, bool EroFS) { pid_t ParentTID = ::getpid(); - MountFolder = fmt::format("{}/.FEXMount{}-XXXXXX", std::filesystem::temp_directory_path().string(), ParentTID); + MountFolder = fmt::format("{}/.FEXMount{}-XXXXXX", FEXServerClient::GetServerTempFolder(), ParentTID); char *MountFolderStr = MountFolder.data(); // Make the temporary mount folder From 83cebea780143cd98a7e194ad3c26e00e167ea42 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Thu, 17 Nov 2022 11:59:02 -0800 Subject: [PATCH 2/3] FEXServerClient: Clean up comments in server mount folder. --- Source/Common/FEXServerClient.cpp | 34 ++++++++++++++++++++++------- Source/Common/FEXServerClient.h | 2 +- Source/Tools/FEXServer/SquashFS.cpp | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/Source/Common/FEXServerClient.cpp b/Source/Common/FEXServerClient.cpp index 903ea6c316..f079ac76af 100644 --- a/Source/Common/FEXServerClient.cpp +++ b/Source/Common/FEXServerClient.cpp @@ -100,21 +100,39 @@ namespace FEXServerClient { return GetServerLockFolder() + "RootFS.lock"; } - std::string GetServerTempFolder() { - // We need a server temporary folder that has the following requirements. - // - Can't be `/tmp/` + std::string GetServerMountFolder() { + // We need a FEXServer mount directory that has some tricky requirements. + // - We don't want to use `/tmp/` if possible. // - systemd services use `PrivateTmp` feature to gives services their own tmp. - // - Can't be `$HOME/.fex-emu/` - // - Can be mounted with a filesystem (sshfs) which can't handle mount points inside it. - auto Folder = std::filesystem::temp_directory_path().string(); + // - We will use this as a fallback path /only/. + // - Can't be `[$XDG_DATA_HOME,$HOME]/.fex-emu/` + // - Might be mounted with a filesystem (sshfs) which can't handle mount points inside it. + // + // Directories it can be in: + // - $XDG_RUNTIME_DIR if set + // - Is typically `/run/user//` + // - systemd `PrivateTmp` feature doesn't touch this. + // - If this path doesn't exist then fallback to `/tmp/` as a last resort. + // - pressure-vessel explicitly creates an internal XDG_RUNTIME_DIR inside its chroot. + // - This is okay since pressure-vessel rbinds the FEX rootfs from the host to `/run/pressure-vessel/interpreter-root`. + std::string Folder{}; auto XDGRuntimeEnv = getenv("XDG_RUNTIME_DIR"); if (XDGRuntimeEnv) { - // If the XDG runtime directory works then use that instead. + // If the XDG runtime directory works then use that. Folder = XDGRuntimeEnv; } + else { + // Fallback to `/tmp/` if XDG_RUNTIME_DIR doesn't exist. + // Might not be ideal but we don't have much of a choice. + Folder = std::filesystem::temp_directory_path().string(); + } if (FEXCore::Config::FindContainer() == "pressure-vessel") { - // In pressure-vessel the server location changes. + // In pressure-vessel the mount point changes location. + // This is due to pressure-vesssel being a chroot environment. + // It by default maps the host-filesystem to `/run/host/` so we need to redirect. + // After pressure-vessel is fully set up it will set the `FEX_ROOTFS` environment variable, + // which the FEXInterpreter will pick up on. Folder = "/run/host/" + Folder; } diff --git a/Source/Common/FEXServerClient.h b/Source/Common/FEXServerClient.h index e6d317597b..7eb55383e8 100644 --- a/Source/Common/FEXServerClient.h +++ b/Source/Common/FEXServerClient.h @@ -50,7 +50,7 @@ namespace FEXServerClient { std::string GetServerLockFolder(); std::string GetServerLockFile(); std::string GetServerRootFSLockFile(); - std::string GetServerTempFolder(); + std::string GetServerMountFolder(); std::string GetServerSocketName(); int GetServerFD(); diff --git a/Source/Tools/FEXServer/SquashFS.cpp b/Source/Tools/FEXServer/SquashFS.cpp index a8c2e53ba2..5c4b207291 100644 --- a/Source/Tools/FEXServer/SquashFS.cpp +++ b/Source/Tools/FEXServer/SquashFS.cpp @@ -106,7 +106,7 @@ namespace SquashFS { bool MountRootFSImagePath(std::string SquashFS, bool EroFS) { pid_t ParentTID = ::getpid(); - MountFolder = fmt::format("{}/.FEXMount{}-XXXXXX", FEXServerClient::GetServerTempFolder(), ParentTID); + MountFolder = fmt::format("{}/.FEXMount{}-XXXXXX", FEXServerClient::GetServerMountFolder(), ParentTID); char *MountFolderStr = MountFolder.data(); // Make the temporary mount folder From 10e35a55eae923bf830aef1ccb6b6365eeb1f436 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Thu, 17 Nov 2022 12:04:11 -0800 Subject: [PATCH 3/3] FEXServerClient: Cleanup AF_UNIX abstract socket string math Makes it a bit easier to reason about. --- Source/Common/FEXServerClient.cpp | 8 ++++---- Source/Tools/FEXServer/ProcessPipe.cpp | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Common/FEXServerClient.cpp b/Source/Common/FEXServerClient.cpp index f079ac76af..e71aac1bfc 100644 --- a/Source/Common/FEXServerClient.cpp +++ b/Source/Common/FEXServerClient.cpp @@ -162,14 +162,14 @@ namespace FEXServerClient { // The entirety of the name is used as a path to a socket that doesn't have any filesystem backing. struct sockaddr_un addr{}; addr.sun_family = AF_UNIX; - // + 1 for null character initializer. - size_t SizeOfSocketString = std::min(ServerSocketName.size(), sizeof(addr.sun_path) - 2); + size_t SizeOfSocketString = std::min(ServerSocketName.size() + 1, sizeof(addr.sun_path) - 1); + addr.sun_path[0] = 0; // Abstract AF_UNIX sockets start with \0 strncpy(addr.sun_path + 1, ServerSocketName.data(), SizeOfSocketString); // Include final null character. - size_t SizeOfAddr = sizeof(addr.sun_family) + SizeOfSocketString + 1; + size_t SizeOfAddr = sizeof(addr.sun_family) + SizeOfSocketString; if (connect(SocketFD, reinterpret_cast(&addr), SizeOfAddr) == -1) { - LogMan::Msg::EFmt("Couldn't connect to FEXServer socket {} {} {}", ServerSocketFile, errno, strerror(errno)); + LogMan::Msg::EFmt("Couldn't connect to FEXServer socket {} {} {}", ServerSocketName, errno, strerror(errno)); close(SocketFD); return -1; } diff --git a/Source/Tools/FEXServer/ProcessPipe.cpp b/Source/Tools/FEXServer/ProcessPipe.cpp index 7dbcb86519..3d1fc06098 100644 --- a/Source/Tools/FEXServer/ProcessPipe.cpp +++ b/Source/Tools/FEXServer/ProcessPipe.cpp @@ -190,11 +190,11 @@ namespace ProcessPipe { struct sockaddr_un addr{}; addr.sun_family = AF_UNIX; - // + 1 for null character initializer. - size_t SizeOfSocketString = std::min(ServerSocketName.size(), sizeof(addr.sun_path) - 2); + size_t SizeOfSocketString = std::min(ServerSocketName.size() + 1, sizeof(addr.sun_path) - 1); + addr.sun_path[0] = 0; // Abstract AF_UNIX sockets start with \0 strncpy(addr.sun_path + 1, ServerSocketName.data(), SizeOfSocketString); // Include final null character. - size_t SizeOfAddr = sizeof(addr.sun_family) + SizeOfSocketString + 1; + size_t SizeOfAddr = sizeof(addr.sun_family) + SizeOfSocketString; // Bind the socket to the path int Result = bind(ServerSocketFD, reinterpret_cast(&addr), SizeOfAddr);