blob: 593945bbc1c77aec2e6db2d862b991ac048d1d0e [file] [log] [blame]
#include "aos/starter/subprocess.h"
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <compare>
#include <iterator>
#include <ostream>
#include <ratio>
#include "absl/flags/flag.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "aos/util/file.h"
#include "aos/util/process_info_generated.h"
namespace aos::starter {
// Blocks all signals while an instance of this class is in scope.
class ScopedCompleteSignalBlocker {
public:
ScopedCompleteSignalBlocker() {
sigset_t mask;
sigfillset(&mask);
// Remember the current mask.
PCHECK(sigprocmask(SIG_SETMASK, &mask, &old_mask_) == 0);
}
~ScopedCompleteSignalBlocker() {
// Restore the remembered mask.
PCHECK(sigprocmask(SIG_SETMASK, &old_mask_, nullptr) == 0);
}
private:
sigset_t old_mask_;
};
namespace {
std::optional<ino_t> GetInodeForPath(const std::filesystem::path &path) {
struct stat stat_buf;
if (0 != stat(path.c_str(), &stat_buf)) {
return std::nullopt;
}
return stat_buf.st_ino;
}
bool InodeChanged(const std::filesystem::path &path, ino_t previous_inode) {
const std::optional<ino_t> current_inode = GetInodeForPath(path);
if (!current_inode.has_value()) {
return true;
}
return current_inode.value() != previous_inode;
}
} // namespace
std::filesystem::path ResolvePath(std::string_view command) {
std::filesystem::path command_path = command;
if (command.find("/") != std::string_view::npos) {
CHECK(std::filesystem::exists(command_path))
<< ": " << command << " does not exist.";
return std::filesystem::canonical(command_path);
}
const char *system_path = getenv("PATH");
std::string system_path_buffer;
if (system_path == nullptr) {
const size_t default_path_length = confstr(_CS_PATH, nullptr, 0);
PCHECK(default_path_length != 0) << ": Unable to resolve " << command;
system_path_buffer.resize(default_path_length);
confstr(_CS_PATH, system_path_buffer.data(), system_path_buffer.size());
system_path = system_path_buffer.c_str();
VLOG(2) << "Using default path of " << system_path
<< " in the absence of PATH being set.";
}
const std::vector<std::string_view> search_paths =
absl::StrSplit(system_path, ':');
for (const std::string_view search_path : search_paths) {
const std::filesystem::path candidate =
std::filesystem::path(search_path) / command_path;
if (std::filesystem::exists(candidate)) {
return std::filesystem::canonical(candidate);
}
}
LOG(FATAL) << "Unable to resolve " << command;
}
// RAII class to become root and restore back to the original user and group
// afterwards.
class Sudo {
public:
Sudo() {
// Save what we were.
PCHECK(getresuid(&ruid_, &euid_, &suid_) == 0);
PCHECK(getresgid(&rgid_, &egid_, &sgid_) == 0);
// Become root.
PCHECK(setresuid(/* ruid */ 0 /* root */, /* euid */ 0, /* suid */ 0) == 0)
<< ": Failed to become root";
PCHECK(setresgid(/* ruid */ 0 /* root */, /* euid */ 0, /* suid */ 0) == 0)
<< ": Failed to become root";
}
~Sudo() {
// And recover.
PCHECK(setresgid(rgid_, egid_, sgid_) == 0);
PCHECK(setresuid(ruid_, euid_, suid_) == 0);
}
uid_t ruid_, euid_, suid_;
gid_t rgid_, egid_, sgid_;
};
MemoryCGroup::MemoryCGroup(std::string_view name, Create should_create)
: cgroup_(absl::StrCat("/sys/fs/cgroup/memory/aos_", name)),
should_create_(should_create) {
if (should_create_ == Create::kDoCreate) {
Sudo sudo;
int ret = mkdir(cgroup_.c_str(), 0755);
if (ret != 0) {
if (errno == EEXIST) {
PCHECK(rmdir(cgroup_.c_str()) == 0)
<< ": Failed to remove previous cgroup " << cgroup_;
ret = mkdir(cgroup_.c_str(), 0755);
}
}
if (ret != 0) {
PLOG(FATAL) << ": Failed to create cgroup aos_" << cgroup_
<< ", do you have permission?";
}
}
}
void MemoryCGroup::AddTid(pid_t pid) {
if (pid == 0) {
pid = getpid();
}
if (should_create_ == Create::kDoCreate) {
Sudo sudo;
util::WriteStringToFileOrDie(absl::StrCat(cgroup_, "/tasks").c_str(),
std::to_string(pid));
} else {
util::WriteStringToFileOrDie(absl::StrCat(cgroup_, "/tasks").c_str(),
std::to_string(pid));
}
}
void MemoryCGroup::SetLimit(std::string_view limit_name, uint64_t limit_value) {
if (should_create_ == Create::kDoCreate) {
Sudo sudo;
util::WriteStringToFileOrDie(absl::StrCat(cgroup_, "/", limit_name).c_str(),
std::to_string(limit_value));
} else {
util::WriteStringToFileOrDie(absl::StrCat(cgroup_, "/", limit_name).c_str(),
std::to_string(limit_value));
}
}
MemoryCGroup::~MemoryCGroup() {
if (should_create_ == Create::kDoCreate) {
Sudo sudo;
PCHECK(rmdir(absl::StrCat(cgroup_).c_str()) == 0);
}
}
SignalListener::SignalListener(aos::ShmEventLoop *loop,
std::function<void(signalfd_siginfo)> callback)
: SignalListener(loop->epoll(), std::move(callback)) {}
SignalListener::SignalListener(aos::internal::EPoll *epoll,
std::function<void(signalfd_siginfo)> callback)
: SignalListener(epoll, 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)
: SignalListener(loop->epoll(), std::move(callback), std::move(signals)) {}
SignalListener::SignalListener(aos::internal::EPoll *epoll,
std::function<void(signalfd_siginfo)> callback,
std::initializer_list<unsigned int> signals)
: epoll_(epoll), callback_(std::move(callback)), signalfd_(signals) {
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() { epoll_->DeleteFd(signalfd_.fd()); }
Application::Application(std::string_view name,
std::string_view executable_name,
aos::EventLoop *event_loop,
std::function<void()> on_change,
QuietLogging quiet_flag)
: name_(name),
path_(ResolvePath(executable_name)),
event_loop_(event_loop),
start_timer_(event_loop_->AddTimer([this] {
status_ = aos::starter::State::RUNNING;
LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
<< "Started '" << name_ << "' pid: " << pid_;
// Check if the file on disk changed while we were starting up. We allow
// this state for the same reason that we don't just use /proc/$pid/exe
// to determine if the file is deleted--we may be running a script or
// sudo or the such and determining the state of the file that we
// actually care about sounds like more work than we want to deal with.
if (InodeChanged(path_, pre_fork_inode_)) {
file_state_ = FileState::CHANGED_DURING_STARTUP;
} else {
file_state_ = FileState::NO_CHANGE;
}
OnChange();
})),
restart_timer_(event_loop_->AddTimer([this] { DoStart(); })),
stop_timer_(event_loop_->AddTimer([this] {
if (kill(pid_, SIGKILL) == 0) {
LOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo ||
quiet_flag_ == QuietLogging::kNotForDebugging)
<< "Failed to stop, sending SIGKILL to '" << name_
<< "' pid: " << pid_;
} else {
PLOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo ||
quiet_flag_ == QuietLogging::kNotForDebugging)
<< "Failed to send SIGKILL to '" << name_ << "' pid: " << pid_;
stop_timer_->Schedule(event_loop_->monotonic_now() +
std::chrono::seconds(1));
}
})),
pipe_timer_(event_loop_->AddTimer([this]() { FetchOutputs(); })),
child_status_handler_(
event_loop_->AddTimer([this]() { MaybeHandleSignal(); })),
on_change_({on_change}),
quiet_flag_(quiet_flag) {
// Keep the length of the timer name bounded to some reasonable length.
start_timer_->set_name(absl::StrCat("app_start_", name.substr(0, 10)));
restart_timer_->set_name(absl::StrCat("app_restart_", name.substr(0, 10)));
stop_timer_->set_name(absl::StrCat("app_stop_", name.substr(0, 10)));
pipe_timer_->set_name(absl::StrCat("app_pipe_", name.substr(0, 10)));
child_status_handler_->set_name(
absl::StrCat("app_status_handler_", name.substr(0, 10)));
// Every second poll to check if the child is dead. This is used as a
// default for the case where the user is not directly catching SIGCHLD
// and calling MaybeHandleSignal for us.
child_status_handler_->Schedule(event_loop_->monotonic_now(),
std::chrono::seconds(1));
}
Application::Application(const aos::Application *application,
aos::EventLoop *event_loop,
std::function<void()> on_change,
QuietLogging quiet_flag)
: Application(application->name()->string_view(),
application->has_executable_name()
? application->executable_name()->string_view()
: application->name()->string_view(),
event_loop, on_change, quiet_flag) {
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();
if (application->has_args()) {
set_args(*application->args());
}
if (application->has_memory_limit() && application->memory_limit() > 0) {
SetMemoryLimit(application->memory_limit());
}
set_stop_grace_period(std::chrono::nanoseconds(application->stop_time()));
}
void Application::DoStart() {
if (status_ != aos::starter::State::WAITING) {
return;
}
start_timer_->Disable();
restart_timer_->Disable();
status_pipes_ = util::ScopedPipe::MakePipe();
if (capture_stdout_) {
stdout_pipes_ = util::ScopedPipe::MakePipe();
stdout_.clear();
}
if (capture_stderr_) {
stderr_pipes_ = util::ScopedPipe::MakePipe();
stderr_.clear();
}
pipe_timer_->Schedule(event_loop_->monotonic_now(),
std::chrono::milliseconds(100));
{
// Block all signals during the fork() call. Together with the default
// signal handler restoration below, This prevents signal handlers from
// getting called in the child and accidentally affecting the parent. In
// particular, the exit handler for shm_event_loop could be called here if
// we don't exec() quickly enough.
ScopedCompleteSignalBlocker signal_blocker;
{
const std::optional<ino_t> inode = GetInodeForPath(path_);
CHECK(inode.has_value())
<< ": " << path_ << " does not seem to be stat'able.";
pre_fork_inode_ = inode.value();
}
const pid_t pid = fork();
if (pid != 0) {
if (pid == -1) {
PLOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo ||
quiet_flag_ == QuietLogging::kNotForDebugging)
<< "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;
latest_timing_report_version_.reset();
LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
<< "Starting '" << name_ << "' pid " << pid_;
// Set up timer which moves application to RUNNING state if it is still
// alive in 1 second.
start_timer_->Schedule(event_loop_->monotonic_now() +
std::chrono::seconds(1));
// Since we are the parent process, clear our write-side of all the
// pipes.
status_pipes_.write.reset();
stdout_pipes_.write.reset();
stderr_pipes_.write.reset();
}
OnChange();
return;
}
// Clear any signal handlers so that they don't accidentally interfere with
// the parent process. Is there a better way to iterate over all the
// signals? Right now we're just dealing with the most common ones.
for (int signal : {SIGINT, SIGHUP, SIGTERM}) {
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = SIG_DFL;
PCHECK(sigaction(signal, &action, nullptr) == 0);
}
}
if (memory_cgroup_) {
memory_cgroup_->AddTid();
}
// Since we are the child process, clear our read-side of all the pipes.
status_pipes_.read.reset();
stdout_pipes_.read.reset();
stderr_pipes_.read.reset();
// The status pipe will not be needed if the execve succeeds.
status_pipes_.write->SetCloexec();
// 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) {
status_pipes_.write->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) {
status_pipes_.write->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) {
status_pipes_.write->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) {
status_pipes_.write->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) {
status_pipes_.write->Write(
static_cast<uint32_t>(aos::starter::LastStopReason::SET_USR_ERR));
PLOG(FATAL) << "Could not set user for " << name_ << " to " << *user_;
}
}
if (capture_stdout_) {
PCHECK(STDOUT_FILENO == dup2(stdout_pipes_.write->fd(), STDOUT_FILENO));
stdout_pipes_.write.reset();
}
if (capture_stderr_) {
PCHECK(STDERR_FILENO == dup2(stderr_pipes_.write->fd(), STDERR_FILENO));
stderr_pipes_.write.reset();
}
if (run_as_sudo_) {
// For sudo we must supply the actual path
args_.insert(args_.begin(), path_.c_str());
args_.insert(args_.begin(), kSudo);
} else {
// argv[0] should be the program name
args_.insert(args_.begin(), name_);
}
std::vector<char *> cargs = CArgs();
const char *path = run_as_sudo_ ? kSudo : path_.c_str();
execvp(path, cargs.data());
// If we got here, something went wrong
status_pipes_.write->Write(
static_cast<uint32_t>(aos::starter::LastStopReason::EXECV_ERR));
PLOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo ||
quiet_flag_ == QuietLogging::kNotForDebugging)
<< "Could not execute " << name_ << " (" << path_ << ')';
_exit(EXIT_FAILURE);
}
void Application::ObserveTimingReport(
const aos::monotonic_clock::time_point send_time,
const aos::timing::Report *msg) {
if (msg->name()->string_view() == name_ && msg->pid() == pid_ &&
msg->has_version()) {
latest_timing_report_version_ = msg->version()->str();
last_timing_report_ = send_time;
}
}
void Application::FetchOutputs() {
if (capture_stdout_) {
stdout_pipes_.read->Read(&stdout_);
}
if (capture_stderr_) {
stderr_pipes_.read->Read(&stderr_);
}
}
const std::string &Application::GetStdout() {
CHECK(capture_stdout_);
FetchOutputs();
return stdout_;
}
const std::string &Application::GetStderr() {
CHECK(capture_stderr_);
FetchOutputs();
return stderr_;
}
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();
FetchOutputs();
switch (status_) {
case aos::starter::State::STARTING:
case aos::starter::State::RUNNING: {
file_state_ = FileState::NOT_RUNNING;
LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo ||
quiet_flag_ == QuietLogging::kNotForDebugging)
<< "Stopping '" << name_ << "' pid: " << pid_ << " with signal "
<< SIGINT;
status_ = aos::starter::State::STOPPING;
if (kill(pid_, SIGINT) != 0) {
PLOG_IF(INFO, quiet_flag_ == QuietLogging::kNo ||
quiet_flag_ == QuietLogging::kNotForDebugging)
<< "Failed to send signal " << SIGINT << " to '" << name_
<< "' pid: " << pid_;
}
// Watchdog timer to SIGKILL application if it is still running 1 second
// after SIGINT
stop_timer_->Schedule(event_loop_->monotonic_now() + stop_grace_period_);
queue_restart_ = restart;
OnChange();
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;
OnChange();
}
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_IF(INFO, quiet_flag_ == QuietLogging::kNo)
<< "Restarting " << name_ << " in 3 seconds";
restart_timer_->Schedule(event_loop_->monotonic_now() +
std::chrono::seconds(3));
start_timer_->Disable();
stop_timer_->Disable();
OnChange();
}
std::vector<char *> Application::CArgs() {
std::vector<char *> cargs;
std::transform(args_.begin(), args_.end(), std::back_inserter(cargs),
[](std::string &str) { return str.data(); });
cargs.push_back(nullptr);
return cargs;
}
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 str->str(); });
}
void Application::set_args(std::vector<std::string> args) {
args_ = std::move(args);
}
void Application::set_capture_stdout(bool capture) {
capture_stdout_ = capture;
}
void Application::set_capture_stderr(bool capture) {
capture_stderr_ = capture;
}
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;
}
}
FileState Application::UpdateFileState() {
// On every call, check if a different file is present on disk. Note that
// while the applications is running, the file cannot be changed without the
// inode changing.
// We could presumably use inotify or the such to watch the file instead,
// but this works and we do not expect substantial cost from reading the inode
// of a file every time we send out a status message.
if (InodeChanged(path_, pre_fork_inode_)) {
switch (file_state_) {
case FileState::NO_CHANGE:
file_state_ = FileState::CHANGED;
break;
case FileState::NOT_RUNNING:
case FileState::CHANGED_DURING_STARTUP:
case FileState::CHANGED:
break;
}
}
return file_state_;
}
flatbuffers::Offset<aos::starter::ApplicationStatus>
Application::PopulateStatus(flatbuffers::FlatBufferBuilder *builder,
util::Top *top) {
UpdateFileState();
CHECK(builder != nullptr);
auto name_fbs = builder->CreateString(name_);
const bool valid_pid = pid_ > 0 && status_ != aos::starter::State::STOPPED;
const flatbuffers::Offset<util::ProcessInfo> process_info =
valid_pid ? top->InfoForProcess(builder, pid_)
: flatbuffers::Offset<util::ProcessInfo>();
aos::starter::ApplicationStatus::Builder status_builder(*builder);
status_builder.add_name(name_fbs);
status_builder.add_state(status_);
if (exit_code_.has_value()) {
status_builder.add_last_exit_code(exit_code_.value());
}
status_builder.add_has_active_timing_report(
last_timing_report_ +
// Leave a bit of margin on the timing report receipt time, to allow
// for timing errors.
3 * std::chrono::milliseconds(absl::GetFlag(FLAGS_timing_report_ms)) >
event_loop_->monotonic_now());
status_builder.add_last_stop_reason(stop_reason_);
if (pid_ != -1) {
status_builder.add_pid(pid_);
status_builder.add_id(id_);
}
// Note that even if process_info is null, calling add_process_info is fine.
status_builder.add_process_info(process_info);
status_builder.add_last_start_time(start_time_.time_since_epoch().count());
status_builder.add_file_state(file_state_);
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;
if (status_ == aos::starter::State::WAITING ||
status_ == aos::starter::State::STOPPED) {
// We can't possibly have received a signal meant for this process.
return false;
}
// Check if the status of this process has changed
// The PID won't be -1 if this application has ever been run successfully
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;
}
start_timer_->Disable();
exit_time_ = event_loop_->monotonic_now();
exit_code_ = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status);
file_state_ = FileState::NOT_RUNNING;
if (auto read_result = status_pipes_.read->Read()) {
stop_reason_ = static_cast<aos::starter::LastStopReason>(*read_result);
}
const std::string starter_version_string =
absl::StrCat("starter version '",
event_loop_->VersionString().value_or("unknown"), "'");
switch (status_) {
case aos::starter::State::STARTING: {
if (exit_code_.value() == 0) {
LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
<< "Application '" << name_ << "' pid " << pid_
<< " exited with status " << exit_code_.value() << " and "
<< starter_version_string;
} else {
LOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo ||
quiet_flag_ == QuietLogging::kNotForDebugging)
<< "Failed to start '" << name_ << "' on pid " << pid_
<< " : Exited with status " << exit_code_.value() << " and "
<< starter_version_string;
}
if (autorestart()) {
QueueStart();
} else {
status_ = aos::starter::State::STOPPED;
OnChange();
}
break;
}
case aos::starter::State::RUNNING: {
if (exit_code_.value() == 0) {
LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
<< "Application '" << name_ << "' pid " << pid_
<< " exited with status " << exit_code_.value();
} else {
if (quiet_flag_ == QuietLogging::kNo ||
quiet_flag_ == QuietLogging::kNotForDebugging) {
const std::string version_string =
latest_timing_report_version_.has_value()
? absl::StrCat("version '",
latest_timing_report_version_.value(), "'")
: starter_version_string;
LOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo)
<< "Application '" << name_ << "' pid " << pid_ << " "
<< version_string << " exited unexpectedly with status "
<< exit_code_.value();
}
}
if (autorestart()) {
QueueStart();
} else {
status_ = aos::starter::State::STOPPED;
OnChange();
}
break;
}
case aos::starter::State::STOPPING: {
LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
<< "Successfully stopped '" << name_ << "' pid: " << pid_
<< " with status " << exit_code_.value();
status_ = aos::starter::State::STOPPED;
// Disable force stop timer since the process already died
stop_timer_->Disable();
OnChange();
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: {
__builtin_unreachable();
break;
}
}
return false;
}
void Application::OnChange() {
for (auto &fn : on_change_) {
fn();
}
}
Application::~Application() {
start_timer_->Disable();
restart_timer_->Disable();
stop_timer_->Disable();
pipe_timer_->Disable();
child_status_handler_->Disable();
}
} // namespace aos::starter