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 
InGroup(bool in_group)144 SubprocessOptions& SubprocessOptions::InGroup(bool in_group) & {
145   in_group_ = in_group;
146   return *this;
147 }
InGroup(bool in_group)148 SubprocessOptions SubprocessOptions::InGroup(bool in_group) && {
149   in_group_ = in_group;
150   return std::move(*this);
151 }
152 
Strace(std::string s)153 SubprocessOptions& SubprocessOptions::Strace(std::string s) & {
154   strace_ = std::move(s);
155   return *this;
156 }
Strace(std::string s)157 SubprocessOptions SubprocessOptions::Strace(std::string s) && {
158   strace_ = std::move(s);
159   return std::move(*this);
160 }
161 
Subprocess(Subprocess && subprocess)162 Subprocess::Subprocess(Subprocess&& subprocess)
163     : pid_(subprocess.pid_.load()),
164       started_(subprocess.started_),
165       stopper_(subprocess.stopper_) {
166   // Make sure the moved object no longer controls this subprocess
167   subprocess.pid_ = -1;
168   subprocess.started_ = false;
169 }
170 
operator =(Subprocess && other)171 Subprocess& Subprocess::operator=(Subprocess&& other) {
172   pid_ = other.pid_.load();
173   started_ = other.started_;
174   stopper_ = other.stopper_;
175 
176   other.pid_ = -1;
177   other.started_ = false;
178   return *this;
179 }
180 
Wait()181 int Subprocess::Wait() {
182   if (pid_ < 0) {
183     LOG(ERROR)
184         << "Attempt to wait on invalid pid(has it been waited on already?): "
185         << pid_;
186     return -1;
187   }
188   int wstatus = 0;
189   auto pid = pid_.load();  // Wait will set pid_ to -1 after waiting
190   auto wait_ret = waitpid(pid, &wstatus, 0);
191   if (wait_ret < 0) {
192     auto error = errno;
193     LOG(ERROR) << "Error on call to waitpid: " << strerror(error);
194     return wait_ret;
195   }
196   int retval = 0;
197   if (WIFEXITED(wstatus)) {
198     pid_ = -1;
199     retval = WEXITSTATUS(wstatus);
200     if (retval) {
201       LOG(DEBUG) << "Subprocess " << pid
202                  << " exited with error code: " << retval;
203     }
204   } else if (WIFSIGNALED(wstatus)) {
205     pid_ = -1;
206     int sig_num = WTERMSIG(wstatus);
207     LOG(ERROR) << "Subprocess " << pid << " was interrupted by a signal '"
208                << strsignal(sig_num) << "' (" << sig_num << ")";
209     retval = -1;
210   }
211   return retval;
212 }
Wait(siginfo_t * infop,int options)213 int Subprocess::Wait(siginfo_t* infop, int options) {
214   if (pid_ < 0) {
215     LOG(ERROR)
216         << "Attempt to wait on invalid pid(has it been waited on already?): "
217         << pid_;
218     return -1;
219   }
220   *infop = {};
221   auto retval = TEMP_FAILURE_RETRY(waitid(P_PID, pid_, infop, options));
222   // We don't want to wait twice for the same process
223   bool exited = infop->si_code == CLD_EXITED || infop->si_code == CLD_DUMPED;
224   bool reaped = !(options & WNOWAIT);
225   if (exited && reaped) {
226     pid_ = -1;
227   }
228   return retval;
229 }
230 
SendSignalImpl(const int signal,const pid_t pid,bool to_group,const bool started)231 static Result<void> SendSignalImpl(const int signal, const pid_t pid,
232                                    bool to_group, const bool started) {
233   if (pid == -1) {
234     return CF_ERR(strerror(ESRCH));
235   }
236   CF_EXPECTF(started == true,
237              "The Subprocess object lost the ownership"
238              "of the process {}.",
239              pid);
240   int ret_code = 0;
241   if (to_group) {
242     ret_code = killpg(getpgid(pid), signal);
243   } else {
244     ret_code = kill(pid, signal);
245   }
246   CF_EXPECTF(ret_code == 0, "kill/killpg returns {} with errno: {}", ret_code,
247              strerror(errno));
248   return {};
249 }
250 
SendSignal(const int signal)251 Result<void> Subprocess::SendSignal(const int signal) {
252   CF_EXPECT(SendSignalImpl(signal, pid_, /* to_group */ false, started_));
253   return {};
254 }
255 
SendSignalToGroup(const int signal)256 Result<void> Subprocess::SendSignalToGroup(const int signal) {
257   CF_EXPECT(SendSignalImpl(signal, pid_, /* to_group */ true, started_));
258   return {};
259 }
260 
KillSubprocess(Subprocess * subprocess)261 StopperResult KillSubprocess(Subprocess* subprocess) {
262   auto pid = subprocess->pid();
263   if (pid > 0) {
264     auto pgid = getpgid(pid);
265     if (pgid < 0) {
266       auto error = errno;
267       LOG(WARNING) << "Error obtaining process group id of process with pid="
268                    << pid << ": " << strerror(error);
269       // Send the kill signal anyways, because pgid will be -1 it will be sent
270       // to the process and not a (non-existent) group
271     }
272     bool is_group_head = pid == pgid;
273     auto kill_ret = (is_group_head ? killpg : kill)(pid, SIGKILL);
274     if (kill_ret == 0) {
275       return StopperResult::kStopSuccess;
276     }
277     auto kill_cmd = is_group_head ? "killpg(" : "kill(";
278     PLOG(ERROR) << kill_cmd << pid << ", SIGKILL) failed: ";
279     return StopperResult::kStopFailure;
280   }
281   return StopperResult::kStopSuccess;
282 }
283 
KillSubprocessFallback(std::function<StopperResult ()> nice)284 SubprocessStopper KillSubprocessFallback(std::function<StopperResult()> nice) {
285   return KillSubprocessFallback([nice](Subprocess*) { return nice(); });
286 }
287 
KillSubprocessFallback(SubprocessStopper nice_stopper)288 SubprocessStopper KillSubprocessFallback(SubprocessStopper nice_stopper) {
289   return [nice_stopper](Subprocess* process) {
290     auto nice_result = nice_stopper(process);
291     if (nice_result == StopperResult::kStopFailure) {
292       auto harsh_result = KillSubprocess(process);
293       return harsh_result == StopperResult::kStopSuccess
294                  ? StopperResult::kStopCrash
295                  : harsh_result;
296     }
297     return nice_result;
298   };
299 }
300 
Command(std::string executable,SubprocessStopper stopper)301 Command::Command(std::string executable, SubprocessStopper stopper)
302     : subprocess_stopper_(stopper) {
303   for (char** env = environ; *env; env++) {
304     env_.emplace_back(*env);
305   }
306   command_.emplace_back(std::move(executable));
307 }
308 
~Command()309 Command::~Command() {
310   // Close all inherited file descriptors
311   for (const auto& entry : inherited_fds_) {
312     close(entry.second);
313   }
314   // Close all redirected file descriptors
315   for (const auto& entry : redirects_) {
316     close(entry.second);
317   }
318 }
319 
BuildParameter(std::stringstream * stream,SharedFD shared_fd)320 void Command::BuildParameter(std::stringstream* stream, SharedFD shared_fd) {
321   int fd;
322   if (inherited_fds_.count(shared_fd)) {
323     fd = inherited_fds_[shared_fd];
324   } else {
325     fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
326     CHECK(fd >= 0) << "Could not acquire a new file descriptor: "
327                    << shared_fd->StrError();
328     inherited_fds_[shared_fd] = fd;
329   }
330   *stream << fd;
331 }
332 
RedirectStdIO(Subprocess::StdIOChannel channel,SharedFD shared_fd)333 Command& Command::RedirectStdIO(Subprocess::StdIOChannel channel,
334                                 SharedFD shared_fd) & {
335   CHECK(shared_fd->IsOpen());
336   CHECK(redirects_.count(channel) == 0)
337       << "Attempted multiple redirections of fd: " << static_cast<int>(channel);
338   auto dup_fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
339   CHECK(dup_fd >= 0) << "Could not acquire a new file descriptor: "
340                      << shared_fd->StrError();
341   redirects_[channel] = dup_fd;
342   return *this;
343 }
RedirectStdIO(Subprocess::StdIOChannel channel,SharedFD shared_fd)344 Command Command::RedirectStdIO(Subprocess::StdIOChannel channel,
345                                SharedFD shared_fd) && {
346   RedirectStdIO(channel, shared_fd);
347   return std::move(*this);
348 }
RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,Subprocess::StdIOChannel parent_channel)349 Command& Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
350                                 Subprocess::StdIOChannel parent_channel) & {
351   return RedirectStdIO(subprocess_channel,
352                        SharedFD::Dup(static_cast<int>(parent_channel)));
353 }
RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,Subprocess::StdIOChannel parent_channel)354 Command Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
355                                Subprocess::StdIOChannel parent_channel) && {
356   RedirectStdIO(subprocess_channel, parent_channel);
357   return std::move(*this);
358 }
359 
SetWorkingDirectory(const std::string & path)360 Command& Command::SetWorkingDirectory(const std::string& path) & {
361 #ifdef __linux__
362   auto fd = SharedFD::Open(path, O_RDONLY | O_PATH | O_DIRECTORY);
363 #elif defined(__APPLE__)
364   auto fd = SharedFD::Open(path, O_RDONLY | O_DIRECTORY);
365 #else
366 #error "Unsupported operating system"
367 #endif
368   CHECK(fd->IsOpen()) << "Could not open \"" << path
369                       << "\" dir fd: " << fd->StrError();
370   return SetWorkingDirectory(fd);
371 }
SetWorkingDirectory(const std::string & path)372 Command Command::SetWorkingDirectory(const std::string& path) && {
373   return std::move(SetWorkingDirectory(path));
374 }
SetWorkingDirectory(SharedFD dirfd)375 Command& Command::SetWorkingDirectory(SharedFD dirfd) & {
376   CHECK(dirfd->IsOpen()) << "Dir fd invalid: " << dirfd->StrError();
377   working_directory_ = std::move(dirfd);
378   return *this;
379 }
SetWorkingDirectory(SharedFD dirfd)380 Command Command::SetWorkingDirectory(SharedFD dirfd) && {
381   return std::move(SetWorkingDirectory(std::move(dirfd)));
382 }
383 
AddPrerequisite(const std::function<Result<void> ()> & prerequisite)384 Command& Command::AddPrerequisite(
385     const std::function<Result<void>()>& prerequisite) & {
386   prerequisites_.push_back(prerequisite);
387   return *this;
388 }
389 
AddPrerequisite(const std::function<Result<void> ()> & prerequisite)390 Command Command::AddPrerequisite(
391     const std::function<Result<void>()>& prerequisite) && {
392   prerequisites_.push_back(prerequisite);
393   return std::move(*this);
394 }
395 
Start(SubprocessOptions options) const396 Subprocess Command::Start(SubprocessOptions options) const {
397   auto cmd = ToCharPointers(command_);
398 
399   if (!options.Strace().empty()) {
400     auto strace_args = {
401         "/usr/bin/strace",
402         "--daemonize",
403         "--output-separately",  // Add .pid suffix
404         "--follow-forks",
405         "-o",  // Write to a separate file.
406         options.Strace().c_str(),
407     };
408     cmd.insert(cmd.begin(), strace_args);
409   }
410 
411   if (!validate_redirects(redirects_, inherited_fds_)) {
412     return Subprocess(-1, {});
413   }
414 
415   pid_t pid = fork();
416   if (!pid) {
417 #ifdef __linux__
418     if (options.ExitWithParent()) {
419       prctl(PR_SET_PDEATHSIG, SIGHUP); // Die when parent dies
420     }
421 #endif
422 
423     do_redirects(redirects_);
424 
425     for (auto& prerequisite : prerequisites_) {
426       auto prerequisiteResult = prerequisite();
427 
428       if (!prerequisiteResult.ok()) {
429         LOG(ERROR) << "Failed to check prerequisites: "
430                    << prerequisiteResult.error().FormatForEnv();
431       }
432     }
433 
434     if (options.InGroup()) {
435       // This call should never fail (see SETPGID(2))
436       if (setpgid(0, 0) != 0) {
437         auto error = errno;
438         LOG(ERROR) << "setpgid failed (" << strerror(error) << ")";
439       }
440     }
441     for (const auto& entry : inherited_fds_) {
442       if (fcntl(entry.second, F_SETFD, 0)) {
443         int error_num = errno;
444         LOG(ERROR) << "fcntl failed: " << strerror(error_num);
445       }
446     }
447     if (working_directory_->IsOpen()) {
448       if (SharedFD::Fchdir(working_directory_) != 0) {
449         LOG(ERROR) << "Fchdir failed: " << working_directory_->StrError();
450       }
451     }
452     int rval;
453     auto envp = ToCharPointers(env_);
454     const char* executable = executable_ ? executable_->c_str() : cmd[0];
455 #ifdef __linux__
456     rval = execvpe(executable, const_cast<char* const*>(cmd.data()),
457                    const_cast<char* const*>(envp.data()));
458 #elif defined(__APPLE__)
459     rval = execve(executable, const_cast<char* const*>(cmd.data()),
460                   const_cast<char* const*>(envp.data()));
461 #else
462 #error "Unsupported architecture"
463 #endif
464     // No need for an if: if exec worked it wouldn't have returned
465     LOG(ERROR) << "exec of " << cmd[0] << " with path \"" << executable
466                << "\" failed (" << strerror(errno) << ")";
467     exit(rval);
468   }
469   if (pid == -1) {
470     LOG(ERROR) << "fork failed (" << strerror(errno) << ")";
471   }
472   if (options.Verbose()) { // "more verbose", and LOG(DEBUG) > LOG(VERBOSE)
473     LOG(DEBUG) << "Started (pid: " << pid << "): " << cmd[0];
474     for (int i = 1; cmd[i]; i++) {
475       LOG(DEBUG) << cmd[i];
476     }
477   } else {
478     LOG(VERBOSE) << "Started (pid: " << pid << "): " << cmd[0];
479     for (int i = 1; cmd[i]; i++) {
480       LOG(VERBOSE) << cmd[i];
481     }
482   }
483   return Subprocess(pid, subprocess_stopper_);
484 }
485 
AsBashScript(const std::string & redirected_stdio_path) const486 std::string Command::AsBashScript(
487     const std::string& redirected_stdio_path) const {
488   CHECK(inherited_fds_.empty())
489       << "Bash wrapper will not have inheritied file descriptors.";
490   CHECK(redirects_.empty()) << "Bash wrapper will not have redirected stdio.";
491 
492   std::string contents =
493       "#!/bin/bash\n\n" + android::base::Join(command_, " \\\n");
494   if (!redirected_stdio_path.empty()) {
495     contents += " &> " + AbsolutePath(redirected_stdio_path);
496   }
497   return contents;
498 }
499 
500 // A class that waits for threads to exit in its destructor.
501 class ThreadJoiner {
502 std::vector<std::thread*> threads_;
503 public:
ThreadJoiner(const std::vector<std::thread * > threads)504   ThreadJoiner(const std::vector<std::thread*> threads) : threads_(threads) {}
~ThreadJoiner()505   ~ThreadJoiner() {
506     for (auto& thread : threads_) {
507       if (thread->joinable()) {
508         thread->join();
509       }
510     }
511   }
512 };
513 
RunWithManagedStdio(Command && cmd_tmp,const std::string * stdin_str,std::string * stdout_str,std::string * stderr_str,SubprocessOptions options)514 int RunWithManagedStdio(Command&& cmd_tmp, const std::string* stdin_str,
515                         std::string* stdout_str, std::string* stderr_str,
516                         SubprocessOptions options) {
517   /*
518    * The order of these declarations is necessary for safety. If the function
519    * returns at any point, the Command will be destroyed first, closing all
520    * of its references to SharedFDs. This will cause the thread internals to fail
521    * their reads or writes. The ThreadJoiner then waits for the threads to
522    * complete, as running the destructor of an active std::thread crashes the
523    * program.
524    *
525    * C++ scoping rules dictate that objects are descoped in reverse order to
526    * construction, so this behavior is predictable.
527    */
528   std::thread stdin_thread, stdout_thread, stderr_thread;
529   ThreadJoiner thread_joiner({&stdin_thread, &stdout_thread, &stderr_thread});
530   Command cmd = std::move(cmd_tmp);
531   bool io_error = false;
532   if (stdin_str != nullptr) {
533     SharedFD pipe_read, pipe_write;
534     if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
535       LOG(ERROR) << "Could not create a pipe to write the stdin of \""
536                 << cmd.GetShortName() << "\"";
537       return -1;
538     }
539     cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read);
540     stdin_thread = std::thread([pipe_write, stdin_str, &io_error]() {
541       int written = WriteAll(pipe_write, *stdin_str);
542       if (written < 0) {
543         io_error = true;
544         LOG(ERROR) << "Error in writing stdin to process";
545       }
546     });
547   }
548   if (stdout_str != nullptr) {
549     SharedFD pipe_read, pipe_write;
550     if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
551       LOG(ERROR) << "Could not create a pipe to read the stdout of \""
552                 << cmd.GetShortName() << "\"";
553       return -1;
554     }
555     cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write);
556     stdout_thread = std::thread([pipe_read, stdout_str, &io_error]() {
557       int read = ReadAll(pipe_read, stdout_str);
558       if (read < 0) {
559         io_error = true;
560         LOG(ERROR) << "Error in reading stdout from process";
561       }
562     });
563   }
564   if (stderr_str != nullptr) {
565     SharedFD pipe_read, pipe_write;
566     if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
567       LOG(ERROR) << "Could not create a pipe to read the stderr of \""
568                 << cmd.GetShortName() << "\"";
569       return -1;
570     }
571     cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write);
572     stderr_thread = std::thread([pipe_read, stderr_str, &io_error]() {
573       int read = ReadAll(pipe_read, stderr_str);
574       if (read < 0) {
575         io_error = true;
576         LOG(ERROR) << "Error in reading stderr from process";
577       }
578     });
579   }
580 
581   auto subprocess = cmd.Start(std::move(options));
582   if (!subprocess.Started()) {
583     return -1;
584   }
585   auto cmd_short_name = cmd.GetShortName();
586   {
587     // Force the destructor to run by moving it into a smaller scope.
588     // This is necessary to close the write end of the pipe.
589     Command forceDelete = std::move(cmd);
590   }
591 
592   int code = subprocess.Wait();
593   {
594     auto join_threads = std::move(thread_joiner);
595   }
596   if (io_error) {
597     LOG(ERROR) << "IO error communicating with " << cmd_short_name;
598     return -1;
599   }
600   return code;
601 }
602 
603 namespace {
604 
605 struct ExtraParam {
606   // option for Subprocess::Start()
607   SubprocessOptions subprocess_options;
608   // options for Subprocess::Wait(...)
609   int wait_options;
610   siginfo_t* infop;
611 };
ExecuteImpl(const std::vector<std::string> & command,const std::optional<std::vector<std::string>> & envs,std::optional<ExtraParam> extra_param)612 Result<int> ExecuteImpl(const std::vector<std::string>& command,
613                         const std::optional<std::vector<std::string>>& envs,
614                         std::optional<ExtraParam> extra_param) {
615   Command cmd(command[0]);
616   for (size_t i = 1; i < command.size(); ++i) {
617     cmd.AddParameter(command[i]);
618   }
619   if (envs) {
620     cmd.SetEnvironment(*envs);
621   }
622   auto subprocess =
623       (!extra_param ? cmd.Start()
624                     : cmd.Start(std::move(extra_param->subprocess_options)));
625   CF_EXPECT(subprocess.Started(), "Subprocess failed to start.");
626 
627   if (extra_param) {
628     CF_EXPECT(extra_param->infop != nullptr,
629               "When ExtraParam is given, the infop buffer address "
630                   << "must not be nullptr.");
631     return subprocess.Wait(extra_param->infop, extra_param->wait_options);
632   } else {
633     return subprocess.Wait();
634   }
635 }
636 
637 }  // namespace
638 
Execute(const std::vector<std::string> & commands,const std::vector<std::string> & envs)639 int Execute(const std::vector<std::string>& commands,
640             const std::vector<std::string>& envs) {
641   auto result = ExecuteImpl(commands, envs, /* extra_param */ std::nullopt);
642   return (!result.ok() ? -1 : *result);
643 }
644 
Execute(const std::vector<std::string> & commands)645 int Execute(const std::vector<std::string>& commands) {
646   std::vector<std::string> envs;
647   auto result = ExecuteImpl(commands, /* envs */ std::nullopt,
648                             /* extra_param */ std::nullopt);
649   return (!result.ok() ? -1 : *result);
650 }
651 
Execute(const std::vector<std::string> & commands,SubprocessOptions subprocess_options,int wait_options)652 Result<siginfo_t> Execute(const std::vector<std::string>& commands,
653                           SubprocessOptions subprocess_options,
654                           int wait_options) {
655   siginfo_t info;
656   auto ret_code = CF_EXPECT(ExecuteImpl(
657       commands, /* envs */ std::nullopt,
658       ExtraParam{.subprocess_options = std::move(subprocess_options),
659                  .wait_options = wait_options,
660                  .infop = &info}));
661   CF_EXPECT(ret_code == 0, "Subprocess::Wait() returned " << ret_code);
662   return info;
663 }
664 
Execute(const std::vector<std::string> & commands,const std::vector<std::string> & envs,SubprocessOptions subprocess_options,int wait_options)665 Result<siginfo_t> Execute(const std::vector<std::string>& commands,
666                           const std::vector<std::string>& envs,
667                           SubprocessOptions subprocess_options,
668                           int wait_options) {
669   siginfo_t info;
670   auto ret_code = CF_EXPECT(ExecuteImpl(
671       commands, envs,
672       ExtraParam{.subprocess_options = std::move(subprocess_options),
673                  .wait_options = wait_options,
674                  .infop = &info}));
675   CF_EXPECT(ret_code == 0, "Subprocess::Wait() returned " << ret_code);
676   return info;
677 }
678 
679 }  // namespace cuttlefish
680