The `ProcessManager` is a singleton class to handle `SIGCHLD` signals and report the exit status to the particular `Process` instance. However, having a singleton in a library is not favourable and it is even less favourable if it installs a signal handler. Using pidfd it is possible to avoid the need for the signal handler; and the `Process` objects can watch their pidfd themselves, eliminating the need for the `ProcessManager` class altogether. `P_PIDFD` for `waitid()` was introduced in Linux 5.4, so this change raises the minimum supported kernel version. `clone3()`, `CLONE_PIDFD`, `pidfd_send_signal()` were all introduced earlier. Furthermore, the call to the `unshare()` system call can be removed as those options can be passed to `clone3()` directly. Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
288 lines
6.6 KiB
C++
288 lines
6.6 KiB
C++
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
/*
|
|
* Copyright (C) 2019, Google Inc.
|
|
*
|
|
* Process object
|
|
*/
|
|
|
|
#include "libcamera/internal/process.h"
|
|
|
|
#include <algorithm>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <linux/sched.h>
|
|
#include <linux/wait.h> /* glibc only provides `P_PIDFD` in `sys/wait.h` since 2.36 */
|
|
|
|
#include <libcamera/base/event_notifier.h>
|
|
#include <libcamera/base/log.h>
|
|
#include <libcamera/base/utils.h>
|
|
|
|
/**
|
|
* \file process.h
|
|
* \brief Process object
|
|
*/
|
|
|
|
namespace libcamera {
|
|
|
|
LOG_DEFINE_CATEGORY(Process)
|
|
|
|
namespace {
|
|
|
|
void closeAllFdsExcept(std::vector<int> v)
|
|
{
|
|
sort(v.begin(), v.end());
|
|
|
|
ASSERT(v.empty() || v.front() >= 0);
|
|
|
|
#if HAVE_CLOSE_RANGE
|
|
/*
|
|
* At the moment libcamera does not require at least Linux 5.9,
|
|
* which introduced the `close_range()` system call, so a runtime
|
|
* check is also needed to make sure that it is supported.
|
|
*/
|
|
static const bool hasCloseRange = [] {
|
|
return close_range(~0u, 0, 0) < 0 && errno == EINVAL;
|
|
}();
|
|
|
|
if (hasCloseRange) {
|
|
unsigned int prev = 0;
|
|
|
|
for (unsigned int curr : v) {
|
|
ASSERT(prev <= curr + 1);
|
|
if (prev < curr)
|
|
close_range(prev, curr - 1, 0);
|
|
prev = curr + 1;
|
|
}
|
|
|
|
close_range(prev, ~0u, 0);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
DIR *dir = opendir("/proc/self/fd");
|
|
if (!dir)
|
|
return;
|
|
|
|
int dfd = dirfd(dir);
|
|
|
|
struct dirent *ent;
|
|
while ((ent = readdir(dir)) != nullptr) {
|
|
char *endp;
|
|
int fd = strtoul(ent->d_name, &endp, 10);
|
|
if (*endp)
|
|
continue;
|
|
|
|
if (fd >= 0 && fd != dfd &&
|
|
!std::binary_search(v.begin(), v.end(), fd))
|
|
close(fd);
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
} /* namespace */
|
|
|
|
/**
|
|
* \class Process
|
|
* \brief Process object
|
|
*
|
|
* The Process class models a process, and simplifies spawning new processes
|
|
* and monitoring the exiting of a process.
|
|
*/
|
|
|
|
/**
|
|
* \enum Process::ExitStatus
|
|
* \brief Exit status of process
|
|
* \var Process::NotExited
|
|
* The process hasn't exited yet
|
|
* \var Process::NormalExit
|
|
* The process exited normally, either via exit() or returning from main
|
|
* \var Process::SignalExit
|
|
* The process was terminated by a signal (this includes crashing)
|
|
*/
|
|
|
|
Process::Process()
|
|
: pid_(-1), exitStatus_(NotExited), exitCode_(0)
|
|
{
|
|
}
|
|
|
|
Process::~Process()
|
|
{
|
|
kill();
|
|
/* \todo wait for child process to exit */
|
|
}
|
|
|
|
/**
|
|
* \brief Fork and exec a process, and close fds
|
|
* \param[in] path Path to executable
|
|
* \param[in] args Arguments to pass to executable (optional)
|
|
* \param[in] fds Vector of file descriptors to keep open (optional)
|
|
*
|
|
* Fork a process, and exec the executable specified by path. Prior to
|
|
* exec'ing, but after forking, all file descriptors except for those
|
|
* specified in fds will be closed.
|
|
*
|
|
* All indexes of args will be incremented by 1 before being fed to exec(),
|
|
* so args[0] should not need to be equal to path.
|
|
*
|
|
* \return Zero on successful fork, exec, and closing the file descriptors,
|
|
* or a negative error code otherwise
|
|
*/
|
|
int Process::start(const std::string &path,
|
|
Span<const std::string> args,
|
|
Span<const int> fds)
|
|
{
|
|
if (pid_ > 0)
|
|
return -EBUSY;
|
|
|
|
for (int fd : fds) {
|
|
if (fd < 0)
|
|
return -EINVAL;
|
|
}
|
|
|
|
clone_args cargs = {};
|
|
int pidfd;
|
|
|
|
cargs.flags = CLONE_PIDFD | CLONE_NEWUSER | CLONE_NEWNET;
|
|
cargs.pidfd = reinterpret_cast<uintptr_t>(&pidfd);
|
|
cargs.exit_signal = SIGCHLD;
|
|
|
|
long childPid = syscall(SYS_clone3, &cargs, sizeof(cargs));
|
|
if (childPid < 0) {
|
|
int ret = -errno;
|
|
LOG(Process, Error) << "Failed to fork: " << strerror(-ret);
|
|
return ret;
|
|
}
|
|
|
|
if (childPid) {
|
|
pid_ = childPid;
|
|
pidfd_ = UniqueFD(pidfd);
|
|
pidfdNotify_ = std::make_unique<EventNotifier>(pidfd_.get(), EventNotifier::Type::Read);
|
|
pidfdNotify_->activated.connect(this, &Process::onPidfdNotify);
|
|
|
|
LOG(Process, Debug) << this << "[" << childPid << ':' << pidfd << "]"
|
|
<< " forked";
|
|
} else {
|
|
std::vector<int> v(fds.begin(), fds.end());
|
|
v.push_back(STDERR_FILENO);
|
|
closeAllFdsExcept(std::move(v));
|
|
|
|
const auto tryDevNullLowestFd = [](int expected, int oflag) {
|
|
int fd = open("/dev/null", oflag);
|
|
if (fd < 0)
|
|
_exit(EXIT_FAILURE);
|
|
if (fd != expected)
|
|
close(fd);
|
|
};
|
|
|
|
tryDevNullLowestFd(STDIN_FILENO, O_RDONLY);
|
|
tryDevNullLowestFd(STDOUT_FILENO, O_WRONLY);
|
|
tryDevNullLowestFd(STDERR_FILENO, O_WRONLY);
|
|
|
|
const char *file = utils::secure_getenv("LIBCAMERA_LOG_FILE");
|
|
if (file && strcmp(file, "syslog"))
|
|
unsetenv("LIBCAMERA_LOG_FILE");
|
|
|
|
const size_t len = args.size();
|
|
auto argv = std::make_unique<const char *[]>(len + 2);
|
|
|
|
argv[0] = path.c_str();
|
|
for (size_t i = 0; i < len; i++)
|
|
argv[i + 1] = args[i].c_str();
|
|
argv[len + 1] = nullptr;
|
|
|
|
execv(path.c_str(), const_cast<char **>(argv.get()));
|
|
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Process::onPidfdNotify()
|
|
{
|
|
auto pidfdNotify = std::exchange(pidfdNotify_, {});
|
|
auto pidfd = std::exchange(pidfd_, {});
|
|
auto pid = std::exchange(pid_, -1);
|
|
|
|
ASSERT(pidfdNotify);
|
|
ASSERT(pidfd.isValid());
|
|
ASSERT(pid > 0);
|
|
|
|
siginfo_t info;
|
|
|
|
/*
|
|
* `static_cast` is needed because `P_PIDFD` is not defined in `sys/wait.h` if the C standard library
|
|
* is old enough. So `P_PIDFD` is taken from `linux/wait.h`, where it is just an integer #define.
|
|
*/
|
|
if (waitid(static_cast<idtype_t>(P_PIDFD), pidfd.get(), &info, WNOHANG | WEXITED) >= 0) {
|
|
ASSERT(info.si_pid == pid);
|
|
|
|
LOG(Process, Debug)
|
|
<< this << "[" << pid << ':' << pidfd.get() << "]"
|
|
<< " code: " << info.si_code
|
|
<< " status: " << info.si_status;
|
|
|
|
exitStatus_ = info.si_code == CLD_EXITED ? Process::NormalExit : Process::SignalExit;
|
|
exitCode_ = info.si_code == CLD_EXITED ? info.si_status : -1;
|
|
|
|
finished.emit(exitStatus_, exitCode_);
|
|
} else {
|
|
int err = errno;
|
|
LOG(Process, Warning)
|
|
<< this << "[" << pid << ":" << pidfd.get() << "]"
|
|
<< " waitid() failed: " << strerror(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \fn Process::exitStatus()
|
|
* \brief Retrieve the exit status of the process
|
|
*
|
|
* Return the exit status of the process, that is, whether the process
|
|
* has exited via exit() or returning from main, or if the process was
|
|
* terminated by a signal.
|
|
*
|
|
* \sa ExitStatus
|
|
*
|
|
* \return The process exit status
|
|
*/
|
|
|
|
/**
|
|
* \fn Process::exitCode()
|
|
* \brief Retrieve the exit code of the process
|
|
*
|
|
* This function is only valid if exitStatus() returned NormalExit.
|
|
*
|
|
* \return Exit code
|
|
*/
|
|
|
|
/**
|
|
* \var Process::finished
|
|
*
|
|
* Signal that is emitted when the process is confirmed to have terminated.
|
|
*/
|
|
|
|
/**
|
|
* \brief Kill the process
|
|
*
|
|
* Sends SIGKILL to the process.
|
|
*/
|
|
void Process::kill()
|
|
{
|
|
if (pidfd_.isValid())
|
|
syscall(SYS_pidfd_send_signal, pidfd_.get(), SIGKILL, nullptr, 0);
|
|
}
|
|
|
|
} /* namespace libcamera */
|