#include "chromeos/process_proxy/process_proxy.h"
#include <stddef.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <utility>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/file_descriptor_posix.h"
#include "base/files/file_util.h"
#include "base/functional/bind.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/strings/string_number_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/uuid.h"
#include "third_party/cros_system_api/switches/chrome_switches.h"
namespace {
enum PseudoTerminalFd {
PT_MASTER_FD,
PT_SLAVE_FD
};
void StopOutputWatcher(
std::unique_ptr<chromeos::ProcessOutputWatcher> watcher) {
}
}
namespace chromeos {
ProcessProxy::ProcessProxy() : process_launched_(false), callback_set_(false) {
ClearFdPair(pt_pair_);
}
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_) {
CloseFd(&pt_pair_[PT_SLAVE_FD]);
} else {
CloseFdPair(pt_pair_);
}
return process_launched_;
}
bool ProcessProxy::StartWatchingOutput(
const scoped_refptr<base::SingleThreadTaskRunner>& watcher_runner,
const scoped_refptr<base::SequencedTaskRunner>& callback_runner,
const OutputCallback& callback) {
DCHECK(process_launched_);
CHECK(!output_watcher_.get());
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;
output_watcher_ = std::make_unique<ProcessOutputWatcher>(
master_copy, base::BindRepeating(&ProcessProxy::OnProcessOutput, this));
watcher_runner_->PostTask(
FROM_HERE, base::BindOnce(&ProcessOutputWatcher::Start,
base::Unretained(output_watcher_.get())));
return true;
}
void ProcessProxy::OnProcessOutput(ProcessOutputType type,
const std::string& output) {
if (!callback_runner_.get())
return;
callback_runner_->PostTask(
FROM_HERE, base::BindOnce(&ProcessProxy::CallOnProcessOutputCallback,
this, type, output));
}
void ProcessProxy::CallOnProcessOutputCallback(ProcessOutputType type,
const std::string& output) {
if (callback_set_) {
callback_.Run(type, output);
}
}
void ProcessProxy::AckOutput() {
watcher_runner_->PostTask(FROM_HERE,
base::BindOnce(&ProcessOutputWatcher::AckOutput,
output_watcher_->GetWeakPtr()));
}
void ProcessProxy::StopWatching() {
if (!output_watcher_.get())
return;
watcher_runner_->PostTask(
FROM_HERE,
base::BindOnce(&StopOutputWatcher, std::move(output_watcher_)));
}
void ProcessProxy::Close() {
if (!process_launched_)
return;
process_launched_ = false;
callback_set_ = false;
callback_.Reset();
callback_runner_.reset();
process_.Terminate(0, false);
base::EnsureProcessTerminated(std::move(process_));
StopWatching();
CloseFdPair(pt_pair_);
}
void ProcessProxy::Write(const std::string& text,
base::OnceCallback<void(bool)> callback) {
if (!process_launched_)
return std::move(callback).Run(false);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
base::BindOnce(
[](int fd, const std::string& text) {
return base::WriteFileDescriptor(fd, text);
},
pt_pair_[PT_MASTER_FD], text),
std::move(callback));
}
bool ProcessProxy::OnTerminalResize(int width, int height) {
if (width < 0 || height < 0)
return false;
winsize ws;
ws.ws_row = height;
ws.ws_col = width;
return (HANDLE_EINTR(ioctl(pt_pair_[PT_MASTER_FD], TIOCSWINSZ, &ws)) != -1);
}
ProcessProxy::~ProcessProxy() {
Close();
}
bool ProcessProxy::CreatePseudoTerminalPair(int *pt_pair) {
ClearFdPair(pt_pair);
UNSAFE_TODO(pt_pair[PT_MASTER_FD]) =
HANDLE_EINTR(posix_openpt(O_RDWR | O_NOCTTY));
if (UNSAFE_TODO(pt_pair[PT_MASTER_FD]) == -1) {
return false;
}
if (grantpt(pt_pair_[PT_MASTER_FD]) != 0 ||
unlockpt(pt_pair_[PT_MASTER_FD]) != 0) {
CloseFd(&UNSAFE_TODO(pt_pair[PT_MASTER_FD]));
return false;
}
char* slave_name = NULL;
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) {
CloseFdPair(pt_pair);
return false;
}
struct termios termios;
if (tcgetattr(pt_pair_[PT_SLAVE_FD], &termios) != 0) {
CloseFdPair(pt_pair);
return false;
}
termios.c_iflag |= IUTF8;
if (tcsetattr(pt_pair_[PT_SLAVE_FD], TCSANOW, &termios) != 0) {
CloseFdPair(pt_pair);
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;
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));
options.allow_new_privs = base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kSystemInDevMode);
options.ctrl_terminal_fd = slave_fd;
options.environment["TERM"] = "xterm-256color";
options.environment["CROS_USER_ID_HASH"] = user_id_hash;
process_ = base::LaunchProcess(cmdline, options);
if (process_.IsValid()) {
*id = base::NumberToString(process_.Pid()) + "-" +
base::Uuid::GenerateRandomV4().AsLowercaseString();
}
return process_.IsValid();
}
void ProcessProxy::CloseFdPair(int* pipe) {
CloseFd(&(UNSAFE_TODO(pipe[PT_MASTER_FD])));
CloseFd(&(UNSAFE_TODO(pipe[PT_SLAVE_FD])));
}
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) {
UNSAFE_TODO(pipe[PT_MASTER_FD]) = base::kInvalidFd;
UNSAFE_TODO(pipe[PT_SLAVE_FD]) = base::kInvalidFd;
}
const base::Process* ProcessProxy::GetProcessForTesting() {
return &process_;
}
}