Home | History | Annotate | Download | only in process_proxy
      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/files/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 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    240 void ProcessProxy::CloseAllFdPairs() {
    241   CloseFdPair(pt_pair_);
    242   CloseFdPair(shutdown_pipe_);
    243 }
    244 
    245 void ProcessProxy::CloseFdPair(int* pipe) {
    246   CloseFd(&(pipe[PIPE_END_READ]));
    247   CloseFd(&(pipe[PIPE_END_WRITE]));
    248 }
    249 
    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 
    258 void ProcessProxy::ClearAllFdPairs() {
    259   ClearFdPair(pt_pair_);
    260   ClearFdPair(shutdown_pipe_);
    261 }
    262 
    263 void ProcessProxy::ClearFdPair(int* pipe) {
    264   pipe[PIPE_END_READ] = kInvalidFd;
    265   pipe[PIPE_END_WRITE] = kInvalidFd;
    266 }
    267 
    268 }  // namespace chromeos
    269