blob: 76a22f29a38ae30d5fff2772970c4f32c3f0f6cd [file] [log] [blame]
James Kuszmaul3224b8e2022-01-07 19:00:39 -08001#include "aos/starter/subprocess.h"
2
3#include <grp.h>
4#include <pwd.h>
5#include <sys/prctl.h>
6#include <sys/types.h>
7#include <sys/wait.h>
8
Tyler Chatowd5db0422022-03-02 20:56:15 -08009#include "absl/strings/str_split.h"
James Kuszmaul3224b8e2022-01-07 19:00:39 -080010#include "glog/logging.h"
Tyler Chatowd5db0422022-03-02 20:56:15 -080011#include "openssl/sha.h"
James Kuszmaul3224b8e2022-01-07 19:00:39 -080012
13namespace aos::starter {
14
15SignalListener::SignalListener(aos::ShmEventLoop *loop,
16 std::function<void(signalfd_siginfo)> callback)
17 : SignalListener(loop, callback,
18 {SIGHUP, SIGINT, SIGQUIT, SIGABRT, SIGFPE, SIGSEGV,
19 SIGPIPE, SIGTERM, SIGBUS, SIGXCPU, SIGCHLD}) {}
20
21SignalListener::SignalListener(aos::ShmEventLoop *loop,
22 std::function<void(signalfd_siginfo)> callback,
23 std::initializer_list<unsigned int> signals)
24 : loop_(loop), callback_(std::move(callback)), signalfd_(signals) {
25 loop->epoll()->OnReadable(signalfd_.fd(), [this] {
26 signalfd_siginfo info = signalfd_.Read();
27
28 if (info.ssi_signo == 0) {
29 LOG(WARNING) << "Could not read " << sizeof(signalfd_siginfo) << " bytes";
30 return;
31 }
32
33 callback_(info);
34 });
35}
36
37SignalListener::~SignalListener() { loop_->epoll()->DeleteFd(signalfd_.fd()); }
38
James Kuszmauld42edb42022-01-07 18:00:16 -080039Application::Application(std::string_view name,
40 std::string_view executable_name,
James Kuszmaul3224b8e2022-01-07 19:00:39 -080041 aos::EventLoop *event_loop,
42 std::function<void()> on_change)
James Kuszmauld42edb42022-01-07 18:00:16 -080043 : name_(name),
44 path_(executable_name),
James Kuszmaul3224b8e2022-01-07 19:00:39 -080045 event_loop_(event_loop),
46 start_timer_(event_loop_->AddTimer([this] {
47 status_ = aos::starter::State::RUNNING;
48 LOG(INFO) << "Started '" << name_ << "' pid: " << pid_;
49 })),
50 restart_timer_(event_loop_->AddTimer([this] { DoStart(); })),
51 stop_timer_(event_loop_->AddTimer([this] {
52 if (kill(pid_, SIGKILL) == 0) {
53 LOG(WARNING) << "Failed to stop, sending SIGKILL to '" << name_
54 << "' pid: " << pid_;
55 }
56 })),
James Kuszmauld42edb42022-01-07 18:00:16 -080057 pipe_timer_(event_loop_->AddTimer([this]() { FetchOutputs(); })),
58 child_status_handler_(
59 event_loop_->AddTimer([this]() { MaybeHandleSignal(); })),
60 on_change_(on_change) {
61 event_loop_->OnRun([this]() {
62 // Every second poll to check if the child is dead. This is used as a
63 // default for the case where the user is not directly catching SIGCHLD and
64 // calling MaybeHandleSignal for us.
65 child_status_handler_->Setup(event_loop_->monotonic_now(),
66 std::chrono::seconds(1));
67 });
68}
69
70Application::Application(const aos::Application *application,
71 aos::EventLoop *event_loop,
72 std::function<void()> on_change)
73 : Application(application->name()->string_view(),
74 application->has_executable_name()
75 ? application->executable_name()->string_view()
76 : application->name()->string_view(),
77 event_loop, on_change) {
78 user_name_ = application->has_user() ? application->user()->str() : "";
79 user_ = application->has_user() ? FindUid(user_name_.c_str()) : std::nullopt;
80 group_ = application->has_user() ? FindPrimaryGidForUser(user_name_.c_str())
81 : std::nullopt;
82 autostart_ = application->autostart();
83 autorestart_ = application->autorestart();
84 if (application->has_args()) {
85 set_args(*application->args());
86 }
87}
James Kuszmaul3224b8e2022-01-07 19:00:39 -080088
89void Application::DoStart() {
90 if (status_ != aos::starter::State::WAITING) {
91 return;
92 }
93
94 start_timer_->Disable();
95 restart_timer_->Disable();
96
Tyler Chatowd5db0422022-03-02 20:56:15 -080097 if (!UpdatePathAndChecksum()) {
98 stop_reason_ = aos::starter::LastStopReason::RESOLVE_ERR;
99 MaybeQueueRestart();
100 return;
101 }
102
James Kuszmauld42edb42022-01-07 18:00:16 -0800103 status_pipes_ = util::ScopedPipe::MakePipe();
104
105 if (capture_stdout_) {
106 stdout_pipes_ = util::ScopedPipe::MakePipe();
107 stdout_.clear();
108 }
109 if (capture_stderr_) {
110 stderr_pipes_ = util::ScopedPipe::MakePipe();
111 stderr_.clear();
112 }
113
114 pipe_timer_->Setup(event_loop_->monotonic_now(),
115 std::chrono::milliseconds(100));
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800116
117 const pid_t pid = fork();
118
119 if (pid != 0) {
120 if (pid == -1) {
121 PLOG(WARNING) << "Failed to fork '" << name_ << "'";
122 stop_reason_ = aos::starter::LastStopReason::FORK_ERR;
123 status_ = aos::starter::State::STOPPED;
124 } else {
125 pid_ = pid;
126 id_ = next_id_++;
127 start_time_ = event_loop_->monotonic_now();
128 status_ = aos::starter::State::STARTING;
129 LOG(INFO) << "Starting '" << name_ << "' pid " << pid_;
130
131 // Setup timer which moves application to RUNNING state if it is still
132 // alive in 1 second.
133 start_timer_->Setup(event_loop_->monotonic_now() +
134 std::chrono::seconds(1));
James Kuszmauld42edb42022-01-07 18:00:16 -0800135 // Since we are the parent process, clear our write-side of all the pipes.
136 status_pipes_.write.reset();
137 stdout_pipes_.write.reset();
138 stderr_pipes_.write.reset();
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800139 }
140 on_change_();
141 return;
142 }
143
James Kuszmauld42edb42022-01-07 18:00:16 -0800144 // Since we are the child process, clear our read-side of all the pipes.
145 status_pipes_.read.reset();
146 stdout_pipes_.read.reset();
147 stderr_pipes_.read.reset();
148
149 // The status pipe will not be needed if the execve succeeds.
150 status_pipes_.write->SetCloexec();
151
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800152 // Clear out signal mask of parent so forked process receives all signals
153 // normally.
154 sigset_t empty_mask;
155 sigemptyset(&empty_mask);
156 sigprocmask(SIG_SETMASK, &empty_mask, nullptr);
157
158 // Cleanup children if starter dies in a way that is not handled gracefully.
159 if (prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) {
James Kuszmauld42edb42022-01-07 18:00:16 -0800160 status_pipes_.write->Write(
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800161 static_cast<uint32_t>(aos::starter::LastStopReason::SET_PRCTL_ERR));
162 PLOG(FATAL) << "Could not set PR_SET_PDEATHSIG to SIGKILL";
163 }
164
165 if (group_) {
166 CHECK(!user_name_.empty());
167 // The manpage for setgroups says we just need CAP_SETGID, but empirically
168 // we also need the effective UID to be 0 to make it work. user_ must also
169 // be set so we change this effective UID back later.
170 CHECK(user_);
171 if (seteuid(0) == -1) {
James Kuszmauld42edb42022-01-07 18:00:16 -0800172 status_pipes_.write->Write(
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800173 static_cast<uint32_t>(aos::starter::LastStopReason::SET_GRP_ERR));
174 PLOG(FATAL) << "Could not seteuid(0) for " << name_
175 << " in preparation for setting groups";
176 }
177 if (initgroups(user_name_.c_str(), *group_) == -1) {
James Kuszmauld42edb42022-01-07 18:00:16 -0800178 status_pipes_.write->Write(
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800179 static_cast<uint32_t>(aos::starter::LastStopReason::SET_GRP_ERR));
180 PLOG(FATAL) << "Could not initialize normal groups for " << name_
181 << " as " << user_name_ << " with " << *group_;
182 }
183 if (setgid(*group_) == -1) {
James Kuszmauld42edb42022-01-07 18:00:16 -0800184 status_pipes_.write->Write(
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800185 static_cast<uint32_t>(aos::starter::LastStopReason::SET_GRP_ERR));
186 PLOG(FATAL) << "Could not set group for " << name_ << " to " << *group_;
187 }
188 }
189
190 if (user_) {
191 if (setuid(*user_) == -1) {
James Kuszmauld42edb42022-01-07 18:00:16 -0800192 status_pipes_.write->Write(
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800193 static_cast<uint32_t>(aos::starter::LastStopReason::SET_USR_ERR));
194 PLOG(FATAL) << "Could not set user for " << name_ << " to " << *user_;
195 }
196 }
197
James Kuszmauld42edb42022-01-07 18:00:16 -0800198 if (capture_stdout_) {
199 PCHECK(STDOUT_FILENO == dup2(stdout_pipes_.write->fd(), STDOUT_FILENO));
200 stdout_pipes_.write.reset();
201 }
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800202
James Kuszmauld42edb42022-01-07 18:00:16 -0800203 if (capture_stderr_) {
204 PCHECK(STDERR_FILENO == dup2(stderr_pipes_.write->fd(), STDERR_FILENO));
205 stderr_pipes_.write.reset();
206 }
207
208 // argv[0] should be the program name
Tyler Chatowd5db0422022-03-02 20:56:15 -0800209 args_.insert(args_.begin(), full_path_);
James Kuszmauld42edb42022-01-07 18:00:16 -0800210
211 std::vector<char *> cargs = CArgs();
Tyler Chatowd5db0422022-03-02 20:56:15 -0800212 execv(full_path_.c_str(), cargs.data());
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800213
214 // If we got here, something went wrong
James Kuszmauld42edb42022-01-07 18:00:16 -0800215 status_pipes_.write->Write(
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800216 static_cast<uint32_t>(aos::starter::LastStopReason::EXECV_ERR));
Tyler Chatowd5db0422022-03-02 20:56:15 -0800217 PLOG(WARNING) << "Could not execute " << name_ << " (" << full_path_ << ')';
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800218
219 _exit(EXIT_FAILURE);
220}
221
James Kuszmauld42edb42022-01-07 18:00:16 -0800222void Application::FetchOutputs() {
223 if (capture_stdout_) {
224 stdout_pipes_.read->Read(&stdout_);
225 }
226 if (capture_stderr_) {
227 stderr_pipes_.read->Read(&stderr_);
228 }
229}
230
231const std::string &Application::GetStdout() {
232 CHECK(capture_stdout_);
233 FetchOutputs();
234 return stdout_;
235}
236
237const std::string &Application::GetStderr() {
238 CHECK(capture_stderr_);
239 FetchOutputs();
240 return stderr_;
241}
242
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800243void Application::DoStop(bool restart) {
244 // If stop or restart received, the old state of these is no longer applicable
245 // so cancel both.
246 restart_timer_->Disable();
247 start_timer_->Disable();
248
James Kuszmauld42edb42022-01-07 18:00:16 -0800249 FetchOutputs();
250
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800251 switch (status_) {
252 case aos::starter::State::STARTING:
253 case aos::starter::State::RUNNING: {
254 LOG(INFO) << "Stopping '" << name_ << "' pid: " << pid_ << " with signal "
255 << SIGINT;
256 status_ = aos::starter::State::STOPPING;
257
258 kill(pid_, SIGINT);
259
260 // Watchdog timer to SIGKILL application if it is still running 1 second
261 // after SIGINT
262 stop_timer_->Setup(event_loop_->monotonic_now() +
263 std::chrono::seconds(1));
264 queue_restart_ = restart;
265 on_change_();
266 break;
267 }
268 case aos::starter::State::WAITING: {
269 // If waiting to restart, and receives restart, skip the waiting period
270 // and restart immediately. If stop received, all we have to do is move
271 // to the STOPPED state.
272 if (restart) {
273 DoStart();
274 } else {
275 status_ = aos::starter::State::STOPPED;
276 on_change_();
277 }
278 break;
279 }
280 case aos::starter::State::STOPPING: {
281 // If the application is already stopping, then we just need to update the
282 // restart flag to the most recent status.
283 queue_restart_ = restart;
284 break;
285 }
286 case aos::starter::State::STOPPED: {
287 // Restart immediately if the application is already stopped
288 if (restart) {
289 status_ = aos::starter::State::WAITING;
290 DoStart();
291 }
292 break;
293 }
294 }
295}
296
297void Application::QueueStart() {
298 status_ = aos::starter::State::WAITING;
299
300 LOG(INFO) << "Restarting " << name_ << " in 3 seconds";
301 restart_timer_->Setup(event_loop_->monotonic_now() + std::chrono::seconds(3));
302 start_timer_->Disable();
303 stop_timer_->Disable();
304 on_change_();
305}
306
Tyler Chatowd5db0422022-03-02 20:56:15 -0800307void Application::MaybeQueueRestart() {
308 if (autorestart()) {
309 QueueStart();
310 } else {
311 status_ = aos::starter::State::STOPPED;
312 on_change_();
313 }
314}
315
James Kuszmauld42edb42022-01-07 18:00:16 -0800316std::vector<char *> Application::CArgs() {
317 std::vector<char *> cargs;
318 std::transform(args_.begin(), args_.end(), std::back_inserter(cargs),
319 [](std::string &str) { return str.data(); });
320 cargs.push_back(nullptr);
321 return cargs;
322}
323
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800324void Application::set_args(
325 const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> &v) {
326 args_.clear();
327 std::transform(v.begin(), v.end(), std::back_inserter(args_),
James Kuszmauld42edb42022-01-07 18:00:16 -0800328 [](const flatbuffers::String *str) { return str->str(); });
329}
330
331void Application::set_args(std::vector<std::string> args) {
332 args_ = std::move(args);
333}
334
335void Application::set_capture_stdout(bool capture) {
336 capture_stdout_ = capture;
337}
338
339void Application::set_capture_stderr(bool capture) {
340 capture_stderr_ = capture;
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800341}
342
343std::optional<uid_t> Application::FindUid(const char *name) {
344 // TODO(austin): Use the reentrant version. This should be safe.
345 struct passwd *user_data = getpwnam(name);
346 if (user_data != nullptr) {
347 return user_data->pw_uid;
348 } else {
349 LOG(FATAL) << "Could not find user " << name;
350 return std::nullopt;
351 }
352}
353
354std::optional<gid_t> Application::FindPrimaryGidForUser(const char *name) {
355 // TODO(austin): Use the reentrant version. This should be safe.
356 struct passwd *user_data = getpwnam(name);
357 if (user_data != nullptr) {
358 return user_data->pw_gid;
359 } else {
360 LOG(FATAL) << "Could not find user " << name;
361 return std::nullopt;
362 }
363}
364
Tyler Chatowd5db0422022-03-02 20:56:15 -0800365bool Application::UpdatePathAndChecksum() {
366 int fin = -1;
367 std::string test_path;
368 if (path_.find('/') != std::string::npos) {
369 test_path = path_;
370 fin = open(path_.c_str(), O_RDONLY);
371 } else {
372 char *path = secure_getenv("PATH");
373 for (std::string_view path_cmp : absl::StrSplit(path, ':')) {
374 test_path = absl::StrCat(path_cmp, "/", path_);
375 fin = open(test_path.c_str(), O_RDONLY);
376 if (fin != -1) break;
377 }
378 }
379 if (fin == -1) {
380 PLOG(WARNING) << "Failed to open binary '" << path_ << "' as file";
381 return false;
382 }
383
384 full_path_ = std::move(test_path);
385
386 // Hash iteratively to avoid reading the entire binary into memory
387 constexpr std::size_t kReadSize = 1024 * 16;
388
389 SHA256_CTX ctx;
390 CHECK_EQ(SHA256_Init(&ctx), 1);
391
392 std::array<uint8_t, kReadSize> buf;
393
394 while (true) {
395 const ssize_t result = read(fin, buf.data(), kReadSize);
396 PCHECK(result != -1);
397 if (result == 0) {
398 break;
399 } else {
400 CHECK_EQ(SHA256_Update(&ctx, buf.data(), result), 1);
401 }
402 }
403 PCHECK(close(fin) == 0);
404
405 std::array<uint8_t, SHA256_DIGEST_LENGTH> hash_buf;
406 CHECK_EQ(SHA256_Final(hash_buf.data(), &ctx), 1);
407
408 static constexpr std::string_view kHexTable = "0123456789abcdef";
409
410 static_assert(hash_buf.size() * 2 == kSha256HexStrSize);
411 for (std::size_t i = 0; i < hash_buf.size(); ++i) {
412 checksum_[i * 2] = kHexTable[(hash_buf[i] & 0xF0U) >> 4U];
413 checksum_[i * 2 + 1] = kHexTable[hash_buf[i] & 0x0FU];
414 }
415
416 return true;
417}
418
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800419flatbuffers::Offset<aos::starter::ApplicationStatus>
420Application::PopulateStatus(flatbuffers::FlatBufferBuilder *builder) {
421 CHECK_NOTNULL(builder);
422 auto name_fbs = builder->CreateString(name_);
Tyler Chatowd5db0422022-03-02 20:56:15 -0800423 auto full_path_fbs = builder->CreateString(full_path_);
424 flatbuffers::Offset<flatbuffers::String> binary_sha256_fbs;
425 if (pid_ != -1) {
426 binary_sha256_fbs =
427 builder->CreateString(checksum_.data(), checksum_.size());
428 }
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800429
430 aos::starter::ApplicationStatus::Builder status_builder(*builder);
431 status_builder.add_name(name_fbs);
432 status_builder.add_state(status_);
James Kuszmauld42edb42022-01-07 18:00:16 -0800433 if (exit_code_.has_value()) {
434 status_builder.add_last_exit_code(exit_code_.value());
435 }
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800436 status_builder.add_last_stop_reason(stop_reason_);
437 if (pid_ != -1) {
438 status_builder.add_pid(pid_);
439 status_builder.add_id(id_);
Tyler Chatowd5db0422022-03-02 20:56:15 -0800440 status_builder.add_binary_sha256(binary_sha256_fbs);
441 status_builder.add_full_path(full_path_fbs);
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800442 }
443 status_builder.add_last_start_time(start_time_.time_since_epoch().count());
444 return status_builder.Finish();
445}
446
447void Application::Terminate() {
448 stop_reason_ = aos::starter::LastStopReason::TERMINATE;
449 DoStop(false);
450 terminating_ = true;
451}
452
453void Application::HandleCommand(aos::starter::Command cmd) {
454 switch (cmd) {
455 case aos::starter::Command::START: {
456 switch (status_) {
457 case aos::starter::State::WAITING: {
458 restart_timer_->Disable();
459 DoStart();
460 break;
461 }
462 case aos::starter::State::STARTING: {
463 break;
464 }
465 case aos::starter::State::RUNNING: {
466 break;
467 }
468 case aos::starter::State::STOPPING: {
469 queue_restart_ = true;
470 break;
471 }
472 case aos::starter::State::STOPPED: {
473 status_ = aos::starter::State::WAITING;
474 DoStart();
475 break;
476 }
477 }
478 break;
479 }
480 case aos::starter::Command::STOP: {
481 stop_reason_ = aos::starter::LastStopReason::STOP_REQUESTED;
482 DoStop(false);
483 break;
484 }
485 case aos::starter::Command::RESTART: {
486 stop_reason_ = aos::starter::LastStopReason::RESTART_REQUESTED;
487 DoStop(true);
488 break;
489 }
490 }
491}
492
493bool Application::MaybeHandleSignal() {
494 int status;
495
496 // Check if the status of this process has changed
497 if (pid_ == -1 || waitpid(pid_, &status, WNOHANG) != pid_) {
498 return false;
499 }
500
501 // Check that the event was the process exiting
502 if (!WIFEXITED(status) && !WIFSIGNALED(status)) {
503 return false;
504 }
505
James Kuszmauld42edb42022-01-07 18:00:16 -0800506 start_timer_->Disable();
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800507 exit_time_ = event_loop_->monotonic_now();
508 exit_code_ = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status);
509
James Kuszmauld42edb42022-01-07 18:00:16 -0800510 if (auto read_result = status_pipes_.read->Read()) {
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800511 stop_reason_ = static_cast<aos::starter::LastStopReason>(*read_result);
512 }
513
514 switch (status_) {
515 case aos::starter::State::STARTING: {
James Kuszmauld42edb42022-01-07 18:00:16 -0800516 if (exit_code_.value() == 0) {
517 LOG(INFO) << "Application '" << name_ << "' pid " << pid_
518 << " exited with status " << exit_code_.value();
519 } else {
520 LOG(WARNING) << "Failed to start '" << name_ << "' on pid " << pid_
521 << " : Exited with status " << exit_code_.value();
522 }
Tyler Chatowd5db0422022-03-02 20:56:15 -0800523 MaybeQueueRestart();
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800524 break;
525 }
526 case aos::starter::State::RUNNING: {
James Kuszmauld42edb42022-01-07 18:00:16 -0800527 if (exit_code_.value() == 0) {
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800528 LOG(INFO) << "Application '" << name_ << "' pid " << pid_
James Kuszmauld42edb42022-01-07 18:00:16 -0800529 << " exited with status " << exit_code_.value();
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800530 } else {
531 LOG(WARNING) << "Application '" << name_ << "' pid " << pid_
James Kuszmauld42edb42022-01-07 18:00:16 -0800532 << " exited unexpectedly with status "
533 << exit_code_.value();
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800534 }
Tyler Chatowd5db0422022-03-02 20:56:15 -0800535 MaybeQueueRestart();
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800536 break;
537 }
538 case aos::starter::State::STOPPING: {
539 LOG(INFO) << "Successfully stopped '" << name_ << "' pid: " << pid_
James Kuszmauld42edb42022-01-07 18:00:16 -0800540 << " with status " << exit_code_.value();
James Kuszmaul3224b8e2022-01-07 19:00:39 -0800541 status_ = aos::starter::State::STOPPED;
542
543 // Disable force stop timer since the process already died
544 stop_timer_->Disable();
545
546 on_change_();
547 if (terminating_) {
548 return true;
549 }
550
551 if (queue_restart_) {
552 queue_restart_ = false;
553 status_ = aos::starter::State::WAITING;
554 DoStart();
555 }
556 break;
557 }
558 case aos::starter::State::WAITING:
559 case aos::starter::State::STOPPED: {
560 LOG(FATAL)
561 << "Received signal on process that was already stopped : name: '"
562 << name_ << "' pid: " << pid_;
563 break;
564 }
565 }
566
567 return false;
568}
569
570} // namespace aos::starter