libjpeg_turbo: Add linux include for android GN build.
[chromium-blink-merge.git] / chromeos / process_proxy / process_output_watcher.cc
blob906fe63440a3b84877fc65bb50553ced8720a723
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.
5 #include "chromeos/process_proxy/process_output_watcher.h"
7 #include <sys/ioctl.h>
8 #include <sys/select.h>
9 #include <unistd.h>
11 #include <algorithm>
12 #include <cstdio>
13 #include <cstring>
15 #include "base/logging.h"
16 #include "base/posix/eintr_wrapper.h"
17 #include "base/third_party/icu/icu_utf.h"
19 namespace {
21 void InitReadFdSet(int out_fd, int stop_fd, fd_set* set) {
22 FD_ZERO(set);
23 if (out_fd != -1)
24 FD_SET(out_fd, set);
25 FD_SET(stop_fd, set);
28 void CloseFd(int* fd) {
29 if (*fd >= 0) {
30 if (IGNORE_EINTR(close(*fd)) != 0)
31 DPLOG(WARNING) << "close fd " << *fd << " failed.";
33 *fd = -1;
36 // Gets byte size for a UTF8 character given it's leading byte. The character
37 // size is encoded as number of leading '1' bits in the character's leading
38 // byte. If the most significant bit is '0', the character is a valid ASCII
39 // and it's byte size is 1.
40 // The method returns 1 if the provided byte is invalid leading byte.
41 size_t UTF8SizeFromLeadingByte(uint8 leading_byte) {
42 size_t byte_count = 0;
43 uint8 mask = 1 << 7;
44 uint8 error_mask = 1 << (7 - CBU8_MAX_LENGTH);
45 while (leading_byte & mask) {
46 if (mask & error_mask)
47 return 1;
48 mask >>= 1;
49 ++byte_count;
51 return byte_count ? byte_count : 1;
54 } // namespace
56 namespace chromeos {
58 ProcessOutputWatcher::ProcessOutputWatcher(
59 int out_fd,
60 int stop_fd,
61 const ProcessOutputCallback& callback)
62 : read_buffer_size_(0),
63 out_fd_(out_fd),
64 stop_fd_(stop_fd),
65 on_read_callback_(callback) {
66 VerifyFileDescriptor(out_fd_);
67 VerifyFileDescriptor(stop_fd_);
68 max_fd_ = std::max(out_fd_, stop_fd_);
69 // We want to be sure we will be able to add 0 at the end of the input, so -1.
70 read_buffer_capacity_ = arraysize(read_buffer_) - 1;
73 void ProcessOutputWatcher::Start() {
74 WatchProcessOutput();
75 OnStop();
78 ProcessOutputWatcher::~ProcessOutputWatcher() {
79 CloseFd(&out_fd_);
80 CloseFd(&stop_fd_);
83 void ProcessOutputWatcher::WatchProcessOutput() {
84 while (true) {
85 // This has to be reset with every watch cycle.
86 fd_set rfds;
87 DCHECK_GE(stop_fd_, 0);
88 InitReadFdSet(out_fd_, stop_fd_, &rfds);
90 int select_result =
91 HANDLE_EINTR(select(max_fd_ + 1, &rfds, NULL, NULL, NULL));
93 if (select_result < 0) {
94 DPLOG(WARNING) << "select failed";
95 return;
98 // Check if we were stopped.
99 if (FD_ISSET(stop_fd_, &rfds)) {
100 return;
103 if (out_fd_ != -1 && FD_ISSET(out_fd_, &rfds)) {
104 ReadFromFd(PROCESS_OUTPUT_TYPE_OUT, &out_fd_);
109 void ProcessOutputWatcher::VerifyFileDescriptor(int fd) {
110 CHECK_LE(0, fd);
111 CHECK_GT(FD_SETSIZE, fd);
114 void ProcessOutputWatcher::ReadFromFd(ProcessOutputType type, int* fd) {
115 // We don't want to necessary read pipe until it is empty so we don't starve
116 // other streams in case data is written faster than we read it. If there is
117 // more than read_buffer_size_ bytes in pipe, it will be read in the next
118 // iteration.
119 DCHECK_GT(read_buffer_capacity_, read_buffer_size_);
120 ssize_t bytes_read =
121 HANDLE_EINTR(read(*fd,
122 &read_buffer_[read_buffer_size_],
123 read_buffer_capacity_ - read_buffer_size_));
124 if (bytes_read < 0)
125 DPLOG(WARNING) << "read from buffer failed";
127 if (bytes_read > 0)
128 ReportOutput(type, bytes_read);
130 // If there is nothing on the output the watched process has exited (slave end
131 // of pty is closed).
132 if (bytes_read <= 0) {
133 // Slave pseudo terminal has been closed, we won't need master fd anymore.
134 CloseFd(fd);
136 // We have lost contact with the process, so report it.
137 on_read_callback_.Run(PROCESS_OUTPUT_TYPE_EXIT, "");
141 size_t ProcessOutputWatcher::OutputSizeWithoutIncompleteUTF8() {
142 // Find the last non-trailing character byte. This byte should be used to
143 // infer the last UTF8 character length.
144 int last_lead_byte = read_buffer_size_ - 1;
145 while (true) {
146 // If the series of trailing bytes is too long, something's not right.
147 // Report the whole output, without waiting for further character bytes.
148 if (read_buffer_size_ - last_lead_byte > CBU8_MAX_LENGTH)
149 return read_buffer_size_;
151 // If there are trailing characters, there must be a leading one in the
152 // buffer for a valid UTF8 character. Getting past the buffer begining
153 // signals something's wrong, or the buffer is empty. In both cases return
154 // the whole current buffer.
155 if (last_lead_byte < 0)
156 return read_buffer_size_;
158 // Found the starting character byte; stop searching.
159 if (!CBU8_IS_TRAIL(read_buffer_[last_lead_byte]))
160 break;
162 --last_lead_byte;
165 size_t last_length = UTF8SizeFromLeadingByte(read_buffer_[last_lead_byte]);
167 // Note that if |last_length| == 0 or
168 // |last_length| + |last_read_byte| < |read_buffer_size_|, the string is
169 // invalid UTF8. In that case, send the whole read buffer to the observer
170 // immediately, just as if there is no trailing incomplete UTF8 bytes.
171 if (!last_length || last_length + last_lead_byte <= read_buffer_size_)
172 return read_buffer_size_;
174 return last_lead_byte;
177 void ProcessOutputWatcher::ReportOutput(ProcessOutputType type,
178 size_t new_bytes_count) {
179 read_buffer_size_ += new_bytes_count;
180 size_t output_to_report = OutputSizeWithoutIncompleteUTF8();
182 on_read_callback_.Run(type, std::string(read_buffer_, output_to_report));
184 // Move the bytes that were left behind to the beginning of the buffer and
185 // update the buffer size accordingly.
186 if (output_to_report < read_buffer_size_) {
187 for (size_t i = output_to_report; i < read_buffer_size_; ++i) {
188 read_buffer_[i - output_to_report] = read_buffer_[i];
191 read_buffer_size_ -= output_to_report;
194 void ProcessOutputWatcher::OnStop() {
195 delete this;
198 } // namespace chromeos