blob: 76571f67ccf5a095c0247da1c5ab034b523e9d75 [file] [log] [blame]
Ben Chan6f391cb2012-03-21 17:38:21 -07001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "cros-disks/process.h"
6
Sergei Datsenkocd676b72019-05-10 11:42:05 +10007#include <algorithm>
8#include <array>
9#include <string>
10
François Degros1ef69942019-10-01 15:31:17 +100011#include <fcntl.h>
Sergei Datsenkocd676b72019-05-10 11:42:05 +100012#include <poll.h>
13
Qijiang Fan713061e2021-03-08 15:45:12 +090014#include <base/check.h>
15#include <base/check_op.h>
Sergei Datsenkocd676b72019-05-10 11:42:05 +100016#include <base/files/file_util.h>
François Degros8c14d382019-09-12 14:50:43 +100017#include <base/posix/eintr_wrapper.h>
Sergei Datsenkocd676b72019-05-10 11:42:05 +100018#include <base/process/kill.h>
19#include <base/strings/string_util.h>
20#include <base/strings/string_split.h>
21#include <base/time/time.h>
22
François Degros899487c2019-07-12 11:57:52 +100023#include "cros-disks/quote.h"
François Degros1ef69942019-10-01 15:31:17 +100024#include "cros-disks/sandboxed_init.h"
François Degros899487c2019-07-12 11:57:52 +100025
Ben Chan6f391cb2012-03-21 17:38:21 -070026namespace cros_disks {
Sergei Datsenkocd676b72019-05-10 11:42:05 +100027namespace {
28
29enum class ReadResult {
30 kSuccess,
31 kWouldBlock,
32 kFailure,
33};
34
35ReadResult ReadFD(int fd, std::string* data) {
36 const size_t kMaxSize = 4096;
37 char buffer[kMaxSize];
38 ssize_t bytes_read = HANDLE_EINTR(read(fd, buffer, kMaxSize));
39
40 if (bytes_read < 0) {
41 if (errno == EAGAIN || errno == EWOULDBLOCK) {
42 data->clear();
43 return ReadResult::kWouldBlock;
44 }
45 PLOG(ERROR) << "Read failed.";
46 return ReadResult::kFailure;
47 }
48
49 data->assign(buffer, bytes_read);
50 return ReadResult::kSuccess;
51}
52
53// Interleaves streams.
54class StreamMerger {
55 public:
François Degrosd5d26882019-09-13 11:23:51 +100056 explicit StreamMerger(std::vector<std::string>* output) : output_(output) {}
Qijiang Fan6bc59e12020-11-11 02:51:06 +090057 StreamMerger(const StreamMerger&) = delete;
58 StreamMerger& operator=(const StreamMerger&) = delete;
Sergei Datsenkocd676b72019-05-10 11:42:05 +100059
60 ~StreamMerger() {
François Degrosd5d26882019-09-13 11:23:51 +100061 for (size_t i = 0; i < kStreamCount; ++i) {
62 const std::string& remaining = remaining_[i];
63 if (!remaining.empty())
64 output_->push_back(base::JoinString({kTags[i], ": ", remaining}, ""));
Sergei Datsenkocd676b72019-05-10 11:42:05 +100065 }
66 }
67
François Degrosd5d26882019-09-13 11:23:51 +100068 void Append(const size_t stream, const base::StringPiece data) {
69 DCHECK_LT(stream, kStreamCount);
70
Sergei Datsenkocd676b72019-05-10 11:42:05 +100071 if (data.empty()) {
72 return;
73 }
74
François Degrosd5d26882019-09-13 11:23:51 +100075 std::string& remaining = remaining_[stream];
76 const base::StringPiece tag = kTags[stream];
Sergei Datsenkocd676b72019-05-10 11:42:05 +100077
François Degrosd5d26882019-09-13 11:23:51 +100078 std::vector<base::StringPiece> lines = base::SplitStringPiece(
Sergei Datsenkocd676b72019-05-10 11:42:05 +100079 data, "\n", base::WhitespaceHandling::KEEP_WHITESPACE,
80 base::SplitResult::SPLIT_WANT_ALL);
François Degrosd5d26882019-09-13 11:23:51 +100081 const base::StringPiece last_line = lines.back();
Sergei Datsenkocd676b72019-05-10 11:42:05 +100082 lines.pop_back();
83
François Degrosd5d26882019-09-13 11:23:51 +100084 for (const base::StringPiece line : lines) {
85 output_->push_back(base::JoinString({tag, ": ", remaining, line}, ""));
Sergei Datsenkocd676b72019-05-10 11:42:05 +100086 remaining.clear();
87 }
François Degrosd5d26882019-09-13 11:23:51 +100088
89 remaining = last_line.as_string();
Sergei Datsenkocd676b72019-05-10 11:42:05 +100090 }
91
92 private:
François Degrosd5d26882019-09-13 11:23:51 +100093 // Number of streams to interleave.
94 static const size_t kStreamCount = 2;
95 static const base::StringPiece kTags[kStreamCount];
96 std::vector<std::string>* const output_;
97 std::string remaining_[kStreamCount];
Sergei Datsenkocd676b72019-05-10 11:42:05 +100098};
99
François Degrosd5d26882019-09-13 11:23:51 +1000100const size_t StreamMerger::kStreamCount;
101const base::StringPiece StreamMerger::kTags[kStreamCount] = {"OUT", "ERR"};
102
François Degros1ef69942019-10-01 15:31:17 +1000103// Opens /dev/null. Dies in case of error.
François Degros597bcf42020-08-28 19:34:19 +1000104base::ScopedFD OpenNull() {
105 const int ret = open("/dev/null", O_WRONLY);
François Degros1ef69942019-10-01 15:31:17 +1000106 PLOG_IF(FATAL, ret < 0) << "Cannot open /dev/null";
107 return base::ScopedFD(ret);
108}
109
François Degros597bcf42020-08-28 19:34:19 +1000110// Creates a pipe holding the given string and returns a file descriptor to the
111// read end of this pipe. If the given string is too big to fit into the pipe's
112// buffer, it is truncated.
113base::ScopedFD WrapStdIn(const base::StringPiece in) {
114 SubprocessPipe p(SubprocessPipe::kParentToChild);
115
116 CHECK(base::SetNonBlocking(p.parent_fd.get()));
117 const ssize_t n =
118 HANDLE_EINTR(write(p.parent_fd.get(), in.data(), in.size()));
119 if (n < 0) {
120 PLOG(ERROR) << "Cannot write to pipe";
121 } else if (n < in.size()) {
122 LOG(ERROR) << "Short write to pipe: Wrote " << n << " bytes instead of "
123 << in.size() << " bytes";
124 }
125
126 return std::move(p.child_fd);
127}
128
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000129} // namespace
130
Ben Chanacac3952012-04-24 22:50:01 -0700131// static
132const pid_t Process::kInvalidProcessId = -1;
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000133const int Process::kInvalidFD = base::ScopedFD::traits_type::InvalidValue();
Ben Chanacac3952012-04-24 22:50:01 -0700134
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000135Process::Process() = default;
Ben Chan6f391cb2012-03-21 17:38:21 -0700136
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000137Process::~Process() = default;
Ben Chan6f391cb2012-03-21 17:38:21 -0700138
François Degrosee318212020-07-14 14:11:42 +1000139void Process::AddArgument(std::string argument) {
François Degros2a4ec242019-10-15 16:28:54 +1100140 DCHECK(arguments_array_.empty());
François Degrosee318212020-07-14 14:11:42 +1000141 arguments_.push_back(std::move(argument));
Ben Chan6f391cb2012-03-21 17:38:21 -0700142}
143
François Degros5593b8c2019-07-25 12:27:42 +1000144char* const* Process::GetArguments() {
Ben Chan5e3ca672014-08-25 15:53:58 -0700145 if (arguments_array_.empty())
Ben Chan6f391cb2012-03-21 17:38:21 -0700146 BuildArgumentsArray();
147
Ben Chan5e3ca672014-08-25 15:53:58 -0700148 return arguments_array_.data();
Ben Chan6f391cb2012-03-21 17:38:21 -0700149}
150
François Degros2a4ec242019-10-15 16:28:54 +1100151void Process::BuildArgumentsArray() {
152 for (std::string& argument : arguments_) {
153 // TODO(fdegros) Remove const_cast when using C++17
154 arguments_array_.push_back(const_cast<char*>(argument.data()));
Ben Chan6f391cb2012-03-21 17:38:21 -0700155 }
156
François Degros2a4ec242019-10-15 16:28:54 +1100157 arguments_array_.push_back(nullptr);
Ben Chan6f391cb2012-03-21 17:38:21 -0700158}
159
François Degrosee318212020-07-14 14:11:42 +1000160void Process::AddEnvironmentVariable(const base::StringPiece name,
161 const base::StringPiece value) {
162 DCHECK(environment_array_.empty());
163 DCHECK(!name.empty());
164 std::string s;
165 s.reserve(name.size() + value.size() + 1);
Qijiang Fan985d7252020-08-04 14:37:37 +0900166 s.append(name.data(), name.size());
François Degrosee318212020-07-14 14:11:42 +1000167 s += '=';
Qijiang Fan985d7252020-08-04 14:37:37 +0900168 s.append(value.data(), value.size());
François Degrosee318212020-07-14 14:11:42 +1000169 environment_.push_back(std::move(s));
170}
171
172char* const* Process::GetEnvironment() {
173 // If there are no extra environment variables, just use the current
174 // environment.
175 if (environment_.empty()) {
176 return environ;
177 }
178
179 if (environment_array_.empty()) {
180 // Prepare the new environment.
181 for (std::string& s : environment_) {
182 // TODO(fdegros) Remove const_cast when using C++17
183 environment_array_.push_back(const_cast<char*>(s.data()));
184 }
185
186 // Append the current environment.
187 for (char* const* p = environ; *p; ++p) {
188 environment_array_.push_back(*p);
189 }
190
191 environment_array_.push_back(nullptr);
192 }
193
194 return environment_array_.data();
195}
196
François Degros1ef69942019-10-01 15:31:17 +1000197bool Process::Start(base::ScopedFD in_fd,
198 base::ScopedFD out_fd,
199 base::ScopedFD err_fd) {
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000200 CHECK_EQ(kInvalidProcessId, pid_);
François Degros01564642019-09-13 14:10:17 +1000201 CHECK(!finished());
François Degros5593b8c2019-07-25 12:27:42 +1000202 CHECK(!arguments_.empty()) << "No arguments provided";
François Degrosee318212020-07-14 14:11:42 +1000203 LOG(INFO) << "Starting program " << quote(arguments_.front())
François Degrosc6f787e2020-09-10 14:29:19 +1000204 << " with arguments " << quote(arguments_);
205 LOG_IF(INFO, !environment_.empty())
206 << "and extra environment " << quote(environment_);
François Degros1ef69942019-10-01 15:31:17 +1000207 pid_ = StartImpl(std::move(in_fd), std::move(out_fd), std::move(err_fd));
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000208 return pid_ != kInvalidProcessId;
209}
210
François Degros1ef69942019-10-01 15:31:17 +1000211bool Process::Start() {
François Degros597bcf42020-08-28 19:34:19 +1000212 return Start(WrapStdIn(input_), OpenNull(), OpenNull());
François Degros1ef69942019-10-01 15:31:17 +1000213}
214
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000215int Process::Wait() {
François Degros01564642019-09-13 14:10:17 +1000216 if (finished()) {
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000217 return status_;
Sergei Datsenko9246e9c2019-03-22 10:26:47 +1100218 }
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000219
220 CHECK_NE(kInvalidProcessId, pid_);
221 status_ = WaitImpl();
François Degros01564642019-09-13 14:10:17 +1000222 CHECK(finished());
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000223 pid_ = kInvalidProcessId;
224 return status_;
225}
226
227bool Process::IsFinished() {
François Degros01564642019-09-13 14:10:17 +1000228 if (finished()) {
229 return true;
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000230 }
François Degros01564642019-09-13 14:10:17 +1000231
232 CHECK_NE(kInvalidProcessId, pid_);
233 status_ = WaitNonBlockingImpl();
234 return finished();
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000235}
236
237int Process::Run(std::vector<std::string>* output) {
François Degrosedaefc52019-10-03 12:00:20 +1000238 DCHECK(output);
239
François Degros1ef69942019-10-01 15:31:17 +1000240 base::ScopedFD out_fd, err_fd;
François Degros597bcf42020-08-28 19:34:19 +1000241 if (!Start(WrapStdIn(input_),
François Degros1ef69942019-10-01 15:31:17 +1000242 SubprocessPipe::Open(SubprocessPipe::kChildToParent, &out_fd),
243 SubprocessPipe::Open(SubprocessPipe::kChildToParent, &err_fd))) {
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000244 return -1;
245 }
246
François Degros1ef69942019-10-01 15:31:17 +1000247 Communicate(output, std::move(out_fd), std::move(err_fd));
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000248
François Degros899487c2019-07-12 11:57:52 +1000249 const int result = Wait();
250
251 LOG(INFO) << "Process finished with return code " << result;
François Degrosedaefc52019-10-03 12:00:20 +1000252 if (LOG_IS_ON(INFO) && !output->empty()) {
François Degros899487c2019-07-12 11:57:52 +1000253 LOG(INFO) << "Process outputted " << output->size() << " lines:";
254 for (const std::string& line : *output) {
François Degros16ad1ae2019-07-17 16:02:39 +1000255 LOG(INFO) << line;
François Degros899487c2019-07-12 11:57:52 +1000256 }
257 }
258
259 return result;
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000260}
261
François Degros1ef69942019-10-01 15:31:17 +1000262void Process::Communicate(std::vector<std::string>* output,
263 base::ScopedFD out_fd,
264 base::ScopedFD err_fd) {
265 DCHECK(output);
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000266
267 if (out_fd.is_valid()) {
268 CHECK(base::SetNonBlocking(out_fd.get()));
269 }
270 if (err_fd.is_valid()) {
271 CHECK(base::SetNonBlocking(err_fd.get()));
272 }
273
274 std::string data;
François Degrosd5d26882019-09-13 11:23:51 +1000275 StreamMerger merger(output);
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000276 std::array<struct pollfd, 2> fds;
277 fds[0] = {out_fd.get(), POLLIN, 0};
278 fds[1] = {err_fd.get(), POLLIN, 0};
279
280 while (!IsFinished()) {
281 size_t still_open = 0;
282 for (const auto& f : fds) {
283 still_open += f.fd != kInvalidFD;
284 }
285 if (still_open == 0) {
286 // No comms expected anymore.
287 break;
288 }
289
François Degros8c14d382019-09-12 14:50:43 +1000290 const int ret =
291 HANDLE_EINTR(poll(fds.data(), fds.size(), 10 /* milliseconds */));
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000292 if (ret == -1) {
293 PLOG(ERROR) << "poll() failed";
294 break;
295 }
296
297 if (ret) {
298 for (size_t i = 0; i < fds.size(); ++i) {
299 auto& f = fds[i];
300 if (f.revents) {
301 if (ReadFD(f.fd, &data) == ReadResult::kFailure) {
302 // Failure.
303 f.fd = kInvalidFD;
304 } else {
305 merger.Append(i, data);
306 }
307 }
308 }
309 }
310 }
311
312 Wait();
313
314 // Final read after process exited.
315 for (size_t i = 0; i < fds.size(); ++i) {
316 auto& f = fds[i];
317 if (f.fd != kInvalidFD) {
Daniel Verkamp2d219af2019-10-03 14:32:15 -0700318 while (ReadFD(f.fd, &data) != ReadResult::kFailure && !data.empty()) {
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000319 merger.Append(i, data);
320 }
321 }
322 }
Sergei Datsenko9246e9c2019-03-22 10:26:47 +1100323}
324
Ben Chan6f391cb2012-03-21 17:38:21 -0700325} // namespace cros_disks