blob: 439ef70b6bdb317ca8d4bee66689c061568103fd [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
14#include <base/files/file_util.h>
François Degros8c14d382019-09-12 14:50:43 +100015#include <base/posix/eintr_wrapper.h>
Sergei Datsenkocd676b72019-05-10 11:42:05 +100016#include <base/process/kill.h>
17#include <base/strings/string_util.h>
18#include <base/strings/string_split.h>
19#include <base/time/time.h>
20
François Degros899487c2019-07-12 11:57:52 +100021#include "cros-disks/quote.h"
François Degros1ef69942019-10-01 15:31:17 +100022#include "cros-disks/sandboxed_init.h"
François Degros899487c2019-07-12 11:57:52 +100023
Ben Chan6f391cb2012-03-21 17:38:21 -070024namespace cros_disks {
Sergei Datsenkocd676b72019-05-10 11:42:05 +100025namespace {
26
27enum class ReadResult {
28 kSuccess,
29 kWouldBlock,
30 kFailure,
31};
32
33ReadResult ReadFD(int fd, std::string* data) {
34 const size_t kMaxSize = 4096;
35 char buffer[kMaxSize];
36 ssize_t bytes_read = HANDLE_EINTR(read(fd, buffer, kMaxSize));
37
38 if (bytes_read < 0) {
39 if (errno == EAGAIN || errno == EWOULDBLOCK) {
40 data->clear();
41 return ReadResult::kWouldBlock;
42 }
43 PLOG(ERROR) << "Read failed.";
44 return ReadResult::kFailure;
45 }
46
47 data->assign(buffer, bytes_read);
48 return ReadResult::kSuccess;
49}
50
51// Interleaves streams.
52class StreamMerger {
53 public:
François Degrosd5d26882019-09-13 11:23:51 +100054 explicit StreamMerger(std::vector<std::string>* output) : output_(output) {}
Qijiang Fan6bc59e12020-11-11 02:51:06 +090055 StreamMerger(const StreamMerger&) = delete;
56 StreamMerger& operator=(const StreamMerger&) = delete;
Sergei Datsenkocd676b72019-05-10 11:42:05 +100057
58 ~StreamMerger() {
François Degrosd5d26882019-09-13 11:23:51 +100059 for (size_t i = 0; i < kStreamCount; ++i) {
60 const std::string& remaining = remaining_[i];
61 if (!remaining.empty())
62 output_->push_back(base::JoinString({kTags[i], ": ", remaining}, ""));
Sergei Datsenkocd676b72019-05-10 11:42:05 +100063 }
64 }
65
François Degrosd5d26882019-09-13 11:23:51 +100066 void Append(const size_t stream, const base::StringPiece data) {
67 DCHECK_LT(stream, kStreamCount);
68
Sergei Datsenkocd676b72019-05-10 11:42:05 +100069 if (data.empty()) {
70 return;
71 }
72
François Degrosd5d26882019-09-13 11:23:51 +100073 std::string& remaining = remaining_[stream];
74 const base::StringPiece tag = kTags[stream];
Sergei Datsenkocd676b72019-05-10 11:42:05 +100075
François Degrosd5d26882019-09-13 11:23:51 +100076 std::vector<base::StringPiece> lines = base::SplitStringPiece(
Sergei Datsenkocd676b72019-05-10 11:42:05 +100077 data, "\n", base::WhitespaceHandling::KEEP_WHITESPACE,
78 base::SplitResult::SPLIT_WANT_ALL);
François Degrosd5d26882019-09-13 11:23:51 +100079 const base::StringPiece last_line = lines.back();
Sergei Datsenkocd676b72019-05-10 11:42:05 +100080 lines.pop_back();
81
François Degrosd5d26882019-09-13 11:23:51 +100082 for (const base::StringPiece line : lines) {
83 output_->push_back(base::JoinString({tag, ": ", remaining, line}, ""));
Sergei Datsenkocd676b72019-05-10 11:42:05 +100084 remaining.clear();
85 }
François Degrosd5d26882019-09-13 11:23:51 +100086
87 remaining = last_line.as_string();
Sergei Datsenkocd676b72019-05-10 11:42:05 +100088 }
89
90 private:
François Degrosd5d26882019-09-13 11:23:51 +100091 // Number of streams to interleave.
92 static const size_t kStreamCount = 2;
93 static const base::StringPiece kTags[kStreamCount];
94 std::vector<std::string>* const output_;
95 std::string remaining_[kStreamCount];
Sergei Datsenkocd676b72019-05-10 11:42:05 +100096};
97
François Degrosd5d26882019-09-13 11:23:51 +100098const size_t StreamMerger::kStreamCount;
99const base::StringPiece StreamMerger::kTags[kStreamCount] = {"OUT", "ERR"};
100
François Degros1ef69942019-10-01 15:31:17 +1000101// Opens /dev/null. Dies in case of error.
François Degros597bcf42020-08-28 19:34:19 +1000102base::ScopedFD OpenNull() {
103 const int ret = open("/dev/null", O_WRONLY);
François Degros1ef69942019-10-01 15:31:17 +1000104 PLOG_IF(FATAL, ret < 0) << "Cannot open /dev/null";
105 return base::ScopedFD(ret);
106}
107
François Degros597bcf42020-08-28 19:34:19 +1000108// Creates a pipe holding the given string and returns a file descriptor to the
109// read end of this pipe. If the given string is too big to fit into the pipe's
110// buffer, it is truncated.
111base::ScopedFD WrapStdIn(const base::StringPiece in) {
112 SubprocessPipe p(SubprocessPipe::kParentToChild);
113
114 CHECK(base::SetNonBlocking(p.parent_fd.get()));
115 const ssize_t n =
116 HANDLE_EINTR(write(p.parent_fd.get(), in.data(), in.size()));
117 if (n < 0) {
118 PLOG(ERROR) << "Cannot write to pipe";
119 } else if (n < in.size()) {
120 LOG(ERROR) << "Short write to pipe: Wrote " << n << " bytes instead of "
121 << in.size() << " bytes";
122 }
123
124 return std::move(p.child_fd);
125}
126
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000127} // namespace
128
Ben Chanacac3952012-04-24 22:50:01 -0700129// static
130const pid_t Process::kInvalidProcessId = -1;
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000131const int Process::kInvalidFD = base::ScopedFD::traits_type::InvalidValue();
Ben Chanacac3952012-04-24 22:50:01 -0700132
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000133Process::Process() = default;
Ben Chan6f391cb2012-03-21 17:38:21 -0700134
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000135Process::~Process() = default;
Ben Chan6f391cb2012-03-21 17:38:21 -0700136
François Degrosee318212020-07-14 14:11:42 +1000137void Process::AddArgument(std::string argument) {
François Degros2a4ec242019-10-15 16:28:54 +1100138 DCHECK(arguments_array_.empty());
François Degrosee318212020-07-14 14:11:42 +1000139 arguments_.push_back(std::move(argument));
Ben Chan6f391cb2012-03-21 17:38:21 -0700140}
141
François Degros5593b8c2019-07-25 12:27:42 +1000142char* const* Process::GetArguments() {
Ben Chan5e3ca672014-08-25 15:53:58 -0700143 if (arguments_array_.empty())
Ben Chan6f391cb2012-03-21 17:38:21 -0700144 BuildArgumentsArray();
145
Ben Chan5e3ca672014-08-25 15:53:58 -0700146 return arguments_array_.data();
Ben Chan6f391cb2012-03-21 17:38:21 -0700147}
148
François Degros2a4ec242019-10-15 16:28:54 +1100149void Process::BuildArgumentsArray() {
150 for (std::string& argument : arguments_) {
151 // TODO(fdegros) Remove const_cast when using C++17
152 arguments_array_.push_back(const_cast<char*>(argument.data()));
Ben Chan6f391cb2012-03-21 17:38:21 -0700153 }
154
François Degros2a4ec242019-10-15 16:28:54 +1100155 arguments_array_.push_back(nullptr);
Ben Chan6f391cb2012-03-21 17:38:21 -0700156}
157
François Degrosee318212020-07-14 14:11:42 +1000158void Process::AddEnvironmentVariable(const base::StringPiece name,
159 const base::StringPiece value) {
160 DCHECK(environment_array_.empty());
161 DCHECK(!name.empty());
162 std::string s;
163 s.reserve(name.size() + value.size() + 1);
Qijiang Fan985d7252020-08-04 14:37:37 +0900164 s.append(name.data(), name.size());
François Degrosee318212020-07-14 14:11:42 +1000165 s += '=';
Qijiang Fan985d7252020-08-04 14:37:37 +0900166 s.append(value.data(), value.size());
François Degrosee318212020-07-14 14:11:42 +1000167 environment_.push_back(std::move(s));
168}
169
170char* const* Process::GetEnvironment() {
171 // If there are no extra environment variables, just use the current
172 // environment.
173 if (environment_.empty()) {
174 return environ;
175 }
176
177 if (environment_array_.empty()) {
178 // Prepare the new environment.
179 for (std::string& s : environment_) {
180 // TODO(fdegros) Remove const_cast when using C++17
181 environment_array_.push_back(const_cast<char*>(s.data()));
182 }
183
184 // Append the current environment.
185 for (char* const* p = environ; *p; ++p) {
186 environment_array_.push_back(*p);
187 }
188
189 environment_array_.push_back(nullptr);
190 }
191
192 return environment_array_.data();
193}
194
François Degros1ef69942019-10-01 15:31:17 +1000195bool Process::Start(base::ScopedFD in_fd,
196 base::ScopedFD out_fd,
197 base::ScopedFD err_fd) {
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000198 CHECK_EQ(kInvalidProcessId, pid_);
François Degros01564642019-09-13 14:10:17 +1000199 CHECK(!finished());
François Degros5593b8c2019-07-25 12:27:42 +1000200 CHECK(!arguments_.empty()) << "No arguments provided";
François Degrosee318212020-07-14 14:11:42 +1000201 LOG(INFO) << "Starting program " << quote(arguments_.front())
François Degrosc6f787e2020-09-10 14:29:19 +1000202 << " with arguments " << quote(arguments_);
203 LOG_IF(INFO, !environment_.empty())
204 << "and extra environment " << quote(environment_);
François Degros1ef69942019-10-01 15:31:17 +1000205 pid_ = StartImpl(std::move(in_fd), std::move(out_fd), std::move(err_fd));
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000206 return pid_ != kInvalidProcessId;
207}
208
François Degros1ef69942019-10-01 15:31:17 +1000209bool Process::Start() {
François Degros597bcf42020-08-28 19:34:19 +1000210 return Start(WrapStdIn(input_), OpenNull(), OpenNull());
François Degros1ef69942019-10-01 15:31:17 +1000211}
212
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000213int Process::Wait() {
François Degros01564642019-09-13 14:10:17 +1000214 if (finished()) {
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000215 return status_;
Sergei Datsenko9246e9c2019-03-22 10:26:47 +1100216 }
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000217
218 CHECK_NE(kInvalidProcessId, pid_);
219 status_ = WaitImpl();
François Degros01564642019-09-13 14:10:17 +1000220 CHECK(finished());
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000221 pid_ = kInvalidProcessId;
222 return status_;
223}
224
225bool Process::IsFinished() {
François Degros01564642019-09-13 14:10:17 +1000226 if (finished()) {
227 return true;
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000228 }
François Degros01564642019-09-13 14:10:17 +1000229
230 CHECK_NE(kInvalidProcessId, pid_);
231 status_ = WaitNonBlockingImpl();
232 return finished();
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000233}
234
235int Process::Run(std::vector<std::string>* output) {
François Degrosedaefc52019-10-03 12:00:20 +1000236 DCHECK(output);
237
François Degros1ef69942019-10-01 15:31:17 +1000238 base::ScopedFD out_fd, err_fd;
François Degros597bcf42020-08-28 19:34:19 +1000239 if (!Start(WrapStdIn(input_),
François Degros1ef69942019-10-01 15:31:17 +1000240 SubprocessPipe::Open(SubprocessPipe::kChildToParent, &out_fd),
241 SubprocessPipe::Open(SubprocessPipe::kChildToParent, &err_fd))) {
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000242 return -1;
243 }
244
François Degros1ef69942019-10-01 15:31:17 +1000245 Communicate(output, std::move(out_fd), std::move(err_fd));
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000246
François Degros899487c2019-07-12 11:57:52 +1000247 const int result = Wait();
248
249 LOG(INFO) << "Process finished with return code " << result;
François Degrosedaefc52019-10-03 12:00:20 +1000250 if (LOG_IS_ON(INFO) && !output->empty()) {
François Degros899487c2019-07-12 11:57:52 +1000251 LOG(INFO) << "Process outputted " << output->size() << " lines:";
252 for (const std::string& line : *output) {
François Degros16ad1ae2019-07-17 16:02:39 +1000253 LOG(INFO) << line;
François Degros899487c2019-07-12 11:57:52 +1000254 }
255 }
256
257 return result;
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000258}
259
François Degros1ef69942019-10-01 15:31:17 +1000260void Process::Communicate(std::vector<std::string>* output,
261 base::ScopedFD out_fd,
262 base::ScopedFD err_fd) {
263 DCHECK(output);
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000264
265 if (out_fd.is_valid()) {
266 CHECK(base::SetNonBlocking(out_fd.get()));
267 }
268 if (err_fd.is_valid()) {
269 CHECK(base::SetNonBlocking(err_fd.get()));
270 }
271
272 std::string data;
François Degrosd5d26882019-09-13 11:23:51 +1000273 StreamMerger merger(output);
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000274 std::array<struct pollfd, 2> fds;
275 fds[0] = {out_fd.get(), POLLIN, 0};
276 fds[1] = {err_fd.get(), POLLIN, 0};
277
278 while (!IsFinished()) {
279 size_t still_open = 0;
280 for (const auto& f : fds) {
281 still_open += f.fd != kInvalidFD;
282 }
283 if (still_open == 0) {
284 // No comms expected anymore.
285 break;
286 }
287
François Degros8c14d382019-09-12 14:50:43 +1000288 const int ret =
289 HANDLE_EINTR(poll(fds.data(), fds.size(), 10 /* milliseconds */));
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000290 if (ret == -1) {
291 PLOG(ERROR) << "poll() failed";
292 break;
293 }
294
295 if (ret) {
296 for (size_t i = 0; i < fds.size(); ++i) {
297 auto& f = fds[i];
298 if (f.revents) {
299 if (ReadFD(f.fd, &data) == ReadResult::kFailure) {
300 // Failure.
301 f.fd = kInvalidFD;
302 } else {
303 merger.Append(i, data);
304 }
305 }
306 }
307 }
308 }
309
310 Wait();
311
312 // Final read after process exited.
313 for (size_t i = 0; i < fds.size(); ++i) {
314 auto& f = fds[i];
315 if (f.fd != kInvalidFD) {
Daniel Verkamp2d219af2019-10-03 14:32:15 -0700316 while (ReadFD(f.fd, &data) != ReadResult::kFailure && !data.empty()) {
Sergei Datsenkocd676b72019-05-10 11:42:05 +1000317 merger.Append(i, data);
318 }
319 }
320 }
Sergei Datsenko9246e9c2019-03-22 10:26:47 +1100321}
322
Ben Chan6f391cb2012-03-21 17:38:21 -0700323} // namespace cros_disks