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