1// Copyright (c) 2012 The Chromium 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 "chromeos/process_proxy/process_proxy.h"
6
7#include <fcntl.h>
8#include <stdlib.h>
9#include <sys/ioctl.h>
10
11#include "base/bind.h"
12#include "base/command_line.h"
13#include "base/files/file_util.h"
14#include "base/logging.h"
15#include "base/posix/eintr_wrapper.h"
16#include "base/process/kill.h"
17#include "base/process/launch.h"
18#include "base/threading/thread.h"
19#include "chromeos/process_proxy/process_output_watcher.h"
20#include "third_party/cros_system_api/switches/chrome_switches.h"
21
22namespace {
23
24enum PipeEnd {
25  PIPE_END_READ,
26  PIPE_END_WRITE
27};
28
29enum PseudoTerminalFd {
30  PT_MASTER_FD,
31  PT_SLAVE_FD
32};
33
34const int kInvalidFd = -1;
35
36}  // namespace
37
38namespace chromeos {
39
40ProcessProxy::ProcessProxy(): process_launched_(false),
41                              callback_set_(false),
42                              watcher_started_(false) {
43  // Set pipes to initial, invalid value so we can easily know if a pipe was
44  // opened by us.
45  ClearAllFdPairs();
46}
47
48bool ProcessProxy::Open(const std::string& command, pid_t* pid) {
49  if (process_launched_)
50    return false;
51
52  if (!CreatePseudoTerminalPair(pt_pair_)) {
53    return false;
54  }
55
56  process_launched_ = LaunchProcess(command, pt_pair_[PT_SLAVE_FD], &pid_);
57
58  if (process_launched_) {
59    // We won't need these anymore. These will be used by the launched process.
60    CloseFd(&pt_pair_[PT_SLAVE_FD]);
61    *pid = pid_;
62    LOG(WARNING) << "Process launched: " << pid_;
63  } else {
64    CloseFdPair(pt_pair_);
65  }
66  return process_launched_;
67}
68
69bool ProcessProxy::StartWatchingOnThread(
70    base::Thread* watch_thread,
71    const ProcessOutputCallback& callback) {
72  DCHECK(process_launched_);
73  if (watcher_started_)
74    return false;
75  if (pipe(shutdown_pipe_))
76    return false;
77
78  // We give ProcessOutputWatcher a copy of master to make life easier during
79  // tear down.
80  // TODO(tbarzic): improve fd managment.
81  int master_copy = HANDLE_EINTR(dup(pt_pair_[PT_MASTER_FD]));
82  if (master_copy == -1)
83    return false;
84
85  callback_set_ = true;
86  callback_ = callback;
87  callback_runner_ = base::MessageLoopProxy::current();
88
89  // This object will delete itself once watching is stopped.
90  // It also takes ownership of the passed fds.
91  ProcessOutputWatcher* output_watcher =
92      new ProcessOutputWatcher(master_copy,
93                               shutdown_pipe_[PIPE_END_READ],
94                               base::Bind(&ProcessProxy::OnProcessOutput,
95                                          this));
96
97  // Output watcher took ownership of the read end of shutdown pipe.
98  shutdown_pipe_[PIPE_END_READ] = -1;
99
100  // |watch| thread is blocked by |output_watcher| from now on.
101  watch_thread->message_loop()->PostTask(FROM_HERE,
102      base::Bind(&ProcessOutputWatcher::Start,
103                 base::Unretained(output_watcher)));
104  watcher_started_ = true;
105  return true;
106}
107
108void ProcessProxy::OnProcessOutput(ProcessOutputType type,
109                                   const std::string& output) {
110  if (!callback_runner_.get())
111    return;
112
113  callback_runner_->PostTask(
114      FROM_HERE,
115      base::Bind(&ProcessProxy::CallOnProcessOutputCallback,
116                 this, type, output));
117}
118
119void ProcessProxy::CallOnProcessOutputCallback(ProcessOutputType type,
120                                               const std::string& output) {
121  // We may receive some output even after Close was called (crosh process does
122  // not have to quit instantly, or there may be some trailing data left in
123  // output stream fds). In that case owner of the callback may be gone so we
124  // don't want to send it anything. |callback_set_| is reset when this gets
125  // closed.
126  if (callback_set_)
127    callback_.Run(type, output);
128}
129
130bool ProcessProxy::StopWatching() {
131  if (!watcher_started_)
132    return true;
133  // Signal Watcher that we are done. We use self-pipe trick to unblock watcher.
134  // Anything may be written to the pipe.
135  const char message[] = "q";
136  return base::WriteFileDescriptor(shutdown_pipe_[PIPE_END_WRITE],
137                                   message, sizeof(message));
138}
139
140void ProcessProxy::Close() {
141  if (!process_launched_)
142    return;
143
144  process_launched_ = false;
145  callback_set_ = false;
146  callback_ = ProcessOutputCallback();
147  callback_runner_ = NULL;
148
149  base::KillProcess(pid_, 0, true /* wait */);
150
151  // TODO(tbarzic): What if this fails?
152  StopWatching();
153
154  CloseAllFdPairs();
155}
156
157bool ProcessProxy::Write(const std::string& text) {
158  if (!process_launched_)
159    return false;
160
161  // We don't want to write '\0' to the pipe.
162  size_t data_size = text.length() * sizeof(*text.c_str());
163  int bytes_written =
164      base::WriteFileDescriptor(pt_pair_[PT_MASTER_FD],
165                                text.c_str(), data_size);
166  return (bytes_written == static_cast<int>(data_size));
167}
168
169bool ProcessProxy::OnTerminalResize(int width, int height) {
170  if (width < 0 || height < 0)
171    return false;
172
173  winsize ws;
174  // Number of rows.
175  ws.ws_row = height;
176  // Number of columns.
177  ws.ws_col = width;
178
179  return (HANDLE_EINTR(ioctl(pt_pair_[PT_MASTER_FD], TIOCSWINSZ, &ws)) != -1);
180}
181
182ProcessProxy::~ProcessProxy() {
183  // In case watcher did not started, we may get deleted without calling Close.
184  // In that case we have to clean up created pipes. If watcher had been
185  // started, there will be a callback with our reference owned by
186  // process_output_watcher until Close is called, so we know Close has been
187  // called  by now (and pipes have been cleaned).
188  if (!watcher_started_)
189    CloseAllFdPairs();
190}
191
192bool ProcessProxy::CreatePseudoTerminalPair(int *pt_pair) {
193  ClearFdPair(pt_pair);
194
195  // Open Master.
196  pt_pair[PT_MASTER_FD] = HANDLE_EINTR(posix_openpt(O_RDWR | O_NOCTTY));
197  if (pt_pair[PT_MASTER_FD] == -1)
198    return false;
199
200  if (grantpt(pt_pair_[PT_MASTER_FD]) != 0 ||
201      unlockpt(pt_pair_[PT_MASTER_FD]) != 0) {
202    CloseFd(&pt_pair[PT_MASTER_FD]);
203    return false;
204  }
205  char* slave_name = NULL;
206  // Per man page, slave_name must not be freed.
207  slave_name = ptsname(pt_pair_[PT_MASTER_FD]);
208  if (slave_name)
209    pt_pair_[PT_SLAVE_FD] = HANDLE_EINTR(open(slave_name, O_RDWR | O_NOCTTY));
210
211  if (pt_pair_[PT_SLAVE_FD] == -1) {
212    CloseFdPair(pt_pair);
213    return false;
214  }
215
216  return true;
217}
218
219bool ProcessProxy::LaunchProcess(const std::string& command, int slave_fd,
220                                 pid_t* pid) {
221  // Redirect crosh  process' output and input so we can read it.
222  base::FileHandleMappingVector fds_mapping;
223  fds_mapping.push_back(std::make_pair(slave_fd, STDIN_FILENO));
224  fds_mapping.push_back(std::make_pair(slave_fd, STDOUT_FILENO));
225  fds_mapping.push_back(std::make_pair(slave_fd, STDERR_FILENO));
226  base::LaunchOptions options;
227  // Do not set NO_NEW_PRIVS on processes if the system is in dev-mode. This
228  // permits sudo in the crosh shell when in developer mode.
229  options.allow_new_privs = base::CommandLine::ForCurrentProcess()->
230      HasSwitch(chromeos::switches::kSystemInDevMode);
231  options.fds_to_remap = &fds_mapping;
232  options.ctrl_terminal_fd = slave_fd;
233  options.environ["TERM"] = "xterm";
234
235  // Launch the process.
236  return base::LaunchProcess(CommandLine(base::FilePath(command)), options,
237                             pid);
238}
239
240void ProcessProxy::CloseAllFdPairs() {
241  CloseFdPair(pt_pair_);
242  CloseFdPair(shutdown_pipe_);
243}
244
245void ProcessProxy::CloseFdPair(int* pipe) {
246  CloseFd(&(pipe[PIPE_END_READ]));
247  CloseFd(&(pipe[PIPE_END_WRITE]));
248}
249
250void ProcessProxy::CloseFd(int* fd) {
251  if (*fd != kInvalidFd) {
252    if (IGNORE_EINTR(close(*fd)) != 0)
253      DPLOG(WARNING) << "close fd failed.";
254  }
255  *fd = kInvalidFd;
256}
257
258void ProcessProxy::ClearAllFdPairs() {
259  ClearFdPair(pt_pair_);
260  ClearFdPair(shutdown_pipe_);
261}
262
263void ProcessProxy::ClearFdPair(int* pipe) {
264  pipe[PIPE_END_READ] = kInvalidFd;
265  pipe[PIPE_END_WRITE] = kInvalidFd;
266}
267
268}  // namespace chromeos
269