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_output_watcher.h" 6 7 #include <sys/ioctl.h> 8 #include <sys/select.h> 9 #include <unistd.h> 10 11 #include <algorithm> 12 #include <cstdio> 13 #include <cstring> 14 15 #include "base/logging.h" 16 #include "base/posix/eintr_wrapper.h" 17 #include "base/third_party/icu/icu_utf.h" 18 19 namespace { 20 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); 26 } 27 28 void CloseFd(int* fd) { 29 if (*fd >= 0) { 30 if (IGNORE_EINTR(close(*fd)) != 0) 31 DPLOG(WARNING) << "close fd " << *fd << " failed."; 32 } 33 *fd = -1; 34 } 35 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; 50 } 51 return byte_count ? byte_count : 1; 52 } 53 54 } // namespace 55 56 namespace chromeos { 57 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; 71 } 72 73 void ProcessOutputWatcher::Start() { 74 WatchProcessOutput(); 75 OnStop(); 76 } 77 78 ProcessOutputWatcher::~ProcessOutputWatcher() { 79 CloseFd(&out_fd_); 80 CloseFd(&stop_fd_); 81 } 82 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); 89 90 int select_result = 91 HANDLE_EINTR(select(max_fd_ + 1, &rfds, NULL, NULL, NULL)); 92 93 if (select_result < 0) { 94 DPLOG(WARNING) << "select failed"; 95 return; 96 } 97 98 // Check if we were stopped. 99 if (FD_ISSET(stop_fd_, &rfds)) { 100 return; 101 } 102 103 if (out_fd_ != -1 && FD_ISSET(out_fd_, &rfds)) { 104 ReadFromFd(PROCESS_OUTPUT_TYPE_OUT, &out_fd_); 105 } 106 } 107 } 108 109 void ProcessOutputWatcher::VerifyFileDescriptor(int fd) { 110 CHECK_LE(0, fd); 111 CHECK_GT(FD_SETSIZE, fd); 112 } 113 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"; 126 127 if (bytes_read > 0) 128 ReportOutput(type, bytes_read); 129 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); 135 136 // We have lost contact with the process, so report it. 137 on_read_callback_.Run(PROCESS_OUTPUT_TYPE_EXIT, ""); 138 } 139 } 140 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_; 150 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_; 157 158 // Found the starting character byte; stop searching. 159 if (!CBU8_IS_TRAIL(read_buffer_[last_lead_byte])) 160 break; 161 162 --last_lead_byte; 163 } 164 165 size_t last_length = UTF8SizeFromLeadingByte(read_buffer_[last_lead_byte]); 166 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_; 173 174 return last_lead_byte; 175 } 176 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(); 181 182 on_read_callback_.Run(type, std::string(read_buffer_, output_to_report)); 183 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]; 189 } 190 } 191 read_buffer_size_ -= output_to_report; 192 } 193 194 void ProcessOutputWatcher::OnStop() { 195 delete this; 196 } 197 198 } // namespace chromeos 199