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>
This commit is contained in:
Paul Elder 2019-07-10 03:18:01 +09:00
parent 099815b853
commit 3d20beca66
6 changed files with 527 additions and 0 deletions

View file

@ -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__ */

View file

@ -21,6 +21,7 @@ libcamera_sources = files([
'message.cpp', 'message.cpp',
'object.cpp', 'object.cpp',
'pipeline_handler.cpp', 'pipeline_handler.cpp',
'process.cpp',
'request.cpp', 'request.cpp',
'signal.cpp', 'signal.cpp',
'stream.cpp', 'stream.cpp',
@ -48,6 +49,7 @@ libcamera_headers = files([
'include/media_object.h', 'include/media_object.h',
'include/message.h', 'include/message.h',
'include/pipeline_handler.h', 'include/pipeline_handler.h',
'include/process.h',
'include/thread.h', 'include/thread.h',
'include/utils.h', 'include/utils.h',
'include/v4l2_device.h', 'include/v4l2_device.h',

357
src/libcamera/process.cpp Normal file
View file

@ -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 */

View file

@ -6,6 +6,7 @@ subdir('ipa')
subdir('ipc') subdir('ipc')
subdir('media_device') subdir('media_device')
subdir('pipeline') subdir('pipeline')
subdir('process')
subdir('stream') subdir('stream')
subdir('v4l2_subdevice') subdir('v4l2_subdevice')
subdir('v4l2_videodevice') subdir('v4l2_videodevice')

12
test/process/meson.build Normal file
View file

@ -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

View file

@ -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();
}