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