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 22namespace { 23 24enum PipeEnd { 25 PIPE_END_READ, 26 PIPE_END_WRITE 27}; 28 29enum PseudoTerminalFd { 30 PT_MASTER_FD, 31 PT_SLAVE_FD 32}; 33 34const int kInvalidFd = -1; 35 36} // namespace 37 38namespace chromeos { 39 40ProcessProxy::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 48bool 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 69bool 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 108void 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 119void 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 130bool 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 140void 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 157bool 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 169bool 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 182ProcessProxy::~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 192bool 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 219bool 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 240void ProcessProxy::CloseAllFdPairs() { 241 CloseFdPair(pt_pair_); 242 CloseFdPair(shutdown_pipe_); 243} 244 245void ProcessProxy::CloseFdPair(int* pipe) { 246 CloseFd(&(pipe[PIPE_END_READ])); 247 CloseFd(&(pipe[PIPE_END_WRITE])); 248} 249 250void 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 258void ProcessProxy::ClearAllFdPairs() { 259 ClearFdPair(pt_pair_); 260 ClearFdPair(shutdown_pipe_); 261} 262 263void ProcessProxy::ClearFdPair(int* pipe) { 264 pipe[PIPE_END_READ] = kInvalidFd; 265 pipe[PIPE_END_WRITE] = kInvalidFd; 266} 267 268} // namespace chromeos 269