1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "common/libs/utils/subprocess.h"
18
19 #ifdef __linux__
20 #include <sys/prctl.h>
21 #endif
22
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <signal.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <sys/wait.h>
30 #include <unistd.h>
31
32 #include <cerrno>
33 #include <cstring>
34 #include <map>
35 #include <optional>
36 #include <ostream>
37 #include <set>
38 #include <sstream>
39 #include <string>
40 #include <thread>
41 #include <type_traits>
42 #include <utility>
43 #include <vector>
44
45 #include <android-base/logging.h>
46 #include <android-base/strings.h>
47
48 #include "common/libs/fs/shared_buf.h"
49 #include "common/libs/utils/files.h"
50
51 extern char** environ;
52
53 namespace cuttlefish {
54 namespace {
55
56 // If a redirected-to file descriptor was already closed, it's possible that
57 // some inherited file descriptor duped to this file descriptor and the redirect
58 // would override that. This function makes sure that doesn't happen.
validate_redirects(const std::map<Subprocess::StdIOChannel,int> & redirects,const std::map<SharedFD,int> & inherited_fds)59 bool validate_redirects(
60 const std::map<Subprocess::StdIOChannel, int>& redirects,
61 const std::map<SharedFD, int>& inherited_fds) {
62 // Add the redirected IO channels to a set as integers. This allows converting
63 // the enum values into integers instead of the other way around.
64 std::set<int> int_redirects;
65 for (const auto& entry : redirects) {
66 int_redirects.insert(static_cast<int>(entry.first));
67 }
68 for (const auto& entry : inherited_fds) {
69 auto dupped_fd = entry.second;
70 if (int_redirects.count(dupped_fd)) {
71 LOG(ERROR) << "Requested redirect of fd(" << dupped_fd
72 << ") conflicts with inherited FD.";
73 return false;
74 }
75 }
76 return true;
77 }
78
do_redirects(const std::map<Subprocess::StdIOChannel,int> & redirects)79 void do_redirects(const std::map<Subprocess::StdIOChannel, int>& redirects) {
80 for (const auto& entry : redirects) {
81 auto std_channel = static_cast<int>(entry.first);
82 auto fd = entry.second;
83 TEMP_FAILURE_RETRY(dup2(fd, std_channel));
84 }
85 }
86
ToCharPointers(const std::vector<std::string> & vect)87 std::vector<const char*> ToCharPointers(const std::vector<std::string>& vect) {
88 std::vector<const char*> ret = {};
89 for (const auto& str : vect) {
90 ret.push_back(str.c_str());
91 }
92 ret.push_back(NULL);
93 return ret;
94 }
95 } // namespace
96
ArgsToVec(char ** argv)97 std::vector<std::string> ArgsToVec(char** argv) {
98 std::vector<std::string> args;
99 for (int i = 0; argv && argv[i]; i++) {
100 args.push_back(argv[i]);
101 }
102 return args;
103 }
104
EnvpToMap(char ** envp)105 std::unordered_map<std::string, std::string> EnvpToMap(char** envp) {
106 std::unordered_map<std::string, std::string> env_map;
107 if (!envp) {
108 return env_map;
109 }
110 for (char** e = envp; *e != nullptr; e++) {
111 std::string env_var_val(*e);
112 auto tokens = android::base::Split(env_var_val, "=");
113 if (tokens.size() <= 1) {
114 LOG(WARNING) << "Environment var in unknown format: " << env_var_val;
115 continue;
116 }
117 const auto var = tokens.at(0);
118 tokens.erase(tokens.begin());
119 env_map[var] = android::base::Join(tokens, "=");
120 }
121 return env_map;
122 }
123
Verbose(bool verbose)124 SubprocessOptions& SubprocessOptions::Verbose(bool verbose) & {
125 verbose_ = verbose;
126 return *this;
127 }
Verbose(bool verbose)128 SubprocessOptions SubprocessOptions::Verbose(bool verbose) && {
129 verbose_ = verbose;
130 return std::move(*this);
131 }
132
133 #ifdef __linux__
ExitWithParent(bool v)134 SubprocessOptions& SubprocessOptions::ExitWithParent(bool v) & {
135 exit_with_parent_ = v;
136 return *this;
137 }
ExitWithParent(bool v)138 SubprocessOptions SubprocessOptions::ExitWithParent(bool v) && {
139 exit_with_parent_ = v;
140 return std::move(*this);
141 }
142 #endif
143
SandboxArguments(std::vector<std::string> args)144 SubprocessOptions& SubprocessOptions::SandboxArguments(
145 std::vector<std::string> args) & {
146 sandbox_arguments_ = std::move(args);
147 return *this;
148 }
149
SandboxArguments(std::vector<std::string> args)150 SubprocessOptions SubprocessOptions::SandboxArguments(
151 std::vector<std::string> args) && {
152 sandbox_arguments_ = std::move(args);
153 return *this;
154 }
155
InGroup(bool in_group)156 SubprocessOptions& SubprocessOptions::InGroup(bool in_group) & {
157 in_group_ = in_group;
158 return *this;
159 }
InGroup(bool in_group)160 SubprocessOptions SubprocessOptions::InGroup(bool in_group) && {
161 in_group_ = in_group;
162 return std::move(*this);
163 }
164
Strace(std::string s)165 SubprocessOptions& SubprocessOptions::Strace(std::string s) & {
166 strace_ = std::move(s);
167 return *this;
168 }
Strace(std::string s)169 SubprocessOptions SubprocessOptions::Strace(std::string s) && {
170 strace_ = std::move(s);
171 return std::move(*this);
172 }
173
Subprocess(Subprocess && subprocess)174 Subprocess::Subprocess(Subprocess&& subprocess)
175 : pid_(subprocess.pid_.load()),
176 started_(subprocess.started_),
177 stopper_(subprocess.stopper_) {
178 // Make sure the moved object no longer controls this subprocess
179 subprocess.pid_ = -1;
180 subprocess.started_ = false;
181 }
182
operator =(Subprocess && other)183 Subprocess& Subprocess::operator=(Subprocess&& other) {
184 pid_ = other.pid_.load();
185 started_ = other.started_;
186 stopper_ = other.stopper_;
187
188 other.pid_ = -1;
189 other.started_ = false;
190 return *this;
191 }
192
Wait()193 int Subprocess::Wait() {
194 if (pid_ < 0) {
195 LOG(ERROR)
196 << "Attempt to wait on invalid pid(has it been waited on already?): "
197 << pid_;
198 return -1;
199 }
200 int wstatus = 0;
201 auto pid = pid_.load(); // Wait will set pid_ to -1 after waiting
202 auto wait_ret = waitpid(pid, &wstatus, 0);
203 if (wait_ret < 0) {
204 auto error = errno;
205 LOG(ERROR) << "Error on call to waitpid: " << strerror(error);
206 return wait_ret;
207 }
208 int retval = 0;
209 if (WIFEXITED(wstatus)) {
210 pid_ = -1;
211 retval = WEXITSTATUS(wstatus);
212 if (retval) {
213 LOG(DEBUG) << "Subprocess " << pid
214 << " exited with error code: " << retval;
215 }
216 } else if (WIFSIGNALED(wstatus)) {
217 pid_ = -1;
218 int sig_num = WTERMSIG(wstatus);
219 LOG(ERROR) << "Subprocess " << pid << " was interrupted by a signal '"
220 << strsignal(sig_num) << "' (" << sig_num << ")";
221 retval = -1;
222 }
223 return retval;
224 }
Wait(siginfo_t * infop,int options)225 int Subprocess::Wait(siginfo_t* infop, int options) {
226 if (pid_ < 0) {
227 LOG(ERROR)
228 << "Attempt to wait on invalid pid(has it been waited on already?): "
229 << pid_;
230 return -1;
231 }
232 *infop = {};
233 auto retval = TEMP_FAILURE_RETRY(waitid(P_PID, pid_, infop, options));
234 // We don't want to wait twice for the same process
235 bool exited = infop->si_code == CLD_EXITED || infop->si_code == CLD_DUMPED;
236 bool reaped = !(options & WNOWAIT);
237 if (exited && reaped) {
238 pid_ = -1;
239 }
240 return retval;
241 }
242
SendSignalImpl(const int signal,const pid_t pid,bool to_group,const bool started)243 static Result<void> SendSignalImpl(const int signal, const pid_t pid,
244 bool to_group, const bool started) {
245 if (pid == -1) {
246 return CF_ERR(strerror(ESRCH));
247 }
248 CF_EXPECTF(started == true,
249 "The Subprocess object lost the ownership"
250 "of the process {}.",
251 pid);
252 int ret_code = 0;
253 if (to_group) {
254 ret_code = killpg(getpgid(pid), signal);
255 } else {
256 ret_code = kill(pid, signal);
257 }
258 CF_EXPECTF(ret_code == 0, "kill/killpg returns {} with errno: {}", ret_code,
259 strerror(errno));
260 return {};
261 }
262
SendSignal(const int signal)263 Result<void> Subprocess::SendSignal(const int signal) {
264 CF_EXPECT(SendSignalImpl(signal, pid_, /* to_group */ false, started_));
265 return {};
266 }
267
SendSignalToGroup(const int signal)268 Result<void> Subprocess::SendSignalToGroup(const int signal) {
269 CF_EXPECT(SendSignalImpl(signal, pid_, /* to_group */ true, started_));
270 return {};
271 }
272
KillSubprocess(Subprocess * subprocess)273 StopperResult KillSubprocess(Subprocess* subprocess) {
274 auto pid = subprocess->pid();
275 if (pid > 0) {
276 auto pgid = getpgid(pid);
277 if (pgid < 0) {
278 auto error = errno;
279 LOG(WARNING) << "Error obtaining process group id of process with pid="
280 << pid << ": " << strerror(error);
281 // Send the kill signal anyways, because pgid will be -1 it will be sent
282 // to the process and not a (non-existent) group
283 }
284 bool is_group_head = pid == pgid;
285 auto kill_ret = (is_group_head ? killpg : kill)(pid, SIGKILL);
286 if (kill_ret == 0) {
287 return StopperResult::kStopSuccess;
288 }
289 auto kill_cmd = is_group_head ? "killpg(" : "kill(";
290 PLOG(ERROR) << kill_cmd << pid << ", SIGKILL) failed: ";
291 return StopperResult::kStopFailure;
292 }
293 return StopperResult::kStopSuccess;
294 }
295
KillSubprocessFallback(std::function<StopperResult ()> nice)296 SubprocessStopper KillSubprocessFallback(std::function<StopperResult()> nice) {
297 return KillSubprocessFallback([nice](Subprocess*) { return nice(); });
298 }
299
KillSubprocessFallback(SubprocessStopper nice_stopper)300 SubprocessStopper KillSubprocessFallback(SubprocessStopper nice_stopper) {
301 return [nice_stopper](Subprocess* process) {
302 auto nice_result = nice_stopper(process);
303 if (nice_result == StopperResult::kStopFailure) {
304 auto harsh_result = KillSubprocess(process);
305 return harsh_result == StopperResult::kStopSuccess
306 ? StopperResult::kStopCrash
307 : harsh_result;
308 }
309 return nice_result;
310 };
311 }
312
Command(std::string executable,SubprocessStopper stopper)313 Command::Command(std::string executable, SubprocessStopper stopper)
314 : subprocess_stopper_(stopper) {
315 for (char** env = environ; *env; env++) {
316 env_.emplace_back(*env);
317 }
318 command_.emplace_back(std::move(executable));
319 }
320
~Command()321 Command::~Command() {
322 // Close all inherited file descriptors
323 for (const auto& entry : inherited_fds_) {
324 close(entry.second);
325 }
326 // Close all redirected file descriptors
327 for (const auto& entry : redirects_) {
328 close(entry.second);
329 }
330 }
331
BuildParameter(std::stringstream * stream,SharedFD shared_fd)332 void Command::BuildParameter(std::stringstream* stream, SharedFD shared_fd) {
333 int fd;
334 if (inherited_fds_.count(shared_fd)) {
335 fd = inherited_fds_[shared_fd];
336 } else {
337 fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
338 CHECK(fd >= 0) << "Could not acquire a new file descriptor: "
339 << shared_fd->StrError();
340 inherited_fds_[shared_fd] = fd;
341 }
342 *stream << fd;
343 }
344
RedirectStdIO(Subprocess::StdIOChannel channel,SharedFD shared_fd)345 Command& Command::RedirectStdIO(Subprocess::StdIOChannel channel,
346 SharedFD shared_fd) & {
347 CHECK(shared_fd->IsOpen());
348 CHECK(redirects_.count(channel) == 0)
349 << "Attempted multiple redirections of fd: " << static_cast<int>(channel);
350 auto dup_fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
351 CHECK(dup_fd >= 0) << "Could not acquire a new file descriptor: "
352 << shared_fd->StrError();
353 redirects_[channel] = dup_fd;
354 return *this;
355 }
RedirectStdIO(Subprocess::StdIOChannel channel,SharedFD shared_fd)356 Command Command::RedirectStdIO(Subprocess::StdIOChannel channel,
357 SharedFD shared_fd) && {
358 RedirectStdIO(channel, shared_fd);
359 return std::move(*this);
360 }
RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,Subprocess::StdIOChannel parent_channel)361 Command& Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
362 Subprocess::StdIOChannel parent_channel) & {
363 return RedirectStdIO(subprocess_channel,
364 SharedFD::Dup(static_cast<int>(parent_channel)));
365 }
RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,Subprocess::StdIOChannel parent_channel)366 Command Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
367 Subprocess::StdIOChannel parent_channel) && {
368 RedirectStdIO(subprocess_channel, parent_channel);
369 return std::move(*this);
370 }
371
SetWorkingDirectory(const std::string & path)372 Command& Command::SetWorkingDirectory(const std::string& path) & {
373 #ifdef __linux__
374 auto fd = SharedFD::Open(path, O_RDONLY | O_PATH | O_DIRECTORY);
375 #elif defined(__APPLE__)
376 auto fd = SharedFD::Open(path, O_RDONLY | O_DIRECTORY);
377 #else
378 #error "Unsupported operating system"
379 #endif
380 CHECK(fd->IsOpen()) << "Could not open \"" << path
381 << "\" dir fd: " << fd->StrError();
382 return SetWorkingDirectory(fd);
383 }
SetWorkingDirectory(const std::string & path)384 Command Command::SetWorkingDirectory(const std::string& path) && {
385 return std::move(SetWorkingDirectory(path));
386 }
SetWorkingDirectory(SharedFD dirfd)387 Command& Command::SetWorkingDirectory(SharedFD dirfd) & {
388 CHECK(dirfd->IsOpen()) << "Dir fd invalid: " << dirfd->StrError();
389 working_directory_ = std::move(dirfd);
390 return *this;
391 }
SetWorkingDirectory(SharedFD dirfd)392 Command Command::SetWorkingDirectory(SharedFD dirfd) && {
393 return std::move(SetWorkingDirectory(std::move(dirfd)));
394 }
395
AddPrerequisite(const std::function<Result<void> ()> & prerequisite)396 Command& Command::AddPrerequisite(
397 const std::function<Result<void>()>& prerequisite) & {
398 prerequisites_.push_back(prerequisite);
399 return *this;
400 }
401
AddPrerequisite(const std::function<Result<void> ()> & prerequisite)402 Command Command::AddPrerequisite(
403 const std::function<Result<void>()>& prerequisite) && {
404 prerequisites_.push_back(prerequisite);
405 return std::move(*this);
406 }
407
Start(SubprocessOptions options) const408 Subprocess Command::Start(SubprocessOptions options) const {
409 auto cmd = ToCharPointers(command_);
410
411 if (!options.Strace().empty()) {
412 auto strace_args = {
413 "/usr/bin/strace",
414 "--daemonize",
415 "--output-separately", // Add .pid suffix
416 "--follow-forks",
417 "-o", // Write to a separate file.
418 options.Strace().c_str(),
419 };
420 cmd.insert(cmd.begin(), strace_args);
421 }
422
423 if (!validate_redirects(redirects_, inherited_fds_)) {
424 return Subprocess(-1, {});
425 }
426
427 std::string fds_arg;
428 if (!options.SandboxArguments().empty()) {
429 std::vector<int> fds;
430 for (const auto& redirect : redirects_) {
431 fds.emplace_back(static_cast<int>(redirect.first));
432 }
433 for (const auto& inherited_fd : inherited_fds_) {
434 fds.emplace_back(inherited_fd.second);
435 }
436 fds_arg = "--inherited_fds=" + fmt::format("{}", fmt::join(fds, ","));
437
438 auto forwarding_args = {fds_arg.c_str(), "--"};
439 cmd.insert(cmd.begin(), forwarding_args);
440 auto sbox_ptrs = ToCharPointers(options.SandboxArguments());
441 sbox_ptrs.pop_back(); // Final null pointer will end argv early
442 cmd.insert(cmd.begin(), sbox_ptrs.begin(), sbox_ptrs.end());
443 }
444
445 pid_t pid = fork();
446 if (!pid) {
447 #ifdef __linux__
448 if (options.ExitWithParent()) {
449 prctl(PR_SET_PDEATHSIG, SIGHUP); // Die when parent dies
450 }
451 #endif
452
453 do_redirects(redirects_);
454
455 for (auto& prerequisite : prerequisites_) {
456 auto prerequisiteResult = prerequisite();
457
458 if (!prerequisiteResult.ok()) {
459 LOG(ERROR) << "Failed to check prerequisites: "
460 << prerequisiteResult.error().FormatForEnv();
461 }
462 }
463
464 if (options.InGroup()) {
465 // This call should never fail (see SETPGID(2))
466 if (setpgid(0, 0) != 0) {
467 auto error = errno;
468 LOG(ERROR) << "setpgid failed (" << strerror(error) << ")";
469 }
470 }
471 for (const auto& entry : inherited_fds_) {
472 if (fcntl(entry.second, F_SETFD, 0)) {
473 int error_num = errno;
474 LOG(ERROR) << "fcntl failed: " << strerror(error_num);
475 }
476 }
477 if (working_directory_->IsOpen()) {
478 if (SharedFD::Fchdir(working_directory_) != 0) {
479 LOG(ERROR) << "Fchdir failed: " << working_directory_->StrError();
480 }
481 }
482 int rval;
483 auto envp = ToCharPointers(env_);
484 const char* executable = executable_ ? executable_->c_str() : cmd[0];
485 #ifdef __linux__
486 rval = execvpe(executable, const_cast<char* const*>(cmd.data()),
487 const_cast<char* const*>(envp.data()));
488 #elif defined(__APPLE__)
489 rval = execve(executable, const_cast<char* const*>(cmd.data()),
490 const_cast<char* const*>(envp.data()));
491 #else
492 #error "Unsupported architecture"
493 #endif
494 // No need for an if: if exec worked it wouldn't have returned
495 LOG(ERROR) << "exec of " << cmd[0] << " with path \"" << executable
496 << "\" failed (" << strerror(errno) << ")";
497 exit(rval);
498 }
499 if (pid == -1) {
500 LOG(ERROR) << "fork failed (" << strerror(errno) << ")";
501 }
502 if (options.Verbose()) { // "more verbose", and LOG(DEBUG) > LOG(VERBOSE)
503 LOG(DEBUG) << "Started (pid: " << pid << "): " << cmd[0];
504 for (int i = 1; cmd[i]; i++) {
505 LOG(DEBUG) << cmd[i];
506 }
507 } else {
508 LOG(VERBOSE) << "Started (pid: " << pid << "): " << cmd[0];
509 for (int i = 1; cmd[i]; i++) {
510 LOG(VERBOSE) << cmd[i];
511 }
512 }
513 return Subprocess(pid, subprocess_stopper_);
514 }
515
AsBashScript(const std::string & redirected_stdio_path) const516 std::string Command::AsBashScript(
517 const std::string& redirected_stdio_path) const {
518 CHECK(inherited_fds_.empty())
519 << "Bash wrapper will not have inheritied file descriptors.";
520 CHECK(redirects_.empty()) << "Bash wrapper will not have redirected stdio.";
521
522 std::string contents =
523 "#!/bin/bash\n\n" + android::base::Join(command_, " \\\n");
524 if (!redirected_stdio_path.empty()) {
525 contents += " &> " + AbsolutePath(redirected_stdio_path);
526 }
527 return contents;
528 }
529
530 // A class that waits for threads to exit in its destructor.
531 class ThreadJoiner {
532 std::vector<std::thread*> threads_;
533 public:
ThreadJoiner(const std::vector<std::thread * > threads)534 ThreadJoiner(const std::vector<std::thread*> threads) : threads_(threads) {}
~ThreadJoiner()535 ~ThreadJoiner() {
536 for (auto& thread : threads_) {
537 if (thread->joinable()) {
538 thread->join();
539 }
540 }
541 }
542 };
543
RunWithManagedStdio(Command && cmd_tmp,const std::string * stdin_str,std::string * stdout_str,std::string * stderr_str,SubprocessOptions options)544 int RunWithManagedStdio(Command&& cmd_tmp, const std::string* stdin_str,
545 std::string* stdout_str, std::string* stderr_str,
546 SubprocessOptions options) {
547 /*
548 * The order of these declarations is necessary for safety. If the function
549 * returns at any point, the Command will be destroyed first, closing all
550 * of its references to SharedFDs. This will cause the thread internals to fail
551 * their reads or writes. The ThreadJoiner then waits for the threads to
552 * complete, as running the destructor of an active std::thread crashes the
553 * program.
554 *
555 * C++ scoping rules dictate that objects are descoped in reverse order to
556 * construction, so this behavior is predictable.
557 */
558 std::thread stdin_thread, stdout_thread, stderr_thread;
559 ThreadJoiner thread_joiner({&stdin_thread, &stdout_thread, &stderr_thread});
560 Command cmd = std::move(cmd_tmp);
561 bool io_error = false;
562 if (stdin_str != nullptr) {
563 SharedFD pipe_read, pipe_write;
564 if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
565 LOG(ERROR) << "Could not create a pipe to write the stdin of \""
566 << cmd.GetShortName() << "\"";
567 return -1;
568 }
569 cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read);
570 stdin_thread = std::thread([pipe_write, stdin_str, &io_error]() {
571 int written = WriteAll(pipe_write, *stdin_str);
572 if (written < 0) {
573 io_error = true;
574 LOG(ERROR) << "Error in writing stdin to process";
575 }
576 });
577 }
578 if (stdout_str != nullptr) {
579 SharedFD pipe_read, pipe_write;
580 if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
581 LOG(ERROR) << "Could not create a pipe to read the stdout of \""
582 << cmd.GetShortName() << "\"";
583 return -1;
584 }
585 cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write);
586 stdout_thread = std::thread([pipe_read, stdout_str, &io_error]() {
587 int read = ReadAll(pipe_read, stdout_str);
588 if (read < 0) {
589 io_error = true;
590 LOG(ERROR) << "Error in reading stdout from process";
591 }
592 });
593 }
594 if (stderr_str != nullptr) {
595 SharedFD pipe_read, pipe_write;
596 if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
597 LOG(ERROR) << "Could not create a pipe to read the stderr of \""
598 << cmd.GetShortName() << "\"";
599 return -1;
600 }
601 cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write);
602 stderr_thread = std::thread([pipe_read, stderr_str, &io_error]() {
603 int read = ReadAll(pipe_read, stderr_str);
604 if (read < 0) {
605 io_error = true;
606 LOG(ERROR) << "Error in reading stderr from process";
607 }
608 });
609 }
610
611 auto subprocess = cmd.Start(std::move(options));
612 if (!subprocess.Started()) {
613 return -1;
614 }
615 auto cmd_short_name = cmd.GetShortName();
616 {
617 // Force the destructor to run by moving it into a smaller scope.
618 // This is necessary to close the write end of the pipe.
619 Command forceDelete = std::move(cmd);
620 }
621
622 int code = subprocess.Wait();
623 {
624 auto join_threads = std::move(thread_joiner);
625 }
626 if (io_error) {
627 LOG(ERROR) << "IO error communicating with " << cmd_short_name;
628 return -1;
629 }
630 return code;
631 }
632
633 namespace {
634
635 struct ExtraParam {
636 // option for Subprocess::Start()
637 SubprocessOptions subprocess_options;
638 // options for Subprocess::Wait(...)
639 int wait_options;
640 siginfo_t* infop;
641 };
ExecuteImpl(const std::vector<std::string> & command,const std::optional<std::vector<std::string>> & envs,std::optional<ExtraParam> extra_param)642 Result<int> ExecuteImpl(const std::vector<std::string>& command,
643 const std::optional<std::vector<std::string>>& envs,
644 std::optional<ExtraParam> extra_param) {
645 Command cmd(command[0]);
646 for (size_t i = 1; i < command.size(); ++i) {
647 cmd.AddParameter(command[i]);
648 }
649 if (envs) {
650 cmd.SetEnvironment(*envs);
651 }
652 auto subprocess =
653 (!extra_param ? cmd.Start()
654 : cmd.Start(std::move(extra_param->subprocess_options)));
655 CF_EXPECT(subprocess.Started(), "Subprocess failed to start.");
656
657 if (extra_param) {
658 CF_EXPECT(extra_param->infop != nullptr,
659 "When ExtraParam is given, the infop buffer address "
660 << "must not be nullptr.");
661 return subprocess.Wait(extra_param->infop, extra_param->wait_options);
662 } else {
663 return subprocess.Wait();
664 }
665 }
666
667 } // namespace
668
Execute(const std::vector<std::string> & commands,const std::vector<std::string> & envs)669 int Execute(const std::vector<std::string>& commands,
670 const std::vector<std::string>& envs) {
671 auto result = ExecuteImpl(commands, envs, /* extra_param */ std::nullopt);
672 return (!result.ok() ? -1 : *result);
673 }
674
Execute(const std::vector<std::string> & commands)675 int Execute(const std::vector<std::string>& commands) {
676 std::vector<std::string> envs;
677 auto result = ExecuteImpl(commands, /* envs */ std::nullopt,
678 /* extra_param */ std::nullopt);
679 return (!result.ok() ? -1 : *result);
680 }
681
Execute(const std::vector<std::string> & commands,SubprocessOptions subprocess_options,int wait_options)682 Result<siginfo_t> Execute(const std::vector<std::string>& commands,
683 SubprocessOptions subprocess_options,
684 int wait_options) {
685 siginfo_t info;
686 auto ret_code = CF_EXPECT(ExecuteImpl(
687 commands, /* envs */ std::nullopt,
688 ExtraParam{.subprocess_options = std::move(subprocess_options),
689 .wait_options = wait_options,
690 .infop = &info}));
691 CF_EXPECT(ret_code == 0, "Subprocess::Wait() returned " << ret_code);
692 return info;
693 }
694
Execute(const std::vector<std::string> & commands,const std::vector<std::string> & envs,SubprocessOptions subprocess_options,int wait_options)695 Result<siginfo_t> Execute(const std::vector<std::string>& commands,
696 const std::vector<std::string>& envs,
697 SubprocessOptions subprocess_options,
698 int wait_options) {
699 siginfo_t info;
700 auto ret_code = CF_EXPECT(ExecuteImpl(
701 commands, envs,
702 ExtraParam{.subprocess_options = std::move(subprocess_options),
703 .wait_options = wait_options,
704 .infop = &info}));
705 CF_EXPECT(ret_code == 0, "Subprocess::Wait() returned " << ret_code);
706 return info;
707 }
708
709 } // namespace cuttlefish
710