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