From 9fc7ebf10b7bb638ddf6d1b4b2d562dd417866fc Mon Sep 17 00:00:00 2001 From: ajs Date: Wed, 23 Feb 2005 15:12:34 +0000 Subject: 2005-02-23 Andrew J. Schorr * buffer.h: Make the struct buffer and struct buffer_data structures private by moving them inside buffer.c. Add comments for all functions. Rename buffer_write as buffer_put (to be more consistent with the buffer_putc and buffer_putstr functions). Declare a new buffer_write function that is used to write data to a file descriptor and/or add it to the buffer queue. Remove unused function buffer_flush_vty_all. Create a new enum typedef buffer_status_t to be used as the return code for all buffer_flush* functions and buffer_write. * buffer.c: The struct buffer and struct buffer_data declarations are now private to this file. In conjunction with that, remove some unnecessary fields: struct buffer (alloc, unused_head, unused_tail, length), struct buffer_data (prev). (buffer_data_new) Removed: functionality incorporated into buffer_add. (buffer_data_free) Removed: use a macro BUFFER_DATA_FREE instead. (buffer_new) Use calloc instead of malloc + memset(zero). Supply an appropriate default size if the specified size is 0. (buffer_free) Eliminate code duplication by calling buffer_reset to free the contents of the buffer (and remove unused code related to unused_head). (buffer_empty,buffer_putc,buffer_putstr) Aesthetic change (make more compact). (buffer_reset) Use macro BUFFER_DATA_FREE. No need to set alloc and length to 0 (these fields have been removed). (buffer_add) Fix scope to be static. Call XMALLOC directly instead of calling removed buffer_data_new function. Simplify the logic (since it's now a singly-linked list instead of doubly-linked). (buffer_write) Renamed to buffer_put. Change to void, since return code of 1 was meaningless. No need to adjust length field, since it has been removed. (buffer_putw,buffer_flush,buffer_flush_vty_all,buffer_flush_vty) Remove unused functions. (buffer_flush_all) Rewrite using buffer_flush_available to eliminate a possible failure mode if IOV_MAX is less than the number of buffers on the queue. (buffer_flush_window) Incorporate logic from buffer_flush_vty. Log an error message if there is a writev error. (buffer_flush_available) Be more paranoid: check for case where buffer is already empty. Use new ERRNO_IO_RETRY macro, and use new enum for return codes. Simplify deletion logic (since it's now a singly-linked list). (buffer_write) New function for use with non-blocking I/O. * vty.h: Replace the struct vty sb_buffer field with a fixed-size (5-character) sb_buf field and an sb_len field, since using a struct buffer was inappropriate for this task. Add some useful comments about telnet window size negotiation. * vty.c: Include (no longer included by zebra.h). Remove VTY_OBUF_SIZE (instead use buffer_new default size). Make telnet_backward_char and telnet_space_char static const. (vty_out) Replace buffer_write with buffer_put. (vty_log_out) Check for I/O errors. If fatal, close the vty session. Consolidate 3 separate writes into a single write call. (vty_will_echo,vty_command,vty_next_line,vty_previous_line, vty_end_config,vty_describe_fold,vty_clear_buf,vty_serv_sock_addrinfo, vty_serv_sock_family,vty_serv_un,vty_use_backup_config,exec_timeout, vty_config_write,vty_save_cwd) Fix scope to static. (vty_new) Let buffer_new use its default buffer size. (vty_write) Fix signature: 2nd arg should be const char *. Replaced buffer_write with buffer_put. (vty_telnet_option) Fix minor bug (window height or width greater than 255 was broken). Use sb_buf and sb_len instead of removed sb_buffer (which was being used improperly). (vty_read) On error, use ERRNO_IO_RETRY to decide whether it's fatal. If the error is fatal, call buffer_reset so vty_close does not attempt to flush the data. Use new sb_buf and sb_len instead of sb_buffer to store the SB negotiation string. (vty_flush) When vty->lines is 0, call buffer_flush_available instead of buffer_flush_window. Look at the return code from buffer_flush to detect I/O errors (and in that case, log an error message and close the vty). (vty_create) Fix scope to static. Initialize sb_len to 0 instead of creating sb_buffer. (vty_accept) Set socket nonblocking. (vtysh_accept) Use new set_nonblocking function instead of calling fcntl directly. (vtysh_flush) New function called from vtysh_read (after command execution) and from vtysh_write. This flushes the buffer and reacts appropriately to the return code (by closing the vty or scheduling further flushes). (vtysh_read) Check whether error is fatal using ERRNO_IO_RETRY. If not, just try again later. Otherwise, call buffer_reset before calling vty_close (to avoid trying to flush the buffer in vty_close). Fix logic to allow case where a command does not arrive atomically in a single read call by checking for the terminating NUL char. (vtysh_write) Use new vtysh_flush helper function. (vty_close) No need to call buffer_empty, just call buffer_flush_all in any case (it will check whether the buffer is empty). Do not free sb_buffer (since it has been removed). (vty_log_fixed) Use writev instead of write. * zebra.h: Do not include , since this is used only by lib/vty.c. --- lib/ChangeLog | 94 +++++++++ lib/buffer.c | 637 ++++++++++++++++++++++------------------------------------ lib/buffer.h | 116 ++++++----- lib/vty.c | 329 ++++++++++++++++++------------ lib/vty.h | 15 +- lib/zebra.h | 1 - 6 files changed, 610 insertions(+), 582 deletions(-) (limited to 'lib') diff --git a/lib/ChangeLog b/lib/ChangeLog index 6d1a229e..af3841a5 100644 --- a/lib/ChangeLog +++ b/lib/ChangeLog @@ -1,3 +1,97 @@ +2005-02-23 Andrew J. Schorr + + * buffer.h: Make the struct buffer and struct buffer_data structures + private by moving them inside buffer.c. Add comments for all + functions. Rename buffer_write as buffer_put (to be more consistent + with the buffer_putc and buffer_putstr functions). Declare a new + buffer_write function that is used to write data to a file descriptor + and/or add it to the buffer queue. Remove unused function + buffer_flush_vty_all. Create a new enum typedef buffer_status_t + to be used as the return code for all buffer_flush* functions + and buffer_write. + * buffer.c: The struct buffer and struct buffer_data declarations + are now private to this file. In conjunction with that, remove + some unnecessary fields: struct buffer (alloc, unused_head, + unused_tail, length), struct buffer_data (prev). + (buffer_data_new) Removed: functionality incorporated into buffer_add. + (buffer_data_free) Removed: use a macro BUFFER_DATA_FREE instead. + (buffer_new) Use calloc instead of malloc + memset(zero). + Supply an appropriate default size if the specified size is 0. + (buffer_free) Eliminate code duplication by calling buffer_reset to + free the contents of the buffer (and remove unused code related + to unused_head). + (buffer_empty,buffer_putc,buffer_putstr) Aesthetic change (make more + compact). + (buffer_reset) Use macro BUFFER_DATA_FREE. No need to set + alloc and length to 0 (these fields have been removed). + (buffer_add) Fix scope to be static. Call XMALLOC directly instead + of calling removed buffer_data_new function. Simplify the logic + (since it's now a singly-linked list instead of doubly-linked). + (buffer_write) Renamed to buffer_put. Change to void, since return + code of 1 was meaningless. No need to adjust length field, since + it has been removed. + (buffer_putw,buffer_flush,buffer_flush_vty_all,buffer_flush_vty) + Remove unused functions. + (buffer_flush_all) Rewrite using buffer_flush_available to eliminate + a possible failure mode if IOV_MAX is less than the number of buffers + on the queue. + (buffer_flush_window) Incorporate logic from buffer_flush_vty. + Log an error message if there is a writev error. + (buffer_flush_available) Be more paranoid: check for case where + buffer is already empty. Use new ERRNO_IO_RETRY macro, and use + new enum for return codes. Simplify deletion logic (since it's + now a singly-linked list). + (buffer_write) New function for use with non-blocking I/O. + * vty.h: Replace the struct vty sb_buffer field with a fixed-size + (5-character) sb_buf field and an sb_len field, since using + a struct buffer was inappropriate for this task. Add some useful + comments about telnet window size negotiation. + * vty.c: Include (no longer included by zebra.h). + Remove VTY_OBUF_SIZE (instead use buffer_new default size). + Make telnet_backward_char and telnet_space_char static const. + (vty_out) Replace buffer_write with buffer_put. + (vty_log_out) Check for I/O errors. If fatal, close the vty session. + Consolidate 3 separate writes into a single write call. + (vty_will_echo,vty_command,vty_next_line,vty_previous_line, + vty_end_config,vty_describe_fold,vty_clear_buf,vty_serv_sock_addrinfo, + vty_serv_sock_family,vty_serv_un,vty_use_backup_config,exec_timeout, + vty_config_write,vty_save_cwd) Fix scope to static. + (vty_new) Let buffer_new use its default buffer size. + (vty_write) Fix signature: 2nd arg should be const char *. + Replaced buffer_write with buffer_put. + (vty_telnet_option) Fix minor bug (window height or width greater than + 255 was broken). Use sb_buf and sb_len instead of removed sb_buffer + (which was being used improperly). + (vty_read) On error, use ERRNO_IO_RETRY to decide whether it's fatal. + If the error is fatal, call buffer_reset so vty_close does not attempt + to flush the data. Use new sb_buf and sb_len instead of sb_buffer + to store the SB negotiation string. + (vty_flush) When vty->lines is 0, call buffer_flush_available instead + of buffer_flush_window. Look at the return code from buffer_flush + to detect I/O errors (and in that case, log an error message and + close the vty). + (vty_create) Fix scope to static. Initialize sb_len to 0 instead + of creating sb_buffer. + (vty_accept) Set socket nonblocking. + (vtysh_accept) Use new set_nonblocking function instead of calling + fcntl directly. + (vtysh_flush) New function called from vtysh_read (after command + execution) and from vtysh_write. This flushes the buffer + and reacts appropriately to the return code (by closing the vty + or scheduling further flushes). + (vtysh_read) Check whether error is fatal using ERRNO_IO_RETRY. + If not, just try again later. Otherwise, call buffer_reset before + calling vty_close (to avoid trying to flush the buffer in vty_close). + Fix logic to allow case where a command does not arrive atomically + in a single read call by checking for the terminating NUL char. + (vtysh_write) Use new vtysh_flush helper function. + (vty_close) No need to call buffer_empty, just call buffer_flush_all + in any case (it will check whether the buffer is empty). + Do not free sb_buffer (since it has been removed). + (vty_log_fixed) Use writev instead of write. + * zebra.h: Do not include , since this is used only + by lib/vty.c. + 2005-02-21 Vincenzo Eramo * pqueue.[ch]: Introduce "update" function to meet ospf spf needs. It diff --git a/lib/buffer.c b/lib/buffer.c index 8666ab78..48fd35af 100644 --- a/lib/buffer.c +++ b/lib/buffer.c @@ -25,24 +25,45 @@ #include "memory.h" #include "buffer.h" #include "log.h" +#include "network.h" #include -/* 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) +/* Buffer master. */ +struct buffer { - XFREE (MTYPE_BUFFER_DATA, d); -} + /* 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[0]; /* 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 * @@ -50,10 +71,20 @@ buffer_new (size_t size) { struct buffer *b; - b = XMALLOC (MTYPE_BUFFER, sizeof (struct buffer)); - memset (b, 0, sizeof (struct buffer)); + b = XCALLOC (MTYPE_BUFFER, sizeof (struct buffer)); - b->size = size; + 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; } @@ -62,25 +93,7 @@ buffer_new (size_t size) 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; - } - + buffer_reset(b); XFREE (MTYPE_BUFFER, b); } @@ -111,10 +124,7 @@ buffer_getstr (struct buffer *b) int buffer_empty (struct buffer *b) { - if (b->tail == NULL || b->tail->cp == b->tail->sp) - return 1; - else - return 0; + return (b->head == NULL); } /* Clear and free all allocated data. */ @@ -127,48 +137,36 @@ buffer_reset (struct buffer *b) for (data = b->head; data; data = next) { next = data->next; - buffer_data_free (data); + BUFFER_DATA_FREE(data); } b->head = b->tail = NULL; - b->alloc = 0; - b->length = 0; } /* Add buffer_data to the end of buffer. */ -void +static struct buffer_data * buffer_add (struct buffer *b) { struct buffer_data *d; - d = buffer_data_new (b->size); + d = XMALLOC(MTYPE_BUFFER_DATA, offsetof(struct buffer_data, data[b->size])); + d->cp = d->sp = 0; + d->next = NULL; - if (b->tail == NULL) - { - d->prev = NULL; - d->next = NULL; - b->head = d; - b->tail = d; - } + if (b->tail) + b->tail->next = d; else - { - d->prev = b->tail; - d->next = NULL; + b->head = d; + b->tail = d; - b->tail->next = d; - b->tail = d; - } - - b->alloc++; + return d; } /* Write data to buffer. */ -int -buffer_write (struct buffer *b, const void *p, size_t size) +void +buffer_put(struct buffer *b, const void *p, size_t size) { - struct buffer_data *data; + struct buffer_data *data = b->tail; const char *ptr = p; - data = b->tail; - b->length += size; /* We use even last one byte of data buffer. */ while (size) @@ -177,10 +175,7 @@ buffer_write (struct buffer *b, const void *p, size_t size) /* If there is no data buffer add it. */ if (data == NULL || data->cp == b->size) - { - buffer_add (b); - data = b->tail; - } + data = buffer_add (b); chunk = ((size <= (b->size - data->cp)) ? size : (b->size - data->cp)); memcpy ((data->data + data->cp), ptr, chunk); @@ -188,127 +183,54 @@ buffer_write (struct buffer *b, const void *p, size_t size) ptr += chunk; data->cp += chunk; } - return 1; } /* Insert character into the buffer. */ -int +void 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; + buffer_put(b, &c, 1); } /* Put string to the buffer. */ -int +void buffer_putstr (struct buffer *b, const char *c) { - size_t size; - - size = strlen (c); - buffer_write (b, (void *) c, size); - return 1; + buffer_put(b, c, strlen(c)); } -/* 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 +/* 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) { - 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) + 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) { - iovec[iov_index].iov_base = (char *)(d->data + d->sp); - iovec[iov_index].iov_len = d->cp - d->sp; - iov_index++; + 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; } - 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) +/* 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]; @@ -317,107 +239,37 @@ buffer_flush_vty_all (struct buffer *b, int fd, int erase_flag, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 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; + int column; - /* Previously print out is performed. */ - if (erase_flag) - { - iov[iov_index].iov_base = erase; - iov[iov_index].iov_len = sizeof erase; - iov_index++; - } + if (!b->head) + return BUFFER_EMPTY; - /* Output data. */ - for (data = b->head; data; data = data->next) + if (height < 1) { - iov[iov_index].iov_base = (char *)(data->data + data->sp); - iov[iov_index].iov_len = data->cp - data->sp; - iov_index++; + zlog_warn("%s called with non-positive window height %d, forcing to 1", + __func__, height); + height = 1; } - - /* In case of `more' display need. */ - if (! buffer_empty (b) && !no_more_flag) + else if (height >= 2) + height--; + if (width < 1) { - iov[iov_index].iov_base = more; - iov[iov_index].iov_len = sizeof more; - iov_index++; + zlog_warn("%s called with non-positive window width %d, forcing to 1", + __func__, width); + width = 1; } - /* We use write or writev*/ - nbytes = writev (fd, iov, iov_index); - - /* Error treatment. */ - if (nbytes < 0) + /* For erase and more data add two to b's buffer_data count.*/ + if (b->head->next == NULL) { - if (errno == EINTR) - ; - if (errno == EWOULDBLOCK) - ; + iov_alloc = sizeof(small_iov)/sizeof(small_iov[0]); + iov = small_iov; } - - /* Free printed buffer data. */ - for (out = b->head; out && out != data; out = next) + else { - 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--; + iov_alloc = ((height*(width+2))/b->size)+10; + iov = XMALLOC(MTYPE_TMP, iov_alloc*sizeof(*iov)); } - - 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. */ @@ -429,159 +281,115 @@ buffer_flush_vty (struct buffer *b, int fd, unsigned int size, } /* Output data. */ - for (data = b->head; data; data = data->next) + 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 (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; + if (iov_index == iov_alloc) + /* This should not ordinarily happen. */ + { + iov_alloc *= 2; + if (iov != small_iov) + { + zlog_warn("%s: growing iov array to %d; " + "width %d, height %d, size %lu", + __func__, iov_alloc, width, height, (u_long)b->size); + iov = XREALLOC(MTYPE_TMP, iov, iov_alloc*sizeof(*iov)); + } + else + { + /* This should absolutely never occur. */ + zlog_err("%s: corruption detected: iov_small overflowed; " + "head %p, tail %p, head->next %p", + __func__, b->head, b->tail, 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 (!buffer_empty (b) && !no_more_flag) + 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++; } - /* We use write or writev*/ #ifdef IOV_MAX /* IOV_MAX are normally defined in , 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; - } + { + 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 */ - nbytes = writev (fd, iov, iov_index); - - /* Error treatment. */ - if (nbytes < 0) - { - if (errno == EINTR) - ; - if (errno == EWOULDBLOCK) - ; - } + 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. */ - for (out = b->head; out && out != data; out = next) + while (b->head && (b->head->sp == b->head->cp)) { - next = out->next; - if (next) - next->prev = NULL; - else - b->tail = next; - b->head = next; - - buffer_data_free (out); - b->alloc--; + 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; -} - -/* 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); + 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, or 1 if more flushing is -required later. */ -int +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) { @@ -596,7 +404,6 @@ in one shot. */ #define MAX_FLUSH 131072 struct buffer_data *d; - struct buffer_data *next; size_t written; struct iovec iov[MAX_CHUNKS]; size_t iovcnt = 0; @@ -609,40 +416,76 @@ in one shot. */ 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 != EAGAIN) && (errno != EINTR)) - zlog_warn("buffer_flush_available write error on fd %d: %s", - fd,safe_strerror(errno)); - return 1; + 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. */ - for (d = b->head; (written > 0) && d; d = next) + while (written > 0) { + struct buffer_data *d; + if (!(d = b->head)) + { + zlog_err("%s: corruption detected: buffer queue empty, " + "but written is %lu", __func__, (u_long)written); + break; + } if (written < d->cp-d->sp) { d->sp += written; - b->length -= written; - return 1; + return BUFFER_PENDING; } 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--; + if (!(b->head = d->next)) + b->tail = NULL; + BUFFER_DATA_FREE(d); } - return (b->head != NULL); + 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; + + /* Attempt to drain the previously buffered data? */ + if (b->head && (buffer_flush_available(b, fd) == BUFFER_ERROR)) + return BUFFER_ERROR; + if (b->head) + /* Buffer still not empty. */ + 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; +} diff --git a/lib/buffer.h b/lib/buffer.h index c0245a7f..249354cb 100644 --- a/lib/buffer.h +++ b/lib/buffer.h @@ -23,68 +23,80 @@ #ifndef _ZEBRA_BUFFER_H #define _ZEBRA_BUFFER_H -/* Buffer master. */ -struct buffer -{ - /* Data list. */ - struct buffer_data *head; - struct buffer_data *tail; - - /* XXX: These unsigned longs should be size_t's */ - /* Current allocated data. */ - unsigned long alloc; - - /* Size of each buffer_data chunk. */ - unsigned long size; - - /* For allocation. */ - struct buffer_data *unused_head; - struct buffer_data *unused_tail; - - /* Current total length of this buffer. */ - unsigned long length; -}; - -/* Data container. */ -struct buffer_data -{ - struct buffer_data *next; - struct buffer_data *prev; - - /* Current pointer. */ - unsigned long cp; - - /* Start pointer. */ - unsigned long sp; - - /* Actual data stream (variable length). */ - unsigned char data[0]; /* real dimension is buffer->size */ -}; - -/* Buffer prototypes. */ + +/* Create a new buffer. Memory will be allocated in chunks of the given + size. If the argument is 0, the library will supply a reasonable + default size suitable for buffering socket I/O. */ struct buffer *buffer_new (size_t); -int buffer_write (struct buffer *, const void *, size_t); + +/* Free all data in the buffer. */ +void buffer_reset (struct buffer *); + +/* This function first calls buffer_reset to release all buffered data. + Then it frees the struct buffer itself. */ void buffer_free (struct buffer *); +/* Add the given data to the end of the buffer. */ +extern void buffer_put (struct buffer *, const void *, size_t); +/* Add a single character to the end of the buffer. */ +extern void buffer_putc (struct buffer *, u_char); +/* Add a NUL-terminated string to the end of the buffer. */ +extern void buffer_putstr (struct buffer *, const char *); + /* Combine all accumulated (and unflushed) data inside the buffer into a single NUL-terminated string allocated using XMALLOC(MTYPE_TMP). Note that this function does not alter the state of the buffer, so the data is still inside waiting to be flushed. */ char *buffer_getstr (struct buffer *); -int buffer_putc (struct buffer *, u_char); -int buffer_putstr (struct buffer *, const char *); -void buffer_reset (struct buffer *); -int buffer_flush_all (struct buffer *, int); -int buffer_flush_vty_all (struct buffer *, int, int, int); -int buffer_flush_window (struct buffer *, int, int, int, int, int); +/* Returns 1 if there is no pending data in the buffer. Otherwise returns 0. */ int buffer_empty (struct buffer *); -/* buffer_flush_available attempts to flush the queued data to the given - file descriptor. It returns 0 if the buffers are now empty (after - flushing), or 1 if more data remains on the buffer queue (must be flushed - later). This function (unlike the other buffer_flush* functions) is - designed to work with non-blocking file descriptors. */ -int buffer_flush_available(struct buffer *, int fd); +typedef enum + { + /* An I/O error occurred. The buffer should be destroyed and the + file descriptor should be closed. */ + BUFFER_ERROR = -1, + + /* The data was written successfully, and the buffer is now empty + (there is no pending data waiting to be flushed). */ + BUFFER_EMPTY = 0, + + /* There is pending data in the buffer waiting to be flushed. Please + try flushing the buffer when select indicates that the file descriptor + is writeable. */ + BUFFER_PENDING = 1 + } buffer_status_t; + +/* Try to write this data to the file descriptor. Any data that cannot + be written immediately is added to the buffer queue. */ +extern buffer_status_t buffer_write(struct buffer *, int fd, + const void *, size_t); + +/* This function attempts to flush some (but perhaps not all) of + the queued data to the given file descriptor. */ +extern buffer_status_t buffer_flush_available(struct buffer *, int fd); + +/* The following 2 functions (buffer_flush_all and buffer_flush_window) + are for use in lib/vty.c only. They should not be used elsewhere. */ + +/* Call buffer_flush_available repeatedly until either all data has been + flushed, or an I/O error has been encountered, or the operation would + block. */ +extern buffer_status_t buffer_flush_all (struct buffer *, int fd); + +/* Attempt to write enough data to the given fd to fill a window of the + given width and height (and remove the data written from the buffer). + + If !no_more, then a message saying " --More-- " is appended. + If erase is true, then first overwrite the previous " --More-- " message + with spaces. + + Any write error (including EAGAIN or EINTR) will cause this function + to return -1 (because the logic for handling the erase and more features + is too complicated to retry the write later). +*/ +extern buffer_status_t buffer_flush_window (struct buffer *, int fd, int width, + int height, int erase, int no_more); #endif /* _ZEBRA_BUFFER_H */ diff --git a/lib/vty.c b/lib/vty.c index 053e9ee8..887e3baa 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -35,6 +35,9 @@ #include "filter.h" #include "vty.h" #include "privs.h" +#include "network.h" + +#include /* Vty events */ enum event @@ -50,9 +53,6 @@ enum event #endif /* VTYSH */ }; -/* Minimum size of output buffers; to be rounded up to multiple of pagesize. */ -#define VTY_OBUF_SIZE 4096 - static void vty_event (enum event, int, struct vty *); /* Extern host structure from command.c */ @@ -137,7 +137,7 @@ vty_out (struct vty *vty, const char *format, ...) p = buf; /* Pointer p must point out buffer. */ - buffer_write (vty->obuf, (u_char *) p, len); + buffer_put (vty->obuf, (u_char *) p, len); /* If p is not different with buf, it is allocated buffer. */ if (p != buf) @@ -151,24 +151,38 @@ static int vty_log_out (struct vty *vty, const char *level, const char *proto_str, const char *format, va_list va) { + int ret; int len; char buf[1024]; if (level) - snprintf (buf, sizeof buf, "%s: %s: ", level, proto_str); + len = snprintf(buf, sizeof(buf), "%s: %s: ", level, proto_str); else - snprintf (buf, sizeof buf, "%s: ", proto_str); - write (vty->fd, buf, strlen (buf)); + len = snprintf(buf, sizeof(buf), "%s: ", proto_str); + if ((len < 0) || ((size_t)len >= sizeof(buf))) + return -1; - len = vsnprintf (buf, sizeof buf, format, va); - if (len < 0) + if (((ret = vsnprintf(buf+len, sizeof(buf)-len, format, va)) < 0) || + ((size_t)((len += ret)+2) > sizeof(buf))) return -1; - write (vty->fd, (u_char *)buf, len); - snprintf (buf, sizeof buf, "\r\n"); - write (vty->fd, buf, 2); + buf[len++] = '\r'; + buf[len++] = '\n'; - return len; + if (write(vty->fd, buf, len) < 0) + { + if (ERRNO_IO_RETRY(errno)) + /* Kernel buffer is full, probably too much debugging output, so just + drop the data and ignore. */ + return -1; + /* Fatal I/O error. */ + zlog_warn("%s: write failed to vty client fd %d, closing: %s", + __func__, vty->fd, safe_strerror(errno)); + buffer_reset(vty->obuf); + vty_close(vty); + return -1; + } + return 0; } /* Output current time to the vty. */ @@ -226,7 +240,7 @@ vty_prompt (struct vty *vty) } /* Send WILL TELOPT_ECHO to remote server. */ -void +static void vty_will_echo (struct vty *vty) { unsigned char cmd[] = { IAC, WILL, TELOPT_ECHO, '\0' }; @@ -272,12 +286,10 @@ struct vty * vty_new () { struct vty *new = XCALLOC (MTYPE_VTY, sizeof (struct vty)); - int pgsz = getpagesize(); - new->obuf = (struct buffer *) buffer_new ((((VTY_OBUF_SIZE-1)/pgsz)+1)*pgsz); + new->obuf = buffer_new(0); /* Use default buffer size. */ new->buf = XCALLOC (MTYPE_VTY, VTY_BUFSIZ); new->max = VTY_BUFSIZ; - new->sb_buffer = NULL; return new; } @@ -349,7 +361,7 @@ vty_auth (struct vty *vty, char *buf) } /* Command execution over the vty interface. */ -int +static int vty_command (struct vty *vty, char *buf) { int ret; @@ -385,18 +397,18 @@ vty_command (struct vty *vty, char *buf) return ret; } -char telnet_backward_char = 0x08; -char telnet_space_char = ' '; +static const char telnet_backward_char = 0x08; +static const char telnet_space_char = ' '; /* Basic function to write buffer to vty. */ static void -vty_write (struct vty *vty, char *buf, size_t nbytes) +vty_write (struct vty *vty, const char *buf, size_t nbytes) { if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) return; /* Should we do buffering here ? And make vty_flush (vty) ? */ - buffer_write (vty->obuf, (u_char *)buf, nbytes); + buffer_put (vty->obuf, buf, nbytes); } /* Ensure length of input buffer. Is buffer is short, double it. */ @@ -517,7 +529,7 @@ vty_history_print (struct vty *vty) } /* Show next command line history. */ -void +static void vty_next_line (struct vty *vty) { int try_index; @@ -542,7 +554,7 @@ vty_next_line (struct vty *vty) } /* Show previous command line history. */ -void +static void vty_previous_line (struct vty *vty) { int try_index; @@ -611,7 +623,7 @@ vty_down_level (struct vty *vty) } /* When '^Z' is received from vty, move down to the enable mode. */ -void +static void vty_end_config (struct vty *vty) { vty_out (vty, "%s", VTY_NEWLINE); @@ -855,7 +867,7 @@ vty_complete_command (struct vty *vty) vector_only_index_free (matched); } -void +static void vty_describe_fold (struct vty *vty, int cmd_width, unsigned int desc_width, struct desc *desc) { @@ -1012,7 +1024,7 @@ vty_describe_command (struct vty *vty) vty_redraw_line (vty); } -void +static void vty_clear_buf (struct vty *vty) { memset (vty->buf, 0, vty->max); @@ -1146,36 +1158,41 @@ vty_telnet_option (struct vty *vty, unsigned char *buf, int nbytes) switch (buf[0]) { case SB: - buffer_reset(vty->sb_buffer); + vty->sb_len = 0; vty->iac_sb_in_progress = 1; return 0; break; case SE: { - char *buffer; - int length; - if (!vty->iac_sb_in_progress) return 0; - buffer = (char *)vty->sb_buffer->head->data; - length = vty->sb_buffer->length; - - if (buffer == NULL) - return 0; - - if (buffer[0] == '\0') + if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0')) { vty->iac_sb_in_progress = 0; return 0; } - switch (buffer[0]) + switch (vty->sb_buf[0]) { case TELOPT_NAWS: - if (length < 5) - break; - vty->width = buffer[2]; - vty->height = vty->lines >= 0 ? vty->lines : buffer[4]; + if (vty->sb_len != TELNET_NAWS_SB_LEN) + zlog_warn("RFC 1073 violation detected: telnet NAWS option " + "should send %d characters, but we received %lu", + TELNET_NAWS_SB_LEN, (u_long)vty->sb_len); + else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN) + zlog_err("Bug detected: sizeof(vty->sb_buf) %lu < %d, " + "too small to handle the telnet NAWS option", + (u_long)sizeof(vty->sb_buf), TELNET_NAWS_SB_LEN); + else + { + vty->width = ((vty->sb_buf[1] << 8)|vty->sb_buf[2]); + vty->height = ((vty->sb_buf[3] << 8)|vty->sb_buf[4]); +#ifdef TELNET_OPTION_DEBUG + vty_out(vty, "TELNET NAWS window size negotiation completed: " + "width %d, height %d%s", + vty->width, vty->height, VTY_NEWLINE); +#endif + } break; } vty->iac_sb_in_progress = 0; @@ -1272,9 +1289,21 @@ vty_read (struct thread *thread) vty->t_read = NULL; /* Read raw data from socket */ - nbytes = read (vty->fd, buf, VTY_READ_BUFSIZ); - if (nbytes <= 0) - vty->status = VTY_CLOSE; + if ((nbytes = read (vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) + { + if (nbytes < 0) + { + if (ERRNO_IO_RETRY(errno)) + { + vty_event (VTY_READ, vty_sock, vty); + return 0; + } + zlog_warn("%s: read error on vty client fd %d, closing: %s", + __func__, vty->fd, safe_strerror(errno)); + } + buffer_reset(vty->obuf); + vty->status = VTY_CLOSE; + } for (i = 0; i < nbytes; i++) { @@ -1293,7 +1322,9 @@ vty_read (struct thread *thread) if (vty->iac_sb_in_progress && !vty->iac) { - buffer_putc(vty->sb_buffer, buf[i]); + if (vty->sb_len < sizeof(vty->sb_buf)) + vty->sb_buf[vty->sb_len] = buf[i]; + vty->sb_len++; continue; } @@ -1459,58 +1490,64 @@ static int vty_flush (struct thread *thread) { int erase; + buffer_status_t flushrc; int vty_sock = THREAD_FD (thread); struct vty *vty = THREAD_ARG (thread); + vty->t_write = NULL; /* Tempolary disable read thread. */ - if (vty->lines == 0) - if (vty->t_read) - { - thread_cancel (vty->t_read); - vty->t_read = NULL; - } + if ((vty->lines == 0) && vty->t_read) + { + thread_cancel (vty->t_read); + vty->t_read = NULL; + } /* Function execution continue. */ - if (vty->status == VTY_MORE || vty->status == VTY_MORELINE) - erase = 1; - else - erase = 0; - - if (vty->lines == 0) - buffer_flush_window (vty->obuf, vty->fd, vty->width, 25, 0, 1); - else if (vty->status == VTY_MORELINE) - buffer_flush_window (vty->obuf, vty->fd, vty->width, 1, erase, 0); - else - buffer_flush_window (vty->obuf, vty->fd, vty->width, - vty->lines >= 0 ? vty->lines : vty->height, - erase, 0); - - if (buffer_empty (vty->obuf)) - { - if (vty->status == VTY_CLOSE) - vty_close (vty); - else - { - vty->status = VTY_NORMAL; - - if (vty->lines == 0) - vty_event (VTY_READ, vty_sock, vty); - } - } + erase = ((vty->status == VTY_MORE || vty->status == VTY_MORELINE)); + + /* N.B. if width is 0, that means we don't know the window size. */ + if ((vty->lines == 0) || (vty->width == 0)) + flushrc = buffer_flush_available(vty->obuf, vty->fd); + else if (vty->status == VTY_MORELINE) + flushrc = buffer_flush_window(vty->obuf, vty->fd, vty->width, + 1, erase, 0); + else + flushrc = buffer_flush_window(vty->obuf, vty->fd, vty->width, + vty->lines >= 0 ? vty->lines : + vty->height, + erase, 0); + switch (flushrc) + { + case BUFFER_ERROR: + zlog_warn("buffer_flush failed on vty client fd %d, closing", + vty->fd); + buffer_reset(vty->obuf); + vty_close(vty); + return 0; + case BUFFER_EMPTY: + if (vty->status == VTY_CLOSE) + vty_close (vty); else { - vty->status = VTY_MORE; - + vty->status = VTY_NORMAL; if (vty->lines == 0) - vty_event (VTY_WRITE, vty_sock, vty); + vty_event (VTY_READ, vty_sock, vty); } + break; + case BUFFER_PENDING: + /* There is more data waiting to be written. */ + vty->status = VTY_MORE; + if (vty->lines == 0) + vty_event (VTY_WRITE, vty_sock, vty); + break; + } return 0; } /* Create new vty structure. */ -struct vty * +static struct vty * vty_create (int vty_sock, union sockunion *su) { struct vty *vty; @@ -1545,7 +1582,7 @@ vty_create (int vty_sock, union sockunion *su) vty->lines = -1; vty->iac = 0; vty->iac_sb_in_progress = 0; - vty->sb_buffer = buffer_new (1024); + vty->sb_len = 0; if (! no_password_check) { @@ -1608,6 +1645,7 @@ vty_accept (struct thread *thread) zlog_warn ("can't accept vty socket : %s", safe_strerror (errno)); return -1; } + set_nonblocking(vty_sock); p = sockunion2hostprefix (&su); @@ -1670,7 +1708,7 @@ vty_accept (struct thread *thread) } #if defined(HAVE_IPV6) && !defined(NRL) -void +static void vty_serv_sock_addrinfo (const char *hostname, unsigned short port) { int ret; @@ -1736,7 +1774,7 @@ vty_serv_sock_addrinfo (const char *hostname, unsigned short port) #endif /* HAVE_IPV6 && ! NRL */ /* Make vty server socket. */ -void +static void vty_serv_sock_family (const char* addr, unsigned short port, int family) { int ret; @@ -1805,7 +1843,7 @@ vty_serv_sock_family (const char* addr, unsigned short port, int family) #include /* VTY shell UNIX domain socket. */ -void +static void vty_serv_un (const char *path) { int ret; @@ -1879,7 +1917,6 @@ vtysh_accept (struct thread *thread) int accept_sock; int sock; int client_len; - int flags; struct sockaddr_un client; struct vty *vty; @@ -1899,12 +1936,10 @@ vtysh_accept (struct thread *thread) return -1; } - /* set to non-blocking*/ - if ( ((flags = fcntl (sock, F_GETFL)) == -1) - || (fcntl (sock, F_SETFL, flags|O_NONBLOCK) == -1) ) + if (set_nonblocking(sock) < 0) { - zlog_warn ("vtysh_accept: could not set vty socket to non-blocking," - " %s, closing", safe_strerror (errno)); + zlog_warn ("vtysh_accept: could not set vty socket %d to non-blocking," + " %s, closing", sock, safe_strerror (errno)); close (sock); return -1; } @@ -1923,6 +1958,26 @@ vtysh_accept (struct thread *thread) return 0; } +static int +vtysh_flush(struct vty *vty) +{ + switch (buffer_flush_available(vty->obuf, vty->fd)) + { + case BUFFER_PENDING: + vty_event(VTYSH_WRITE, vty->fd, vty); + break; + case BUFFER_ERROR: + zlog_warn("%s: write error to fd %d, closing", __func__, vty->fd); + buffer_reset(vty->obuf); + vty_close(vty); + return -1; + break; + case BUFFER_EMPTY: + break; + } + return 0; +} + static int vtysh_read (struct thread *thread) { @@ -1931,15 +1986,26 @@ vtysh_read (struct thread *thread) int nbytes; struct vty *vty; unsigned char buf[VTY_READ_BUFSIZ]; + unsigned char *p; u_char header[4] = {0, 0, 0, 0}; sock = THREAD_FD (thread); vty = THREAD_ARG (thread); vty->t_read = NULL; - nbytes = read (sock, buf, VTY_READ_BUFSIZ); - if (nbytes <= 0) + if ((nbytes = read (sock, buf, VTY_READ_BUFSIZ)) <= 0) { + if (nbytes < 0) + { + if (ERRNO_IO_RETRY(errno)) + { + vty_event (VTYSH_READ, sock, vty); + return 0; + } + zlog_warn("%s: read failed on vtysh client fd %d, closing: %s", + __func__, sock, safe_strerror(errno)); + } + buffer_reset(vty->obuf); vty_close (vty); #ifdef VTYSH_DEBUG printf ("close vtysh\n"); @@ -1948,28 +2014,35 @@ vtysh_read (struct thread *thread) } #ifdef VTYSH_DEBUG - printf ("line: %s\n", buf); + printf ("line: %.*s\n", nbytes, buf); #endif /* VTYSH_DEBUG */ - vty_ensure (vty, nbytes); - memcpy (vty->buf, buf, nbytes); - - /* Pass this line to parser. */ - ret = vty_execute (vty); - - vty_clear_buf (vty); + for (p = buf; p < buf+nbytes; p++) + { + vty_ensure(vty, vty->length+1); + vty->buf[vty->length++] = *p; + if (*p == '\0') + { + /* Pass this line to parser. */ + ret = vty_execute (vty); + /* Note that vty_execute clears the command buffer and resets + vty->length to 0. */ - /* Return result. */ + /* Return result. */ #ifdef VTYSH_DEBUG - printf ("result: %d\n", ret); - printf ("vtysh node: %d\n", vty->node); + printf ("result: %d\n", ret); + printf ("vtysh node: %d\n", vty->node); #endif /* VTYSH_DEBUG */ - header[3] = ret; - buffer_write(vty->obuf, header, 4); + header[3] = ret; + buffer_put(vty->obuf, header, 4); + + if (!vty->t_write && (vtysh_flush(vty) < 0)) + /* Try to flush results; exit if a write error occurs. */ + return 0; + } + } - if (!vty->t_write && buffer_flush_available(vty->obuf, vty->fd)) - vty_event (VTYSH_WRITE, vty->fd, vty); vty_event (VTYSH_READ, sock, vty); return 0; @@ -1981,8 +2054,7 @@ vtysh_write (struct thread *thread) struct vty *vty = THREAD_ARG (thread); vty->t_write = NULL; - if (buffer_flush_available(vty->obuf, vty->fd)) - vty_event (VTYSH_WRITE, vty->fd, vty); + vtysh_flush(vty); return 0; } @@ -2028,16 +2100,11 @@ vty_close (struct vty *vty) thread_cancel (vty->t_timeout); /* Flush buffer. */ - if (! buffer_empty (vty->obuf)) - buffer_flush_all (vty->obuf, vty->fd); + buffer_flush_all (vty->obuf, vty->fd); /* Free input buffer. */ buffer_free (vty->obuf); - /* Free SB buffer. */ - if (vty->sb_buffer) - buffer_free (vty->sb_buffer); - /* Free command history. */ for (i = 0; i < VTY_MAXHIST; i++) if (vty->hist[i]) @@ -2118,7 +2185,7 @@ vty_read_file (FILE *confp) vty_close (vty); } -FILE * +static FILE * vty_use_backup_config (char *fullpath) { char *fullpath_sav, *fullpath_tmp; @@ -2298,16 +2365,20 @@ void vty_log_fixed (const char *buf, size_t len) { unsigned int i; + struct iovec iov[2]; + + iov[0].iov_base = buf; + iov[0].iov_len = len; + iov[1].iov_base = "\r\n"; + iov[1].iov_len = 2; for (i = 0; i < vector_max (vtyvec); i++) { struct vty *vty; - if ((vty = vector_slot (vtyvec, i)) != NULL) - if (vty->monitor) - { - write(vty->fd, buf, len); - write(vty->fd, "\r\n", 2); - } + if (((vty = vector_slot (vtyvec, i)) != NULL) && vty->monitor) + /* N.B. We don't care about the return code, since process is + most likely just about to die anyway. */ + writev(vty->fd, iov, 2); } } @@ -2417,7 +2488,7 @@ DEFUN (line_vty, } /* Set time out value. */ -int +static int exec_timeout (struct vty *vty, const char *min_str, const char *sec_str) { unsigned long timeout = 0; @@ -2635,7 +2706,7 @@ DEFUN (show_history, } /* Display current configuration. */ -int +static int vty_config_write (struct vty *vty) { vty_out (vty, "line vty%s", VTY_NEWLINE); @@ -2749,8 +2820,8 @@ vty_finish () } } -void -vty_save_cwd () +static void +vty_save_cwd (void) { char cwd[MAXPATHLEN]; char *c; diff --git a/lib/vty.h b/lib/vty.h index 8cd2b4e3..0290a836 100644 --- a/lib/vty.h +++ b/lib/vty.h @@ -81,12 +81,21 @@ struct vty /* Current vty status. */ enum {VTY_NORMAL, VTY_CLOSE, VTY_MORE, VTY_MORELINE} status; - /* IAC handling */ + /* IAC handling: was the last character received the + IAC (interpret-as-command) escape character (and therefore the next + character will be the command code)? Refer to Telnet RFC 854. */ unsigned char iac; - /* IAC SB handling */ + /* IAC SB (option subnegotiation) handling */ unsigned char iac_sb_in_progress; - struct buffer *sb_buffer; + /* At the moment, we care only about the NAWS (window size) negotiation, + and that requires just a 5-character buffer (RFC 1073): + <16-bit width> <16-bit height> */ +#define TELNET_NAWS_SB_LEN 5 + unsigned char sb_buf[TELNET_NAWS_SB_LEN]; + /* How many subnegotiation characters have we received? We just drop + those that do not fit in the buffer. */ + size_t sb_len; /* Window width/height. */ int width; diff --git a/lib/zebra.h b/lib/zebra.h index b952ee4f..ac236e5d 100644 --- a/lib/zebra.h +++ b/lib/zebra.h @@ -161,7 +161,6 @@ typedef int socklen_t; #endif /* HAVE_NETDB_H */ #include -#include #ifdef HAVE_INET_ND_H #include -- cgit v1.2.1