1 // +------------------------------------------------------------------+
2 // | ____ _ _ __ __ _ __ |
3 // | / ___| |__ ___ ___| | __ | \/ | |/ / |
4 // | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
5 // | | |___| | | | __/ (__| < | | | | . \ |
6 // | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
7 // | |
8 // | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
9 // +------------------------------------------------------------------+
10 //
11 // This file is part of Check_MK.
12 // The official homepage is at http://mathias-kettner.de/check_mk.
13 //
14 // check_mk is free software; you can redistribute it and/or modify it
15 // under the terms of the GNU General Public License as published by
16 // the Free Software Foundation in version 2. check_mk is distributed
17 // in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
18 // out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
19 // PARTICULAR PURPOSE. See the GNU General Public License for more de-
20 // ails. You should have received a copy of the GNU General Public
21 // License along with GNU Make; see the file COPYING. If not, write
22 // to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
23 // Boston, MA 02110-1301 USA.
24
25 #include "InputBuffer.h"
26 #include <ctype.h>
27 #include <stdint.h>
28 #include <string.h>
29 #include <sys/select.h>
30 #include <sys/time.h>
31 #include <unistd.h>
32 #include "logger.h"
33
34 using std::list;
35 using std::string;
36
37 extern int g_query_timeout_msec;
38 extern int g_idle_timeout_msec;
39
40 namespace {
41 const size_t initial_buffer_size = 4096;
42 // TODO(sp): Make this configurable?
43 const size_t maximum_buffer_size = 500 * 1024 * 1024;
44
45 const int read_timeout_usec = 200000;
46
timeout_reached(const struct timeval * start,int timeout_ms)47 bool timeout_reached(const struct timeval *start, int timeout_ms) {
48 if (timeout_ms == 0) {
49 return false; // timeout disabled
50 }
51
52 struct timeval now;
53 gettimeofday(&now, nullptr);
54 int64_t elapsed = (now.tv_sec - start->tv_sec) * 1000000;
55 elapsed += now.tv_usec - start->tv_usec;
56 return elapsed / 1000 >= timeout_ms;
57 }
58 } // namespace
59
InputBuffer(int fd,const int * termination_flag)60 InputBuffer::InputBuffer(int fd, const int *termination_flag)
61 : _fd(fd)
62 , _termination_flag(termination_flag)
63 , _readahead_buffer(initial_buffer_size) {
64 _read_index = 0; // points to data not yet processed
65 _write_index = 0; // points to end of data in buffer
66 }
67
68 // read in data enough for one complete request (and maybe more).
readRequest()69 InputBuffer::Result InputBuffer::readRequest() {
70 // Remember when we started waiting for a request. This
71 // is needed for the idle_timeout. A connection may
72 // not be idle longer than that value.
73 struct timeval start_of_idle; // Waiting for the first line
74 gettimeofday(&start_of_idle, nullptr);
75
76 // Remember if we have read some part of the query. During
77 // a query the timeout is another (short) than between
78 // queries.
79 bool query_started = false;
80
81 // _read_index points to the place in the buffer, where the
82 // next valid data begins. This data ends at _write_index.
83 // That data might have been read while reading the previous
84 // request.
85
86 // r is used to find the end of the line
87 size_t r = _read_index;
88
89 while (true) {
90 // Try to find end of the current line in buffer
91 while (r < _write_index && _readahead_buffer[r] != '\n') {
92 r++; // now r is at end of data or at '\n'
93 }
94
95 // If we cannot find the end of line in the data
96 // already read, then we need to read new data from
97 // the client.
98 if (r == _write_index) {
99 // Is there still space left in the buffer => read in
100 // further data into the buffer.
101 if (_write_index < _readahead_buffer.capacity()) {
102 Result rd =
103 readData(); // tries to read in further data into buffer
104 if (rd == Result::timeout) {
105 if (query_started) {
106 logger(LG_INFO,
107 "Timeout of %d ms exceeded while reading query",
108 g_query_timeout_msec);
109 return Result::timeout;
110 }
111 // Check if we exceeded the maximum time between two queries
112 if (timeout_reached(&start_of_idle, g_idle_timeout_msec)) {
113 logger(LG_INFO,
114 "Idle timeout of %d ms exceeded. Going to close "
115 "connection.",
116 g_idle_timeout_msec);
117 return Result::timeout;
118 }
119 }
120
121 // Are we at end of file? That is only an error, if we've
122 // read an incomplete line. If the last thing we read was
123 // a linefeed, then we consider the current request to
124 // be valid, if it is not empty.
125 else if (
126 rd == Result::eof &&
127 r == _read_index /* currently at beginning of a line */) {
128 if (_request_lines.empty()) {
129 return Result::eof; // empty request -> no request
130 }
131 // socket has been closed but request is complete
132 return Result::request_read;
133 // the current state is now:
134 // _read_index == r == _write_index => buffer is empty
135 // that way, if the main program tries to read the
136 // next request, it will get an IB_UNEXPECTED_EOF
137
138 }
139 // if we are *not* at an end of line while reading
140 // a request, we got an invalid request.
141 else if (rd == Result::eof) {
142 return Result::unexpected_eof;
143
144 // Other status codes
145 } else if (rd == Result::should_terminate) {
146 return rd;
147 }
148 }
149 // OK. So no space is left in the buffer. But maybe at the
150 // *beginning* of the buffer is space left again. This is
151 // very probable if _write_index == _readahead_buffer.capacity().
152 // Most
153 // of the buffer's content is already processed. So we simply
154 // shift the yet unprocessed data to the very left of the buffer.
155 else if (_read_index > 0) {
156 int shift_by = _read_index; // distance to beginning of buffer
157 int size =
158 _write_index - _read_index; // amount of data to shift
159 memmove(&_readahead_buffer[0], &_readahead_buffer[_read_index],
160 size);
161 _read_index = 0; // unread data is now at the beginning
162 _write_index -= shift_by; // write pointer shifted to the left
163 r -= shift_by; // current scan position also shift left
164 // continue -> still no data in buffer, but it will
165 // be read, as now is space
166 }
167 // buffer is full, but still no end of line found
168 else {
169 size_t new_capacity = _readahead_buffer.capacity() * 2;
170 if (new_capacity > maximum_buffer_size) {
171 logger(LG_INFO,
172 "Error: maximum length of request line exceeded");
173 return Result::line_too_long;
174 }
175 _readahead_buffer.resize(new_capacity);
176 }
177 } else // end of line found
178 {
179 if (_read_index == r) { // empty line found => end of request
180 _read_index = r + 1;
181 // Was ist, wenn noch keine korrekte Zeile gelesen wurde?
182 if (_request_lines.empty()) {
183 return Result::empty_request;
184 }
185 return Result::request_read;
186
187 } // non-empty line: belongs to current request
188 int length = r - _read_index;
189 for (size_t end = r; end > _read_index &&
190 (isspace(_readahead_buffer[--end]) != 0);) {
191 length--;
192 }
193 if (length > 0) {
194 _request_lines.push_back(
195 string(&_readahead_buffer[_read_index], length));
196 } else {
197 logger(LG_INFO,
198 "Warning ignoring line containing only whitespace");
199 }
200 query_started = true;
201 _read_index = r + 1;
202 r = _read_index;
203 }
204 }
205 }
206
207 // read at least *some* data. Return IB_TIMEOUT if that
208 // lasts more than g_query_timeout_msec msecs.
readData()209 InputBuffer::Result InputBuffer::readData() {
210 struct timeval start;
211 gettimeofday(&start, nullptr);
212
213 struct timeval tv;
214 while (*_termination_flag == 0) {
215 if (timeout_reached(&start, g_query_timeout_msec)) {
216 return Result::timeout;
217 }
218
219 tv.tv_sec = read_timeout_usec / 1000000;
220 tv.tv_usec = read_timeout_usec % 1000000;
221
222 fd_set fds;
223 FD_ZERO(&fds);
224 FD_SET(_fd, &fds);
225
226 int retval = select(_fd + 1, &fds, nullptr, nullptr, &tv);
227 if (retval > 0 && FD_ISSET(_fd, &fds)) {
228 ssize_t r = read(_fd, &_readahead_buffer[_write_index],
229 _readahead_buffer.capacity() - _write_index);
230 if (r < 0) {
231 return Result::eof;
232 }
233 if (r == 0) {
234 return Result::eof;
235 }
236 _write_index += r;
237 return Result::data_read;
238 }
239 }
240 return Result::should_terminate;
241 }
242
empty() const243 bool InputBuffer::empty() const { return _request_lines.empty(); }
244
nextLine()245 string InputBuffer::nextLine() {
246 string s = _request_lines.front();
247 _request_lines.pop_front();
248 return s;
249 }
250