• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include <fcntl.h>
20 #include <signal.h>
21 #include <stdlib.h>
22 #include <sys/prctl.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <unistd.h>
26 
27 #include <cerrno>
28 #include <cstring>
29 #include <map>
30 #include <memory>
31 #include <optional>
32 #include <ostream>
33 #include <set>
34 #include <sstream>
35 #include <string>
36 #include <thread>
37 #include <type_traits>
38 #include <utility>
39 #include <vector>
40 
41 #include <android-base/logging.h>
42 #include <android-base/strings.h>
43 
44 #include "common/libs/fs/shared_buf.h"
45 #include "common/libs/utils/files.h"
46 
47 extern char** environ;
48 
49 namespace cuttlefish {
50 namespace {
51 
52 // If a redirected-to file descriptor was already closed, it's possible that
53 // some inherited file descriptor duped to this file descriptor and the redirect
54 // 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)55 bool validate_redirects(
56     const std::map<Subprocess::StdIOChannel, int>& redirects,
57     const std::map<SharedFD, int>& inherited_fds) {
58   // Add the redirected IO channels to a set as integers. This allows converting
59   // the enum values into integers instead of the other way around.
60   std::set<int> int_redirects;
61   for (const auto& entry : redirects) {
62     int_redirects.insert(static_cast<int>(entry.first));
63   }
64   for (const auto& entry : inherited_fds) {
65     auto dupped_fd = entry.second;
66     if (int_redirects.count(dupped_fd)) {
67       LOG(ERROR) << "Requested redirect of fd(" << dupped_fd
68                  << ") conflicts with inherited FD.";
69       return false;
70     }
71   }
72   return true;
73 }
74 
do_redirects(const std::map<Subprocess::StdIOChannel,int> & redirects)75 void do_redirects(const std::map<Subprocess::StdIOChannel, int>& redirects) {
76   for (const auto& entry : redirects) {
77     auto std_channel = static_cast<int>(entry.first);
78     auto fd = entry.second;
79     TEMP_FAILURE_RETRY(dup2(fd, std_channel));
80   }
81 }
82 
ToCharPointers(const std::vector<std::string> & vect)83 std::vector<const char*> ToCharPointers(const std::vector<std::string>& vect) {
84   std::vector<const char*> ret = {};
85   for (const auto& str : vect) {
86     ret.push_back(str.c_str());
87   }
88   ret.push_back(NULL);
89   return ret;
90 }
91 }  // namespace
92 
Verbose(bool verbose)93 SubprocessOptions& SubprocessOptions::Verbose(bool verbose) & {
94   verbose_ = verbose;
95   return *this;
96 }
Verbose(bool verbose)97 SubprocessOptions SubprocessOptions::Verbose(bool verbose) && {
98   verbose_ = verbose;
99   return *this;
100 }
101 
ExitWithParent(bool v)102 SubprocessOptions& SubprocessOptions::ExitWithParent(bool v) & {
103   exit_with_parent_ = v;
104   return *this;
105 }
ExitWithParent(bool v)106 SubprocessOptions SubprocessOptions::ExitWithParent(bool v) && {
107   exit_with_parent_ = v;
108   return *this;
109 }
110 
InGroup(bool in_group)111 SubprocessOptions& SubprocessOptions::InGroup(bool in_group) & {
112   in_group_ = in_group;
113   return *this;
114 }
InGroup(bool in_group)115 SubprocessOptions SubprocessOptions::InGroup(bool in_group) && {
116   in_group_ = in_group;
117   return *this;
118 }
119 
Subprocess(Subprocess && subprocess)120 Subprocess::Subprocess(Subprocess&& subprocess)
121     : pid_(subprocess.pid_),
122       started_(subprocess.started_),
123       stopper_(subprocess.stopper_) {
124   // Make sure the moved object no longer controls this subprocess
125   subprocess.pid_ = -1;
126   subprocess.started_ = false;
127 }
128 
operator =(Subprocess && other)129 Subprocess& Subprocess::operator=(Subprocess&& other) {
130   pid_ = other.pid_;
131   started_ = other.started_;
132   stopper_ = other.stopper_;
133 
134   other.pid_ = -1;
135   other.started_ = false;
136   return *this;
137 }
138 
Wait()139 int Subprocess::Wait() {
140   if (pid_ < 0) {
141     LOG(ERROR)
142         << "Attempt to wait on invalid pid(has it been waited on already?): "
143         << pid_;
144     return -1;
145   }
146   int wstatus = 0;
147   auto pid = pid_;  // Wait will set pid_ to -1 after waiting
148   auto wait_ret = waitpid(pid, &wstatus, 0);
149   if (wait_ret < 0) {
150     auto error = errno;
151     LOG(ERROR) << "Error on call to waitpid: " << strerror(error);
152     return wait_ret;
153   }
154   int retval = 0;
155   if (WIFEXITED(wstatus)) {
156     retval = WEXITSTATUS(wstatus);
157     if (retval) {
158       LOG(DEBUG) << "Subprocess " << pid
159                  << " exited with error code: " << retval;
160     }
161   } else if (WIFSIGNALED(wstatus)) {
162     LOG(ERROR) << "Subprocess " << pid
163                << " was interrupted by a signal: " << WTERMSIG(wstatus);
164     retval = -1;
165   }
166   return retval;
167 }
Wait(siginfo_t * infop,int options)168 int Subprocess::Wait(siginfo_t* infop, int options) {
169   if (pid_ < 0) {
170     LOG(ERROR)
171         << "Attempt to wait on invalid pid(has it been waited on already?): "
172         << pid_;
173     return -1;
174   }
175   *infop = {};
176   auto retval = waitid(P_PID, pid_, infop, options);
177   // We don't want to wait twice for the same process
178   bool exited = infop->si_code == CLD_EXITED || infop->si_code == CLD_DUMPED ||
179                 infop->si_code == CLD_DUMPED;
180   bool reaped = !(options & WNOWAIT);
181   if (exited && reaped) {
182     pid_ = -1;
183   }
184   return retval;
185 }
186 
KillSubprocess(Subprocess * subprocess)187 StopperResult KillSubprocess(Subprocess* subprocess) {
188   auto pid = subprocess->pid();
189   if (pid > 0) {
190     auto pgid = getpgid(pid);
191     if (pgid < 0) {
192       auto error = errno;
193       LOG(WARNING) << "Error obtaining process group id of process with pid="
194                    << pid << ": " << strerror(error);
195       // Send the kill signal anyways, because pgid will be -1 it will be sent
196       // to the process and not a (non-existent) group
197     }
198     bool is_group_head = pid == pgid;
199     auto kill_ret = (is_group_head ? killpg : kill)(pid, SIGKILL);
200     if (kill_ret == 0) {
201       return StopperResult::kStopSuccess;
202     }
203     auto kill_cmd = is_group_head ? "killpg(" : "kill(";
204     PLOG(ERROR) << kill_cmd << pid << ", SIGKILL) failed: ";
205     return StopperResult::kStopFailure;
206   }
207   return StopperResult::kStopSuccess;
208 }
209 
Command(std::string executable,SubprocessStopper stopper)210 Command::Command(std::string executable, SubprocessStopper stopper)
211     : subprocess_stopper_(stopper) {
212   for (char** env = environ; *env; env++) {
213     env_.emplace_back(*env);
214   }
215   command_.emplace_back(std::move(executable));
216 }
217 
~Command()218 Command::~Command() {
219   // Close all inherited file descriptors
220   for (const auto& entry : inherited_fds_) {
221     close(entry.second);
222   }
223   // Close all redirected file descriptors
224   for (const auto& entry : redirects_) {
225     close(entry.second);
226   }
227 }
228 
BuildParameter(std::stringstream * stream,SharedFD shared_fd)229 void Command::BuildParameter(std::stringstream* stream, SharedFD shared_fd) {
230   int fd;
231   if (inherited_fds_.count(shared_fd)) {
232     fd = inherited_fds_[shared_fd];
233   } else {
234     fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
235     CHECK(fd >= 0) << "Could not acquire a new file descriptor: "
236                    << shared_fd->StrError();
237     inherited_fds_[shared_fd] = fd;
238   }
239   *stream << fd;
240 }
241 
RedirectStdIO(Subprocess::StdIOChannel channel,SharedFD shared_fd)242 Command& Command::RedirectStdIO(Subprocess::StdIOChannel channel,
243                                 SharedFD shared_fd) & {
244   CHECK(shared_fd->IsOpen());
245   CHECK(redirects_.count(channel) == 0)
246       << "Attempted multiple redirections of fd: " << static_cast<int>(channel);
247   auto dup_fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
248   CHECK(dup_fd >= 0) << "Could not acquire a new file descriptor: "
249                      << shared_fd->StrError();
250   redirects_[channel] = dup_fd;
251   return *this;
252 }
RedirectStdIO(Subprocess::StdIOChannel channel,SharedFD shared_fd)253 Command Command::RedirectStdIO(Subprocess::StdIOChannel channel,
254                                SharedFD shared_fd) && {
255   RedirectStdIO(channel, shared_fd);
256   return std::move(*this);
257 }
RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,Subprocess::StdIOChannel parent_channel)258 Command& Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
259                                 Subprocess::StdIOChannel parent_channel) & {
260   return RedirectStdIO(subprocess_channel,
261                        SharedFD::Dup(static_cast<int>(parent_channel)));
262 }
RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,Subprocess::StdIOChannel parent_channel)263 Command Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
264                                Subprocess::StdIOChannel parent_channel) && {
265   RedirectStdIO(subprocess_channel, parent_channel);
266   return std::move(*this);
267 }
268 
SetWorkingDirectory(const std::string & path)269 Command& Command::SetWorkingDirectory(const std::string& path) & {
270   auto fd = SharedFD::Open(path, O_RDONLY | O_PATH | O_DIRECTORY);
271   CHECK(fd->IsOpen()) << "Could not open \"" << path
272                       << "\" dir fd: " << fd->StrError();
273   return SetWorkingDirectory(fd);
274 }
SetWorkingDirectory(const std::string & path)275 Command Command::SetWorkingDirectory(const std::string& path) && {
276   return std::move(SetWorkingDirectory(path));
277 }
SetWorkingDirectory(SharedFD dirfd)278 Command& Command::SetWorkingDirectory(SharedFD dirfd) & {
279   CHECK(dirfd->IsOpen()) << "Dir fd invalid: " << dirfd->StrError();
280   working_directory_ = std::move(dirfd);
281   return *this;
282 }
SetWorkingDirectory(SharedFD dirfd)283 Command Command::SetWorkingDirectory(SharedFD dirfd) && {
284   return std::move(SetWorkingDirectory(std::move(dirfd)));
285 }
286 
Start(SubprocessOptions options) const287 Subprocess Command::Start(SubprocessOptions options) const {
288   auto cmd = ToCharPointers(command_);
289 
290   if (!validate_redirects(redirects_, inherited_fds_)) {
291     return Subprocess(-1, {});
292   }
293 
294   pid_t pid = fork();
295   if (!pid) {
296     if (options.ExitWithParent()) {
297       prctl(PR_SET_PDEATHSIG, SIGHUP); // Die when parent dies
298     }
299 
300     do_redirects(redirects_);
301     if (options.InGroup()) {
302       // This call should never fail (see SETPGID(2))
303       if (setpgid(0, 0) != 0) {
304         auto error = errno;
305         LOG(ERROR) << "setpgid failed (" << strerror(error) << ")";
306       }
307     }
308     for (const auto& entry : inherited_fds_) {
309       if (fcntl(entry.second, F_SETFD, 0)) {
310         int error_num = errno;
311         LOG(ERROR) << "fcntl failed: " << strerror(error_num);
312       }
313     }
314     if (working_directory_->IsOpen()) {
315       if (SharedFD::Fchdir(working_directory_) != 0) {
316         LOG(ERROR) << "Fchdir failed: " << working_directory_->StrError();
317       }
318     }
319     int rval;
320     auto envp = ToCharPointers(env_);
321     const char* executable = executable_ ? executable_->c_str() : cmd[0];
322     rval = execvpe(executable, const_cast<char* const*>(cmd.data()),
323                    const_cast<char* const*>(envp.data()));
324     // No need for an if: if exec worked it wouldn't have returned
325     LOG(ERROR) << "exec of " << cmd[0] << " with path \"" << executable
326                << "\" failed (" << strerror(errno) << ")";
327     exit(rval);
328   }
329   if (pid == -1) {
330     LOG(ERROR) << "fork failed (" << strerror(errno) << ")";
331   }
332   if (options.Verbose()) { // "more verbose", and LOG(DEBUG) > LOG(VERBOSE)
333     LOG(DEBUG) << "Started (pid: " << pid << "): " << cmd[0];
334     for (int i = 1; cmd[i]; i++) {
335       LOG(DEBUG) << cmd[i];
336     }
337   } else {
338     LOG(VERBOSE) << "Started (pid: " << pid << "): " << cmd[0];
339     for (int i = 1; cmd[i]; i++) {
340       LOG(VERBOSE) << cmd[i];
341     }
342   }
343   return Subprocess(pid, subprocess_stopper_);
344 }
345 
AsBashScript(const std::string & redirected_stdio_path) const346 std::string Command::AsBashScript(
347     const std::string& redirected_stdio_path) const {
348   CHECK(inherited_fds_.empty())
349       << "Bash wrapper will not have inheritied file descriptors.";
350   CHECK(redirects_.empty()) << "Bash wrapper will not have redirected stdio.";
351 
352   std::string contents =
353       "#!/bin/bash\n\n" + android::base::Join(command_, " \\\n");
354   if (!redirected_stdio_path.empty()) {
355     contents += " &> " + AbsolutePath(redirected_stdio_path);
356   }
357   return contents;
358 }
359 
360 // A class that waits for threads to exit in its destructor.
361 class ThreadJoiner {
362 std::vector<std::thread*> threads_;
363 public:
ThreadJoiner(const std::vector<std::thread * > threads)364   ThreadJoiner(const std::vector<std::thread*> threads) : threads_(threads) {}
~ThreadJoiner()365   ~ThreadJoiner() {
366     for (auto& thread : threads_) {
367       if (thread->joinable()) {
368         thread->join();
369       }
370     }
371   }
372 };
373 
RunWithManagedStdio(Command && cmd_tmp,const std::string * stdin_str,std::string * stdout_str,std::string * stderr_str,SubprocessOptions options)374 int RunWithManagedStdio(Command&& cmd_tmp, const std::string* stdin_str,
375                         std::string* stdout_str, std::string* stderr_str,
376                         SubprocessOptions options) {
377   /*
378    * The order of these declarations is necessary for safety. If the function
379    * returns at any point, the Command will be destroyed first, closing all
380    * of its references to SharedFDs. This will cause the thread internals to fail
381    * their reads or writes. The ThreadJoiner then waits for the threads to
382    * complete, as running the destructor of an active std::thread crashes the
383    * program.
384    *
385    * C++ scoping rules dictate that objects are descoped in reverse order to
386    * construction, so this behavior is predictable.
387    */
388   std::thread stdin_thread, stdout_thread, stderr_thread;
389   ThreadJoiner thread_joiner({&stdin_thread, &stdout_thread, &stderr_thread});
390   Command cmd = std::move(cmd_tmp);
391   bool io_error = false;
392   if (stdin_str != nullptr) {
393     SharedFD pipe_read, pipe_write;
394     if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
395       LOG(ERROR) << "Could not create a pipe to write the stdin of \""
396                 << cmd.GetShortName() << "\"";
397       return -1;
398     }
399     cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, pipe_read);
400     stdin_thread = std::thread([pipe_write, stdin_str, &io_error]() {
401       int written = WriteAll(pipe_write, *stdin_str);
402       if (written < 0) {
403         io_error = true;
404         LOG(ERROR) << "Error in writing stdin to process";
405       }
406     });
407   }
408   if (stdout_str != nullptr) {
409     SharedFD pipe_read, pipe_write;
410     if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
411       LOG(ERROR) << "Could not create a pipe to read the stdout of \""
412                 << cmd.GetShortName() << "\"";
413       return -1;
414     }
415     cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, pipe_write);
416     stdout_thread = std::thread([pipe_read, stdout_str, &io_error]() {
417       int read = ReadAll(pipe_read, stdout_str);
418       if (read < 0) {
419         io_error = true;
420         LOG(ERROR) << "Error in reading stdout from process";
421       }
422     });
423   }
424   if (stderr_str != nullptr) {
425     SharedFD pipe_read, pipe_write;
426     if (!SharedFD::Pipe(&pipe_read, &pipe_write)) {
427       LOG(ERROR) << "Could not create a pipe to read the stderr of \""
428                 << cmd.GetShortName() << "\"";
429       return -1;
430     }
431     cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, pipe_write);
432     stderr_thread = std::thread([pipe_read, stderr_str, &io_error]() {
433       int read = ReadAll(pipe_read, stderr_str);
434       if (read < 0) {
435         io_error = true;
436         LOG(ERROR) << "Error in reading stderr from process";
437       }
438     });
439   }
440 
441   auto subprocess = cmd.Start(options);
442   if (!subprocess.Started()) {
443     return -1;
444   }
445   auto cmd_short_name = cmd.GetShortName();
446   {
447     // Force the destructor to run by moving it into a smaller scope.
448     // This is necessary to close the write end of the pipe.
449     Command forceDelete = std::move(cmd);
450   }
451 
452   int code = subprocess.Wait();
453   {
454     auto join_threads = std::move(thread_joiner);
455   }
456   if (io_error) {
457     LOG(ERROR) << "IO error communicating with " << cmd_short_name;
458     return -1;
459   }
460   return code;
461 }
462 
execute(const std::vector<std::string> & command,const std::vector<std::string> & env)463 int execute(const std::vector<std::string>& command,
464             const std::vector<std::string>& env) {
465   Command cmd(command[0]);
466   for (size_t i = 1; i < command.size(); ++i) {
467     cmd.AddParameter(command[i]);
468   }
469   cmd.SetEnvironment(env);
470   auto subprocess = cmd.Start();
471   if (!subprocess.Started()) {
472     return -1;
473   }
474   return subprocess.Wait();
475 }
execute(const std::vector<std::string> & command)476 int execute(const std::vector<std::string>& command) {
477   Command cmd(command[0]);
478   for (size_t i = 1; i < command.size(); ++i) {
479     cmd.AddParameter(command[i]);
480   }
481   auto subprocess = cmd.Start();
482   if (!subprocess.Started()) {
483     return -1;
484   }
485   return subprocess.Wait();
486 }
487 
488 }  // namespace cuttlefish
489