summaryrefslogtreecommitdiffstats
path: root/chromeos/process_proxy/process_output_watcher.cc
blob: 906fe63440a3b84877fc65bb50553ced8720a723 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// 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 <sys/ioctl.h>
#include <sys/select.h>
#include <unistd.h>

#include <algorithm>
#include <cstdio>
#include <cstring>

#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/third_party/icu/icu_utf.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;
}

// Gets byte size for a UTF8 character given it's leading byte. The character
// size is encoded as number of leading '1' bits in the character's leading
// byte. If the most significant bit is '0', the character is a valid ASCII
// and it's byte size is 1.
// The method returns 1 if the provided byte is invalid leading byte.
size_t UTF8SizeFromLeadingByte(uint8 leading_byte) {
  size_t byte_count = 0;
  uint8 mask = 1 << 7;
  uint8 error_mask = 1 << (7 - CBU8_MAX_LENGTH);
  while (leading_byte & mask) {
    if (mask & error_mask)
      return 1;
    mask >>= 1;
    ++byte_count;
  }
  return byte_count ? byte_count : 1;
}

}  // namespace

namespace chromeos {

ProcessOutputWatcher::ProcessOutputWatcher(
    int out_fd,
    int stop_fd,
    const ProcessOutputCallback& callback)
    : read_buffer_size_(0),
      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_capacity_ = 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_GE(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.
  DCHECK_GT(read_buffer_capacity_, read_buffer_size_);
  ssize_t bytes_read =
      HANDLE_EINTR(read(*fd,
                        &read_buffer_[read_buffer_size_],
                        read_buffer_capacity_ - read_buffer_size_));
  if (bytes_read < 0)
    DPLOG(WARNING) << "read from buffer failed";

  if (bytes_read > 0)
    ReportOutput(type, 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, "");
  }
}

size_t ProcessOutputWatcher::OutputSizeWithoutIncompleteUTF8() {
  // Find the last non-trailing character byte. This byte should be used to
  // infer the last UTF8 character length.
  int last_lead_byte = read_buffer_size_ - 1;
  while (true) {
    // If the series of trailing bytes is too long, something's not right.
    // Report the whole output, without waiting for further character bytes.
    if (read_buffer_size_ - last_lead_byte > CBU8_MAX_LENGTH)
      return read_buffer_size_;

    // If there are trailing characters, there must be a leading one in the
    // buffer for a valid UTF8 character. Getting past the buffer begining
    // signals something's wrong, or the buffer is empty. In both cases return
    // the whole current buffer.
    if (last_lead_byte < 0)
      return read_buffer_size_;

    // Found the starting character byte; stop searching.
    if (!CBU8_IS_TRAIL(read_buffer_[last_lead_byte]))
      break;

    --last_lead_byte;
  }

  size_t last_length = UTF8SizeFromLeadingByte(read_buffer_[last_lead_byte]);

  // Note that if |last_length| == 0 or
  // |last_length| + |last_read_byte| < |read_buffer_size_|, the string is
  // invalid UTF8. In that case, send the whole read buffer to the observer
  // immediately, just as if there is no trailing incomplete UTF8 bytes.
  if (!last_length || last_length + last_lead_byte <= read_buffer_size_)
    return read_buffer_size_;

  return last_lead_byte;
}

void ProcessOutputWatcher::ReportOutput(ProcessOutputType type,
                                        size_t new_bytes_count) {
  read_buffer_size_ += new_bytes_count;
  size_t output_to_report = OutputSizeWithoutIncompleteUTF8();

  on_read_callback_.Run(type, std::string(read_buffer_, output_to_report));

  // Move the bytes that were left behind to the beginning of the buffer and
  // update the buffer size accordingly.
  if (output_to_report < read_buffer_size_) {
    for (size_t i = output_to_report; i < read_buffer_size_; ++i) {
      read_buffer_[i - output_to_report] = read_buffer_[i];
    }
  }
  read_buffer_size_ -= output_to_report;
}

void ProcessOutputWatcher::OnStop() {
  delete this;
}

}  // namespace chromeos