mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-10 01:00:18 +02:00
721 lines
22 KiB
C++
721 lines
22 KiB
C++
|
|
#include <FEXHeaderUtils/Syscalls.h>
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <condition_variable>
|
|
#include <cstdlib>
|
|
#include <cstdint>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <filesystem>
|
|
#include <limits.h>
|
|
#include <linux/in.h>
|
|
#include <mutex>
|
|
#include <poll.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <string>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/inotify.h>
|
|
#include <sys/un.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#include <sys/utsname.h>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
std::atomic<bool> ShuttingDown{};
|
|
|
|
void SignalShutdown();
|
|
namespace EPollWatcher {
|
|
static int epoll_fd{};
|
|
static std::thread EPollThread{};
|
|
static std::atomic<pid_t> EPollThreadTID{};
|
|
static rlimit MaxFDs{};
|
|
constexpr size_t static MAX_FD_DISTANCE = 32;
|
|
std::atomic<uint64_t> NumPipesWatched{};
|
|
std::atomic<size_t> NumFilesOpened{};
|
|
std::atomic<bool> EPollWatcherShutdown {false};
|
|
std::chrono::time_point<std::chrono::system_clock> TimeWhileZeroFDs{};
|
|
// Timeout is ten seconds
|
|
constexpr std::chrono::duration<size_t> TimeoutPeriod = std::chrono::seconds(10);
|
|
|
|
uint64_t NumPipesRemaining() {
|
|
return NumPipesWatched.load();
|
|
}
|
|
|
|
size_t GetNumFilesOpen() {
|
|
// Walk /proc/self/fd/ to see how many open files we currently have
|
|
const std::filesystem::path self{"/proc/self/fd/"};
|
|
|
|
return std::distance(std::filesystem::directory_iterator{self}, std::filesystem::directory_iterator{});
|
|
}
|
|
|
|
void GetMaxFDs() {
|
|
// Get our kernel limit for the number of open files
|
|
if (getrlimit(RLIMIT_NOFILE, &MaxFDs) != 0) {
|
|
fprintf(stderr, "[FEXMountDaemon] getrlimit(RLIMIT_NOFILE) returned error %d %s\n", errno, strerror(errno));
|
|
}
|
|
|
|
// Walk /proc/self/fd/ to see how many open files we currently have
|
|
NumFilesOpened = GetNumFilesOpen();
|
|
}
|
|
|
|
void RaiseFDLimit() {
|
|
if (MaxFDs.rlim_cur == MaxFDs.rlim_max) {
|
|
fprintf(stderr, "[FEXMountDaemon] Our open FD limit is already set to max and we are wanting to increase it\n");
|
|
fprintf(stderr, "[FEXMountDaemon] FEXMountDaemon will now no longer be able to track new instances of FEX\n");
|
|
fprintf(stderr, "[FEXMountDaemon] Current limit is %zd(hard %zd) FDs and we are at %zd\n", MaxFDs.rlim_cur, MaxFDs.rlim_max, GetNumFilesOpen());
|
|
fprintf(stderr, "[FEXMountDaemon] Ask your administrator to raise your kernel's hard limit on open FDs\n");
|
|
return;
|
|
}
|
|
|
|
rlimit NewLimit = MaxFDs;
|
|
|
|
// Just multiply by two
|
|
NewLimit.rlim_cur <<= 1;
|
|
|
|
// Now limit to the hard max
|
|
NewLimit.rlim_cur = std::min(NewLimit.rlim_cur, NewLimit.rlim_max);
|
|
|
|
if (setrlimit(RLIMIT_NOFILE, &NewLimit) != 0) {
|
|
fprintf(stderr, "[FEXMountDaemon] Couldn't raise FD limit to %zd even though our hard limit is %zd\n", NewLimit.rlim_cur, NewLimit.rlim_max);
|
|
}
|
|
else {
|
|
// Set the new limit
|
|
MaxFDs = NewLimit;
|
|
}
|
|
}
|
|
|
|
void AddPipeToWatch(int pipe) {
|
|
struct epoll_event evt{};
|
|
evt.events = EPOLLERR; // This event will return when the read end of a pipe is closed
|
|
evt.data.fd = pipe; // Just return the pipe in the user data
|
|
int Result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pipe, &evt);
|
|
if (Result == -1) {
|
|
fprintf(stderr, "[FEXMountDaemon] epoll_ctl returned error %d %s\n", errno, strerror(errno));
|
|
}
|
|
else {
|
|
++NumPipesWatched;
|
|
|
|
if ((NumPipesWatched + NumFilesOpened) >= (MaxFDs.rlim_cur - MAX_FD_DISTANCE)) {
|
|
// We are close to the maximum FD distance
|
|
// Try to raise the limit
|
|
RaiseFDLimit();
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemovePipeToWatch(int pipe) {
|
|
int Result = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, pipe, nullptr);
|
|
|
|
// Make sure to close the pipe to not leak the FD
|
|
close(pipe);
|
|
if (Result == -1) {
|
|
fprintf(stderr, "[FEXMountDaemon] epoll_ctl returned error %d %s\n", errno, strerror(errno));
|
|
}
|
|
else {
|
|
--NumPipesWatched;
|
|
}
|
|
|
|
// If the number of pipes we are watching drops to zero then start a timer so we can exit
|
|
if (NumPipesRemaining() == 0) {
|
|
TimeWhileZeroFDs = std::chrono::system_clock::now();
|
|
}
|
|
}
|
|
|
|
void EPollWatch() {
|
|
pthread_setname_np(pthread_self(), "EPollWatcher");
|
|
EPollThreadTID = FHU::Syscalls::gettid();
|
|
constexpr size_t MAX_EVENTS = 16;
|
|
struct epoll_event Events[MAX_EVENTS]{};
|
|
|
|
// Spin while we are not shutting down
|
|
// Also spin while we have pipes to watch
|
|
while (!EPollWatcherShutdown.load()) {
|
|
// Loop every ten seconds
|
|
// epoll_pwait2 only available since kernel 5.11...
|
|
int Result = epoll_pwait(epoll_fd, Events, MAX_EVENTS, 10 * 1000, nullptr);
|
|
if (Result == -1) {
|
|
// EINTR is common here
|
|
}
|
|
else {
|
|
for (size_t i = 0; i < Result; ++i) {
|
|
auto &Event = Events[i];
|
|
if (Event.events & EPOLLERR) {
|
|
// This pipe's read end has closed
|
|
// No need to watch it anymore
|
|
RemovePipeToWatch(Event.data.fd);
|
|
}
|
|
}
|
|
|
|
// If we are at zero pipes then check our timer if we are past the timeout period to exit
|
|
if (NumPipesRemaining() == 0) {
|
|
auto Now = std::chrono::system_clock::now();
|
|
auto Dur = Now - TimeWhileZeroFDs;
|
|
if (Dur >= TimeoutPeriod) {
|
|
// We need to shutdown now
|
|
::SignalShutdown();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close the poll_fd
|
|
close(epoll_fd);
|
|
}
|
|
|
|
void SetupEPoll() {
|
|
GetMaxFDs();
|
|
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
|
EPollThread = std::thread{EPollWatcher::EPollWatch};
|
|
}
|
|
|
|
void SignalShutdown() {
|
|
EPollWatcherShutdown = true;
|
|
// Send a signal to wake up epoll immediately
|
|
FHU::Syscalls::tgkill(::getpid(), EPollThreadTID, SIGUSR1);
|
|
}
|
|
|
|
void ShutdownEPoll() {
|
|
SignalShutdown();
|
|
EPollThread.join();
|
|
}
|
|
}
|
|
|
|
namespace SocketWatcher {
|
|
static int socket_fd{};
|
|
static std::thread SocketThread{};
|
|
static std::atomic<pid_t> SocketThreadTID{};
|
|
const std::string *SocketWatcherPath{};
|
|
std::atomic<bool> SocketShutdown {false};
|
|
|
|
void SocketFunction() {
|
|
pthread_setname_np(pthread_self(), "SocketWatcher");
|
|
SocketThreadTID = FHU::Syscalls::gettid();
|
|
listen(socket_fd, 16);
|
|
while (!SocketShutdown.load()) {
|
|
// Listen for connections
|
|
// Wait for new clients coming in
|
|
struct pollfd pfd{};
|
|
pfd.fd = socket_fd;
|
|
pfd.events = POLLIN;
|
|
|
|
// Wait for ten seconds
|
|
struct timespec ts{};
|
|
ts.tv_sec = 10;
|
|
|
|
int Result = ppoll(&pfd, 1, &ts, nullptr);
|
|
|
|
if (Result > 0) {
|
|
struct sockaddr_in from{};
|
|
socklen_t addrlen = sizeof(from);
|
|
int new_client = accept(socket_fd, reinterpret_cast<struct sockaddr *>(&from), &addrlen);
|
|
|
|
// Wait for data coming in
|
|
struct pollfd pfd{};
|
|
pfd.fd = new_client;
|
|
pfd.events = POLLIN;
|
|
|
|
// Wait for ten seconds
|
|
struct timespec ts{};
|
|
ts.tv_sec = 10;
|
|
|
|
int Result = ppoll(&pfd, 1, &ts, nullptr);
|
|
if (Result == -1) {
|
|
// EINTR is common here
|
|
}
|
|
else if (Result > 0) {
|
|
// We got data to grab
|
|
struct msghdr msg{};
|
|
struct iovec iov{};
|
|
char iov_data{};
|
|
|
|
// Setup the ancillary buffer. This is where we will be getting pipe FDs
|
|
// We only need 4 bytes for the FD
|
|
constexpr size_t CMSG_SIZE = CMSG_SPACE(sizeof(int));
|
|
union AncillaryBuffer {
|
|
struct cmsghdr Header;
|
|
uint8_t Buffer[CMSG_SIZE];
|
|
};
|
|
AncillaryBuffer AncBuf{};
|
|
|
|
// Set up message header
|
|
msg.msg_name = nullptr;
|
|
msg.msg_namelen = 0;
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
|
|
// Setup iov. We won't be receiving any real data here
|
|
iov.iov_base = &iov_data;
|
|
iov.iov_len = sizeof(iov_data);
|
|
|
|
// Now link to our ancilllary buffer
|
|
msg.msg_control = AncBuf.Buffer;
|
|
msg.msg_controllen = CMSG_SIZE;
|
|
|
|
ssize_t DataResult = recvmsg(new_client, &msg, 0);
|
|
if (DataResult == -1) {
|
|
// Sometimes get a spurious read?
|
|
}
|
|
else {
|
|
// Now that we have the data, we can extract the FD from the ancillary buffer
|
|
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
|
|
|
// Do some error checking
|
|
if (cmsg == nullptr ||
|
|
cmsg->cmsg_len != CMSG_LEN(sizeof(int)) ||
|
|
cmsg->cmsg_level != SOL_SOCKET ||
|
|
cmsg->cmsg_type != SCM_RIGHTS) {
|
|
fprintf(stderr, "[FEXMountDaemon:SocketWatcher] cmsg data was incorrect\n");
|
|
}
|
|
else {
|
|
// Now that we know the cmsg is sane, read the FD
|
|
int NewFD{};
|
|
memcpy(&NewFD, CMSG_DATA(cmsg), sizeof(NewFD));
|
|
|
|
// Add to to the epoll watcher
|
|
EPollWatcher::AddPipeToWatch(NewFD);
|
|
// If we received an event while in the process of shutting down then we have raced
|
|
|
|
// Send back an ack message
|
|
uint8_t c = 0;
|
|
send(new_client, &c, sizeof(c), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now that we have received data on this socket we don't need to keep it around
|
|
close(new_client);
|
|
}
|
|
else {
|
|
// EINTR common here
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CreateServerSocket(std::string &SocketPath) {
|
|
// 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
|
|
remove(SocketPath.c_str());
|
|
|
|
// Create the initial unix socket
|
|
socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (socket_fd == -1) {
|
|
fprintf(stderr, "[FEXMountDaemon:SocketWatcher] Couldn't create AF_UNIX socket: %d %s\n", errno, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
struct sockaddr_un addr{};
|
|
addr.sun_family = AF_UNIX;
|
|
strncpy(addr.sun_path, SocketPath.data(), sizeof(addr.sun_path));
|
|
|
|
// Bind the socket to the path
|
|
int Result = bind(socket_fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
|
|
if (Result == -1) {
|
|
fprintf(stderr, "[FEXMountDaemon:SocketWatcher] Couldn't bind AF_UNIX socket '%s': %d %s\n", addr.sun_path, errno, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SetupSocketWatcher(std::string &SocketPath) {
|
|
SocketWatcherPath = &SocketPath;
|
|
if (!CreateServerSocket(SocketPath)) {
|
|
return false;
|
|
}
|
|
|
|
SocketThread = std::thread{SocketWatcher::SocketFunction};
|
|
return true;
|
|
}
|
|
|
|
void SignalShutdown() {
|
|
SocketShutdown = true;
|
|
// Send a signal to wake up ppoll immediately
|
|
FHU::Syscalls::tgkill(::getpid(), SocketThreadTID, SIGUSR1);
|
|
}
|
|
|
|
void ShutdownSocketWatcher() {
|
|
SignalShutdown();
|
|
SocketThread.join();
|
|
|
|
// Close the socket and delete the socket file
|
|
close(socket_fd);
|
|
remove(SocketWatcherPath->c_str());
|
|
}
|
|
}
|
|
|
|
namespace INotifyWatcher {
|
|
static int lock_fd {-1};
|
|
static std::condition_variable WaitCV{};
|
|
static std::mutex WaitMutex{};
|
|
static std::atomic<bool> INotifyShutdown {false};
|
|
const std::string *INotifyLockPath{};
|
|
enum LockFailure {
|
|
LOCK_FAIL_FATAL,
|
|
LOCK_FAIL_EXISTS,
|
|
LOCK_FAIL_CREATION_RACE,
|
|
LOCK_FAIL_CREATED,
|
|
};
|
|
|
|
constexpr int USER_PERMS = S_IRWXU | S_IRWXG | S_IRWXO;
|
|
|
|
void RemoveLock();
|
|
|
|
LockFailure CreateINotifyLock(const std::string &LockPath, const char *MountPath) {
|
|
INotifyLockPath = &LockPath;
|
|
lock_fd = open(LockPath.c_str(), O_RDONLY, USER_PERMS);
|
|
if (lock_fd != -1) {
|
|
// LockFD already existed!
|
|
// This will have now refcounted the existing daemon!
|
|
close(lock_fd);
|
|
return LOCK_FAIL_EXISTS;
|
|
}
|
|
|
|
lock_fd = open(LockPath.c_str(), O_CREAT | O_RDWR, USER_PERMS);
|
|
if (lock_fd == -1) {
|
|
// Couldn't open lock file for some reason
|
|
// Likely read only file system
|
|
return LOCK_FAIL_FATAL;
|
|
}
|
|
|
|
LockFailure Failure = LOCK_FAIL_FATAL;
|
|
|
|
// Set up the write lock to ensure this doesn't race
|
|
{
|
|
// Attempt to open a write lease on the lock file
|
|
// First thing, mask the signal from the least interface
|
|
// By default it is SIGIO
|
|
{
|
|
sigset_t set;
|
|
sigemptyset(&set);
|
|
sigaddset(&set, SIGIO);
|
|
if (sigprocmask(SIG_BLOCK, &set, nullptr) == -1) {
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
// Set the file's lease signal
|
|
// Even if we set it to default, this is necessary
|
|
{
|
|
int Res = fcntl(lock_fd, F_SETSIG, SIGIO);
|
|
if (Res == -1) {
|
|
// Shouldn't fail
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
// Now attempt to get a write lock on this file
|
|
{
|
|
int Res = fcntl(lock_fd, F_SETLEASE, F_WRLCK);
|
|
if (Res == -1) {
|
|
// Couldn't get a write lock
|
|
// This means another FEXMountDaemon is in the process of setting up a rootfs
|
|
// Early exit, this will be mounted in another process
|
|
Failure = LOCK_FAIL_CREATION_RACE;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
// Now that we have a lock on the file.
|
|
// Write where we are going to be mounting
|
|
// Nothing else can currently open the file for reads yet to see this
|
|
write(lock_fd, MountPath, strlen(MountPath));
|
|
}
|
|
|
|
return LOCK_FAIL_CREATED;
|
|
err:
|
|
if (lock_fd != -1) {
|
|
close (lock_fd);
|
|
}
|
|
|
|
return Failure;
|
|
}
|
|
|
|
void WatchLock() {
|
|
pthread_setname_np(pthread_self(), "INotifyWatcher");
|
|
|
|
// Let go of the file lease file to allow FEX to continue
|
|
fcntl(lock_fd, F_SETLEASE, F_UNLCK);
|
|
|
|
// Wait in a mutex to shutdown
|
|
std::unique_lock<std::mutex> lk (WaitMutex);
|
|
WaitCV.wait(lk, [] { return INotifyShutdown.load(); });
|
|
|
|
// Remove the locks immediately after shutdown
|
|
INotifyWatcher::RemoveLock();
|
|
}
|
|
|
|
void RemoveLock() {
|
|
if (lock_fd != -1) {
|
|
// Grab the file lock so FEX can't open it
|
|
int Res = fcntl(lock_fd, F_SETLEASE, F_WRLCK);
|
|
if (Res == -1) {
|
|
fprintf(stderr, "Couldn't lock lock file: %d %s\n", errno, strerror(errno));
|
|
}
|
|
|
|
// remove the contents in case any client is in the process of opening this
|
|
ftruncate(lock_fd, 0);
|
|
|
|
// Remove the lock file itself
|
|
unlink(INotifyLockPath->c_str());
|
|
|
|
// Clear the lease on it since we are shutting down
|
|
fcntl(lock_fd, F_SETLEASE, F_UNLCK);
|
|
|
|
// Close the lock fd
|
|
close(lock_fd);
|
|
lock_fd = -1;
|
|
}
|
|
}
|
|
|
|
void SignalShutdown() {
|
|
INotifyShutdown = true;
|
|
WaitCV.notify_all();
|
|
}
|
|
}
|
|
|
|
void ActionHandler(int sig, siginfo_t *info, void *context) {
|
|
if (sig == SIGCHLD) {
|
|
if (EPollWatcher::NumPipesRemaining() != 0) {
|
|
// Check the pipes we are watching to see if any exist
|
|
// If our child process shutdown while our parent is still running
|
|
// Then the parent loses its rootfs and problems occur
|
|
fprintf(stderr, "FEXMountDaemon child process from squashfuse has closed while FEX instances still exist\n");
|
|
fprintf(stderr, "Expect errors!\n");
|
|
}
|
|
}
|
|
else if (sig == SIGUSR1) {
|
|
// Ignore
|
|
}
|
|
else {
|
|
// Signal sent directly to process
|
|
// Force a shutdown
|
|
fprintf(stderr, "We are being told to shutdown with SIGTERM\n");
|
|
fprintf(stderr, "Watch out! You might get dangling mount points!\n");
|
|
::SignalShutdown();
|
|
}
|
|
}
|
|
|
|
void SignalShutdown() {
|
|
::ShuttingDown = true;
|
|
// We need to very carefully deconstruct our state to ensure we don't have a race condition
|
|
// First thing to do is to shutdown the lock thread
|
|
// Race here where the application opens the lock file, reads the mount directory while the file is getting deleted
|
|
// Nothing we can really do on our side
|
|
INotifyWatcher::SignalShutdown();
|
|
|
|
// Now that we have shutdown the lock file. No more NEW clients of FEX will find this daemon
|
|
// Next race is that any clients that managed to read the lock file before it was erased can find the socket
|
|
// for this rootfs daemon
|
|
// Depending on when the socket closes either the client will fail to send an FD to us OR
|
|
// we receive the FD and start watching it
|
|
//
|
|
// On failure to send FD, the client needs to retry FEXMountDaemon mounting
|
|
SocketWatcher::SignalShutdown();
|
|
|
|
// Time to handle the tail end of the last race condition
|
|
// In the case of FEX sending an FD while SocketWatcher is shutting down
|
|
//
|
|
// Shutdown()
|
|
// -> INotifyWatcher::Shutdown()
|
|
// -> SocketWatcher:: Receives FD
|
|
// -> EPollWatcher::SignalShutdown
|
|
// -> EPollWatcher::EPollWatch keeps running until all FDs leave
|
|
EPollWatcher::SignalShutdown();
|
|
}
|
|
|
|
}
|
|
|
|
int main(int argc, char **argv, char **envp) {
|
|
if (argc < 4) {
|
|
fprintf(stderr, "usage: %s <SquashFS> <MountPoint> <pipe wr>\n", argv[0]);
|
|
return -1;
|
|
}
|
|
|
|
pid_t pid = fork();
|
|
if (pid != 0) {
|
|
// Parent is leaving to force this process to deparent itself
|
|
// This lets this process become the child of whatever the reaper parent is
|
|
// Do this up front so a FEXInterptreter instance doesn't erroneously pick up a SIGCHLD
|
|
return 0;
|
|
}
|
|
|
|
const char *SquashFSPath = argv[1];
|
|
const char *MountPath = argv[2];
|
|
int pipe_wr = std::atoi(argv[3]);
|
|
|
|
// Setup our signal handlers now so we can capture some events
|
|
struct sigaction act{};
|
|
act.sa_sigaction = ActionHandler;
|
|
act.sa_flags = SA_SIGINFO;
|
|
|
|
// SIGCHLD if squashfuse exits early
|
|
sigaction(SIGCHLD, &act, nullptr);
|
|
// SIGTERM if something is trying to terminate us
|
|
sigaction(SIGTERM, &act, nullptr);
|
|
// SIGUSR1 just to interrupt syscalls
|
|
sigaction(SIGUSR1, &act, nullptr);
|
|
|
|
// Ignore SIGPIPE, we will be checking for pipe closure which could send this signal
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
// Start the epoll watcher
|
|
EPollWatcher::SetupEPoll();
|
|
|
|
// Switch this process over to a new session id
|
|
// Probably not required but allows this to become the process group leader of its session
|
|
::setsid();
|
|
|
|
// Create local FDs so our internal forks can communicate
|
|
int localfds[2];
|
|
pipe2(localfds, 0);
|
|
|
|
::prctl(PR_SET_CHILD_SUBREAPER, 1);
|
|
|
|
struct utsname uts{};
|
|
uname (&uts);
|
|
|
|
std::string LockPath = "/tmp/.FEX-";
|
|
LockPath += std::filesystem::path(SquashFSPath).filename();
|
|
LockPath += ".lock.";
|
|
LockPath += uts.nodename;
|
|
|
|
std::string SocketPath = MountPath;
|
|
SocketPath += ".socket";
|
|
|
|
if (!SocketWatcher::SetupSocketWatcher(SocketPath)) {
|
|
EPollWatcher::ShutdownEPoll();
|
|
return -1;
|
|
}
|
|
|
|
// Use lock files to ensure we aren't racing to mount multiple FSes
|
|
auto Failure = INotifyWatcher::CreateINotifyLock(LockPath, MountPath);
|
|
if (Failure == INotifyWatcher::LOCK_FAIL_FATAL) {
|
|
EPollWatcher::ShutdownEPoll();
|
|
// Shutdown the signal watcher
|
|
SocketWatcher::ShutdownSocketWatcher();
|
|
return -1;
|
|
}
|
|
|
|
if (Failure == INotifyWatcher::LOCK_FAIL_EXISTS ||
|
|
Failure == INotifyWatcher::LOCK_FAIL_CREATION_RACE) {
|
|
EPollWatcher::ShutdownEPoll();
|
|
// Shutdown the signal watcher
|
|
SocketWatcher::ShutdownSocketWatcher();
|
|
|
|
// If the lock already exists
|
|
// Then we don't need to spin up the mounts at all
|
|
// Cleanly exit early and let FEX know it can continue
|
|
uint64_t c = 0;
|
|
write(pipe_wr, &c, sizeof(c));
|
|
return 0;
|
|
}
|
|
|
|
pid = fork();
|
|
if (pid == 0) {
|
|
// Child
|
|
close(localfds[0]); // Close read side
|
|
const char *argv[4];
|
|
argv[0] = "squashfuse";
|
|
argv[1] = SquashFSPath;
|
|
argv[2] = MountPath;
|
|
argv[3] = nullptr;
|
|
|
|
// Try and execute squashfuse to mount our rootfs
|
|
if (execvpe(argv[0], (char * const*)argv, envp) == -1) {
|
|
// Let the parent know that we couldn't execute for some reason
|
|
uint64_t error{1};
|
|
write(localfds[1], &error, sizeof(error));
|
|
|
|
// Give a hopefully helpful error message for users
|
|
fprintf(stderr, "'%s' Couldn't execute for some reason: %d %s\n", argv[0], errno, strerror(errno));
|
|
fprintf(stderr, "To mount squashfs rootfs files you need squashfuse installed\n");
|
|
fprintf(stderr, "Check your FUSE setup.\n");
|
|
|
|
// End the child
|
|
exit(1);
|
|
}
|
|
}
|
|
else {
|
|
// Parent
|
|
close(localfds[1]); // Close the write side
|
|
// Wait for the child to exit
|
|
// This will happen with execvpe of squashmount or exit on failure
|
|
waitpid(pid, nullptr, 0);
|
|
|
|
// Check the child pipe for messages
|
|
pollfd PollFD;
|
|
PollFD.fd = localfds[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(MountPath);
|
|
|
|
// Tell FEX that we encountered an issue
|
|
uint64_t c = 1;
|
|
write(pipe_wr, &c, sizeof(c));
|
|
return 1;
|
|
}
|
|
|
|
// Close the pipe now
|
|
close(localfds[0]);
|
|
|
|
// Tell FEX that it can continue booting
|
|
// FEX will stall on opening the lock file until we let go of the lease
|
|
uint64_t c = 0;
|
|
write(pipe_wr, &c, sizeof(c));
|
|
|
|
// Watch our lock file now for users
|
|
// Blocks here until no more users
|
|
INotifyWatcher::WatchLock();
|
|
|
|
// Shutdown the socket watcher fully if it still hasn't
|
|
SocketWatcher::ShutdownSocketWatcher();
|
|
|
|
// Shutdown and spin on any epoll FDs remaining
|
|
EPollWatcher::ShutdownEPoll();
|
|
|
|
// Handle final mount removal
|
|
// fusermount for unmounting the mountpoint, then the squashfuse will exit automatically
|
|
pid = fork();
|
|
|
|
if (pid == 0) {
|
|
const char *argv[5];
|
|
argv[0] = "fusermount";
|
|
argv[1] = "-u";
|
|
argv[2] = "-q";
|
|
argv[3] = MountPath;
|
|
argv[4] = nullptr;
|
|
|
|
if (execvpe(argv[0], (char * const*)argv, envp) == -1) {
|
|
fprintf(stderr, "fusermount failed to execute. You may have an mount living at '%s' to clean up now\n", SquashFSPath);
|
|
fprintf(stderr, "Try `%s %s %s %s`\n", argv[0], argv[1], argv[2], argv[3]);
|
|
exit(1);
|
|
}
|
|
}
|
|
else {
|
|
// Wait for fusermount to leave
|
|
waitpid(pid, nullptr, 0);
|
|
|
|
// Remove the mount path and leave
|
|
rmdir(MountPath);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|