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