Line | Count | Source (jump to first uncovered line) |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * Buffering of output and input. |
4 | | * Copyright (C) 1998 Kunihiro Ishiguro |
5 | | */ |
6 | | |
7 | | #include <zebra.h> |
8 | | |
9 | | #include "memory.h" |
10 | | #include "buffer.h" |
11 | | #include "log.h" |
12 | | #include "network.h" |
13 | | #include "lib_errors.h" |
14 | | |
15 | | #include <stddef.h> |
16 | | |
17 | | DEFINE_MTYPE_STATIC(LIB, BUFFER, "Buffer"); |
18 | | DEFINE_MTYPE_STATIC(LIB, BUFFER_DATA, "Buffer data"); |
19 | | |
20 | | /* Buffer master. */ |
21 | | struct buffer { |
22 | | /* Data list. */ |
23 | | struct buffer_data *head; |
24 | | struct buffer_data *tail; |
25 | | |
26 | | /* Size of each buffer_data chunk. */ |
27 | | size_t size; |
28 | | }; |
29 | | |
30 | | /* Data container. */ |
31 | | struct buffer_data { |
32 | | struct buffer_data *next; |
33 | | |
34 | | /* Location to add new data. */ |
35 | | size_t cp; |
36 | | |
37 | | /* Pointer to data not yet flushed. */ |
38 | | size_t sp; |
39 | | |
40 | | /* Actual data stream (variable length). */ |
41 | | unsigned char data[]; /* real dimension is buffer->size */ |
42 | | }; |
43 | | |
44 | | /* It should always be true that: 0 <= sp <= cp <= size */ |
45 | | |
46 | | /* Default buffer size (used if none specified). It is rounded up to the |
47 | | next page boundary. */ |
48 | 2 | #define BUFFER_SIZE_DEFAULT 4096 |
49 | | |
50 | 0 | #define BUFFER_DATA_FREE(D) XFREE(MTYPE_BUFFER_DATA, (D)) |
51 | | |
52 | | /* Make new buffer. */ |
53 | | struct buffer *buffer_new(size_t size) |
54 | 4 | { |
55 | 4 | struct buffer *b; |
56 | | |
57 | 4 | b = XCALLOC(MTYPE_BUFFER, sizeof(struct buffer)); |
58 | | |
59 | 4 | if (size) |
60 | 0 | b->size = size; |
61 | 4 | else { |
62 | 4 | static size_t default_size; |
63 | 4 | if (!default_size) { |
64 | 2 | long pgsz = sysconf(_SC_PAGESIZE); |
65 | 2 | default_size = ((((BUFFER_SIZE_DEFAULT - 1) / pgsz) + 1) |
66 | 2 | * pgsz); |
67 | 2 | } |
68 | 4 | b->size = default_size; |
69 | 4 | } |
70 | | |
71 | 4 | return b; |
72 | 4 | } |
73 | | |
74 | | /* Free buffer. */ |
75 | | void buffer_free(struct buffer *b) |
76 | 0 | { |
77 | 0 | buffer_reset(b); |
78 | 0 | XFREE(MTYPE_BUFFER, b); |
79 | 0 | } |
80 | | |
81 | | /* Make string clone. */ |
82 | | char *buffer_getstr(struct buffer *b) |
83 | 0 | { |
84 | 0 | size_t totlen = 0; |
85 | 0 | struct buffer_data *data; |
86 | 0 | char *s; |
87 | 0 | char *p; |
88 | |
|
89 | 0 | for (data = b->head; data; data = data->next) |
90 | 0 | totlen += data->cp - data->sp; |
91 | 0 | if (!(s = XMALLOC(MTYPE_TMP, totlen + 1))) |
92 | 0 | return NULL; |
93 | 0 | p = s; |
94 | 0 | for (data = b->head; data; data = data->next) { |
95 | 0 | memcpy(p, data->data + data->sp, data->cp - data->sp); |
96 | 0 | p += data->cp - data->sp; |
97 | 0 | } |
98 | 0 | *p = '\0'; |
99 | 0 | return s; |
100 | 0 | } |
101 | | |
102 | | /* Clear and free all allocated data. */ |
103 | | void buffer_reset(struct buffer *b) |
104 | 0 | { |
105 | 0 | struct buffer_data *data; |
106 | 0 | struct buffer_data *next; |
107 | |
|
108 | 0 | for (data = b->head; data; data = next) { |
109 | 0 | next = data->next; |
110 | 0 | BUFFER_DATA_FREE(data); |
111 | 0 | } |
112 | 0 | b->head = b->tail = NULL; |
113 | 0 | } |
114 | | |
115 | | /* Add buffer_data to the end of buffer. */ |
116 | | static struct buffer_data *buffer_add(struct buffer *b) |
117 | 0 | { |
118 | 0 | struct buffer_data *d; |
119 | |
|
120 | 0 | d = XMALLOC(MTYPE_BUFFER_DATA, |
121 | 0 | offsetof(struct buffer_data, data) + b->size); |
122 | 0 | d->cp = d->sp = 0; |
123 | 0 | d->next = NULL; |
124 | |
|
125 | 0 | if (b->tail) |
126 | 0 | b->tail->next = d; |
127 | 0 | else |
128 | 0 | b->head = d; |
129 | 0 | b->tail = d; |
130 | |
|
131 | 0 | return d; |
132 | 0 | } |
133 | | |
134 | | /* Write data to buffer. */ |
135 | | void buffer_put(struct buffer *b, const void *p, size_t size) |
136 | 0 | { |
137 | 0 | struct buffer_data *data = b->tail; |
138 | 0 | const char *ptr = p; |
139 | | |
140 | | /* We use even last one byte of data buffer. */ |
141 | 0 | while (size) { |
142 | 0 | size_t chunk; |
143 | | |
144 | | /* If there is no data buffer add it. */ |
145 | 0 | if (data == NULL || data->cp == b->size) |
146 | 0 | data = buffer_add(b); |
147 | |
|
148 | 0 | chunk = ((size <= (b->size - data->cp)) ? size |
149 | 0 | : (b->size - data->cp)); |
150 | 0 | memcpy((data->data + data->cp), ptr, chunk); |
151 | 0 | size -= chunk; |
152 | 0 | ptr += chunk; |
153 | 0 | data->cp += chunk; |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | | /* Insert character into the buffer. */ |
158 | | void buffer_putc(struct buffer *b, uint8_t c) |
159 | 0 | { |
160 | 0 | buffer_put(b, &c, 1); |
161 | 0 | } |
162 | | |
163 | | /* Put string to the buffer. */ |
164 | | void buffer_putstr(struct buffer *b, const char *c) |
165 | 0 | { |
166 | 0 | buffer_put(b, c, strlen(c)); |
167 | 0 | } |
168 | | |
169 | | /* Expand \n to \r\n */ |
170 | | void buffer_put_crlf(struct buffer *b, const void *origp, size_t origsize) |
171 | 0 | { |
172 | 0 | struct buffer_data *data = b->tail; |
173 | 0 | const char *p = origp, *end = p + origsize, *lf; |
174 | 0 | size_t size; |
175 | |
|
176 | 0 | lf = memchr(p, '\n', end - p); |
177 | | |
178 | | /* We use even last one byte of data buffer. */ |
179 | 0 | while (p < end) { |
180 | 0 | size_t avail, chunk; |
181 | | |
182 | | /* If there is no data buffer add it. */ |
183 | 0 | if (data == NULL || data->cp == b->size) |
184 | 0 | data = buffer_add(b); |
185 | |
|
186 | 0 | size = (lf ? lf : end) - p; |
187 | 0 | avail = b->size - data->cp; |
188 | |
|
189 | 0 | chunk = (size <= avail) ? size : avail; |
190 | 0 | memcpy(data->data + data->cp, p, chunk); |
191 | |
|
192 | 0 | p += chunk; |
193 | 0 | data->cp += chunk; |
194 | |
|
195 | 0 | if (lf && size <= avail) { |
196 | | /* we just copied up to (including) a '\n' */ |
197 | 0 | if (data->cp == b->size) |
198 | 0 | data = buffer_add(b); |
199 | 0 | data->data[data->cp++] = '\r'; |
200 | 0 | if (data->cp == b->size) |
201 | 0 | data = buffer_add(b); |
202 | 0 | data->data[data->cp++] = '\n'; |
203 | |
|
204 | 0 | p++; |
205 | 0 | lf = memchr(p, '\n', end - p); |
206 | 0 | } |
207 | 0 | } |
208 | 0 | } |
209 | | |
210 | | /* Keep flushing data to the fd until the buffer is empty or an error is |
211 | | encountered or the operation would block. */ |
212 | | buffer_status_t buffer_flush_all(struct buffer *b, int fd) |
213 | 0 | { |
214 | 0 | buffer_status_t ret; |
215 | 0 | struct buffer_data *head; |
216 | 0 | size_t head_sp; |
217 | |
|
218 | 0 | if (!b->head) |
219 | 0 | return BUFFER_EMPTY; |
220 | 0 | head_sp = (head = b->head)->sp; |
221 | | /* Flush all data. */ |
222 | 0 | while ((ret = buffer_flush_available(b, fd)) == BUFFER_PENDING) { |
223 | 0 | if ((b->head == head) && (head_sp == head->sp) |
224 | 0 | && (errno != EINTR)) |
225 | | /* No data was flushed, so kernel buffer must be full. |
226 | | */ |
227 | 0 | return ret; |
228 | 0 | head_sp = (head = b->head)->sp; |
229 | 0 | } |
230 | | |
231 | 0 | return ret; |
232 | 0 | } |
233 | | |
234 | | /* Flush enough data to fill a terminal window of the given scene (used only |
235 | | by vty telnet interface). */ |
236 | | buffer_status_t buffer_flush_window(struct buffer *b, int fd, int width, |
237 | | int height, int erase_flag, |
238 | | int no_more_flag) |
239 | 0 | { |
240 | 0 | int nbytes; |
241 | 0 | int iov_alloc; |
242 | 0 | int iov_index; |
243 | 0 | struct iovec *iov; |
244 | 0 | struct iovec small_iov[3]; |
245 | 0 | char more[] = " --More-- "; |
246 | 0 | char erase[] = {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, |
247 | 0 | 0x08, 0x08, ' ', ' ', ' ', ' ', ' ', ' ', |
248 | 0 | ' ', ' ', ' ', ' ', 0x08, 0x08, 0x08, 0x08, |
249 | 0 | 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}; |
250 | 0 | struct buffer_data *data; |
251 | 0 | int column; |
252 | |
|
253 | 0 | if (!b->head) |
254 | 0 | return BUFFER_EMPTY; |
255 | | |
256 | 0 | if (height < 1) |
257 | 0 | height = 1; |
258 | 0 | else if (height >= 2) |
259 | 0 | height--; |
260 | 0 | if (width < 1) |
261 | 0 | width = 1; |
262 | | |
263 | | /* For erase and more data add two to b's buffer_data count.*/ |
264 | 0 | if (b->head->next == NULL) { |
265 | 0 | iov_alloc = array_size(small_iov); |
266 | 0 | iov = small_iov; |
267 | 0 | } else { |
268 | 0 | iov_alloc = ((height * (width + 2)) / b->size) + 10; |
269 | 0 | iov = XMALLOC(MTYPE_TMP, iov_alloc * sizeof(*iov)); |
270 | 0 | } |
271 | 0 | iov_index = 0; |
272 | | |
273 | | /* Previously print out is performed. */ |
274 | 0 | if (erase_flag) { |
275 | 0 | iov[iov_index].iov_base = erase; |
276 | 0 | iov[iov_index].iov_len = sizeof(erase); |
277 | 0 | iov_index++; |
278 | 0 | } |
279 | | |
280 | | /* Output data. */ |
281 | 0 | column = 1; /* Column position of next character displayed. */ |
282 | 0 | for (data = b->head; data && (height > 0); data = data->next) { |
283 | 0 | size_t cp; |
284 | |
|
285 | 0 | cp = data->sp; |
286 | 0 | while ((cp < data->cp) && (height > 0)) { |
287 | | /* Calculate lines remaining and column position after |
288 | | displaying |
289 | | this character. */ |
290 | 0 | if (data->data[cp] == '\r') |
291 | 0 | column = 1; |
292 | 0 | else if ((data->data[cp] == '\n') |
293 | 0 | || (column == width)) { |
294 | 0 | column = 1; |
295 | 0 | height--; |
296 | 0 | } else |
297 | 0 | column++; |
298 | 0 | cp++; |
299 | 0 | } |
300 | 0 | iov[iov_index].iov_base = (char *)(data->data + data->sp); |
301 | 0 | iov[iov_index++].iov_len = cp - data->sp; |
302 | 0 | data->sp = cp; |
303 | |
|
304 | 0 | if (iov_index == iov_alloc) |
305 | | /* This should not ordinarily happen. */ |
306 | 0 | { |
307 | 0 | iov_alloc *= 2; |
308 | 0 | if (iov != small_iov) { |
309 | 0 | iov = XREALLOC(MTYPE_TMP, iov, |
310 | 0 | iov_alloc * sizeof(*iov)); |
311 | 0 | } else { |
312 | | /* This should absolutely never occur. */ |
313 | 0 | flog_err_sys( |
314 | 0 | EC_LIB_SYSTEM_CALL, |
315 | 0 | "%s: corruption detected: iov_small overflowed; head %p, tail %p, head->next %p", |
316 | 0 | __func__, (void *)b->head, |
317 | 0 | (void *)b->tail, (void *)b->head->next); |
318 | 0 | iov = XMALLOC(MTYPE_TMP, |
319 | 0 | iov_alloc * sizeof(*iov)); |
320 | 0 | memcpy(iov, small_iov, sizeof(small_iov)); |
321 | 0 | } |
322 | 0 | } |
323 | 0 | } |
324 | | |
325 | | /* In case of `more' display need. */ |
326 | 0 | if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag) { |
327 | 0 | iov[iov_index].iov_base = more; |
328 | 0 | iov[iov_index].iov_len = sizeof(more); |
329 | 0 | iov_index++; |
330 | 0 | } |
331 | | |
332 | |
|
333 | 0 | #ifdef IOV_MAX |
334 | | /* IOV_MAX are normally defined in <sys/uio.h> , Posix.1g. |
335 | | example: Solaris2.6 are defined IOV_MAX size at 16. */ |
336 | 0 | { |
337 | 0 | struct iovec *c_iov = iov; |
338 | 0 | nbytes = 0; /* Make sure it's initialized. */ |
339 | |
|
340 | 0 | while (iov_index > 0) { |
341 | 0 | int iov_size; |
342 | |
|
343 | 0 | iov_size = |
344 | 0 | ((iov_index > IOV_MAX) ? IOV_MAX : iov_index); |
345 | 0 | nbytes = writev(fd, c_iov, iov_size); |
346 | 0 | if (nbytes < 0) { |
347 | 0 | flog_err(EC_LIB_SOCKET, |
348 | 0 | "%s: writev to fd %d failed: %s", |
349 | 0 | __func__, fd, safe_strerror(errno)); |
350 | 0 | break; |
351 | 0 | } |
352 | | |
353 | | /* move pointer io-vector */ |
354 | 0 | c_iov += iov_size; |
355 | 0 | iov_index -= iov_size; |
356 | 0 | } |
357 | 0 | } |
358 | | #else /* IOV_MAX */ |
359 | | nbytes = writev(fd, iov, iov_index); |
360 | | if (nbytes < 0) |
361 | | flog_err(EC_LIB_SOCKET, "%s: writev to fd %d failed: %s", |
362 | | __func__, fd, safe_strerror(errno)); |
363 | | #endif /* IOV_MAX */ |
364 | | |
365 | | /* Free printed buffer data. */ |
366 | 0 | while (b->head && (b->head->sp == b->head->cp)) { |
367 | 0 | struct buffer_data *del; |
368 | 0 | if (!(b->head = (del = b->head)->next)) |
369 | 0 | b->tail = NULL; |
370 | 0 | BUFFER_DATA_FREE(del); |
371 | 0 | } |
372 | |
|
373 | 0 | if (iov != small_iov) |
374 | 0 | XFREE(MTYPE_TMP, iov); |
375 | |
|
376 | 0 | return (nbytes < 0) ? BUFFER_ERROR |
377 | 0 | : (b->head ? BUFFER_PENDING : BUFFER_EMPTY); |
378 | 0 | } |
379 | | |
380 | | /* This function (unlike other buffer_flush* functions above) is designed |
381 | | to work with non-blocking sockets. It does not attempt to write out |
382 | | all of the queued data, just a "big" chunk. It returns 0 if it was |
383 | | able to empty out the buffers completely, 1 if more flushing is |
384 | | required later, or -1 on a fatal write error. */ |
385 | | buffer_status_t buffer_flush_available(struct buffer *b, int fd) |
386 | 0 | { |
387 | | |
388 | | /* These are just reasonable values to make sure a significant amount of |
389 | | data is written. There's no need to go crazy and try to write it all |
390 | | in one shot. */ |
391 | 0 | #ifdef IOV_MAX |
392 | 0 | #define MAX_CHUNKS ((IOV_MAX >= 16) ? 16 : IOV_MAX) |
393 | | #else |
394 | | #define MAX_CHUNKS 16 |
395 | | #endif |
396 | 0 | #define MAX_FLUSH 131072 |
397 | |
|
398 | 0 | struct buffer_data *d; |
399 | 0 | size_t written; |
400 | 0 | struct iovec iov[MAX_CHUNKS]; |
401 | 0 | size_t iovcnt = 0; |
402 | 0 | size_t nbyte = 0; |
403 | |
|
404 | 0 | if (fd < 0) |
405 | 0 | return BUFFER_ERROR; |
406 | | |
407 | 0 | for (d = b->head; d && (iovcnt < MAX_CHUNKS) && (nbyte < MAX_FLUSH); |
408 | 0 | d = d->next, iovcnt++) { |
409 | 0 | iov[iovcnt].iov_base = d->data + d->sp; |
410 | 0 | nbyte += (iov[iovcnt].iov_len = d->cp - d->sp); |
411 | 0 | } |
412 | |
|
413 | 0 | if (!nbyte) |
414 | | /* No data to flush: should we issue a warning message? */ |
415 | 0 | return BUFFER_EMPTY; |
416 | | |
417 | | /* only place where written should be sign compared */ |
418 | 0 | if ((ssize_t)(written = writev(fd, iov, iovcnt)) < 0) { |
419 | 0 | if (ERRNO_IO_RETRY(errno)) |
420 | | /* Calling code should try again later. */ |
421 | 0 | return BUFFER_PENDING; |
422 | 0 | flog_err(EC_LIB_SOCKET, "%s: write error on fd %d: %s", |
423 | 0 | __func__, fd, safe_strerror(errno)); |
424 | 0 | return BUFFER_ERROR; |
425 | 0 | } |
426 | | |
427 | | /* Free printed buffer data. */ |
428 | 0 | while (written > 0) { |
429 | 0 | if (!(d = b->head)) { |
430 | 0 | flog_err( |
431 | 0 | EC_LIB_DEVELOPMENT, |
432 | 0 | "%s: corruption detected: buffer queue empty, but written is %lu", |
433 | 0 | __func__, (unsigned long)written); |
434 | 0 | break; |
435 | 0 | } |
436 | 0 | if (written < d->cp - d->sp) { |
437 | 0 | d->sp += written; |
438 | 0 | return BUFFER_PENDING; |
439 | 0 | } |
440 | | |
441 | 0 | written -= (d->cp - d->sp); |
442 | 0 | if (!(b->head = d->next)) |
443 | 0 | b->tail = NULL; |
444 | 0 | BUFFER_DATA_FREE(d); |
445 | 0 | } |
446 | | |
447 | 0 | return b->head ? BUFFER_PENDING : BUFFER_EMPTY; |
448 | |
|
449 | 0 | #undef MAX_CHUNKS |
450 | 0 | #undef MAX_FLUSH |
451 | 0 | } |
452 | | |
453 | | buffer_status_t buffer_write(struct buffer *b, int fd, const void *p, |
454 | | size_t size) |
455 | 0 | { |
456 | 0 | ssize_t nbytes; |
457 | |
|
458 | 0 | if (b->head) |
459 | | /* Buffer is not empty, so do not attempt to write the new data. |
460 | | */ |
461 | 0 | nbytes = 0; |
462 | 0 | else { |
463 | 0 | nbytes = write(fd, p, size); |
464 | 0 | if (nbytes < 0) { |
465 | 0 | if (ERRNO_IO_RETRY(errno)) |
466 | 0 | nbytes = 0; |
467 | 0 | else { |
468 | 0 | flog_err(EC_LIB_SOCKET, |
469 | 0 | "%s: write error on fd %d: %s", |
470 | 0 | __func__, fd, safe_strerror(errno)); |
471 | 0 | return BUFFER_ERROR; |
472 | 0 | } |
473 | 0 | } |
474 | 0 | } |
475 | | /* Add any remaining data to the buffer. */ |
476 | 0 | { |
477 | 0 | size_t written = nbytes; |
478 | 0 | if (written < size) |
479 | 0 | buffer_put(b, ((const char *)p) + written, |
480 | 0 | size - written); |
481 | 0 | } |
482 | 0 | return b->head ? BUFFER_PENDING : BUFFER_EMPTY; |
483 | 0 | } |