mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-10-30 23:27:35 +00:00 
			
		
		
		
	 07436e2a52
			
		
	
	
		07436e2a52
		
	
	
	
	
		
			
			zlog_warn was being used to inform user of impossible situations or for normal operations. Remove these from the code. Signed-off-by: Donald Sharp <sharpd@cumulusnetworks.com>
		
			
				
	
	
		
			510 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			510 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Buffering of output and input.
 | |
|  * Copyright (C) 1998 Kunihiro Ishiguro
 | |
|  *
 | |
|  * This file is part of GNU Zebra.
 | |
|  *
 | |
|  * GNU Zebra is free software; you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU General Public License as published
 | |
|  * by the Free Software Foundation; either version 2, or (at your
 | |
|  * option) any later version.
 | |
|  *
 | |
|  * GNU Zebra is distributed in the hope that it will be useful, but
 | |
|  * WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|  * General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License along
 | |
|  * with this program; see the file COPYING; if not, write to the Free Software
 | |
|  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 | |
|  */
 | |
| 
 | |
| #include <zebra.h>
 | |
| 
 | |
| #include "memory.h"
 | |
| #include "buffer.h"
 | |
| #include "log.h"
 | |
| #include "network.h"
 | |
| #include "lib_errors.h"
 | |
| 
 | |
| #include <stddef.h>
 | |
| 
 | |
| DEFINE_MTYPE_STATIC(LIB, BUFFER, "Buffer")
 | |
| DEFINE_MTYPE_STATIC(LIB, BUFFER_DATA, "Buffer data")
 | |
| 
 | |
| /* Buffer master. */
 | |
| struct buffer {
 | |
| 	/* Data list. */
 | |
| 	struct buffer_data *head;
 | |
| 	struct buffer_data *tail;
 | |
| 
 | |
| 	/* Size of each buffer_data chunk. */
 | |
| 	size_t size;
 | |
| };
 | |
| 
 | |
| /* Data container. */
 | |
| struct buffer_data {
 | |
| 	struct buffer_data *next;
 | |
| 
 | |
| 	/* Location to add new data. */
 | |
| 	size_t cp;
 | |
| 
 | |
| 	/* Pointer to data not yet flushed. */
 | |
| 	size_t sp;
 | |
| 
 | |
| 	/* Actual data stream (variable length). */
 | |
| 	unsigned char data[]; /* real dimension is buffer->size */
 | |
| };
 | |
| 
 | |
| /* It should always be true that: 0 <= sp <= cp <= size */
 | |
| 
 | |
| /* Default buffer size (used if none specified).  It is rounded up to the
 | |
|    next page boundery. */
 | |
| #define BUFFER_SIZE_DEFAULT		4096
 | |
| 
 | |
| #define BUFFER_DATA_FREE(D) XFREE(MTYPE_BUFFER_DATA, (D))
 | |
| 
 | |
| /* Make new buffer. */
 | |
| struct buffer *buffer_new(size_t size)
 | |
| {
 | |
| 	struct buffer *b;
 | |
| 
 | |
| 	b = XCALLOC(MTYPE_BUFFER, sizeof(struct buffer));
 | |
| 
 | |
| 	if (size)
 | |
| 		b->size = size;
 | |
| 	else {
 | |
| 		static size_t default_size;
 | |
| 		if (!default_size) {
 | |
| 			long pgsz = sysconf(_SC_PAGESIZE);
 | |
| 			default_size = ((((BUFFER_SIZE_DEFAULT - 1) / pgsz) + 1)
 | |
| 					* pgsz);
 | |
| 		}
 | |
| 		b->size = default_size;
 | |
| 	}
 | |
| 
 | |
| 	return b;
 | |
| }
 | |
| 
 | |
| /* Free buffer. */
 | |
| void buffer_free(struct buffer *b)
 | |
| {
 | |
| 	buffer_reset(b);
 | |
| 	XFREE(MTYPE_BUFFER, b);
 | |
| }
 | |
| 
 | |
| /* Make string clone. */
 | |
| char *buffer_getstr(struct buffer *b)
 | |
