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