• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
21 namespace {
22 
23 enum PipeEnd {
24   PIPE_END_READ,
25   PIPE_END_WRITE
26 };
27 
28 enum PseudoTerminalFd {
29   PT_MASTER_FD,
30   PT_SLAVE_FD
31 };
32 
33 const int kInvalidFd = -1;
34 
35 }  // namespace
36 
37 namespace chromeos {
38 
ProcessProxy()39 ProcessProxy::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 
Open(const std::string & command,pid_t * pid)47 bool 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 
StartWatchingOnThread(base::Thread * watch_thread,const ProcessOutputCallback & callback)68 bool 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 
OnProcessOutput(ProcessOutputType type,const std::string & output)107 void 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 
CallOnProcessOutputCallback(ProcessOutputType type,const std::string & output)118 void 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 
StopWatching()129 bool 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 
Close()139 void 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 
Write(const std::string & text)156 bool 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 
OnTerminalResize(int width,int height)168 bool 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 
~ProcessProxy()181 ProcessProxy::~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 
CreatePseudoTerminalPair(int * pt_pair)191 bool 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 
LaunchProcess(const std::string & command,int slave_fd,pid_t * pid)218 bool 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 
CloseAllFdPairs()235 void ProcessProxy::CloseAllFdPairs() {
236   CloseFdPair(pt_pair_);
237   CloseFdPair(shutdown_pipe_);
238 }
239 
CloseFdPair(int * pipe)240 void ProcessProxy::CloseFdPair(int* pipe) {
241   CloseFd(&(pipe[PIPE_END_READ]));
242   CloseFd(&(pipe[PIPE_END_WRITE]));
243 }
244 
CloseFd(int * fd)245 void 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 
ClearAllFdPairs()253 void ProcessProxy::ClearAllFdPairs() {
254   ClearFdPair(pt_pair_);
255   ClearFdPair(shutdown_pipe_);
256 }
257 
ClearFdPair(int * pipe)258 void ProcessProxy::ClearFdPair(int* pipe) {
259   pipe[PIPE_END_READ] = kInvalidFd;
260   pipe[PIPE_END_WRITE] = kInvalidFd;
261 }
262 
263 }  // namespace chromeos
264