Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
510 lines (434 sloc) 12.2 KB
/*
* 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(
EC_LIB_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) {
flog_err(EC_LIB_SOCKET,
"%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)
flog_err(EC_LIB_SOCKET, "%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;
flog_err(EC_LIB_SOCKET, "%s: write error on fd %d: %s",
__func__, fd, safe_strerror(errno));
return BUFFER_ERROR;
}
/* Free printed buffer data. */
while (written > 0) {
if (!(d = b->head)) {
flog_err(
EC_LIB_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 {
flog_err(EC_LIB_SOCKET, "%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;
}
You can’t perform that action at this time.