Move ScopedPipe/Application classes out of starterd_lib.*
Change-Id: I1b66ef343b6d4d1129fdc8d40781d1e5b711d2b2
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/starter/BUILD b/aos/starter/BUILD
index ec323a9..f998a93 100644
--- a/aos/starter/BUILD
+++ b/aos/starter/BUILD
@@ -23,6 +23,21 @@
)
cc_library(
+ name = "subprocess",
+ srcs = ["subprocess.cc"],
+ hdrs = ["subprocess.h"],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":starter_fbs",
+ ":starter_rpc_fbs",
+ "//aos/events:event_loop",
+ "//aos/events:shm_event_loop",
+ "//aos/util:scoped_pipe",
+ "@com_github_google_glog//:glog",
+ ],
+)
+
+cc_library(
name = "starterd_lib",
srcs = ["starterd_lib.cc"],
hdrs = ["starterd_lib.h"],
@@ -30,6 +45,7 @@
deps = [
":starter_fbs",
":starter_rpc_fbs",
+ ":subprocess",
"//aos:configuration",
"//aos:macros",
"//aos/events:shm_event_loop",
diff --git a/aos/starter/starterd_lib.cc b/aos/starter/starterd_lib.cc
index f280282..03b40c8 100644
--- a/aos/starter/starterd_lib.cc
+++ b/aos/starter/starterd_lib.cc
@@ -1,11 +1,5 @@
#include "starterd_lib.h"
-#include <fcntl.h>
-#include <grp.h>
-#include <pwd.h>
-#include <sys/fsuid.h>
-#include <sys/prctl.h>
-
#include <algorithm>
#include <utility>
@@ -17,425 +11,6 @@
namespace aos {
namespace starter {
-Application::Application(const aos::Application *application,
- aos::ShmEventLoop *event_loop,
- std::function<void()> on_change)
- : name_(application->name()->string_view()),
- path_(application->has_executable_name()
- ? application->executable_name()->string_view()
- : application->name()->string_view()),
- args_(1),
- user_name_(application->has_user() ? application->user()->str() : ""),
- user_(application->has_user() ? FindUid(user_name_.c_str())
- : std::nullopt),
- group_(application->has_user() ? FindPrimaryGidForUser(user_name_.c_str())
- : std::nullopt),
- autostart_(application->autostart()),
- autorestart_(application->autorestart()),
- event_loop_(event_loop),
- start_timer_(event_loop_->AddTimer([this] {
- status_ = aos::starter::State::RUNNING;
- LOG(INFO) << "Started '" << name_ << "' pid: " << pid_;
- })),
- restart_timer_(event_loop_->AddTimer([this] { DoStart(); })),
- stop_timer_(event_loop_->AddTimer([this] {
- if (kill(pid_, SIGKILL) == 0) {
- LOG(WARNING) << "Failed to stop, sending SIGKILL to '" << name_
- << "' pid: " << pid_;
- }
- })),
- on_change_(on_change) {}
-
-void Application::DoStart() {
- if (status_ != aos::starter::State::WAITING) {
- return;
- }
-
- start_timer_->Disable();
- restart_timer_->Disable();
-
- std::tie(read_pipe_, write_pipe_) = ScopedPipe::MakePipe();
-
- const pid_t pid = fork();
-
- if (pid != 0) {
- if (pid == -1) {
- PLOG(WARNING) << "Failed to fork '" << name_ << "'";
- stop_reason_ = aos::starter::LastStopReason::FORK_ERR;
- status_ = aos::starter::State::STOPPED;
- } else {
- pid_ = pid;
- id_ = next_id_++;
- start_time_ = event_loop_->monotonic_now();
- status_ = aos::starter::State::STARTING;
- LOG(INFO) << "Starting '" << name_ << "' pid " << pid_;
-
- // Setup timer which moves application to RUNNING state if it is still
- // alive in 1 second.
- start_timer_->Setup(event_loop_->monotonic_now() +
- std::chrono::seconds(1));
- }
- on_change_();
- return;
- }
-
- // Clear out signal mask of parent so forked process receives all signals
- // normally.
- sigset_t empty_mask;
- sigemptyset(&empty_mask);
- sigprocmask(SIG_SETMASK, &empty_mask, nullptr);
-
- // Cleanup children if starter dies in a way that is not handled gracefully.
- if (prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) {
- write_pipe_.Write(
- static_cast<uint32_t>(aos::starter::LastStopReason::SET_PRCTL_ERR));
- PLOG(FATAL) << "Could not set PR_SET_PDEATHSIG to SIGKILL";
- }
-
- if (group_) {
- CHECK(!user_name_.empty());
- // The manpage for setgroups says we just need CAP_SETGID, but empirically
- // we also need the effective UID to be 0 to make it work. user_ must also
- // be set so we change this effective UID back later.
- CHECK(user_);
- if (seteuid(0) == -1) {
- write_pipe_.Write(
- static_cast<uint32_t>(aos::starter::LastStopReason::SET_GRP_ERR));
- PLOG(FATAL) << "Could not seteuid(0) for " << name_
- << " in preparation for setting groups";
- }
- if (initgroups(user_name_.c_str(), *group_) == -1) {
- write_pipe_.Write(
- static_cast<uint32_t>(aos::starter::LastStopReason::SET_GRP_ERR));
- PLOG(FATAL) << "Could not initialize normal groups for " << name_
- << " as " << user_name_ << " with " << *group_;
- }
- if (setgid(*group_) == -1) {
- write_pipe_.Write(
- static_cast<uint32_t>(aos::starter::LastStopReason::SET_GRP_ERR));
- PLOG(FATAL) << "Could not set group for " << name_ << " to " << *group_;
- }
- }
-
- if (user_) {
- if (setuid(*user_) == -1) {
- write_pipe_.Write(
- static_cast<uint32_t>(aos::starter::LastStopReason::SET_USR_ERR));
- PLOG(FATAL) << "Could not set user for " << name_ << " to " << *user_;
- }
- }
-
- // argv[0] should be the program name
- args_.insert(args_.begin(), path_.data());
-
- execvp(path_.c_str(), args_.data());
-
- // If we got here, something went wrong
- write_pipe_.Write(
- static_cast<uint32_t>(aos::starter::LastStopReason::EXECV_ERR));
- PLOG(WARNING) << "Could not execute " << name_ << " (" << path_ << ')';
-
- _exit(EXIT_FAILURE);
-}
-
-void Application::DoStop(bool restart) {
- // If stop or restart received, the old state of these is no longer applicable
- // so cancel both.
- restart_timer_->Disable();
- start_timer_->Disable();
-
- switch (status_) {
- case aos::starter::State::STARTING:
- case aos::starter::State::RUNNING: {
- LOG(INFO) << "Stopping '" << name_ << "' pid: " << pid_ << " with signal "
- << SIGINT;
- status_ = aos::starter::State::STOPPING;
-
- kill(pid_, SIGINT);
-
- // Watchdog timer to SIGKILL application if it is still running 1 second
- // after SIGINT
- stop_timer_->Setup(event_loop_->monotonic_now() +
- std::chrono::seconds(1));
- queue_restart_ = restart;
- on_change_();
- break;
- }
- case aos::starter::State::WAITING: {
- // If waiting to restart, and receives restart, skip the waiting period
- // and restart immediately. If stop received, all we have to do is move
- // to the STOPPED state.
- if (restart) {
- DoStart();
- } else {
- status_ = aos::starter::State::STOPPED;
- on_change_();
- }
- break;
- }
- case aos::starter::State::STOPPING: {
- // If the application is already stopping, then we just need to update the
- // restart flag to the most recent status.
- queue_restart_ = restart;
- break;
- }
- case aos::starter::State::STOPPED: {
- // Restart immediately if the application is already stopped
- if (restart) {
- status_ = aos::starter::State::WAITING;
- DoStart();
- }
- break;
- }
- }
-}
-
-void Application::QueueStart() {
- status_ = aos::starter::State::WAITING;
-
- LOG(INFO) << "Restarting " << name_ << " in 3 seconds";
- restart_timer_->Setup(event_loop_->monotonic_now() + std::chrono::seconds(3));
- start_timer_->Disable();
- stop_timer_->Disable();
- on_change_();
-}
-
-void Application::set_args(
- const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> &v) {
- args_.clear();
- std::transform(v.begin(), v.end(), std::back_inserter(args_),
- [](const flatbuffers::String *str) {
- return const_cast<char *>(str->c_str());
- });
- args_.push_back(nullptr);
-}
-
-std::optional<uid_t> Application::FindUid(const char *name) {
- // TODO(austin): Use the reentrant version. This should be safe.
- struct passwd *user_data = getpwnam(name);
- if (user_data != nullptr) {
- return user_data->pw_uid;
- } else {
- LOG(FATAL) << "Could not find user " << name;
- return std::nullopt;
- }
-}
-
-std::optional<gid_t> Application::FindPrimaryGidForUser(const char *name) {
- // TODO(austin): Use the reentrant version. This should be safe.
- struct passwd *user_data = getpwnam(name);
- if (user_data != nullptr) {
- return user_data->pw_gid;
- } else {
- LOG(FATAL) << "Could not find user " << name;
- return std::nullopt;
- }
-}
-
-flatbuffers::Offset<aos::starter::ApplicationStatus>
-Application::PopulateStatus(flatbuffers::FlatBufferBuilder *builder) {
- CHECK_NOTNULL(builder);
- auto name_fbs = builder->CreateString(name_);
-
- aos::starter::ApplicationStatus::Builder status_builder(*builder);
- status_builder.add_name(name_fbs);
- status_builder.add_state(status_);
- status_builder.add_last_exit_code(exit_code_);
- status_builder.add_last_stop_reason(stop_reason_);
- if (pid_ != -1) {
- status_builder.add_pid(pid_);
- status_builder.add_id(id_);
- }
- status_builder.add_last_start_time(start_time_.time_since_epoch().count());
- return status_builder.Finish();
-}
-
-void Application::Terminate() {
- stop_reason_ = aos::starter::LastStopReason::TERMINATE;
- DoStop(false);
- terminating_ = true;
-}
-
-void Application::HandleCommand(aos::starter::Command cmd) {
- switch (cmd) {
- case aos::starter::Command::START: {
- switch (status_) {
- case aos::starter::State::WAITING: {
- restart_timer_->Disable();
- DoStart();
- break;
- }
- case aos::starter::State::STARTING: {
- break;
- }
- case aos::starter::State::RUNNING: {
- break;
- }
- case aos::starter::State::STOPPING: {
- queue_restart_ = true;
- break;
- }
- case aos::starter::State::STOPPED: {
- status_ = aos::starter::State::WAITING;
- DoStart();
- break;
- }
- }
- break;
- }
- case aos::starter::Command::STOP: {
- stop_reason_ = aos::starter::LastStopReason::STOP_REQUESTED;
- DoStop(false);
- break;
- }
- case aos::starter::Command::RESTART: {
- stop_reason_ = aos::starter::LastStopReason::RESTART_REQUESTED;
- DoStop(true);
- break;
- }
- }
-}
-
-bool Application::MaybeHandleSignal() {
- int status;
-
- // Check if the status of this process has changed
- if (pid_ == -1 || waitpid(pid_, &status, WNOHANG) != pid_) {
- return false;
- }
-
- // Check that the event was the process exiting
- if (!WIFEXITED(status) && !WIFSIGNALED(status)) {
- return false;
- }
-
- exit_time_ = event_loop_->monotonic_now();
- exit_code_ = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status);
-
- if (auto read_result = read_pipe_.Read()) {
- stop_reason_ = static_cast<aos::starter::LastStopReason>(*read_result);
- }
-
- switch (status_) {
- case aos::starter::State::STARTING: {
- LOG(WARNING) << "Failed to start '" << name_ << "' on pid " << pid_
- << " : Exited with status " << exit_code_;
- if (autorestart()) {
- QueueStart();
- }
- break;
- }
- case aos::starter::State::RUNNING: {
- if (exit_code_ == 0) {
- LOG(INFO) << "Application '" << name_ << "' pid " << pid_
- << " exited with status " << exit_code_;
- } else {
- LOG(WARNING) << "Application '" << name_ << "' pid " << pid_
- << " exited unexpectedly with status " << exit_code_;
- }
- if (autorestart()) {
- QueueStart();
- }
- break;
- }
- case aos::starter::State::STOPPING: {
- LOG(INFO) << "Successfully stopped '" << name_ << "' pid: " << pid_
- << " with status " << exit_code_;
- status_ = aos::starter::State::STOPPED;
-
- // Disable force stop timer since the process already died
- stop_timer_->Disable();
-
- on_change_();
- if (terminating_) {
- return true;
- }
-
- if (queue_restart_) {
- queue_restart_ = false;
- status_ = aos::starter::State::WAITING;
- DoStart();
- }
- break;
- }
- case aos::starter::State::WAITING:
- case aos::starter::State::STOPPED: {
- LOG(FATAL)
- << "Received signal on process that was already stopped : name: '"
- << name_ << "' pid: " << pid_;
- break;
- }
- }
-
- return false;
-}
-
-ScopedPipe::ScopedPipe(int fd) : fd_(fd) {}
-
-ScopedPipe::~ScopedPipe() {
- if (fd_ != -1) {
- PCHECK(close(fd_) != -1);
- }
-}
-
-ScopedPipe::ScopedPipe(ScopedPipe &&scoped_pipe) : fd_(scoped_pipe.fd_) {
- scoped_pipe.fd_ = -1;
-}
-
-ScopedPipe &ScopedPipe::operator=(ScopedPipe &&scoped_pipe) {
- if (fd_ != -1) {
- PCHECK(close(fd_) != -1);
- }
- fd_ = scoped_pipe.fd_;
- scoped_pipe.fd_ = -1;
- return *this;
-}
-
-std::tuple<ScopedPipe::ScopedReadPipe, ScopedPipe::ScopedWritePipe>
-ScopedPipe::MakePipe() {
- int fds[2];
- PCHECK(pipe(fds) != -1);
- PCHECK(fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK) != -1);
- PCHECK(fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | O_NONBLOCK) != -1);
- return {ScopedReadPipe(fds[0]), ScopedWritePipe(fds[1])};
-}
-
-std::optional<uint32_t> ScopedPipe::ScopedReadPipe::Read() {
- uint32_t buf;
- ssize_t result = read(fd(), &buf, sizeof(buf));
- if (result == sizeof(buf)) {
- return buf;
- } else {
- return std::nullopt;
- }
-}
-
-void ScopedPipe::ScopedWritePipe::Write(uint32_t data) {
- ssize_t result = write(fd(), &data, sizeof(data));
- PCHECK(result != -1);
- CHECK(result == sizeof(data));
-}
-
-SignalListener::SignalListener(aos::ShmEventLoop *loop,
- std::function<void(signalfd_siginfo)> callback)
- : loop_(loop),
- callback_(std::move(callback)),
- signalfd_({SIGHUP, SIGINT, SIGQUIT, SIGABRT, SIGFPE, SIGSEGV, SIGPIPE,
- SIGTERM, SIGBUS, SIGXCPU, SIGCHLD}) {
- loop->epoll()->OnReadable(signalfd_.fd(), [this] {
- signalfd_siginfo info = signalfd_.Read();
-
- if (info.ssi_signo == 0) {
- LOG(WARNING) << "Could not read " << sizeof(signalfd_siginfo) << " bytes";
- return;
- }
-
- callback_(info);
- });
-}
-
-SignalListener::~SignalListener() { loop_->epoll()->DeleteFd(signalfd_.fd()); }
-
const aos::Channel *StatusChannelForNode(const aos::Configuration *config,
const aos::Node *node) {
return configuration::GetChannel<Status>(config, "/aos", "", node);
diff --git a/aos/starter/starterd_lib.h b/aos/starter/starterd_lib.h
index e1f82a0..834e191 100644
--- a/aos/starter/starterd_lib.h
+++ b/aos/starter/starterd_lib.h
@@ -16,153 +16,11 @@
#include "aos/macros.h"
#include "aos/starter/starter_generated.h"
#include "aos/starter/starter_rpc_generated.h"
+#include "aos/starter/subprocess.h"
namespace aos {
namespace starter {
-// RAII Pipe for sending individual ints between reader and writer.
-class ScopedPipe {
- public:
- class ScopedReadPipe;
- class ScopedWritePipe;
-
- static std::tuple<ScopedReadPipe, ScopedWritePipe> MakePipe();
-
- virtual ~ScopedPipe();
-
- int fd() const { return fd_; }
-
- private:
- ScopedPipe(int fd = -1);
-
- int fd_;
-
- ScopedPipe(const ScopedPipe &) = delete;
- ScopedPipe &operator=(const ScopedPipe &) = delete;
- ScopedPipe(ScopedPipe &&);
- ScopedPipe &operator=(ScopedPipe &&);
-};
-
-class ScopedPipe::ScopedReadPipe : public ScopedPipe {
- public:
- std::optional<uint32_t> Read();
-
- private:
- using ScopedPipe::ScopedPipe;
-
- friend class ScopedPipe;
-};
-
-class ScopedPipe::ScopedWritePipe : public ScopedPipe {
- public:
- void Write(uint32_t data);
-
- private:
- using ScopedPipe::ScopedPipe;
-
- friend class ScopedPipe;
-};
-
-// Manages a running process, allowing starting and stopping, and restarting
-// automatically.
-class Application {
- public:
- Application(const aos::Application *application,
- aos::ShmEventLoop *event_loop, std::function<void()> on_change);
-
- flatbuffers::Offset<aos::starter::ApplicationStatus> PopulateStatus(
- flatbuffers::FlatBufferBuilder *builder);
-
- // Returns the last pid of this process. -1 if not started yet.
- pid_t get_pid() const { return pid_; }
-
- // Handles a SIGCHLD signal received by the parent. Does nothing if this
- // process was not the target. Returns true if this Application should be
- // removed.
- bool MaybeHandleSignal();
-
- // Handles a command. May do nothing if application is already in the desired
- // state.
- void HandleCommand(aos::starter::Command cmd);
-
- void Start() { HandleCommand(aos::starter::Command::START); }
-
- void Stop() { HandleCommand(aos::starter::Command::STOP); }
-
- void Terminate();
-
- void set_args(
- const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>
- &args);
-
- bool autostart() const { return autostart_; }
-
- bool autorestart() const { return autorestart_; }
-
- private:
- void DoStart();
-
- void DoStop(bool restart);
-
- void QueueStart();
-
- // Copy flatbuffer vector of strings to vector of std::string.
- static std::vector<std::string> FbsVectorToVector(
- const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> &v);
-
- static std::optional<uid_t> FindUid(const char *name);
- static std::optional<gid_t> FindPrimaryGidForUser(const char *name);
-
- // Next unique id for all applications
- static inline uint64_t next_id_ = 0;
-
- std::string name_;
- std::string path_;
- std::vector<char *> args_;
- std::string user_name_;
- std::optional<uid_t> user_;
- std::optional<gid_t> group_;
-
- pid_t pid_ = -1;
- ScopedPipe::ScopedReadPipe read_pipe_;
- ScopedPipe::ScopedWritePipe write_pipe_;
- uint64_t id_;
- int exit_code_ = 0;
- aos::monotonic_clock::time_point start_time_, exit_time_;
- bool queue_restart_ = false;
- bool terminating_ = false;
- bool autostart_ = true;
- bool autorestart_ = true;
-
- aos::starter::State status_ = aos::starter::State::STOPPED;
- aos::starter::LastStopReason stop_reason_ =
- aos::starter::LastStopReason::STOP_REQUESTED;
-
- aos::ShmEventLoop *event_loop_;
- aos::TimerHandler *start_timer_, *restart_timer_, *stop_timer_;
-
- std::function<void()> on_change_;
-
- DISALLOW_COPY_AND_ASSIGN(Application);
-};
-
-// Registers a signalfd listener with the given event loop and calls callback
-// whenever a signal is received.
-class SignalListener {
- public:
- SignalListener(aos::ShmEventLoop *loop,
- std::function<void(signalfd_siginfo)> callback);
-
- ~SignalListener();
-
- private:
- aos::ShmEventLoop *loop_;
- std::function<void(signalfd_siginfo)> callback_;
- aos::ipc_lib::SignalFd signalfd_;
-
- DISALLOW_COPY_AND_ASSIGN(SignalListener);
-};
-
const aos::Channel *StatusChannelForNode(const aos::Configuration *config,
const aos::Node *node);
const aos::Channel *StarterRpcChannelForNode(const aos::Configuration *config,
diff --git a/aos/starter/subprocess.cc b/aos/starter/subprocess.cc
new file mode 100644
index 0000000..e68f604
--- /dev/null
+++ b/aos/starter/subprocess.cc
@@ -0,0 +1,390 @@
+#include "aos/starter/subprocess.h"
+
+#include <grp.h>
+#include <pwd.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "glog/logging.h"
+
+namespace aos::starter {
+
+SignalListener::SignalListener(aos::ShmEventLoop *loop,
+ std::function<void(signalfd_siginfo)> callback)
+ : SignalListener(loop, callback,
+ {SIGHUP, SIGINT, SIGQUIT, SIGABRT, SIGFPE, SIGSEGV,
+ SIGPIPE, SIGTERM, SIGBUS, SIGXCPU, SIGCHLD}) {}
+
+SignalListener::SignalListener(aos::ShmEventLoop *loop,
+ std::function<void(signalfd_siginfo)> callback,
+ std::initializer_list<unsigned int> signals)
+ : loop_(loop), callback_(std::move(callback)), signalfd_(signals) {
+ loop->epoll()->OnReadable(signalfd_.fd(), [this] {
+ signalfd_siginfo info = signalfd_.Read();
+
+ if (info.ssi_signo == 0) {
+ LOG(WARNING) << "Could not read " << sizeof(signalfd_siginfo) << " bytes";
+ return;
+ }
+
+ callback_(info);
+ });
+}
+
+SignalListener::~SignalListener() { loop_->epoll()->DeleteFd(signalfd_.fd()); }
+
+Application::Application(const aos::Application *application,
+ aos::EventLoop *event_loop,
+ std::function<void()> on_change)
+ : name_(application->name()->string_view()),
+ path_(application->has_executable_name()
+ ? application->executable_name()->string_view()
+ : application->name()->string_view()),
+ args_(1),
+ user_name_(application->has_user() ? application->user()->str() : ""),
+ user_(application->has_user() ? FindUid(user_name_.c_str())
+ : std::nullopt),
+ group_(application->has_user() ? FindPrimaryGidForUser(user_name_.c_str())
+ : std::nullopt),
+ autostart_(application->autostart()),
+ autorestart_(application->autorestart()),
+ event_loop_(event_loop),
+ start_timer_(event_loop_->AddTimer([this] {
+ status_ = aos::starter::State::RUNNING;
+ LOG(INFO) << "Started '" << name_ << "' pid: " << pid_;
+ })),
+ restart_timer_(event_loop_->AddTimer([this] { DoStart(); })),
+ stop_timer_(event_loop_->AddTimer([this] {
+ if (kill(pid_, SIGKILL) == 0) {
+ LOG(WARNING) << "Failed to stop, sending SIGKILL to '" << name_
+ << "' pid: " << pid_;
+ }
+ })),
+ on_change_(on_change) {}
+
+void Application::DoStart() {
+ if (status_ != aos::starter::State::WAITING) {
+ return;
+ }
+
+ start_timer_->Disable();
+ restart_timer_->Disable();
+
+ std::tie(read_pipe_, write_pipe_) = util::ScopedPipe::MakePipe();
+
+ const pid_t pid = fork();
+
+ if (pid != 0) {
+ if (pid == -1) {
+ PLOG(WARNING) << "Failed to fork '" << name_ << "'";
+ stop_reason_ = aos::starter::LastStopReason::FORK_ERR;
+ status_ = aos::starter::State::STOPPED;
+ } else {
+ pid_ = pid;
+ id_ = next_id_++;
+ start_time_ = event_loop_->monotonic_now();
+ status_ = aos::starter::State::STARTING;
+ LOG(INFO) << "Starting '" << name_ << "' pid " << pid_;
+
+ // Setup timer which moves application to RUNNING state if it is still
+ // alive in 1 second.
+ start_timer_->Setup(event_loop_->monotonic_now() +
+ std::chrono::seconds(1));
+ }
+ on_change_();
+ return;
+ }
+
+ // Clear out signal mask of parent so forked process receives all signals
+ // normally.
+ sigset_t empty_mask;
+ sigemptyset(&empty_mask);
+ sigprocmask(SIG_SETMASK, &empty_mask, nullptr);
+
+ // Cleanup children if starter dies in a way that is not handled gracefully.
+ if (prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) {
+ write_pipe_.Write(
+ static_cast<uint32_t>(aos::starter::LastStopReason::SET_PRCTL_ERR));
+ PLOG(FATAL) << "Could not set PR_SET_PDEATHSIG to SIGKILL";
+ }
+
+ if (group_) {
+ CHECK(!user_name_.empty());
+ // The manpage for setgroups says we just need CAP_SETGID, but empirically
+ // we also need the effective UID to be 0 to make it work. user_ must also
+ // be set so we change this effective UID back later.
+ CHECK(user_);
+ if (seteuid(0) == -1) {
+ write_pipe_.Write(
+ static_cast<uint32_t>(aos::starter::LastStopReason::SET_GRP_ERR));
+ PLOG(FATAL) << "Could not seteuid(0) for " << name_
+ << " in preparation for setting groups";
+ }
+ if (initgroups(user_name_.c_str(), *group_) == -1) {
+ write_pipe_.Write(
+ static_cast<uint32_t>(aos::starter::LastStopReason::SET_GRP_ERR));
+ PLOG(FATAL) << "Could not initialize normal groups for " << name_
+ << " as " << user_name_ << " with " << *group_;
+ }
+ if (setgid(*group_) == -1) {
+ write_pipe_.Write(
+ static_cast<uint32_t>(aos::starter::LastStopReason::SET_GRP_ERR));
+ PLOG(FATAL) << "Could not set group for " << name_ << " to " << *group_;
+ }
+ }
+
+ if (user_) {
+ if (setuid(*user_) == -1) {
+ write_pipe_.Write(
+ static_cast<uint32_t>(aos::starter::LastStopReason::SET_USR_ERR));
+ PLOG(FATAL) << "Could not set user for " << name_ << " to " << *user_;
+ }
+ }
+
+ // argv[0] should be the program name
+ args_.insert(args_.begin(), path_.data());
+
+ execvp(path_.c_str(), args_.data());
+
+ // If we got here, something went wrong
+ write_pipe_.Write(
+ static_cast<uint32_t>(aos::starter::LastStopReason::EXECV_ERR));
+ PLOG(WARNING) << "Could not execute " << name_ << " (" << path_ << ')';
+
+ _exit(EXIT_FAILURE);
+}
+
+void Application::DoStop(bool restart) {
+ // If stop or restart received, the old state of these is no longer applicable
+ // so cancel both.
+ restart_timer_->Disable();
+ start_timer_->Disable();
+
+ switch (status_) {
+ case aos::starter::State::STARTING:
+ case aos::starter::State::RUNNING: {
+ LOG(INFO) << "Stopping '" << name_ << "' pid: " << pid_ << " with signal "
+ << SIGINT;
+ status_ = aos::starter::State::STOPPING;
+
+ kill(pid_, SIGINT);
+
+ // Watchdog timer to SIGKILL application if it is still running 1 second
+ // after SIGINT
+ stop_timer_->Setup(event_loop_->monotonic_now() +
+ std::chrono::seconds(1));
+ queue_restart_ = restart;
+ on_change_();
+ break;
+ }
+ case aos::starter::State::WAITING: {
+ // If waiting to restart, and receives restart, skip the waiting period
+ // and restart immediately. If stop received, all we have to do is move
+ // to the STOPPED state.
+ if (restart) {
+ DoStart();
+ } else {
+ status_ = aos::starter::State::STOPPED;
+ on_change_();
+ }
+ break;
+ }
+ case aos::starter::State::STOPPING: {
+ // If the application is already stopping, then we just need to update the
+ // restart flag to the most recent status.
+ queue_restart_ = restart;
+ break;
+ }
+ case aos::starter::State::STOPPED: {
+ // Restart immediately if the application is already stopped
+ if (restart) {
+ status_ = aos::starter::State::WAITING;
+ DoStart();
+ }
+ break;
+ }
+ }
+}
+
+void Application::QueueStart() {
+ status_ = aos::starter::State::WAITING;
+
+ LOG(INFO) << "Restarting " << name_ << " in 3 seconds";
+ restart_timer_->Setup(event_loop_->monotonic_now() + std::chrono::seconds(3));
+ start_timer_->Disable();
+ stop_timer_->Disable();
+ on_change_();
+}
+
+void Application::set_args(
+ const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> &v) {
+ args_.clear();
+ std::transform(v.begin(), v.end(), std::back_inserter(args_),
+ [](const flatbuffers::String *str) {
+ return const_cast<char *>(str->c_str());
+ });
+ args_.push_back(nullptr);
+}
+
+std::optional<uid_t> Application::FindUid(const char *name) {
+ // TODO(austin): Use the reentrant version. This should be safe.
+ struct passwd *user_data = getpwnam(name);
+ if (user_data != nullptr) {
+ return user_data->pw_uid;
+ } else {
+ LOG(FATAL) << "Could not find user " << name;
+ return std::nullopt;
+ }
+}
+
+std::optional<gid_t> Application::FindPrimaryGidForUser(const char *name) {
+ // TODO(austin): Use the reentrant version. This should be safe.
+ struct passwd *user_data = getpwnam(name);
+ if (user_data != nullptr) {
+ return user_data->pw_gid;
+ } else {
+ LOG(FATAL) << "Could not find user " << name;
+ return std::nullopt;
+ }
+}
+
+flatbuffers::Offset<aos::starter::ApplicationStatus>
+Application::PopulateStatus(flatbuffers::FlatBufferBuilder *builder) {
+ CHECK_NOTNULL(builder);
+ auto name_fbs = builder->CreateString(name_);
+
+ aos::starter::ApplicationStatus::Builder status_builder(*builder);
+ status_builder.add_name(name_fbs);
+ status_builder.add_state(status_);
+ status_builder.add_last_exit_code(exit_code_);
+ status_builder.add_last_stop_reason(stop_reason_);
+ if (pid_ != -1) {
+ status_builder.add_pid(pid_);
+ status_builder.add_id(id_);
+ }
+ status_builder.add_last_start_time(start_time_.time_since_epoch().count());
+ return status_builder.Finish();
+}
+
+void Application::Terminate() {
+ stop_reason_ = aos::starter::LastStopReason::TERMINATE;
+ DoStop(false);
+ terminating_ = true;
+}
+
+void Application::HandleCommand(aos::starter::Command cmd) {
+ switch (cmd) {
+ case aos::starter::Command::START: {
+ switch (status_) {
+ case aos::starter::State::WAITING: {
+ restart_timer_->Disable();
+ DoStart();
+ break;
+ }
+ case aos::starter::State::STARTING: {
+ break;
+ }
+ case aos::starter::State::RUNNING: {
+ break;
+ }
+ case aos::starter::State::STOPPING: {
+ queue_restart_ = true;
+ break;
+ }
+ case aos::starter::State::STOPPED: {
+ status_ = aos::starter::State::WAITING;
+ DoStart();
+ break;
+ }
+ }
+ break;
+ }
+ case aos::starter::Command::STOP: {
+ stop_reason_ = aos::starter::LastStopReason::STOP_REQUESTED;
+ DoStop(false);
+ break;
+ }
+ case aos::starter::Command::RESTART: {
+ stop_reason_ = aos::starter::LastStopReason::RESTART_REQUESTED;
+ DoStop(true);
+ break;
+ }
+ }
+}
+
+bool Application::MaybeHandleSignal() {
+ int status;
+
+ // Check if the status of this process has changed
+ if (pid_ == -1 || waitpid(pid_, &status, WNOHANG) != pid_) {
+ return false;
+ }
+
+ // Check that the event was the process exiting
+ if (!WIFEXITED(status) && !WIFSIGNALED(status)) {
+ return false;
+ }
+
+ exit_time_ = event_loop_->monotonic_now();
+ exit_code_ = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status);
+
+ if (auto read_result = read_pipe_.Read()) {
+ stop_reason_ = static_cast<aos::starter::LastStopReason>(*read_result);
+ }
+
+ switch (status_) {
+ case aos::starter::State::STARTING: {
+ LOG(WARNING) << "Failed to start '" << name_ << "' on pid " << pid_
+ << " : Exited with status " << exit_code_;
+ if (autorestart()) {
+ QueueStart();
+ }
+ break;
+ }
+ case aos::starter::State::RUNNING: {
+ if (exit_code_ == 0) {
+ LOG(INFO) << "Application '" << name_ << "' pid " << pid_
+ << " exited with status " << exit_code_;
+ } else {
+ LOG(WARNING) << "Application '" << name_ << "' pid " << pid_
+ << " exited unexpectedly with status " << exit_code_;
+ }
+ if (autorestart()) {
+ QueueStart();
+ }
+ break;
+ }
+ case aos::starter::State::STOPPING: {
+ LOG(INFO) << "Successfully stopped '" << name_ << "' pid: " << pid_
+ << " with status " << exit_code_;
+ status_ = aos::starter::State::STOPPED;
+
+ // Disable force stop timer since the process already died
+ stop_timer_->Disable();
+
+ on_change_();
+ if (terminating_) {
+ return true;
+ }
+
+ if (queue_restart_) {
+ queue_restart_ = false;
+ status_ = aos::starter::State::WAITING;
+ DoStart();
+ }
+ break;
+ }
+ case aos::starter::State::WAITING:
+ case aos::starter::State::STOPPED: {
+ LOG(FATAL)
+ << "Received signal on process that was already stopped : name: '"
+ << name_ << "' pid: " << pid_;
+ break;
+ }
+ }
+
+ return false;
+}
+
+} // namespace aos::starter
diff --git a/aos/starter/subprocess.h b/aos/starter/subprocess.h
new file mode 100644
index 0000000..c1d7c87
--- /dev/null
+++ b/aos/starter/subprocess.h
@@ -0,0 +1,119 @@
+#ifndef AOS_STARTER_SUBPROCESS_H_
+#define AOS_STARTER_SUBPROCESS_H_
+
+#include <string>
+#include <vector>
+
+#include "aos/events/event_loop.h"
+#include "aos/events/shm_event_loop.h"
+#include "aos/starter/starter_generated.h"
+#include "aos/starter/starter_rpc_generated.h"
+#include "aos/util/scoped_pipe.h"
+
+namespace aos::starter {
+
+// Registers a signalfd listener with the given event loop and calls callback
+// whenever a signal is received.
+class SignalListener {
+ public:
+ SignalListener(aos::ShmEventLoop *loop,
+ std::function<void(signalfd_siginfo)> callback);
+ SignalListener(aos::ShmEventLoop *loop,
+ std::function<void(signalfd_siginfo)> callback,
+ std::initializer_list<unsigned int> signals);
+
+ ~SignalListener();
+
+ private:
+ aos::ShmEventLoop *loop_;
+ std::function<void(signalfd_siginfo)> callback_;
+ aos::ipc_lib::SignalFd signalfd_;
+
+ DISALLOW_COPY_AND_ASSIGN(SignalListener);
+};
+
+// Manages a running process, allowing starting and stopping, and restarting
+// automatically.
+class Application {
+ public:
+ Application(const aos::Application *application,
+ aos::EventLoop *event_loop, std::function<void()> on_change);
+
+ flatbuffers::Offset<aos::starter::ApplicationStatus> PopulateStatus(
+ flatbuffers::FlatBufferBuilder *builder);
+
+ // Returns the last pid of this process. -1 if not started yet.
+ pid_t get_pid() const { return pid_; }
+
+ // Handles a SIGCHLD signal received by the parent. Does nothing if this
+ // process was not the target. Returns true if this Application should be
+ // removed.
+ bool MaybeHandleSignal();
+
+ // Handles a command. May do nothing if application is already in the desired
+ // state.
+ void HandleCommand(aos::starter::Command cmd);
+
+ void Start() { HandleCommand(aos::starter::Command::START); }
+
+ void Stop() { HandleCommand(aos::starter::Command::STOP); }
+
+ void Terminate();
+
+ void set_args(
+ const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>
+ &args);
+
+ bool autostart() const { return autostart_; }
+
+ bool autorestart() const { return autorestart_; }
+
+ private:
+ void DoStart();
+
+ void DoStop(bool restart);
+
+ void QueueStart();
+
+ // Copy flatbuffer vector of strings to vector of std::string.
+ static std::vector<std::string> FbsVectorToVector(
+ const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> &v);
+
+ static std::optional<uid_t> FindUid(const char *name);
+ static std::optional<gid_t> FindPrimaryGidForUser(const char *name);
+
+ // Next unique id for all applications
+ static inline uint64_t next_id_ = 0;
+
+ std::string name_;
+ std::string path_;
+ std::vector<char *> args_;
+ std::string user_name_;
+ std::optional<uid_t> user_;
+ std::optional<gid_t> group_;
+
+ pid_t pid_ = -1;
+ util::ScopedPipe::ScopedReadPipe read_pipe_;
+ util::ScopedPipe::ScopedWritePipe write_pipe_;
+ uint64_t id_;
+ int exit_code_ = 0;
+ aos::monotonic_clock::time_point start_time_, exit_time_;
+ bool queue_restart_ = false;
+ bool terminating_ = false;
+ bool autostart_ = true;
+ bool autorestart_ = true;
+
+ aos::starter::State status_ = aos::starter::State::STOPPED;
+ aos::starter::LastStopReason stop_reason_ =
+ aos::starter::LastStopReason::STOP_REQUESTED;
+
+ aos::EventLoop *event_loop_;
+ aos::TimerHandler *start_timer_, *restart_timer_, *stop_timer_;
+
+ std::function<void()> on_change_;
+
+ DISALLOW_COPY_AND_ASSIGN(Application);
+};
+
+} // namespace aos::starter
+#endif // AOS_STARTER_SUBPROCESS_H_
diff --git a/aos/util/BUILD b/aos/util/BUILD
index d75d421..a57ec79 100644
--- a/aos/util/BUILD
+++ b/aos/util/BUILD
@@ -223,6 +223,16 @@
],
)
+cc_library(
+ name = "scoped_pipe",
+ srcs = ["scoped_pipe.cc"],
+ hdrs = ["scoped_pipe.h"],
+ target_compatible_with = ["@platforms//os:linux"],
+ deps = [
+ "@com_github_google_glog//:glog",
+ ],
+)
+
cc_test(
name = "file_test",
size = "small",
diff --git a/aos/util/scoped_pipe.cc b/aos/util/scoped_pipe.cc
new file mode 100644
index 0000000..8cf2957
--- /dev/null
+++ b/aos/util/scoped_pipe.cc
@@ -0,0 +1,54 @@
+#include "aos/util/scoped_pipe.h"
+
+#include <fcntl.h>
+#include "glog/logging.h"
+
+namespace aos::util {
+
+ScopedPipe::ScopedPipe(int fd) : fd_(fd) {}
+
+ScopedPipe::~ScopedPipe() {
+ if (fd_ != -1) {
+ PCHECK(close(fd_) != -1);
+ }
+}
+
+ScopedPipe::ScopedPipe(ScopedPipe &&scoped_pipe) : fd_(scoped_pipe.fd_) {
+ scoped_pipe.fd_ = -1;
+}
+
+ScopedPipe &ScopedPipe::operator=(ScopedPipe &&scoped_pipe) {
+ if (fd_ != -1) {
+ PCHECK(close(fd_) != -1);
+ }
+ fd_ = scoped_pipe.fd_;
+ scoped_pipe.fd_ = -1;
+ return *this;
+}
+
+std::tuple<ScopedPipe::ScopedReadPipe, ScopedPipe::ScopedWritePipe>
+ScopedPipe::MakePipe() {
+ int fds[2];
+ PCHECK(pipe(fds) != -1);
+ PCHECK(fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK) != -1);
+ PCHECK(fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | O_NONBLOCK) != -1);
+ return {ScopedReadPipe(fds[0]), ScopedWritePipe(fds[1])};
+}
+
+std::optional<uint32_t> ScopedPipe::ScopedReadPipe::Read() {
+ uint32_t buf;
+ ssize_t result = read(fd(), &buf, sizeof(buf));
+ if (result == sizeof(buf)) {
+ return buf;
+ } else {
+ return std::nullopt;
+ }
+}
+
+void ScopedPipe::ScopedWritePipe::Write(uint32_t data) {
+ ssize_t result = write(fd(), &data, sizeof(data));
+ PCHECK(result != -1);
+ CHECK(result == sizeof(data));
+}
+
+} // namespace aos::util
diff --git a/aos/util/scoped_pipe.h b/aos/util/scoped_pipe.h
new file mode 100644
index 0000000..5f86510
--- /dev/null
+++ b/aos/util/scoped_pipe.h
@@ -0,0 +1,56 @@
+#ifndef AOS_UTIL_SCOPED_PIPE_H_
+#define AOS_UTIL_SCOPED_PIPE_H_
+
+#include <stdint.h>
+
+#include <optional>
+#include <tuple>
+
+namespace aos::util {
+
+// RAII Pipe for sending individual ints between reader and writer.
+class ScopedPipe {
+ public:
+ class ScopedReadPipe;
+ class ScopedWritePipe;
+
+ static std::tuple<ScopedReadPipe, ScopedWritePipe> MakePipe();
+
+ virtual ~ScopedPipe();
+
+ int fd() const { return fd_; }
+
+ private:
+ ScopedPipe(int fd = -1);
+
+ int fd_;
+
+ ScopedPipe(const ScopedPipe &) = delete;
+ ScopedPipe &operator=(const ScopedPipe &) = delete;
+ ScopedPipe(ScopedPipe &&);
+ ScopedPipe &operator=(ScopedPipe &&);
+};
+
+class ScopedPipe::ScopedReadPipe : public ScopedPipe {
+ public:
+ std::optional<uint32_t> Read();
+
+ private:
+ using ScopedPipe::ScopedPipe;
+
+ friend class ScopedPipe;
+};
+
+class ScopedPipe::ScopedWritePipe : public ScopedPipe {
+ public:
+ void Write(uint32_t data);
+
+ private:
+ using ScopedPipe::ScopedPipe;
+
+ friend class ScopedPipe;
+};
+
+} // namespace aos::util
+
+#endif // AOS_UTIL_SCOPED_PIPE_H_