summaryrefslogtreecommitdiff
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
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>
-rw-r--r--src/libcamera/include/process.h55
-rw-r--r--src/libcamera/meson.build2
-rw-r--r--src/libcamera/process.cpp357
-rw-r--r--test/meson.build1
-rw-r--r--test/process/meson.build12
-rw-r--r--test/process/process_test.cpp100
6 files changed, 527 insertions, 0 deletions
diff --git a/src/libcamera/include/process.h b/src/libcamera/include/process.h
new file mode 100644
index 00000000..d322fce1
--- /dev/null
+++ b/src/libcamera/include/process.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * process.h - Process object
+ */
+#ifndef __LIBCAMERA_PROCESS_H__
+#define __LIBCAMERA_PROCESS_H__
+
+#include <string>
+#include <vector>
+
+#include <libcamera/event_notifier.h>
+
+namespace libcamera {
+
+class Process final
+{
+public:
+ enum ExitStatus {
+ NotExited,
+ NormalExit,
+ SignalExit,
+ };
+
+ Process();
+ ~Process();
+
+ int start(const std::string &path,
+ const std::vector<std::string> &args = std::vector<std::string>(),
+ const std::vector<int> &fds = std::vector<int>());
+
+ ExitStatus exitStatus() const { return exitStatus_; }
+ int exitCode() const { return exitCode_; }
+
+ void kill();
+
+ Signal<Process *, enum ExitStatus, int> finished;
+
+private:
+ void closeAllFdsExcept(const std::vector<int> &fds);
+ int isolate();
+ void died(int wstatus);
+
+ pid_t pid_;
+ bool running_;
+ enum ExitStatus exitStatus_;
+ int exitCode_;
+
+ friend class ProcessManager;
+};
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_PROCESS_H__ */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index eda506b2..9eae6ce8 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -21,6 +21,7 @@ libcamera_sources = files([
'message.cpp',
'object.cpp',
'pipeline_handler.cpp',
+ 'process.cpp',
'request.cpp',
'signal.cpp',
'stream.cpp',
@@ -48,6 +49,7 @@ libcamera_headers = files([
'include/media_object.h',
'include/message.h',
'include/pipeline_handler.h',
+ 'include/process.h',
'include/thread.h',
'include/utils.h',
'include/v4l2_device.h',
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 */
diff --git a/test/meson.build b/test/meson.build
index d308ac9c..ad1a2f2a 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -6,6 +6,7 @@ subdir('ipa')
subdir('ipc')
subdir('media_device')
subdir('pipeline')
+subdir('process')
subdir('stream')
subdir('v4l2_subdevice')
subdir('v4l2_videodevice')
diff --git a/test/process/meson.build b/test/process/meson.build
new file mode 100644
index 00000000..c4d83d6c
--- /dev/null
+++ b/test/process/meson.build
@@ -0,0 +1,12 @@
+process_tests = [
+ [ 'process_test', 'process_test.cpp' ],
+]
+
+foreach t : process_tests
+ exe = executable(t[0], t[1],
+ dependencies : libcamera_dep,
+ link_with : test_libraries,
+ include_directories : test_includes_internal)
+
+ test(t[0], exe, suite : 'process', is_parallel : false)
+endforeach
diff --git a/test/process/process_test.cpp b/test/process/process_test.cpp
new file mode 100644
index 00000000..acb16145
--- /dev/null
+++ b/test/process/process_test.cpp
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * process_test.cpp - Process test
+ */
+
+#include <iostream>
+#include <unistd.h>
+#include <vector>
+
+#include <libcamera/camera_manager.h>
+#include <libcamera/event_dispatcher.h>
+#include <libcamera/timer.h>
+
+#include "process.h"
+#include "test.h"
+#include "utils.h"
+
+using namespace std;
+using namespace libcamera;
+
+class ProcessTestChild
+{
+public:
+ int run(int status)
+ {
+ usleep(50000);
+
+ return status;
+ }
+};
+
+class ProcessTest : public Test
+{
+public:
+ ProcessTest()
+ {
+ }
+
+protected:
+ int run()
+ {
+ EventDispatcher *dispatcher = CameraManager::instance()->eventDispatcher();
+ Timer timeout;
+
+ int exitCode = 42;
+ vector<std::string> args;
+ args.push_back(to_string(exitCode));
+ int ret = proc_.start("/proc/self/exe", args);
+ if (ret) {
+ cerr << "failed to start process" << endl;
+ return TestFail;
+ }
+ proc_.finished.connect(this, &ProcessTest::procFinished);
+
+ timeout.start(100);
+ while (timeout.isRunning())
+ dispatcher->processEvents();
+
+ if (exitStatus_ != Process::NormalExit) {
+ cerr << "process did not exit normally" << endl;
+ return TestFail;
+ }
+
+ if (exitCode != exitCode_) {
+ cerr << "exit code should be " << exitCode
+ << ", actual is " << exitCode_ << endl;
+ return TestFail;
+ }
+
+ return TestPass;
+ }
+
+private:
+ void procFinished(Process *proc, enum Process::ExitStatus exitStatus, int exitCode)
+ {
+ exitStatus_ = exitStatus;
+ exitCode_ = exitCode;
+ }
+
+ Process proc_;
+ enum Process::ExitStatus exitStatus_;
+ int exitCode_;
+};
+
+/*
+ * Can't use TEST_REGISTER() as single binary needs to act as both
+ * parent and child processes.
+ */
+int main(int argc, char **argv)
+{
+ if (argc == 2) {
+ int status = std::stoi(argv[1]);
+ ProcessTestChild child;
+ return child.run(status);
+ }
+
+ return ProcessTest().execute();
+}