summaryrefslogtreecommitdiff
path: root/src/libcamera/process.cpp
diff options
context:
space:
mode:
authorPaul Elder <paul.elder@ideasonboard.com>2019-07-10 03:18:01 +0900
committerPaul Elder <paul.elder@ideasonboard.com>2019-07-12 16:32:29 +0900
commit3d20beca6616095b2bc2952d645e7562410e79e5 (patch)
tree7704496cfd2757a9af2b8616da4bb6c07a6df679 /src/libcamera/process.cpp
parent099815b85377ac68147e789f491ef26c57da3956 (diff)
libcamera: Add Process and ProcessManager classes
Add a Process class to abstract a process, and a ProcessManager singleton to monitor and manage the processes. Signed-off-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Diffstat (limited to 'src/libcamera/process.cpp')
-rw-r--r--src/libcamera/process.cpp357
1 files changed, 357 insertions, 0 deletions
diff --git a/src/libcamera/process.cpp b/src/libcamera/process.cpp
new file mode 100644
index 00000000..9d8829d0
--- /dev/null
+++ b/src/libcamera/process.cpp
@@ -0,0 +1,357 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * process.cpp - Process object
+ */
+
+#include "process.h"
+
+#include <algorithm>
+#include <dirent.h>
+#include <fcntl.h>
+#include <iostream>
+#include <list>
+#include <signal.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <vector>
+
+#include <libcamera/event_notifier.h>
+
+#include "log.h"
+#include "utils.h"
+
+/**
+ * \file process.h
+ * \brief Process object
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Process)
+
+/**
+ * \class ProcessManager
+ * \brief Manager of processes
+ *
+ * The ProcessManager singleton keeps track of all created Process instances,
+ * and manages the signal handling involved in terminating processes.
+ */
+class ProcessManager
+{
+public:
+ void registerProcess(Process *proc);
+
+ static ProcessManager *instance();
+
+ int writePipe() const;
+
+ const struct sigaction &oldsa() const;
+
+private:
+ void sighandler(EventNotifier *notifier);
+ ProcessManager();
+ ~ProcessManager();
+
+ std::list<Process *> processes_;
+
+ struct sigaction oldsa_;
+ EventNotifier *sigEvent_;
+ int pipe_[2];
+};
+
+namespace {
+
+void sigact(int signal, siginfo_t *info, void *ucontext)
+{
+ char data = 0;
+ write(ProcessManager::instance()->writePipe(), &data, sizeof(data));
+
+ const struct sigaction &oldsa = ProcessManager::instance()->oldsa();
+ if (oldsa.sa_flags & SA_SIGINFO) {
+ oldsa.sa_sigaction(signal, info, ucontext);
+ } else {
+ if (oldsa.sa_handler != SIG_IGN && oldsa.sa_handler != SIG_DFL)
+ oldsa.sa_handler(signal);
+ }
+}
+
+} /* namespace */
+
+void ProcessManager::sighandler(EventNotifier *notifier)
+{
+ char data;
+ read(pipe_[0], &data, sizeof(data));
+
+ for (auto it = processes_.begin(); it != processes_.end(); ) {
+ Process *process = *it;
+
+ int wstatus;
+ pid_t pid = waitpid(process->pid_, &wstatus, WNOHANG);
+ if (process->pid_ != pid) {
+ ++it;
+ continue;
+ }
+
+ it = processes_.erase(it);
+ process->died(wstatus);
+ }
+}
+
+/**
+ * \brief Register process with process manager
+ * \param[in] proc Process to register
+ *
+ * This method registers the \a proc with the process manager. It
+ * shall be called by the parent process after successfully forking, in
+ * order to let the parent signal process termination.
+ */
+void ProcessManager::registerProcess(Process *proc)
+{
+ processes_.push_back(proc);
+}
+
+ProcessManager::ProcessManager()
+{
+ sigaction(SIGCHLD, NULL, &oldsa_);
+
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = &sigact;
+ memcpy(&sa.sa_mask, &oldsa_.sa_mask, sizeof(sa.sa_mask));
+ sigaddset(&sa.sa_mask, SIGCHLD);
+ sa.sa_flags = oldsa_.sa_flags | SA_SIGINFO;
+
+ sigaction(SIGCHLD, &sa, NULL);
+
+ pipe2(pipe_, O_CLOEXEC | O_DIRECT | O_NONBLOCK);
+ sigEvent_ = new EventNotifier(pipe_[0], EventNotifier::Read);
+ sigEvent_->activated.connect(this, &ProcessManager::sighandler);
+}
+
+ProcessManager::~ProcessManager()
+{
+ sigaction(SIGCHLD, &oldsa_, NULL);
+ delete sigEvent_;
+ close(pipe_[0]);
+ close(pipe_[1]);
+}
+
+/**
+ * \brief Retrieve the Process manager instance
+ *
+ * The ProcessManager is a singleton and can't be constructed manually. This
+ * method shall instead be used to retrieve the single global instance of the
+ * manager.
+ *
+ * \return The Process manager instance
+ */
+ProcessManager *ProcessManager::instance()
+{
+ static ProcessManager processManager;
+ return &processManager;
+}
+
+/**
+ * \brief Retrieve the Process manager's write pipe
+ *
+ * This method is meant only to be used by the static signal handler.
+ *
+ * \return Pipe for writing
+ */
+int ProcessManager::writePipe() const
+{
+ return pipe_[1];
+}
+
+/**
+ * \brief Retrive the old signal action data
+ *
+ * This method is meant only to be used by the static signal handler.
+ *
+ * \return The old signal action data
+ */
+const struct sigaction &ProcessManager::oldsa() const
+{
+ return oldsa_;
+}
+
+
+/**
+ * \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), running_(false), 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,
+ const std::vector<std::string> &args,
+ const std::vector<int> &fds)
+{
+ int ret;
+
+ if (running_)
+ return 0;
+
+ int childPid = fork();
+ if (childPid == -1) {
+ ret = -errno;
+ LOG(Process, Error) << "Failed to fork: " << strerror(-ret);
+ return ret;
+ } else if (childPid) {
+ pid_ = childPid;
+ ProcessManager::instance()->registerProcess(this);
+
+ running_ = true;
+
+ return 0;
+ } else {
+ if (isolate())
+ _exit(EXIT_FAILURE);
+
+ closeAllFdsExcept(fds);
+
+ unsetenv("LIBCAMERA_LOG_FILE");
+
+ const char **argv = new const char *[args.size() + 2];
+ unsigned int len = args.size();
+ argv[0] = path.c_str();
+ for (unsigned int i = 0; i < len; i++)
+ argv[i+1] = args[i].c_str();
+ argv[len+1] = nullptr;
+
+ execv(path.c_str(), (char **)argv);
+
+ exit(EXIT_FAILURE);
+ }
+}
+
+void Process::closeAllFdsExcept(const std::vector<int> &fds)
+{
+ std::vector<int> v(fds);
+ sort(v.begin(), v.end());
+
+ 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);
+}
+
+int Process::isolate()
+{
+ return unshare(CLONE_NEWUSER | CLONE_NEWNET);
+}
+
+/**
+ * \brief SIGCHLD handler
+ * \param[in] wstatus The status as output by waitpid()
+ *
+ * This method is called when the process associated with Process terminates.
+ * It emits the Process::finished signal.
+ */
+void Process::died(int wstatus)
+{
+ running_ = false;
+ exitStatus_ = WIFEXITED(wstatus) ? NormalExit : SignalExit;
+ exitCode_ = exitStatus_ == NormalExit ? WEXITSTATUS(wstatus) : -1;
+
+ finished.emit(this, exitStatus_, exitCode_);
+}
+
+/**
+ * \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 method 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()
+{
+ ::kill(pid_, SIGKILL);
+}
+
+} /* namespace libcamera */