| {
 | |
| 	size_t totlen = 0;
 | |
| 	struct buffer_data *data;
 | |
| 	char *s;
 | |
| 	char *p;
 | |
| 
 | |
| 	for (data = b->head; data; data = data->next)
 | |
| 		totlen += data->cp - data->sp;
 | |
| 	if (!(s = XMALLOC(MTYPE_TMP, totlen + 1)))
 | |
| 		return NULL;
 | |
| 	p = s;
 | |
| 	for (data = b->head; data; data = data->next) {
 | |
| 		memcpy(p, data->data + data->sp, data->cp - data->sp);
 | |
| 		p += data->cp - data->sp;
 | |
| 	}
 | |
| 	*p = '\0';
 | |
| 	return s;
 | |
| }
 | |
| 
 | |
| /* Return 1 if buffer is empty. */
 | |
| int buffer_empty(struct buffer *b)
 | |
| {
 | |
| 	return (b->head == NULL);
 | |
| }
 | |
| 
 | |
| /* Clear and free all allocated data. */
 | |
| void buffer_reset(struct buffer *b)
 | |
| {
 | |
| 	struct buffer_data *data;
 | |
| 	struct buffer_data *next;
 | |
| 
 | |
| 	for (data = b->head; data; data = next) {
 | |
| 		next = data->next;
 | |
| 		BUFFER_DATA_FREE(data);
 | |
| 	}
 | |
| 	b->head = b->tail = NULL;
 | |
| }
 | |
| 
 | |
| /* Add buffer_data to the end of buffer. */
 | |
| static struct buffer_data *buffer_add(struct buffer *b)
 | |
| {
 | |
| 	struct buffer_data *d;
 | |
| 
 | |
| 	d = XMALLOC(MTYPE_BUFFER_DATA,
 | |
| 		    offsetof(struct buffer_data, data) + b->size);
 | |
| 	d->cp = d->sp = 0;
 | |
| 	d->next = NULL;
 | |
| 
 | |
| 	if (b->tail)
 | |
| 		b->tail->next = d;
 | |
| 	else
 | |
| 		b->head = d;
 | |
| 	b->tail = d;
 | |
| 
 | |
| 	return d;
 | |
| }
 | |
| 
 | |
| /* Write data to buffer. */
 | |
| void buffer_put(struct buffer *b, const void *p, size_t size)
 | |
