| /* |
| * 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 GNU Zebra; see the file COPYING. If not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| #include <zebra.h> |
| |
| #include "memory.h" |
| #include "buffer.h" |
| #include "log.h" |
| #include <stddef.h> |
| |
| /* Make buffer data. */ |
| static struct buffer_data * |
| buffer_data_new (size_t size) |
| { |
| struct buffer_data *d; |
| |
| d = XMALLOC (MTYPE_BUFFER_DATA, offsetof(struct buffer_data,data[size])); |
| d->cp = d->sp = 0; |
| return d; |
| } |
| |
| static void |
| buffer_data_free (struct buffer_data *d) |
| { |
| XFREE (MTYPE_BUFFER_DATA, d); |
| } |
| |
| /* Make new buffer. */ |
| struct buffer * |
| buffer_new (size_t size) |
| { |
| struct buffer *b; |
| |
| b = XMALLOC (MTYPE_BUFFER, sizeof (struct buffer)); |
| memset (b, 0, sizeof (struct buffer)); |
| |
| b->size = size; |
| |
| return b; |
| } |
| |
| /* Free buffer. */ |
| void |
| buffer_free (struct buffer *b) |
| { |
| struct buffer_data *d; |
| struct buffer_data *next; |
| |
| d = b->head; |
| while (d) |
| { |
| next = d->next; |
| buffer_data_free (d); |
| d = next; |
| } |
| |
| d = b->unused_head; |
| while (d) |
| { |
| next = d->next; |
| buffer_data_free (d); |
| d = next; |
| } |
| |
| 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) |
| { |
| if (b->tail == NULL || b->tail->cp == b->tail->sp) |
| return 1; |
| else |
| return 0; |
| } |
| |
| /* 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; |
| b->alloc = 0; |
| b->length = 0; |
| } |
| |
| /* Add buffer_data to the end of buffer. */ |
| void |
| buffer_add (struct buffer *b) |
| { |
| struct buffer_data *d; |
| |
| d = buffer_data_new (b->size); |
| |
| if (b->tail == NULL) |
| { |
| d->prev = NULL; |
| d->next = NULL; |
| b->head = d; |
| b->tail = d; |
| } |
| else |
| { |
| d->prev = b->tail; |
| d->next = NULL; |
| |
| b->tail->next = d; |
| b->tail = d; |
| } |
| |
| b->alloc++; |
| } |
| |
| /* Write data to buffer. */ |
| int |
| buffer_write (struct buffer *b, const void *p, size_t size) |
| { |
| struct buffer_data *data; |
| const char *ptr = p; |
| data = b->tail; |
| b->length += size; |
| |
| /* 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) |
| { |
| buffer_add (b); |
| data = b->tail; |
| } |
| |
| 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; |
| } |
| return 1; |
| } |
| |
| /* Insert character into the buffer. */ |
| int |
| buffer_putc (struct buffer *b, u_char c) |
| { |
| buffer_write (b, &c, 1); |
| return 1; |
| } |
| |
| /* Insert word (2 octets) into ther buffer. */ |
| int |
| buffer_putw (struct buffer *b, u_short c) |
| { |
| buffer_write (b, (char *)&c, 2); |
| return 1; |
| } |
| |
| /* Put string to the buffer. */ |
| int |
| buffer_putstr (struct buffer *b, const char *c) |
| { |
| size_t size; |
| |
| size = strlen (c); |
| buffer_write (b, (void *) c, size); |
| return 1; |
| } |
| |
| /* Flush specified size to the fd. */ |
| void |
| buffer_flush (struct buffer *b, int fd, size_t size) |
| { |
| int iov_index; |
| struct iovec *iovec; |
| struct buffer_data *data; |
| struct buffer_data *out; |
| struct buffer_data *next; |
| |
| iovec = malloc (sizeof (struct iovec) * b->alloc); |
| iov_index = 0; |
| |
| for (data = b->head; data; data = data->next) |
| { |
| iovec[iov_index].iov_base = (char *)(data->data + data->sp); |
| |
| if (size <= (data->cp - data->sp)) |
| { |
| iovec[iov_index++].iov_len = size; |
| data->sp += size; |
| b->length -= size; |
| if (data->sp == data->cp) |
| data = data->next; |
| break; |
| } |
| else |
| { |
| iovec[iov_index++].iov_len = data->cp - data->sp; |
| b->length -= (data->cp - data->sp); |
| size -= data->cp - data->sp; |
| data->sp = data->cp; |
| } |
| } |
| |
| /* Write buffer to the fd. */ |
| writev (fd, iovec, iov_index); |
| |
| /* Free printed buffer data. */ |
| for (out = b->head; out && out != data; out = next) |
| { |
| next = out->next; |
| if (next) |
| next->prev = NULL; |
| else |
| b->tail = next; |
| b->head = next; |
| |
| buffer_data_free (out); |
| b->alloc--; |
| } |
| |
| free (iovec); |
| } |
| |
| /* Flush all buffer to the fd. */ |
| int |
| buffer_flush_all (struct buffer *b, int fd) |
| { |
| int ret; |
| struct buffer_data *d; |
| int iov_index; |
| struct iovec *iovec; |
| |
| if (buffer_empty (b)) |
| return 0; |
| |
| iovec = malloc (sizeof (struct iovec) * b->alloc); |
| iov_index = 0; |
| |
| for (d = b->head; d; d = d->next) |
| { |
| iovec[iov_index].iov_base = (char *)(d->data + d->sp); |
| iovec[iov_index].iov_len = d->cp - d->sp; |
| iov_index++; |
| } |
| ret = writev (fd, iovec, iov_index); |
| |
| free (iovec); |
| |
| buffer_reset (b); |
| |
| return ret; |
| } |
| |
| /* Flush all buffer to the fd. */ |
| int |
| buffer_flush_vty_all (struct buffer *b, int fd, int erase_flag, |
| int no_more_flag) |
| { |
| int nbytes; |
| 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; |
| struct buffer_data *out; |
| struct buffer_data *next; |
| |
| /* For erase and more data add two to b's buffer_data count.*/ |
| if (b->alloc == 1) |
| iov = small_iov; |
| else |
| iov = XCALLOC (MTYPE_TMP, sizeof (struct iovec) * (b->alloc + 2)); |
| |
| data = b->head; |
| 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. */ |
| for (data = b->head; data; data = data->next) |
| { |
| iov[iov_index].iov_base = (char *)(data->data + data->sp); |
| iov[iov_index].iov_len = data->cp - data->sp; |
| iov_index++; |
| } |
| |
| /* In case of `more' display need. */ |
| if (! buffer_empty (b) && !no_more_flag) |
| { |
| iov[iov_index].iov_base = more; |
| iov[iov_index].iov_len = sizeof more; |
| iov_index++; |
| } |
| |
| /* We use write or writev*/ |
| nbytes = writev (fd, iov, iov_index); |
| |
| /* Error treatment. */ |
| if (nbytes < 0) |
| { |
| if (errno == EINTR) |
| ; |
| if (errno == EWOULDBLOCK) |
| ; |
| } |
| |
| /* Free printed buffer data. */ |
| for (out = b->head; out && out != data; out = next) |
| { |
| next = out->next; |
| if (next) |
| next->prev = NULL; |
| else |
| b->tail = next; |
| b->head = next; |
| |
| b->length -= (out->cp-out->sp); |
| buffer_data_free (out); |
| b->alloc--; |
| } |
| |
| if (iov != small_iov) |
| XFREE (MTYPE_TMP, iov); |
| |
| return nbytes; |
| } |
| |
| /* Flush buffer to the file descriptor. Mainly used from vty |
| interface. */ |
| int |
| buffer_flush_vty (struct buffer *b, int fd, unsigned int size, |
| int erase_flag, int no_more_flag) |
| { |
| int nbytes; |
| 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; |
| struct buffer_data *out; |
| struct buffer_data *next; |
| |
| #ifdef IOV_MAX |
| int iov_size; |
| int total_size; |
| struct iovec *c_iov; |
| int c_nbytes; |
| #endif /* IOV_MAX */ |
| |
| /* For erase and more data add two to b's buffer_data count.*/ |
| if (b->alloc == 1) |
| iov = small_iov; |
| else |
| iov = XCALLOC (MTYPE_TMP, sizeof (struct iovec) * (b->alloc + 2)); |
| |
| data = b->head; |
| 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. */ |
| for (data = b->head; data; data = data->next) |
| { |
| iov[iov_index].iov_base = (char *)(data->data + data->sp); |
| |
| if (size <= (data->cp - data->sp)) |
| { |
| iov[iov_index++].iov_len = size; |
| data->sp += size; |
| b->length -= size; |
| if (data->sp == data->cp) |
| data = data->next; |
| break; |
| } |
| else |
| { |
| iov[iov_index++].iov_len = data->cp - data->sp; |
| size -= (data->cp - data->sp); |
| b->length -= (data->cp - data->sp); |
| data->sp = data->cp; |
| } |
| } |
| |
| /* In case of `more' display need. */ |
| if (!buffer_empty (b) && !no_more_flag) |
| { |
| iov[iov_index].iov_base = more; |
| iov[iov_index].iov_len = sizeof more; |
| iov_index++; |
| } |
| |
| /* We use write or writev*/ |
| |
| #ifdef IOV_MAX |
| /* IOV_MAX are normally defined in <sys/uio.h> , Posix.1g. |
| example: Solaris2.6 are defined IOV_MAX size at 16. */ |
| c_iov = iov; |
| total_size = iov_index; |
| nbytes = 0; |
| |
| while( total_size > 0 ) |
| { |
| /* initialize write vector size at once */ |
| iov_size = ( total_size > IOV_MAX ) ? IOV_MAX : total_size; |
| |
| c_nbytes = writev (fd, c_iov, iov_size ); |
| |
| if( c_nbytes < 0 ) |
| { |
| if(errno == EINTR) |
| ; |
| ; |
| if(errno == EWOULDBLOCK) |
| ; |
| ; |
| nbytes = c_nbytes; |
| break; |
| |
| } |
| |
| nbytes += c_nbytes; |
| |
| /* move pointer io-vector */ |
| c_iov += iov_size; |
| total_size -= iov_size; |
| } |
| #else /* IOV_MAX */ |
| nbytes = writev (fd, iov, iov_index); |
| |
| /* Error treatment. */ |
| if (nbytes < 0) |
| { |
| if (errno == EINTR) |
| ; |
| if (errno == EWOULDBLOCK) |
| ; |
| } |
| #endif /* IOV_MAX */ |
| |
| /* Free printed buffer data. */ |
| for (out = b->head; out && out != data; out = next) |
| { |
| next = out->next; |
| if (next) |
| next->prev = NULL; |
| else |
| b->tail = next; |
| b->head = next; |
| |
| buffer_data_free (out); |
| b->alloc--; |
| } |
| |
| if (iov != small_iov) |
| XFREE (MTYPE_TMP, iov); |
| |
| return nbytes; |
| } |
| |
| /* Calculate size of outputs then flush buffer to the file |
| descriptor. */ |
| int |
| buffer_flush_window (struct buffer *b, int fd, int width, int height, |
| int erase, int no_more) |
| { |
| unsigned long cp; |
| unsigned long size; |
| int lp; |
| int lineno; |
| struct buffer_data *data; |
| |
| if (height >= 2) |
| height--; |
| |
| /* We have to calculate how many bytes should be written. */ |
| lp = 0; |
| lineno = 0; |
| size = 0; |
| |
| for (data = b->head; data; data = data->next) |
| { |
| cp = data->sp; |
| |
| while (cp < data->cp) |
| { |
| if (data->data[cp] == '\n' || lp == width) |
| { |
| lineno++; |
| if (lineno == height) |
| { |
| cp++; |
| size++; |
| goto flush; |
| } |
| lp = 0; |
| } |
| cp++; |
| lp++; |
| size++; |
| } |
| } |
| |
| /* Write data to the file descriptor. */ |
| flush: |
| |
| return buffer_flush_vty (b, fd, size, erase, no_more); |
| } |
| |
| /* 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, or 1 if more flushing is |
| required later. */ |
| int |
| 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; |
| struct buffer_data *next; |
| size_t written; |
| struct iovec iov[MAX_CHUNKS]; |
| size_t iovcnt = 0; |
| size_t nbyte = 0; |
| |
| 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); |
| } |
| |
| /* only place where written should be sign compared */ |
| if ((ssize_t)(written = writev(fd,iov,iovcnt)) < 0) |
| { |
| if ((errno != EAGAIN) && (errno != EINTR)) |
| zlog_warn("buffer_flush_available write error on fd %d: %s", |
| fd,safe_strerror(errno)); |
| return 1; |
| } |
| |
| /* Free printed buffer data. */ |
| for (d = b->head; (written > 0) && d; d = next) |
| { |
| if (written < d->cp-d->sp) |
| { |
| d->sp += written; |
| b->length -= written; |
| return 1; |
| } |
| |
| written -= (d->cp-d->sp); |
| next = d->next; |
| if (next) |
| next->prev = NULL; |
| else |
| b->tail = next; |
| b->head = next; |
| |
| b->length -= (d->cp-d->sp); |
| buffer_data_free (d); |
| b->alloc--; |
| } |
| |
| return (b->head != NULL); |
| |
| #undef MAX_CHUNKS |
| #undef MAX_FLUSH |
| } |