// 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_output_watcher.h" #include <algorithm> #include <cstdio> #include <cstring> #include <sys/ioctl.h> #include <sys/select.h> #include <unistd.h> #include "base/logging.h" #include "base/posix/eintr_wrapper.h" namespace { void InitReadFdSet(int out_fd, int stop_fd, fd_set* set) { FD_ZERO(set); if (out_fd != -1) FD_SET(out_fd, set); FD_SET(stop_fd, set); } void CloseFd(int* fd) { if (*fd >= 0) { if (IGNORE_EINTR(close(*fd)) != 0) DPLOG(WARNING) << "close fd " << *fd << " failed."; } *fd = -1; } } // namespace namespace chromeos { ProcessOutputWatcher::ProcessOutputWatcher(int out_fd, int stop_fd, const ProcessOutputCallback& callback) : out_fd_(out_fd), stop_fd_(stop_fd), on_read_callback_(callback) { VerifyFileDescriptor(out_fd_); VerifyFileDescriptor(stop_fd_); max_fd_ = std::max(out_fd_, stop_fd_); // We want to be sure we will be able to add 0 at the end of the input, so -1. read_buffer_size_ = arraysize(read_buffer_) - 1; } void ProcessOutputWatcher::Start() { WatchProcessOutput(); OnStop(); } ProcessOutputWatcher::~ProcessOutputWatcher() { CloseFd(&out_fd_); CloseFd(&stop_fd_); } void ProcessOutputWatcher::WatchProcessOutput() { while (true) { // This has to be reset with every watch cycle. fd_set rfds; DCHECK(stop_fd_ >= 0); InitReadFdSet(out_fd_, stop_fd_, &rfds); int select_result = HANDLE_EINTR(select(max_fd_ + 1, &rfds, NULL, NULL, NULL)); if (select_result < 0) { DPLOG(WARNING) << "select failed"; return; } // Check if we were stopped. if (FD_ISSET(stop_fd_, &rfds)) { return; } if (out_fd_ != -1 && FD_ISSET(out_fd_, &rfds)) { ReadFromFd(PROCESS_OUTPUT_TYPE_OUT, &out_fd_); } } } void ProcessOutputWatcher::VerifyFileDescriptor(int fd) { CHECK_LE(0, fd); CHECK_GT(FD_SETSIZE, fd); } void ProcessOutputWatcher::ReadFromFd(ProcessOutputType type, int* fd) { // We don't want to necessary read pipe until it is empty so we don't starve // other streams in case data is written faster than we read it. If there is // more than read_buffer_size_ bytes in pipe, it will be read in the next // iteration. ssize_t bytes_read = HANDLE_EINTR(read(*fd, read_buffer_, read_buffer_size_)); if (bytes_read < 0) DPLOG(WARNING) << "read from buffer failed"; if (bytes_read > 0) { on_read_callback_.Run(type, std::string(read_buffer_, bytes_read)); } // If there is nothing on the output the watched process has exited (slave end // of pty is closed). if (bytes_read <= 0) { // Slave pseudo terminal has been closed, we won't need master fd anymore. CloseFd(fd); // We have lost contact with the process, so report it. on_read_callback_.Run(PROCESS_OUTPUT_TYPE_EXIT, ""); } } void ProcessOutputWatcher::OnStop() { delete this; } } // namespace chromeos