]> git.proxmox.com Git - mirror_frr.git/blob - lib/buffer.c
Merge pull request #13649 from donaldsharp/unlock_the_node_or_else
[mirror_frr.git] / lib / buffer.c
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 #define BUFFER_SIZE_DEFAULT 4096
49
50 #define BUFFER_DATA_FREE(D) XFREE(MTYPE_BUFFER_DATA, (D))
51
52 /* Make new buffer. */
53 struct buffer *buffer_new(size_t size)
54 {
55 struct buffer *b;
56
57 b = XCALLOC(MTYPE_BUFFER, sizeof(struct buffer));
58
59 if (size)
60 b->size = size;
61 else {
62 static size_t default_size;
63 if (!default_size) {
64 long pgsz = sysconf(_SC_PAGESIZE);
65 default_size = ((((BUFFER_SIZE_DEFAULT - 1) / pgsz) + 1)
66 * pgsz);
67 }
68 b->size = default_size;
69 }
70
71 return b;
72 }
73
74 /* Free buffer. */
75 void buffer_free(struct buffer *b)
76 {
77 buffer_reset(b);
78 XFREE(MTYPE_BUFFER, b);
79 }
80
81 /* Make string clone. */
82 char *buffer_getstr(struct buffer *b)
83 {
84 size_t totlen = 0;
85 struct buffer_data *data;
86 char *s;
87 char *p;
88
89 for (data = b->head; data; data = data->next)
90 totlen += data->cp - data->sp;
91 if (!(s = XMALLOC(MTYPE_TMP, totlen + 1)))
92 return NULL;
93 p = s;
94 for (data = b->head; data; data = data->next) {
95 memcpy(p, data->data + data->sp, data->cp - data->sp);
96 p += data->cp - data->sp;
97 }
98 *p = '\0';
99 return s;
100 }
101
102 /* Clear and free all allocated data. */
103 void buffer_reset(struct buffer *b)
104 {
105 struct buffer_data *data;
106 struct buffer_data *next;
107
108 for (data = b->head; data; data = next) {
109 next = data->next;
110 BUFFER_DATA_FREE(data);
111 }
112 b->head = b->tail = NULL;
113 }
114
115 /* Add buffer_data to the end of buffer. */
116 static struct buffer_data *buffer_add(struct buffer *b)
117 {
118 struct buffer_data *d;
119
120 d = XMALLOC(MTYPE_BUFFER_DATA,
121 offsetof(struct buffer_data, data) + b->size);
122 d->cp = d->sp = 0;
123 d->next = NULL;
124
125 if (b->tail)
126 b->tail->next = d;
127 else
128 b->head = d;
129 b->tail = d;
130
131 return d;
132 }
133
134 /* Write data to buffer. */
135 void buffer_put(struct buffer *b, const void *p, size_t size)
136 {
137 struct buffer_data *data = b->tail;
138 const char *ptr = p;
139
140 /* We use even last one byte of data buffer. */
141 while (size) {
142 size_t chunk;
143
144 /* If there is no data buffer add it. */
145 if (data == NULL || data->cp == b->size)
146 data = buffer_add(b);
147
148 chunk = ((size <= (b->size - data->cp)) ? size
149 : (b->size - data->cp));
150 memcpy((data->data + data->cp), ptr, chunk);
151 size -= chunk;
152 ptr += chunk;
153 data->cp += chunk;
154 }
155 }
156
157 /* Insert character into the buffer. */
158 void buffer_putc(struct buffer *b, uint8_t c)
159 {
160 buffer_put(b, &c, 1);
161 }
162
163 /* Put string to the buffer. */
164 void buffer_putstr(struct buffer *b, const char *c)
165 {
166 buffer_put(b, c, strlen(c));
167 }
168
169 /* Expand \n to \r\n */
170 void buffer_put_crlf(struct buffer *b, const void *origp, size_t origsize)
171 {
172 struct buffer_data *data = b->tail;
173 const char *p = origp, *end = p + origsize, *lf;
174 size_t size;
175
176 lf = memchr(p, '\n', end - p);
177
178 /* We use even last one byte of data buffer. */
179 while (p < end) {
180 size_t avail, chunk;
181
182 /* If there is no data buffer add it. */
183 if (data == NULL || data->cp == b->size)
184 data = buffer_add(b);
185
186 size = (lf ? lf : end) - p;
187 avail = b->size - data->cp;
188
189 chunk = (size <= avail) ? size : avail;
190 memcpy(data->data + data->cp, p, chunk);
191
192 p += chunk;
193 data->cp += chunk;
194
195 if (lf && size <= avail) {
196 /* we just copied up to (including) a '\n' */
197 if (data->cp == b->size)
198 data = buffer_add(b);
199 data->data[data->cp++] = '\r';
200 if (data->cp == b->size)
201 data = buffer_add(b);
202 data->data[data->cp++] = '\n';
203
204 p++;
205 lf = memchr(p, '\n', end - p);
206 }
207 }
208 }
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 {
214 buffer_status_t ret;
215 struct buffer_data *head;
216 size_t head_sp;
217
218 if (!b->head)
219 return BUFFER_EMPTY;
220 head_sp = (head = b->head)->sp;
221 /* Flush all data. */
222 while ((ret = buffer_flush_available(b, fd)) == BUFFER_PENDING) {
223 if ((b->head == head) && (head_sp == head->sp)
224 && (errno != EINTR))
225 /* No data was flushed, so kernel buffer must be full.
226 */
227 return ret;
228 head_sp = (head = b->head)->sp;
229 }
230
231 return ret;
232 }
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 {
240 int nbytes;
241 int iov_alloc;
242 int iov_index;
243 struct iovec *iov;
244 struct iovec small_iov[3];
245 char more[] = " --More-- ";
246 char erase[] = {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
247 0x08, 0x08, ' ', ' ', ' ', ' ', ' ', ' ',
248 ' ', ' ', ' ', ' ', 0x08, 0x08, 0x08, 0x08,
249 0x08, 0x08, 0x08, 0x08, 0x08, 0x08};
250 struct buffer_data *data;
251 int column;
252
253 if (!b->head)
254 return BUFFER_EMPTY;
255
256 if (height < 1)
257 height = 1;
258 else if (height >= 2)
259 height--;
260 if (width < 1)
261 width = 1;
262
263 /* For erase and more data add two to b's buffer_data count.*/
264 if (b->head->next == NULL) {
265 iov_alloc = array_size(small_iov);
266 iov = small_iov;
267 } else {
268 iov_alloc = ((height * (width + 2)) / b->size) + 10;
269 iov = XMALLOC(MTYPE_TMP, iov_alloc * sizeof(*iov));
270 }
271 iov_index = 0;
272
273 /* Previously print out is performed. */
274 if (erase_flag) {
275 iov[iov_index].iov_base = erase;
276 iov[iov_index].iov_len = sizeof(erase);
277 iov_index++;
278 }
279
280 /* Output data. */
281 column = 1; /* Column position of next character displayed. */
282 for (data = b->head; data && (height > 0); data = data->next) {
283 size_t cp;
284
285 cp = data->sp;
286 while ((cp < data->cp) && (height > 0)) {
287 /* Calculate lines remaining and column position after
288 displaying
289 this character. */
290 if (data->data[cp] == '\r')
291 column = 1;
292 else if ((data->data[cp] == '\n')
293 || (column == width)) {
294 column = 1;
295 height--;
296 } else
297 column++;
298 cp++;
299 }
300 iov[iov_index].iov_base = (char *)(data->data + data->sp);
301 iov[iov_index++].iov_len = cp - data->sp;
302 data->sp = cp;
303
304 if (iov_index == iov_alloc)
305 /* This should not ordinarily happen. */
306 {
307 iov_alloc *= 2;
308 if (iov != small_iov) {
309 iov = XREALLOC(MTYPE_TMP, iov,
310 iov_alloc * sizeof(*iov));
311 } else {
312 /* This should absolutely never occur. */
313 flog_err_sys(
314 EC_LIB_SYSTEM_CALL,
315 "%s: corruption detected: iov_small overflowed; head %p, tail %p, head->next %p",
316 __func__, (void *)b->head,
317 (void *)b->tail, (void *)b->head->next);
318 iov = XMALLOC(MTYPE_TMP,
319 iov_alloc * sizeof(*iov));
320 memcpy(iov, small_iov, sizeof(small_iov));
321 }
322 }
323 }
324
325 /* In case of `more' display need. */
326 if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag) {
327 iov[iov_index].iov_base = more;
328 iov[iov_index].iov_len = sizeof(more);
329 iov_index++;
330 }
331
332
333 #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 {
337 struct iovec *c_iov = iov;
338 nbytes = 0; /* Make sure it's initialized. */
339
340 while (iov_index > 0) {
341 int iov_size;
342
343 iov_size =
344 ((iov_index > IOV_MAX) ? IOV_MAX : iov_index);
345 nbytes = writev(fd, c_iov, iov_size);
346 if (nbytes < 0) {
347 flog_err(EC_LIB_SOCKET,
348 "%s: writev to fd %d failed: %s",
349 __func__, fd, safe_strerror(errno));
350 break;
351 }
352
353 /* move pointer io-vector */
354 c_iov += iov_size;
355 iov_index -= iov_size;
356 }
357 }
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 while (b->head && (b->head->sp == b->head->cp)) {
367 struct buffer_data *del;
368 if (!(b->head = (del = b->head)->next))
369 b->tail = NULL;
370 BUFFER_DATA_FREE(del);
371 }
372
373 if (iov != small_iov)
374 XFREE(MTYPE_TMP, iov);
375
376 return (nbytes < 0) ? BUFFER_ERROR
377 : (b->head ? BUFFER_PENDING : BUFFER_EMPTY);
378 }
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 {
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 #ifdef IOV_MAX
392 #define MAX_CHUNKS ((IOV_MAX >= 16) ? 16 : IOV_MAX)
393 #else
394 #define MAX_CHUNKS 16
395 #endif
396 #define MAX_FLUSH 131072
397
398 struct buffer_data *d;
399 size_t written;
400 struct iovec iov[MAX_CHUNKS];
401 size_t iovcnt = 0;
402 size_t nbyte = 0;
403
404 if (fd < 0)
405 return BUFFER_ERROR;
406
407 for (d = b->head; d && (iovcnt < MAX_CHUNKS) && (nbyte < MAX_FLUSH);
408 d = d->next, iovcnt++) {
409 iov[iovcnt].iov_base = d->data + d->sp;
410 nbyte += (iov[iovcnt].iov_len = d->cp - d->sp);
411 }
412
413 if (!nbyte)
414 /* No data to flush: should we issue a warning message? */
415 return BUFFER_EMPTY;
416
417 /* only place where written should be sign compared */
418 if ((ssize_t)(written = writev(fd, iov, iovcnt)) < 0) {
419 if (ERRNO_IO_RETRY(errno))
420 /* Calling code should try again later. */
421 return BUFFER_PENDING;
422 flog_err(EC_LIB_SOCKET, "%s: write error on fd %d: %s",
423 __func__, fd, safe_strerror(errno));
424 return BUFFER_ERROR;
425 }
426
427 /* Free printed buffer data. */
428 while (written > 0) {
429 if (!(d = b->head)) {
430 flog_err(
431 EC_LIB_DEVELOPMENT,
432 "%s: corruption detected: buffer queue empty, but written is %lu",
433 __func__, (unsigned long)written);
434 break;
435 }
436 if (written < d->cp - d->sp) {
437 d->sp += written;
438 return BUFFER_PENDING;
439 }
440
441 written -= (d->cp - d->sp);
442 if (!(b->head = d->next))
443 b->tail = NULL;
444 BUFFER_DATA_FREE(d);
445 }
446
447 return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
448
449 #undef MAX_CHUNKS
450 #undef MAX_FLUSH
451 }
452
453 buffer_status_t buffer_write(struct buffer *b, int fd, const void *p,
454 size_t size)
455 {
456 ssize_t nbytes;
457
458 if (b->head)
459 /* Buffer is not empty, so do not attempt to write the new data.
460 */
461 nbytes = 0;
462 else {
463 nbytes = write(fd, p, size);
464 if (nbytes < 0) {
465 if (ERRNO_IO_RETRY(errno))
466 nbytes = 0;
467 else {
468 flog_err(EC_LIB_SOCKET,
469 "%s: write error on fd %d: %s",
470 __func__, fd, safe_strerror(errno));
471 return BUFFER_ERROR;
472 }
473 }
474 }
475 /* Add any remaining data to the buffer. */
476 {
477 size_t written = nbytes;
478 if (written < size)
479 buffer_put(b, ((const char *)p) + written,
480 size - written);
481 }
482 return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
483 }