Files
FEX-Emu--FEX/Source/Tools/FEXServer/SquashFS.cpp
T
LC 9575506391 FEXServer: Fix some missing declaration warnings
Makes sure prototypes are visible to their implementation. Also marks
functions internally linked where applicable.

Also makes it a little more visibly obvious which bits are exposed for
use elsewhere.
2026-07-16 07:10:32 -04:00

280 lines
7.8 KiB
C++

// SPDX-License-Identifier: MIT
#include "SquashFS.h"
#include "Common/FEXServerClient.h"
#include "Common/FileFormatCheck.h"
#include <FEXCore/Config/Config.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/fextl/string.h>
#include <FEXHeaderUtils/Syscalls.h>
#include <fcntl.h>
#include <filesystem>
#include <poll.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <thread>
namespace SquashFS {
constexpr int USER_PERMS = S_IRWXU | S_IRWXG | S_IRWXO;
static int ServerRootFSLockFD {-1};
static int FuseMountPID {};
static fextl::string MountFolder {};
static void ShutdownImagePID() {
if (FuseMountPID) {
FHU::Syscalls::tgkill(FuseMountPID, FuseMountPID, SIGINT);
}
}
static bool InitializeSquashFSPipe() {
auto RootFSLockFile = FEXServerClient::GetServerRootFSLockFile();
int Ret = open(RootFSLockFile.c_str(), O_CREAT | O_RDWR | O_TRUNC | O_EXCL | O_CLOEXEC, USER_PERMS);
ServerRootFSLockFD = Ret;
if (Ret == -1 && errno == EEXIST) {
// If the fifo exists then it might be a stale connection.
// Check the lock status to see if another process is still alive.
ServerRootFSLockFD = open(RootFSLockFile.c_str(), O_RDWR | O_CLOEXEC, USER_PERMS);
if (ServerRootFSLockFD != -1) {
// Now that we have opened the file, try to get a write lock.
flock lk {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
};
Ret = fcntl(ServerRootFSLockFD, F_SETLK, &lk);
if (Ret != -1) {
// Write lock was gained, we can now continue onward.
} else {
// We couldn't get a write lock, this means that another process already owns a lock on the fifo
close(ServerRootFSLockFD);
ServerRootFSLockFD = -1;
return false;
}
} else {
// File couldn't get opened even though it existed?
// Must have raced something here.
return false;
}
} else if (Ret == -1) {
// Unhandled error.
LogMan::Msg::EFmt("[FEXServer] Unable to create FEXServer RootFS lock file at: {} {} {}", RootFSLockFile, errno, strerror(errno));
return false;
} else {
// FIFO file was created. Try to get a write lock
flock lk {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
};
Ret = fcntl(ServerRootFSLockFD, F_SETLK, &lk);
if (Ret == -1) {
// Couldn't get a write lock, something else must have got it
close(ServerRootFSLockFD);
ServerRootFSLockFD = -1;
return false;
}
}
return true;
}
static bool DowngradeRootFSPipeToReadLock() {
flock lk {
.l_type = F_RDLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
};
int Ret = fcntl(ServerRootFSLockFD, F_SETLK, &lk);
if (Ret == -1) {
// This shouldn't occur
LogMan::Msg::EFmt("[FEXServer] Unable to downgrade a rootfs write lock to a read lock {} {}", errno, strerror(errno));
close(ServerRootFSLockFD);
ServerRootFSLockFD = -1;
return false;
}
return true;
}
static bool MountRootFSImagePath(const fextl::string& SquashFS, bool EroFS) {
pid_t ParentTID = ::getpid();
MountFolder = fmt::format("{}/.FEXMount{}-XXXXXX", FEXServerClient::GetServerMountFolder(), ParentTID);
char* MountFolderStr = MountFolder.data();
// Make the temporary mount folder
if (mkdtemp(MountFolderStr) == nullptr) {
LogMan::Msg::EFmt("[FEXServer] Couldn't create temporary mount name: {}", MountFolder);
return false;
}
// Change the permissions
if (chmod(MountFolderStr, 0777) != 0) {
LogMan::Msg::EFmt("[FEXServer] Couldn't change permissions on temporary mount: {}", MountFolder);
rmdir(MountFolderStr);
return false;
}
// Create local FDs so our internal forks can communicate
int fds[2];
pipe2(fds, 0);
int pid = fork();
if (pid == 0) {
// Child
close(fds[0]); // Close read side
const char* argv[4];
argv[0] = EroFS ? "erofsfuse" : "squashfuse";
argv[1] = SquashFS.c_str();
argv[2] = MountFolder.c_str();
argv[3] = nullptr;
// Try and execute {erofsfuse, squashfuse} to mount our rootfs
if (execvpe(argv[0], (char* const*)argv, environ) == -1) {
// Give a hopefully helpful error message for users
LogMan::Msg::EFmt("[FEXServer] '{}' Couldn't execute for some reason: {} {}\n", argv[0], errno, strerror(errno));
LogMan::Msg::EFmt("[FEXServer] To mount squashfs rootfs files you need {} installed\n", argv[0]);
LogMan::Msg::EFmt("[FEXServer] Check your FUSE setup.\n");
// Let the parent know that we couldn't execute for some reason
uint64_t error {1};
write(fds[1], &error, sizeof(error));
// End the child
exit(1);
}
} else {
FuseMountPID = pid;
// Parent
// Wait for the child to exit
// This will happen with execvpe of squashmount or exit on failure
while (waitpid(pid, nullptr, 0) == -1 && errno == EINTR)
;
// Check the child pipe for messages
pollfd PollFD;
PollFD.fd = fds[0];
PollFD.events = POLLIN;
int Result = poll(&PollFD, 1, 0);
if (Result == 1 && PollFD.revents & POLLIN) {
// Child couldn't execvpe for whatever reason
// Remove the mount path and leave Just in case it was created
rmdir(MountFolderStr);
// Close the pipe now
close(fds[0]);
LogMan::Msg::EFmt("[FEXServer] Couldn't mount squashfs\n");
return false;
}
// Close the pipe now
close(fds[0]);
}
// Write to the lock file where we are mounted
write(ServerRootFSLockFD, MountFolder.c_str(), MountFolder.size());
fdatasync(ServerRootFSLockFD);
return true;
}
void UnmountRootFS() {
FEX_CONFIG_OPT(LDPath, ROOTFS);
if (!FEX::FormatCheck::IsSquashFS(LDPath()) && !FEX::FormatCheck::IsEroFS(LDPath())) {
return;
}
SquashFS::ShutdownImagePID();
// Handle final mount removal
// fusermount for unmounting the mountpoint, then the {erfsfuse, squashfuse} will exit automatically
int pid = fork();
if (pid == 0) {
const char* argv[5];
argv[0] = "fusermount3";
argv[1] = "-u";
argv[2] = "-q";
argv[3] = MountFolder.c_str();
argv[4] = nullptr;
if (execvp(argv[0], (char* const*)argv) == -1) {
// Try again with `fusermount`
argv[0] = "fusermount";
if (execvp(argv[0], (char* const*)argv) == -1) {
fprintf(stderr, "fusermount{3,} failed to execute. You may have an mount living at '%s' to clean up now\n", MountFolder.c_str());
fprintf(stderr, "Try `%s %s %s %s`\n", argv[0], argv[1], argv[2], argv[3]);
exit(1);
}
}
} else {
// Wait for fusermount to leave
while (waitpid(pid, nullptr, 0) == -1 && errno == EINTR)
;
// Remove the mount path
rmdir(MountFolder.c_str());
// Remove the rootfs lock file
auto RootFSLockFile = FEXServerClient::GetServerRootFSLockFile();
unlink(RootFSLockFile.c_str());
}
}
bool InitializeSquashFS() {
FEX_CONFIG_OPT(LDPath, ROOTFS);
MountFolder = LDPath();
bool IsSquashFS {false};
bool IsEroFS {false};
// Check if the image is an EroFS
IsEroFS = FEX::FormatCheck::IsEroFS(MountFolder);
if (!IsEroFS) {
// Check if the image is an SquashFS
IsSquashFS = FEX::FormatCheck::IsSquashFS(MountFolder);
}
if (!IsSquashFS && !IsEroFS) {
// If this isn't a rootfs image then we have nothing to do here
return true;
}
if (!InitializeSquashFSPipe()) {
LogMan::Msg::EFmt("[FEXServer] Couldn't initialize SquashFSPipe");
return false;
}
// Setup rootfs here
if (!MountRootFSImagePath(LDPath(), IsEroFS)) {
LogMan::Msg::EFmt("[FEXServer] Couldn't mount squashfs path");
return false;
}
if (!DowngradeRootFSPipeToReadLock()) {
LogMan::Msg::EFmt("[FEXServer] Couldn't downgrade read lock");
return false;
}
return true;
}
const fextl::string& GetMountFolder() {
return MountFolder;
}
} // namespace SquashFS