| {
 | |
| 	struct buffer_data *data = b->tail;
 | |
| 	const char *ptr = p;
 | |
| 
 | |
| 	/* We use even last one byte of data buffer. */
 | |
| 	while (size) {
 | |
| 		size_t chunk;
 | |
| 
 | |
| 		/* If there is no data buffer add it. */
 | |
| 		if (data == NULL || data->cp == b->size)
 | |
| 			data = buffer_add(b);
 | |
| 
 | |
| 		chunk = ((size <= (b->size - data->cp)) ? size
 | |
| 							: (b->size - data->cp));
 | |
| 		memcpy((data->data + data->cp), ptr, chunk);
 | |
| 		size -= chunk;
 | |
| 		ptr += chunk;
 | |
| 		data->cp += chunk;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /* Insert character into the buffer. */
 | |
| void buffer_putc(struct buffer *b, uint8_t c)
 | |
| {
 | |
| 	buffer_put(b, &c, 1);
 | |
| }
 | |
| 
 | |
| /* Put string to the buffer. */
 | |
| void buffer_putstr(struct buffer *b, const char *c)
 | |
| {
 | |
| 	buffer_put(b, c, strlen(c));
 | |
| }
 | |
| 
 | |
| /* Expand \n to \r\n */
 | |
| void buffer_put_crlf(struct buffer *b, const void *origp, size_t origsize)
 | |
| {
 | |
| 	struct buffer_data *data = b->tail;
 | |
| 	const char *p = origp, *end = p + origsize, *lf;
 | |
| 	size_t size;
 | |
| 
 | |
| 	lf = memchr(p, '\n', end - p);
 | |
| 
 | |
| 	/* We use even last one byte of data buffer. */
 | |
| 	while (p < end) {
 | |
| 		size_t avail, chunk;
 | |
| 
 | |
| 		/* If there is no data buffer add it. */
 | |
| 		if (data == NULL || data->cp == b->size)
 | |
| 			data = buffer_add(b);
 | |
| 
 | |
| 		size = (lf ? lf : end) - p;
 | |
| 		avail = b->size - data->cp;
 | |
| 
 | |
| 		chunk = (size <= avail) ? size : avail;
 | |
| 		memcpy(data->data + data->cp, p, chunk);
 | |
| 
 | |
| 		p += chunk;
 | |
| 		data->cp += chunk;
 | |
| 
 | |
| 		if (lf && size <= avail) {
 | |
| 			/* we just copied up to (including) a '\n' */
 | |
| 			if (data->cp == b->size)
 | |
| 				data = buffer_add(b);
 | |
| 			data->data[data->cp++] = '\r';
 | |
| 			if (data->cp == b->size)
 | |
| 				data = buffer_add(b);
 | |
| 			data->data[data->cp++] = '\n';
 | |
| 
 | |
| 			p++;
 | |
| 			lf = memchr(p, '\n', end - p);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /* Keep flushing data to the fd until the buffer is empty or an error is
 | |
|    encountered or the operation would block. */
 | |
| buffer_status_t buffer_flush_all(struct buffer *b, int fd)
 | |
| {
 | |
| 	buffer_status_t ret;
 | |
| 	struct buffer_data *head;
 | |
| 	size_t head_sp;
 | |
| 
 | |
| 	if (!b->head)
 | |
| 		return BUFFER_EMPTY;
 | |
| 	head_sp = (head = b->head)->sp;
 | |
| 	/* Flush all data. */
 | |
| 	while ((ret = buffer_flush_available(b, fd)) == BUFFER_PENDING) {
 | |
| 		if ((b->head == head) && (head_sp == head->sp)
 | |
| 		    && (errno != EINTR))
 | |
| 			/* No data was flushed, so kernel buffer must be full.
 | |
| 			 */
 | |
| 			return ret;
 | |
| 		head_sp = (head = b->head)->sp;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /* Flush enough data to fill a terminal window of the given scene (used only
 | |
|    by vty telnet interface). */
 | |
| buffer_status_t buffer_flush_window(struct buffer *b, int fd, int width,
 | |
| 				    int height, int erase_flag,
 | |
| 				    int no_more_flag)
 | |
| {
 | |
| 	int nbytes;
 | |
| 	int iov_alloc;
 | |
| 	int iov_index;
 | |
| 	struct iovec *iov;
 | |
| 	struct iovec small_iov[3];
 | |
| 	char more[] = " --More-- ";
 | |
| 	char erase[] = {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
 | |
| 			0x08, 0x08, ' ',  ' ',  ' ',  ' ',  ' ',  ' ',
 | |
| 			' ',  ' ',  ' ',  ' ',  0x08, 0x08, 0x08, 0x08,
 | |
| 			0x08, 0x08, 0x08, 0x08, 0x08, 0x08};
 | |
| 	struct buffer_data *data;
 | |
| 	int column;
 | |
| 
 | |
| 	if (!b->head)
 | |
| 		return BUFFER_EMPTY;
 | |
| 
 | |
| 	if (height < 1)
 | |
| 		height = 1;
 | |
| 	else if (height >= 2)
 | |
| 		height--;
 | |
| 	if (width < 1)
 | |
| 		width = 1;
 | |
| 
 | |
| 	/* For erase and more data add two to b's buffer_data count.*/
 | |
| 	if (b->head->next == NULL) {
 | |
| 		iov_alloc = array_size(small_iov);
 | |
| 		iov = small_iov;
 | |
| 	} else {
 | |
| 		iov_alloc = ((height * (width + 2)) / b->size) + 10;
 | |
| 		iov = XMALLOC(MTYPE_TMP, iov_alloc * sizeof(*iov));
 | |
| 	}
 | |
| 	iov_index = 0;
 | |
| 
 | |
| 	/* Previously print out is performed. */
 | |
| 	if (erase_flag) {
 | |
| 		iov[iov_index].iov_base = erase;
 | |
| 		iov[iov_index].iov_len = sizeof erase;
 | |
| 		iov_index++;
 | |
| 	}
 | |
| 
 | |
| 	/* Output data. */
 | |
| 	column = 1; /* Column position of next character displayed. */
 | |
| 	for (data = b->head; data && (height > 0); data = data->next) {
 | |
| 		size_t cp;
 | |
| 
 | |
| 		cp = data->sp;
 | |
| 		while ((cp < data->cp) && (height > 0)) {
 | |
| 			/* Calculate lines remaining and column position after
 | |
| 			   displaying
 | |
| 			   this character. */
 | |
| 			if (data->data[cp] == '\r')
 | |
| 				column = 1;
 | |
| 			else if ((data->data[cp] == '\n')
 | |
| 				 || (column == width)) {
 | |
| 				column = 1;
 | |
| 				height--;
 | |
| 			} else
 | |
| 				column++;
 | |
| 			cp++;
 | |
| 		}
 | |
| 		iov[iov_index].iov_base = (char *)(data->data + data->sp);
 | |
| 		iov[iov_index++].iov_len = cp - data->sp;
 | |
| 		data->sp = cp;
 | |
| 
 | |
| 		if (iov_index == iov_alloc)
 | |
| 		/* This should not ordinarily happen. */
 | |
| 		{
 | |
| 			iov_alloc *= 2;
 | |
| 			if (iov != small_iov) {
 | |
| 				iov = XREALLOC(MTYPE_TMP, iov,
 | |
| 					       iov_alloc * sizeof(*iov));
 | |
| 			} else {
 | |
| 				/* This should absolutely never occur. */
 | |
| 				flog_err_sys(
 | |
| 					LIB_ERR_SYSTEM_CALL,
 | |
| 					"%s: corruption detected: iov_small overflowed; "
 | |
| 					"head %p, tail %p, head->next %p",
 | |
| 					__func__, (void *)b->head,
 | |
| 					(void *)b->tail, (void *)b->head->next);
 | |
| 				iov = XMALLOC(MTYPE_TMP,
 | |
| 					      iov_alloc * sizeof(*iov));
 | |
| 				memcpy(iov, small_iov, sizeof(small_iov));
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* In case of `more' display need. */
 | |
| 	if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag) {
 | |
| 		iov[iov_index].iov_base = more;
 | |
| 		iov[iov_index].iov_len = sizeof more;
 | |
| 		iov_index++;
 | |
| 	}
 | |
| 
 | |
| 
 | |
| #ifdef IOV_MAX
 | |
| 	/* IOV_MAX are normally defined in <sys/uio.h> , Posix.1g.
 | |
| 	   example: Solaris2.6 are defined IOV_MAX size at 16.     */
 | |
| 	{
 | |
| 		struct iovec *c_iov = iov;
 | |
| 		nbytes = 0; /* Make sure it's initialized. */
 | |
| 
 | |
| 		while (iov_index > 0) {
 | |
| 			int iov_size;
 | |
| 
 | |
| 			iov_size =
 | |
| 				((iov_index > IOV_MAX) ? IOV_MAX : iov_index);
 | |
| 			if ((nbytes = writev(fd, c_iov, iov_size)) < 0) {
 | |
| 				zlog_warn("%s: writev to fd %d failed: %s",
 | |
| 					  __func__, fd, safe_strerror(errno));
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			/* move pointer io-vector */
 | |
| 			c_iov += iov_size;
 | |
| 			iov_index -= iov_size;
 | |
| 		}
 | |
| 	}
 | |
| #else  /* IOV_MAX */
 | |
| 	if ((nbytes = writev(fd, iov, iov_index)) < 0)
 | |
| 		zlog_warn("%s: writev to fd %d failed: %s", __func__, fd,
 | |
| 			  safe_strerror(errno));
 | |
| #endif /* IOV_MAX */
 | |
| 
 | |
| 	/* Free printed buffer data. */
 | |
| 	while (b->head && (b->head->sp == b->head->cp)) {
 | |
| 		struct buffer_data *del;
 | |
| 		if (!(b->head = (del = b->head)->next))
 | |
| 			b->tail = NULL;
 | |
| 		BUFFER_DATA_FREE(del);
 | |
| 	}
 | |
| 
 | |
| 	if (iov != small_iov)
 | |
| 		XFREE(MTYPE_TMP, iov);
 | |
| 
 | |
| 	return (nbytes < 0) ? BUFFER_ERROR
 | |
| 			    : (b->head ? BUFFER_PENDING : BUFFER_EMPTY);
 | |
| }
 | |
| 
 | |
| /* This function (unlike other buffer_flush* functions above) is designed
 | |
| to work with non-blocking sockets.  It does not attempt to write out
 | |
| all of the queued data, just a "big" chunk.  It returns 0 if it was
 | |
| able to empty out the buffers completely, 1 if more flushing is
 | |
| required later, or -1 on a fatal write error. */
 | |
| buffer_status_t buffer_flush_available(struct buffer *b, int fd)
 | |
| {
 | |
| 
 | |
| /* These are just reasonable values to make sure a significant amount of
 | |
| data is written.  There's no need to go crazy and try to write it all
 | |
| in one shot. */
 | |
| #ifdef IOV_MAX
 | |
| #define MAX_CHUNKS ((IOV_MAX >= 16) ? 16 : IOV_MAX)
 | |
| #else
 | |
| #define MAX_CHUNKS 16
 | |
| #endif
 | |
| #define MAX_FLUSH 131072
 | |
| 
 | |
| 	struct buffer_data *d;
 | |
| 	size_t written;
 | |
| 	struct iovec iov[MAX_CHUNKS];
 | |
| 	size_t iovcnt = 0;
 | |
| 	size_t nbyte = 0;
 | |
| 
 | |
| 	if (fd < 0)
 | |
| 		return BUFFER_ERROR;
 | |
| 
 | |
| 	for (d = b->head; d && (iovcnt < MAX_CHUNKS) && (nbyte < MAX_FLUSH);
 | |
| 	     d = d->next, iovcnt++) {
 | |
| 		iov[iovcnt].iov_base = d->data + d->sp;
 | |
| 		nbyte += (iov[iovcnt].iov_len = d->cp - d->sp);
 | |
| 	}
 | |
| 
 | |
| 	if (!nbyte)
 | |
| 		/* No data to flush: should we issue a warning message? */
 | |
| 		return BUFFER_EMPTY;
 | |
| 
 | |
| 	/* only place where written should be sign compared */
 | |
| 	if ((ssize_t)(written = writev(fd, iov, iovcnt)) < 0) {
 | |
| 		if (ERRNO_IO_RETRY(errno))
 | |
| 			/* Calling code should try again later. */
 | |
| 			return BUFFER_PENDING;
 | |
| 		zlog_warn("%s: write error on fd %d: %s", __func__, fd,
 | |
| 			  safe_strerror(errno));
 | |
| 		return BUFFER_ERROR;
 | |
| 	}
 | |
| 
 | |
| 	/* Free printed buffer data. */
 | |
| 	while (written > 0) {
 | |
| 		struct buffer_data *d;
 | |
| 		if (!(d = b->head)) {
 | |
| 			flog_err(
 | |
| 				LIB_ERR_DEVELOPMENT,
 | |
| 				"%s: corruption detected: buffer queue empty, but written is %lu",
 | |
| 				__func__, (unsigned long)written);
 | |
| 			break;
 | |
| 		}
 | |
| 		if (written < d->cp - d->sp) {
 | |
| 			d->sp += written;
 | |
| 			return BUFFER_PENDING;
 | |
| 		}
 | |
| 
 | |
| 		written -= (d->cp - d->sp);
 | |
| 		if (!(b->head = d->next))
 | |
| 			b->tail = NULL;
 | |
| 		BUFFER_DATA_FREE(d);
 | |
| 	}
 | |
| 
 | |
| 	return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
 | |
| 
 | |
| #undef MAX_CHUNKS
 | |
| #undef MAX_FLUSH
 | |
| }
 | |
| 
 | |
| buffer_status_t buffer_write(struct buffer *b, int fd, const void *p,
 | |
| 			     size_t size)
 | |
| {
 | |
| 	ssize_t nbytes;
 | |
| 
 | |
| #if 0
 | |
| 	/*
 | |
| 	 * Should we attempt to drain any previously buffered data?
 | |
| 	 * This could help reduce latency in pushing out the data if
 | |
| 	 * we are stuck in a long-running thread that is preventing
 | |
| 	 * the main select loop from calling the flush thread...
 | |
| 	 */
 | |
| 	if (b->head && (buffer_flush_available(b, fd) == BUFFER_ERROR))
 | |
| 		return BUFFER_ERROR;
 | |
| #endif
 | |
| 	if (b->head)
 | |
| 		/* Buffer is not empty, so do not attempt to write the new data.
 | |
| 		 */
 | |
| 		nbytes = 0;
 | |
| 	else if ((nbytes = write(fd, p, size)) < 0) {
 | |
| 		if (ERRNO_IO_RETRY(errno))
 | |
| 			nbytes = 0;
 | |
| 		else {
 | |
| 			zlog_warn("%s: write error on fd %d: %s", __func__, fd,
 | |
| 				  safe_strerror(errno));
 | |
| 			return BUFFER_ERROR;
 | |
| 		}
 | |
| 	}
 | |
| 	/* Add any remaining data to the buffer. */
 | |
| 	{
 | |
| 		size_t written = nbytes;
 | |
| 		if (written < size)
 | |
| 			buffer_put(b, ((const char *)p) + written,
 | |
| 				   size - written);
 | |
| 	}
 | |
| 	return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
 | |
| }
 |