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 <stddef.h>
8 #include <stdlib.h>
9 #include <sys/ioctl.h>
10 #include <termios.h>
11 
12 #include <utility>
13 
14 #include "base/bind.h"
15 #include "base/command_line.h"
16 #include "base/file_descriptor_posix.h"
17 #include "base/files/file_util.h"
18 #include "base/guid.h"
19 #include "base/location.h"
20 #include "base/logging.h"
21 #include "base/posix/eintr_wrapper.h"
22 #include "base/process/kill.h"
23 #include "base/process/launch.h"
24 #include "base/process/process.h"
25 #include "base/single_thread_task_runner.h"
26 #include "base/threading/thread_task_runner_handle.h"
27 #include "third_party/cros_system_api/switches/chrome_switches.h"
28 
29 namespace {
30 
31 enum PseudoTerminalFd {
32   PT_MASTER_FD,
33   PT_SLAVE_FD
34 };
35 
StopOutputWatcher(std::unique_ptr<chromeos::ProcessOutputWatcher> watcher)36 void StopOutputWatcher(
37     std::unique_ptr<chromeos::ProcessOutputWatcher> watcher) {
38   // Just deleting |watcher| if sufficient to stop it.
39 }
40 
41 }  // namespace
42 
43 namespace chromeos {
44 
ProcessProxy()45 ProcessProxy::ProcessProxy() : process_launched_(false), callback_set_(false) {
46   // Set pipes to initial, invalid value so we can easily know if a pipe was
47   // opened by us.
48   ClearFdPair(pt_pair_);
49 }
50 
Open(const base::CommandLine & cmdline,const std::string & user_id_hash,std::string * id)51 bool ProcessProxy::Open(const base::CommandLine& cmdline,
52                         const std::string& user_id_hash,
53                         std::string* id) {
54   if (process_launched_)
55     return false;
56 
57   if (!CreatePseudoTerminalPair(pt_pair_)) {
58     return false;
59   }
60 
61   process_launched_ =
62       LaunchProcess(cmdline, user_id_hash, pt_pair_[PT_SLAVE_FD], id);
63 
64   if (process_launched_) {
65     CloseFd(&pt_pair_[PT_SLAVE_FD]);
66   } else {
67     CloseFdPair(pt_pair_);
68   }
69   return process_launched_;
70 }
71 
StartWatchingOutput(const scoped_refptr<base::SingleThreadTaskRunner> & watcher_runner,const scoped_refptr<base::SequencedTaskRunner> & callback_runner,const OutputCallback & callback)72 bool ProcessProxy::StartWatchingOutput(
73     const scoped_refptr<base::SingleThreadTaskRunner>& watcher_runner,
74     const scoped_refptr<base::SequencedTaskRunner>& callback_runner,
75     const OutputCallback& callback) {
76   DCHECK(process_launched_);
77   CHECK(!output_watcher_.get());
78 
79   // We give ProcessOutputWatcher a copy of master to make life easier during
80   // tear down.
81   // TODO(tbarzic): improve fd managment.
82   int master_copy = HANDLE_EINTR(dup(pt_pair_[PT_MASTER_FD]));
83   if (master_copy < 0)
84     return false;
85 
86   callback_set_ = true;
87   callback_ = callback;
88   callback_runner_ = callback_runner;
89   watcher_runner_ = watcher_runner;
90 
91   // This object will delete itself once watching is stopped.
92   // It also takes ownership of the passed fds.
93   output_watcher_ = std::make_unique<ProcessOutputWatcher>(
94       master_copy, base::BindRepeating(&ProcessProxy::OnProcessOutput, this));
95 
96   watcher_runner_->PostTask(
97       FROM_HERE, base::BindOnce(&ProcessOutputWatcher::Start,
98                                 base::Unretained(output_watcher_.get())));
99 
100   return true;
101 }
102 
OnProcessOutput(ProcessOutputType type,const std::string & output,base::OnceClosure callback)103 void ProcessProxy::OnProcessOutput(ProcessOutputType type,
104                                    const std::string& output,
105                                    base::OnceClosure callback) {
106   if (!callback_runner_.get())
107     return;
108 
109   callback_runner_->PostTask(
110       FROM_HERE, base::BindOnce(&ProcessProxy::CallOnProcessOutputCallback,
111                                 this, type, output, std::move(callback)));
112 }
113 
CallOnProcessOutputCallback(ProcessOutputType type,const std::string & output,base::OnceClosure callback)114 void ProcessProxy::CallOnProcessOutputCallback(ProcessOutputType type,
115                                                const std::string& output,
116                                                base::OnceClosure callback) {
117   // We may receive some output even after Close was called (crosh process does
118   // not have to quit instantly, or there may be some trailing data left in
119   // output stream fds). In that case owner of the callback may be gone so we
120   // don't want to send it anything. |callback_set_| is reset when this gets
121   // closed.
122   if (callback_set_) {
123     output_ack_callback_ = std::move(callback);
124     callback_.Run(type, output);
125   }
126 }
127 
AckOutput()128 void ProcessProxy::AckOutput() {
129   if (output_ack_callback_) {
130     std::move(output_ack_callback_).Run();
131     output_ack_callback_.Reset();
132   }
133 }
134 
StopWatching()135 void ProcessProxy::StopWatching() {
136   if (!output_watcher_.get())
137     return;
138 
139   watcher_runner_->PostTask(
140       FROM_HERE,
141       base::BindOnce(&StopOutputWatcher, std::move(output_watcher_)));
142 }
143 
Close()144 void ProcessProxy::Close() {
145   if (!process_launched_)
146     return;
147 
148   process_launched_ = false;
149   callback_set_ = false;
150   callback_.Reset();
151   callback_runner_.reset();
152 
153   process_.Terminate(0, /* wait */ false);
154   base::EnsureProcessTerminated(std::move(process_));
155 
156   StopWatching();
157   CloseFdPair(pt_pair_);
158 }
159 
Write(const std::string & text)160 bool ProcessProxy::Write(const std::string& text) {
161   if (!process_launched_)
162     return false;
163 
164   // We don't want to write '\0' to the pipe.
165   size_t data_size = text.length() * sizeof(*text.c_str());
166   return base::WriteFileDescriptor(
167              pt_pair_[PT_MASTER_FD], text.c_str(), data_size);
168 }
169 
OnTerminalResize(int width,int height)170 bool ProcessProxy::OnTerminalResize(int width, int height) {
171   if (width < 0 || height < 0)
172     return false;
173 
174   winsize ws;
175   // Number of rows.
176   ws.ws_row = height;
177   // Number of columns.
178   ws.ws_col = width;
179 
180   return (HANDLE_EINTR(ioctl(pt_pair_[PT_MASTER_FD], TIOCSWINSZ, &ws)) != -1);
181 }
182 
~ProcessProxy()183 ProcessProxy::~ProcessProxy() {
184   Close();
185 }
186 
CreatePseudoTerminalPair(int * pt_pair)187 bool ProcessProxy::CreatePseudoTerminalPair(int *pt_pair) {
188   ClearFdPair(pt_pair);
189 
190   // Open Master.
191   pt_pair[PT_MASTER_FD] = HANDLE_EINTR(posix_openpt(O_RDWR | O_NOCTTY));
192   if (pt_pair[PT_MASTER_FD] == -1)
193     return false;
194 
195   if (grantpt(pt_pair_[PT_MASTER_FD]) != 0 ||
196       unlockpt(pt_pair_[PT_MASTER_FD]) != 0) {
197     CloseFd(&pt_pair[PT_MASTER_FD]);
198     return false;
199   }
200   char* slave_name = NULL;
201   // Per man page, slave_name must not be freed.
202   slave_name = ptsname(pt_pair_[PT_MASTER_FD]);
203   if (slave_name)
204     pt_pair_[PT_SLAVE_FD] = HANDLE_EINTR(open(slave_name, O_RDWR | O_NOCTTY));
205 
206   if (pt_pair_[PT_SLAVE_FD] == -1) {
207     CloseFdPair(pt_pair);
208     return false;
209   }
210 
211   // Get the current tty settings so we can overlay our updates.
212   struct termios termios;
213   if (tcgetattr(pt_pair_[PT_SLAVE_FD], &termios) != 0) {
214     CloseFdPair(pt_pair);
215     return false;
216   }
217 
218   // Set the IUTF8 bit on the tty as we should be UTF-8 clean everywhere.
219   termios.c_iflag |= IUTF8;
220   if (tcsetattr(pt_pair_[PT_SLAVE_FD], TCSANOW, &termios) != 0) {
221     CloseFdPair(pt_pair);
222     return false;
223   }
224 
225   return true;
226 }
227 
LaunchProcess(const base::CommandLine & cmdline,const std::string & user_id_hash,int slave_fd,std::string * id)228 bool ProcessProxy::LaunchProcess(const base::CommandLine& cmdline,
229                                  const std::string& user_id_hash,
230                                  int slave_fd,
231                                  std::string* id) {
232   base::LaunchOptions options;
233 
234   // Redirect crosh  process' output and input so we can read it.
235   options.fds_to_remap.push_back(std::make_pair(slave_fd, STDIN_FILENO));
236   options.fds_to_remap.push_back(std::make_pair(slave_fd, STDOUT_FILENO));
237   options.fds_to_remap.push_back(std::make_pair(slave_fd, STDERR_FILENO));
238   // Do not set NO_NEW_PRIVS on processes if the system is in dev-mode. This
239   // permits sudo in the crosh shell when in developer mode.
240   options.allow_new_privs = base::CommandLine::ForCurrentProcess()->
241       HasSwitch(chromeos::switches::kSystemInDevMode);
242   options.ctrl_terminal_fd = slave_fd;
243   // TODO(vapier): Ideally we'd just use the env settings from hterm itself.
244   // We can't let the user inject any env var they want, but we should be able
245   // to filter the $TERM value dynamically.
246   options.environment["TERM"] = "xterm-256color";
247   options.environment["CROS_USER_ID_HASH"] = user_id_hash;
248 
249   // Launch the process.
250   process_ = base::LaunchProcess(cmdline, options);
251 
252   // If the process is valid, generate a new random id.
253   // https://crbug.com/352465
254   if (process_.IsValid()) {
255     // We use the GUID API as it's trivial and works well enough.
256     // We prepend the pid to avoid random number collisions.  It should be a
257     // guaranteed unique id for the life of this Chrome session.
258     *id = std::to_string(process_.Pid()) + "-" + base::GenerateGUID();
259   }
260 
261   // TODO(rvargas) crbug/417532: This is somewhat wrong but the interface of
262   // Open vends pid_t* so ownership is quite vague anyway, and Process::Close
263   // doesn't do much in POSIX.
264   return process_.IsValid();
265 }
266 
CloseFdPair(int * pipe)267 void ProcessProxy::CloseFdPair(int* pipe) {
268   CloseFd(&(pipe[PT_MASTER_FD]));
269   CloseFd(&(pipe[PT_SLAVE_FD]));
270 }
271 
CloseFd(int * fd)272 void ProcessProxy::CloseFd(int* fd) {
273   if (*fd != base::kInvalidFd) {
274     if (IGNORE_EINTR(close(*fd)) != 0)
275       DPLOG(WARNING) << "close fd failed.";
276   }
277   *fd = base::kInvalidFd;
278 }
279 
ClearFdPair(int * pipe)280 void ProcessProxy::ClearFdPair(int* pipe) {
281   pipe[PT_MASTER_FD] = base::kInvalidFd;
282   pipe[PT_SLAVE_FD] = base::kInvalidFd;
283 }
284 
GetProcessHandleForTesting()285 base::ProcessHandle ProcessProxy::GetProcessHandleForTesting() {
286   return process_.IsValid() ? process_.Handle() : base::kNullProcessHandle;
287 }
288 
289 }  // namespace chromeos
290