blob: aed28de8acf5d411933f855d66287187930c83a6 [file] [log] [blame]
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -07001/*
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
Jorge E. Moreira2a777f62018-06-13 17:28:10 -070017#include "common/libs/utils/subprocess.h"
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -070018
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -080019#include <errno.h>
Jorge E. Moreirac4339ad2019-09-05 13:36:06 -070020#include <signal.h>
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -070021#include <stdlib.h>
Cody Schuffelen1cf4f132019-12-09 18:54:51 -080022#include <sys/prctl.h>
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -070023#include <sys/types.h>
24#include <sys/wait.h>
25#include <unistd.h>
26
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -080027#include <map>
28#include <set>
Cody Schuffelen7e35fb32019-10-03 16:28:35 -070029#include <thread>
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -080030
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -070031#include <glog/logging.h>
Cody Schuffelen5133a312019-08-02 17:58:00 -070032
33#include "common/libs/fs/shared_buf.h"
34
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -070035namespace {
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -070036
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -080037// If a redirected-to file descriptor was already closed, it's possible that
38// some inherited file descriptor duped to this file descriptor and the redirect
39// would override that. This function makes sure that doesn't happen.
40bool validate_redirects(
41 const std::map<cvd::Subprocess::StdIOChannel, int>& redirects,
42 const std::map<cvd::SharedFD, int>& inherited_fds) {
43 // Add the redirected IO channels to a set as integers. This allows converting
44 // the enum values into integers instead of the other way around.
45 std::set<int> int_redirects;
Jorge E. Moreira189d8232019-08-30 11:43:05 -070046 for (const auto& entry : redirects) {
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -080047 int_redirects.insert(static_cast<int>(entry.first));
48 }
Jorge E. Moreira189d8232019-08-30 11:43:05 -070049 for (const auto& entry : inherited_fds) {
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -080050 auto dupped_fd = entry.second;
51 if (int_redirects.count(dupped_fd)) {
52 LOG(ERROR) << "Requested redirect of fd(" << dupped_fd
53 << ") conflicts with inherited FD.";
54 return false;
55 }
56 }
57 return true;
58}
59
Jorge E. Moreira189d8232019-08-30 11:43:05 -070060void do_redirects(
61 const std::map<cvd::Subprocess::StdIOChannel, int>& redirects) {
62 for (const auto& entry : redirects) {
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -080063 auto std_channel = static_cast<int>(entry.first);
64 auto fd = entry.second;
65 TEMP_FAILURE_RETRY(dup2(fd, std_channel));
66 }
67}
68
69cvd::Subprocess subprocess_impl(
Jorge E. Moreira189d8232019-08-30 11:43:05 -070070 const char* const* command, const char* const* envp,
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -080071 const std::map<cvd::Subprocess::StdIOChannel, int>& redirects,
Jorge E. Moreira189d8232019-08-30 11:43:05 -070072 const std::map<cvd::SharedFD, int>& inherited_fds, bool with_control_socket,
Cody Schuffelen1cf4f132019-12-09 18:54:51 -080073 cvd::SubprocessStopper stopper, bool in_group = false, bool verbose = false,
74 bool exit_with_parent = true) {
Jorge E. Moreira1a62e762018-11-05 22:05:57 -080075 // The parent socket will get closed on the child on the call to exec, the
76 // child socket will be closed on the parent when this function returns and no
77 // references to the fd are left
78 cvd::SharedFD parent_socket, child_socket;
79 if (with_control_socket) {
80 if (!cvd::SharedFD::SocketPair(AF_LOCAL, SOCK_STREAM, 0, &parent_socket,
81 &child_socket)) {
82 LOG(ERROR) << "Unable to create control socket pair: " << strerror(errno);
83 return cvd::Subprocess(-1, {});
84 }
85 // Remove FD_CLOEXEC from the child socket, ensure the parent has it
86 child_socket->Fcntl(F_SETFD, 0);
87 parent_socket->Fcntl(F_SETFD, FD_CLOEXEC);
88 }
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -080089
90 if (!validate_redirects(redirects, inherited_fds)) {
91 return cvd::Subprocess(-1, {});
92 }
93
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -070094 pid_t pid = fork();
95 if (!pid) {
Cody Schuffelen1cf4f132019-12-09 18:54:51 -080096 if (exit_with_parent) {
97 prctl(PR_SET_PDEATHSIG, SIGHUP); // Die when parent dies
98 }
99
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -0800100 do_redirects(redirects);
Jorge E. Moreira189d8232019-08-30 11:43:05 -0700101 if (in_group) {
102 // This call should never fail (see SETPGID(2))
103 if (setpgid(0, 0) != 0) {
104 auto error = errno;
105 LOG(ERROR) << "setpgid failed (" << strerror(error) << ")";
106 }
107 }
Cody Schuffelenc33a1dc2019-11-18 18:29:00 -0800108 for (const auto& entry : inherited_fds) {
109 if (fcntl(entry.second, F_SETFD, 0)) {
110 int error_num = errno;
111 LOG(ERROR) << "fcntl failed: " << strerror(error_num);
112 }
113 }
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700114 int rval;
115 // If envp is NULL, the current process's environment is used as the
116 // environment of the child process. To force an empty emvironment for
117 // the child process pass the address of a pointer to NULL
118 if (envp == NULL) {
119 rval = execv(command[0], const_cast<char* const*>(command));
120 } else {
Jorge E. Moreira189d8232019-08-30 11:43:05 -0700121 rval = execve(command[0], const_cast<char* const*>(command),
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700122 const_cast<char* const*>(envp));
123 }
124 // No need for an if: if exec worked it wouldn't have returned
125 LOG(ERROR) << "exec of " << command[0] << " failed (" << strerror(errno)
126 << ")";
127 exit(rval);
128 }
129 if (pid == -1) {
130 LOG(ERROR) << "fork failed (" << strerror(errno) << ")";
131 }
Cody Schuffelenc79185d2019-09-27 12:37:00 -0700132 if (verbose) {
133 LOG(INFO) << "Started (pid: " << pid << "): " << command[0];
134 int i = 1;
135 while (command[i]) {
136 LOG(INFO) << command[i++];
137 }
Jorge E. Moreira415eea22018-07-02 18:06:26 -0700138 }
Jorge E. Moreirac4339ad2019-09-05 13:36:06 -0700139 return cvd::Subprocess(pid, parent_socket, stopper);
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700140}
141
Jorge E. Moreira189d8232019-08-30 11:43:05 -0700142std::vector<const char*> ToCharPointers(const std::vector<std::string>& vect) {
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700143 std::vector<const char*> ret = {};
144 for (const auto& str : vect) {
145 ret.push_back(str.c_str());
146 }
147 ret.push_back(NULL);
148 return ret;
149}
150} // namespace
151namespace cvd {
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700152
Jorge E. Moreira8e9793e2018-11-05 21:57:26 -0800153Subprocess::Subprocess(Subprocess&& subprocess)
154 : pid_(subprocess.pid_),
Jorge E. Moreira1a62e762018-11-05 22:05:57 -0800155 started_(subprocess.started_),
Jorge E. Moreirac4339ad2019-09-05 13:36:06 -0700156 control_socket_(subprocess.control_socket_),
157 stopper_(subprocess.stopper_) {
Jorge E. Moreira8e9793e2018-11-05 21:57:26 -0800158 // Make sure the moved object no longer controls this subprocess
159 subprocess.pid_ = -1;
160 subprocess.started_ = false;
Jorge E. Moreira1a62e762018-11-05 22:05:57 -0800161 subprocess.control_socket_ = SharedFD();
Jorge E. Moreira8e9793e2018-11-05 21:57:26 -0800162}
163
164Subprocess& Subprocess::operator=(Subprocess&& other) {
165 pid_ = other.pid_;
166 started_ = other.started_;
Jorge E. Moreira1a62e762018-11-05 22:05:57 -0800167 control_socket_ = other.control_socket_;
Jorge E. Moreirac4339ad2019-09-05 13:36:06 -0700168 stopper_ = other.stopper_;
Jorge E. Moreira8e9793e2018-11-05 21:57:26 -0800169
170 other.pid_ = -1;
171 other.started_ = false;
Jorge E. Moreira1a62e762018-11-05 22:05:57 -0800172 other.control_socket_ = SharedFD();
Jorge E. Moreira8e9793e2018-11-05 21:57:26 -0800173 return *this;
174}
175
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700176int Subprocess::Wait() {
177 if (pid_ < 0) {
178 LOG(ERROR)
179 << "Attempt to wait on invalid pid(has it been waited on already?): "
180 << pid_;
181 return -1;
182 }
183 int wstatus = 0;
Jorge E. Moreira189d8232019-08-30 11:43:05 -0700184 auto pid = pid_; // Wait will set pid_ to -1 after waiting
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700185 auto wait_ret = Wait(&wstatus, 0);
186 if (wait_ret < 0) {
Jorge E. Moreirac4339ad2019-09-05 13:36:06 -0700187 auto error = errno;
188 LOG(ERROR) << "Error on call to waitpid: " << strerror(error);
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700189 return wait_ret;
190 }
191 int retval = 0;
192 if (WIFEXITED(wstatus)) {
193 retval = WEXITSTATUS(wstatus);
194 if (retval) {
195 LOG(ERROR) << "Subprocess " << pid
196 << " exited with error code: " << retval;
197 }
198 } else if (WIFSIGNALED(wstatus)) {
199 LOG(ERROR) << "Subprocess " << pid
200 << " was interrupted by a signal: " << WTERMSIG(wstatus);
201 retval = -1;
202 }
203 return retval;
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700204}
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700205pid_t Subprocess::Wait(int* wstatus, int options) {
206 if (pid_ < 0) {
207 LOG(ERROR)
208 << "Attempt to wait on invalid pid(has it been waited on already?): "
209 << pid_;
210 return -1;
211 }
212 auto retval = waitpid(pid_, wstatus, options);
213 // We don't want to wait twice for the same process
214 pid_ = -1;
215 return retval;
216}
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700217
Jorge E. Moreirac4339ad2019-09-05 13:36:06 -0700218bool KillSubprocess(Subprocess* subprocess) {
219 auto pid = subprocess->pid();
220 if (pid > 0) {
221 auto pgid = getpgid(pid);
222 if (pgid < 0) {
223 auto error = errno;
224 LOG(WARNING) << "Error obtaining process group id of process with pid="
225 << pid << ": " << strerror(error);
226 // Send the kill signal anyways, because pgid will be -1 it will be sent
227 // to the process and not a (non-existent) group
228 }
229 bool is_group_head = pid == pgid;
230 if (is_group_head) {
231 return killpg(pid, SIGKILL) == 0;
232 } else {
233 return kill(pid, SIGKILL) == 0;
234 }
235 }
236 return true;
237}
Jorge E. Moreira189d8232019-08-30 11:43:05 -0700238Command::ParameterBuilder::~ParameterBuilder() { Build(); }
239void Command::ParameterBuilder::Build() {
Jorge E. Moreirab945eb12019-05-06 17:20:38 -0700240 auto param = stream_.str();
241 stream_ = std::stringstream();
242 if (param.size()) {
243 cmd_->AddParameter(param);
244 }
245}
246
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700247Command::~Command() {
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -0800248 // Close all inherited file descriptors
Jorge E. Moreira189d8232019-08-30 11:43:05 -0700249 for (const auto& entry : inherited_fds_) {
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700250 close(entry.second);
251 }
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -0800252 // Close all redirected file descriptors
Jorge E. Moreira189d8232019-08-30 11:43:05 -0700253 for (const auto& entry : redirects_) {
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -0800254 close(entry.second);
255 }
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700256}
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700257
258bool Command::BuildParameter(std::stringstream* stream, SharedFD shared_fd) {
259 int fd;
260 if (inherited_fds_.count(shared_fd)) {
261 fd = inherited_fds_[shared_fd];
262 } else {
Cody Schuffelenc33a1dc2019-11-18 18:29:00 -0800263 fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700264 if (fd < 0) {
Cody Schuffelenc33a1dc2019-11-18 18:29:00 -0800265 LOG(ERROR) << "Could not acquire a new file descriptor: " << shared_fd->StrError();
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700266 return false;
267 }
268 inherited_fds_[shared_fd] = fd;
269 }
270 *stream << fd;
271 return true;
272}
273
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -0800274bool Command::RedirectStdIO(cvd::Subprocess::StdIOChannel channel,
Jorge E. Moreira189d8232019-08-30 11:43:05 -0700275 cvd::SharedFD shared_fd) {
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -0800276 if (!shared_fd->IsOpen()) {
277 return false;
278 }
279 if (redirects_.count(channel)) {
280 LOG(ERROR) << "Attempted multiple redirections of fd: "
281 << static_cast<int>(channel);
282 return false;
283 }
Cody Schuffelenc33a1dc2019-11-18 18:29:00 -0800284 auto dup_fd = shared_fd->Fcntl(F_DUPFD_CLOEXEC, 3);
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -0800285 if (dup_fd < 0) {
Cody Schuffelenc33a1dc2019-11-18 18:29:00 -0800286 LOG(ERROR) << "Could not acquire a new file descriptor: " << shared_fd->StrError();
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -0800287 return false;
288 }
289 redirects_[channel] = dup_fd;
290 return true;
291}
Cody Schuffelenc374c8c2019-08-21 18:47:42 -0700292bool Command::RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
293 Subprocess::StdIOChannel parent_channel) {
294 return RedirectStdIO(subprocess_channel,
295 cvd::SharedFD::Dup(static_cast<int>(parent_channel)));
296}
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -0800297
Cody Schuffelenc79185d2019-09-27 12:37:00 -0700298void Command::SetVerbose(bool verbose) {
299 verbose_ = verbose;
300}
301
Cody Schuffelen1cf4f132019-12-09 18:54:51 -0800302void Command::SetExitWithParent(bool exit_with_parent) {
303 exit_with_parent_ = exit_with_parent;
304}
305
Cody Schuffelene5670872019-12-10 15:04:59 -0800306void Command::SetWithControlSocket(bool with_control_socket) {
307 with_control_socket_ = with_control_socket;
308}
309
310Subprocess Command::StartHelper(bool in_group) const {
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700311 auto cmd = ToCharPointers(command_);
312 if (use_parent_env_) {
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -0800313 return subprocess_impl(cmd.data(), nullptr, redirects_, inherited_fds_,
Cody Schuffelene5670872019-12-10 15:04:59 -0800314 with_control_socket_, subprocess_stopper_, in_group,
Cody Schuffelen1cf4f132019-12-09 18:54:51 -0800315 verbose_, exit_with_parent_);
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700316 } else {
317 auto envp = ToCharPointers(env_);
Jorge E. Moreiraa4dac8b2019-01-25 18:14:51 -0800318 return subprocess_impl(cmd.data(), envp.data(), redirects_, inherited_fds_,
Cody Schuffelene5670872019-12-10 15:04:59 -0800319 with_control_socket_, subprocess_stopper_, in_group,
Cody Schuffelen1cf4f132019-12-09 18:54:51 -0800320 verbose_, exit_with_parent_);
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700321 }
322}
323
Cody Schuffelene5670872019-12-10 15:04:59 -0800324Subprocess Command::Start() const {
325 return StartHelper(false);
Jorge E. Moreira189d8232019-08-30 11:43:05 -0700326}
327
Cody Schuffelene5670872019-12-10 15:04:59 -0800328Subprocess Command::StartInGroup() const {
329 return StartHelper(true);
Jorge E. Moreira189d8232019-08-30 11:43:05 -0700330}
331
Cody Schuffelen7e35fb32019-10-03 16:28:35 -0700332// A class that waits for threads to exit in its destructor.
333class ThreadJoiner {
334std::vector<std::thread*> threads_;
335public:
336 ThreadJoiner(const std::vector<std::thread*> threads) : threads_(threads) {}
337 ~ThreadJoiner() {
338 for (auto& thread : threads_) {
339 if (thread->joinable()) {
340 thread->join();
341 }
342 }
343 }
344};
345
346int RunWithManagedStdio(cvd::Command&& cmd_tmp, const std::string* stdin,
347 std::string* stdout, std::string* stderr) {
348 /*
349 * The order of these declarations is necessary for safety. If the function
350 * returns at any point, the cvd::Command will be destroyed first, closing all
351 * of its references to SharedFDs. This will cause the thread internals to fail
352 * their reads or writes. The ThreadJoiner then waits for the threads to
353 * complete, as running the destructor of an active std::thread crashes the
354 * program.
355 *
356 * C++ scoping rules dictate that objects are descoped in reverse order to
357 * construction, so this behavior is predictable.
358 */
359 std::thread stdin_thread, stdout_thread, stderr_thread;
360 ThreadJoiner thread_joiner({&stdin_thread, &stdout_thread, &stderr_thread});
361 cvd::Command cmd = std::move(cmd_tmp);
362 bool io_error = false;
363 if (stdin != nullptr) {
364 cvd::SharedFD pipe_read, pipe_write;
365 if (!cvd::SharedFD::Pipe(&pipe_read, &pipe_write)) {
366 LOG(ERROR) << "Could not create a pipe to write the stdin of \""
367 << cmd.GetShortName() << "\"";
368 return -1;
369 }
370 if (!cmd.RedirectStdIO(cvd::Subprocess::StdIOChannel::kStdIn, pipe_read)) {
371 LOG(ERROR) << "Could not set stdout of \"" << cmd.GetShortName()
372 << "\", was already set.";
373 return -1;
374 }
375 stdin_thread = std::thread([pipe_write, stdin, &io_error]() {
376 int written = cvd::WriteAll(pipe_write, *stdin);
377 if (written < 0) {
378 io_error = true;
379 LOG(ERROR) << "Error in writing stdin to process";
380 }
381 });
382 }
383 if (stdout != nullptr) {
384 cvd::SharedFD pipe_read, pipe_write;
385 if (!cvd::SharedFD::Pipe(&pipe_read, &pipe_write)) {
386 LOG(ERROR) << "Could not create a pipe to read the stdout of \""
387 << cmd.GetShortName() << "\"";
388 return -1;
389 }
390 if (!cmd.RedirectStdIO(cvd::Subprocess::StdIOChannel::kStdOut, pipe_write)) {
391 LOG(ERROR) << "Could not set stdout of \"" << cmd.GetShortName()
392 << "\", was already set.";
393 return -1;
394 }
395 stdout_thread = std::thread([pipe_read, stdout, &io_error]() {
396 int read = cvd::ReadAll(pipe_read, stdout);
397 if (read < 0) {
398 io_error = true;
399 LOG(ERROR) << "Error in reading stdout from process";
400 }
401 });
402 }
403 if (stderr != nullptr) {
404 cvd::SharedFD pipe_read, pipe_write;
405 if (!cvd::SharedFD::Pipe(&pipe_read, &pipe_write)) {
406 LOG(ERROR) << "Could not create a pipe to read the stderr of \""
407 << cmd.GetShortName() << "\"";
408 return -1;
409 }
410 if (!cmd.RedirectStdIO(cvd::Subprocess::StdIOChannel::kStdErr, pipe_write)) {
411 LOG(ERROR) << "Could not set stderr of \"" << cmd.GetShortName()
412 << "\", was already set.";
413 return -1;
414 }
415 stderr_thread = std::thread([pipe_read, stderr, &io_error]() {
416 int read = cvd::ReadAll(pipe_read, stderr);
417 if (read < 0) {
418 io_error = true;
419 LOG(ERROR) << "Error in reading stderr from process";
420 }
421 });
422 }
423
424 auto subprocess = cmd.Start();
425 if (!subprocess.Started()) {
426 return -1;
427 }
428 {
429 // Force the destructor to run by moving it into a smaller scope.
430 // This is necessary to close the write end of the pipe.
431 cvd::Command forceDelete = std::move(cmd);
432 }
433 int wstatus;
434 subprocess.Wait(&wstatus, 0);
435 if (WIFSIGNALED(wstatus)) {
436 LOG(ERROR) << "Command was interrupted by a signal: " << WTERMSIG(wstatus);
437 return -1;
438 }
439 {
440 auto join_threads = std::move(thread_joiner);
441 }
442 if (io_error) {
443 LOG(ERROR) << "IO error communicating with " << cmd.GetShortName();
444 return -1;
445 }
446 return WEXITSTATUS(wstatus);
447}
448
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700449int execute(const std::vector<std::string>& command,
450 const std::vector<std::string>& env) {
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700451 Command cmd(command[0]);
452 for (size_t i = 1; i < command.size(); ++i) {
453 cmd.AddParameter(command[i]);
454 }
455 cmd.SetEnvironment(env);
456 auto subprocess = cmd.Start();
457 if (!subprocess.Started()) {
458 return -1;
459 }
460 return subprocess.Wait();
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700461}
462int execute(const std::vector<std::string>& command) {
Jorge E. Moreira6ffa0cb2018-10-24 17:19:39 -0700463 Command cmd(command[0]);
464 for (size_t i = 1; i < command.size(); ++i) {
465 cmd.AddParameter(command[i]);
466 }
467 auto subprocess = cmd.Start();
468 if (!subprocess.Started()) {
469 return -1;
470 }
471 return subprocess.Wait();
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700472}
Cody Schuffelen5133a312019-08-02 17:58:00 -0700473
Jorge E. Moreira6a9d6292018-06-11 11:52:57 -0700474} // namespace cvd