• 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 #include "third_party/cros_system_api/switches/chrome_switches.h"
21 
22 namespace {
23 
24 enum PipeEnd {
25   PIPE_END_READ,
26   PIPE_END_WRITE
27 };
28 
29 enum PseudoTerminalFd {
30   PT_MASTER_FD,
31   PT_SLAVE_FD
32 };
33 
34 const int kInvalidFd = -1;
35 
36 }  // namespace
37 
38 namespace chromeos {
39 
ProcessProxy()40 ProcessProxy::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 
Open(const std::string & command,pid_t * pid)48 bool 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 
StartWatchingOnThread(base::Thread * watch_thread,const ProcessOutputCallback & callback)69 bool 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 
OnProcessOutput(ProcessOutputType type,const std::string & output)108 void 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 
CallOnProcessOutputCallback(ProcessOutputType type,const std::string & output)119 void 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 
StopWatching()130 bool 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 
Close()140 void 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 
Write(const std::string & text)157 bool 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 
OnTerminalResize(int width,int height)169 bool 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 
~ProcessProxy()182 ProcessProxy::~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 
CreatePseudoTerminalPair(int * pt_pair)192 bool 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 
LaunchProcess(const std::string & command,int slave_fd,pid_t * pid)219 bool 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 
CloseAllFdPairs()240 void ProcessProxy::CloseAllFdPairs() {
241   CloseFdPair(pt_pair_);
242   CloseFdPair(shutdown_pipe_);
243 }
244 
CloseFdPair(int * pipe)245 void ProcessProxy::CloseFdPair(int* pipe) {
246   CloseFd(&(pipe[PIPE_END_READ]));
247   CloseFd(&(pipe[PIPE_END_WRITE]));
248 }
249 
CloseFd(int * fd)250 void 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 
ClearAllFdPairs()258 void ProcessProxy::ClearAllFdPairs() {
259   ClearFdPair(pt_pair_);
260   ClearFdPair(shutdown_pipe_);
261 }
262 
ClearFdPair(int * pipe)263 void ProcessProxy::ClearFdPair(int* pipe) {
264   pipe[PIPE_END_READ] = kInvalidFd;
265   pipe[PIPE_END_WRITE] = kInvalidFd;
266 }
267 
268 }  // namespace chromeos
269