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:
parent
099815b853
commit
3d20beca66
6 changed files with 527 additions and 0 deletions
55
src/libcamera/include/process.h
Normal file
55
src/libcamera/include/process.h
Normal 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__ */
|
|
@ -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',
|
||||
|
|
357
src/libcamera/process.cpp
Normal file
357
src/libcamera/process.cpp
Normal 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 */
|
|
@ -6,6 +6,7 @@ subdir('ipa')
|
|||
subdir('ipc')
|
||||
subdir('media_device')
|
||||
subdir('pipeline')
|
||||
subdir('process')
|
||||
subdir('stream')
|
||||
subdir('v4l2_subdevice')
|
||||
subdir('v4l2_videodevice')
|
||||
|
|
12
test/process/meson.build
Normal file
12
test/process/meson.build
Normal 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
|
100
test/process/process_test.cpp
Normal file
100
test/process/process_test.cpp
Normal 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();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue