blob: 87d1bc47ce46541684e9154f3b5806f47dcfa340 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/process_proxy/process_proxy.h"
#include <stddef.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_descriptor_posix.h"
#include "base/files/file_util.h"
#include "base/guid.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "third_party/cros_system_api/switches/chrome_switches.h"
namespace {
enum PseudoTerminalFd {
void StopOutputWatcher(
std::unique_ptr<chromeos::ProcessOutputWatcher> watcher) {
// Just deleting |watcher| if sufficient to stop it.
} // namespace
namespace chromeos {
ProcessProxy::ProcessProxy() : process_launched_(false), callback_set_(false) {
// Set pipes to initial, invalid value so we can easily know if a pipe was
// opened by us.
bool ProcessProxy::Open(const base::CommandLine& cmdline,
const std::string& user_id_hash,
std::string* id) {
if (process_launched_)
return false;
if (!CreatePseudoTerminalPair(pt_pair_)) {
return false;
process_launched_ =
LaunchProcess(cmdline, user_id_hash, pt_pair_[PT_SLAVE_FD], id);
if (process_launched_) {
} else {
return process_launched_;
bool ProcessProxy::StartWatchingOutput(
const scoped_refptr<base::SingleThreadTaskRunner>& watcher_runner,
const scoped_refptr<base::SequencedTaskRunner>& callback_runner,
const OutputCallback& callback) {
// We give ProcessOutputWatcher a copy of master to make life easier during
// tear down.
// TODO(tbarzic): improve fd managment.
int master_copy = HANDLE_EINTR(dup(pt_pair_[PT_MASTER_FD]));
if (master_copy < 0)
return false;
callback_set_ = true;
callback_ = callback;
callback_runner_ = callback_runner;
watcher_runner_ = watcher_runner;
// This object will delete itself once watching is stopped.
// It also takes ownership of the passed fds.
output_watcher_.reset(new ProcessOutputWatcher(
master_copy, base::Bind(&ProcessProxy::OnProcessOutput, this)));
FROM_HERE, base::BindOnce(&ProcessOutputWatcher::Start,
return true;
void ProcessProxy::OnProcessOutput(ProcessOutputType type,
const std::string& output,
const base::Closure& callback) {
if (!callback_runner_.get())
FROM_HERE, base::BindOnce(&ProcessProxy::CallOnProcessOutputCallback,
this, type, output, callback));
void ProcessProxy::CallOnProcessOutputCallback(ProcessOutputType type,
const std::string& output,
const base::Closure& callback) {
// We may receive some output even after Close was called (crosh process does
// not have to quit instantly, or there may be some trailing data left in
// output stream fds). In that case owner of the callback may be gone so we
// don't want to send it anything. |callback_set_| is reset when this gets
// closed.
if (callback_set_) {
output_ack_callback_ = callback;
callback_.Run(type, output);
void ProcessProxy::AckOutput() {
if (!output_ack_callback_.is_null()) {
void ProcessProxy::StopWatching() {
if (!output_watcher_.get())
base::BindOnce(&StopOutputWatcher, std::move(output_watcher_)));
void ProcessProxy::Close() {
if (!process_launched_)
process_launched_ = false;
callback_set_ = false;
callback_runner_ = NULL;
process_.Terminate(0, /* wait */ false);
bool ProcessProxy::Write(const std::string& text) {
if (!process_launched_)
return false;
// We don't want to write '\0' to the pipe.
size_t data_size = text.length() * sizeof(*text.c_str());
return base::WriteFileDescriptor(
pt_pair_[PT_MASTER_FD], text.c_str(), data_size);
bool ProcessProxy::OnTerminalResize(int width, int height) {
if (width < 0 || height < 0)
return false;
winsize ws;
// Number of rows.
ws.ws_row = height;
// Number of columns.
ws.ws_col = width;
return (HANDLE_EINTR(ioctl(pt_pair_[PT_MASTER_FD], TIOCSWINSZ, &ws)) != -1);
ProcessProxy::~ProcessProxy() {
bool ProcessProxy::CreatePseudoTerminalPair(int *pt_pair) {
// Open Master.
pt_pair[PT_MASTER_FD] = HANDLE_EINTR(posix_openpt(O_RDWR | O_NOCTTY));
if (pt_pair[PT_MASTER_FD] == -1)
return false;
if (grantpt(pt_pair_[PT_MASTER_FD]) != 0 ||
unlockpt(pt_pair_[PT_MASTER_FD]) != 0) {
return false;
char* slave_name = NULL;
// Per man page, slave_name must not be freed.
slave_name = ptsname(pt_pair_[PT_MASTER_FD]);
if (slave_name)
pt_pair_[PT_SLAVE_FD] = HANDLE_EINTR(open(slave_name, O_RDWR | O_NOCTTY));
if (pt_pair_[PT_SLAVE_FD] == -1) {
return false;
// Get the current tty settings so we can overlay our updates.
struct termios termios;
if (tcgetattr(pt_pair_[PT_SLAVE_FD], &termios) != 0) {
return false;
// Set the IUTF8 bit on the tty as we should be UTF-8 clean everywhere.
termios.c_iflag |= IUTF8;
if (tcsetattr(pt_pair_[PT_SLAVE_FD], TCSANOW, &termios) != 0) {
return false;
return true;
bool ProcessProxy::LaunchProcess(const base::CommandLine& cmdline,
const std::string& user_id_hash,
int slave_fd,
std::string* id) {
base::LaunchOptions options;
// Redirect crosh process' output and input so we can read it.
options.fds_to_remap.push_back(std::make_pair(slave_fd, STDIN_FILENO));
options.fds_to_remap.push_back(std::make_pair(slave_fd, STDOUT_FILENO));
options.fds_to_remap.push_back(std::make_pair(slave_fd, STDERR_FILENO));
// Do not set NO_NEW_PRIVS on processes if the system is in dev-mode. This
// permits sudo in the crosh shell when in developer mode.
options.allow_new_privs = base::CommandLine::ForCurrentProcess()->
options.ctrl_terminal_fd = slave_fd;
// TODO(vapier): Ideally we'd just use the env settings from hterm itself.
// We can't let the user inject any env var they want, but we should be able
// to filter the $TERM value dynamically.
options.environ["TERM"] = "xterm-256color";
options.environ["CROS_USER_ID_HASH"] = user_id_hash;
// Launch the process.
process_ = base::LaunchProcess(cmdline, options);
// If the process is valid, generate a new random id.
if (process_.IsValid()) {
// We use the GUID API as it's trivial and works well enough.
// We prepend the pid to avoid random number collisions. It should be a
// guaranteed unique id for the life of this Chrome session.
*id = std::to_string(process_.Pid()) + "-" + base::GenerateGUID();
// TODO(rvargas) crbug/417532: This is somewhat wrong but the interface of
// Open vends pid_t* so ownership is quite vague anyway, and Process::Close
// doesn't do much in POSIX.
return process_.IsValid();
void ProcessProxy::CloseFdPair(int* pipe) {
void ProcessProxy::CloseFd(int* fd) {
if (*fd != base::kInvalidFd) {
if (IGNORE_EINTR(close(*fd)) != 0)
DPLOG(WARNING) << "close fd failed.";
*fd = base::kInvalidFd;
void ProcessProxy::ClearFdPair(int* pipe) {
pipe[PT_MASTER_FD] = base::kInvalidFd;
pipe[PT_SLAVE_FD] = base::kInvalidFd;
base::ProcessHandle ProcessProxy::GetProcessHandleForTesting() {
return process_.IsValid() ? process_.Handle() : base::kNullProcessHandle;
} // namespace chromeos