2005-02-23 Andrew J. Schorr <ajschorr@alumni.princeton.edu>

	* 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 <arpa/telnet.h> (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 <arpa/telnet.h>, since this is used only
	  by lib/vty.c.
diff --git a/lib/ChangeLog b/lib/ChangeLog
index 6d1a229..af3841a 100644
--- a/lib/ChangeLog
+++ b/lib/ChangeLog
@@ -1,3 +1,97 @@
+2005-02-23 Andrew J. Schorr <ajschorr@alumni.princeton.edu>
+
+	* 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 <arpa/telnet.h> (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 <arpa/telnet.h>, since this is used only
+	  by lib/vty.c.
+
 2005-02-21 Vincenzo Eramo <eramo at infocom.ing.uniroma1.it>
 
 	* pqueue.[ch]: Introduce "update" function to meet ospf spf needs. It
diff --git a/lib/buffer.c b/lib/buffer.c
index 8666ab7..48fd35a 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 <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)
+/* 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 @@
 {
   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 @@
 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 @@
 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 @@
   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 @@
 
       /* 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 @@
       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;
+  buffer_status_t ret;
+  struct buffer_data *head;
+  size_t head_sp;
 
-  if (buffer_empty (b))
-    return 0;
-
-  iovec = malloc (sizeof (struct iovec) * b->alloc);
-  iov_index = 0;
-
-  for (d = b->head; d; d = d->next)
+  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,16 +239,37 @@
 		   ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
 		   0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08};
   struct buffer_data *data;
-  struct buffer_data *out;
-  struct buffer_data *next;
+  int column;
+
+  if (!b->head)
+    return BUFFER_EMPTY;
+
+  if (height < 1)
+    {
+      zlog_warn("%s called with non-positive window height %d, forcing to 1",
+      		__func__, height);
+      height = 1;
+    }
+  else if (height >= 2)
+    height--;
+  if (width < 1)
+    {
+      zlog_warn("%s called with non-positive window width %d, forcing to 1",
+      		__func__, width);
+      width = 1;
+    }
 
   /* For erase and more data add two to b's buffer_data count.*/
-  if (b->alloc == 1)
-    iov = small_iov;
+  if (b->head->next == NULL)
+    {
+      iov_alloc = sizeof(small_iov)/sizeof(small_iov[0]);
+      iov = small_iov;
+    }
   else
-    iov = XCALLOC (MTYPE_TMP, sizeof (struct iovec) * (b->alloc + 2));
-
-  data = b->head;
+    {
+      iov_alloc = ((height*(width+2))/b->size)+10;
+      iov = XMALLOC(MTYPE_TMP, iov_alloc*sizeof(*iov));
+    }
   iov_index = 0;
 
   /* Previously print out is performed. */
@@ -338,250 +281,115 @@
     }
 
   /* 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 = data->cp - data->sp;
-      iov_index++;
+      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)
+	    {
+	      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*/
-  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;
+  {
+    struct iovec *c_iov = iov;
+    nbytes = 0; /* Make sure it's initialized. */
 
-  while( total_size > 0 )
-    {
-       /* initialize write vector size at once */
-       iov_size = ( total_size > IOV_MAX ) ? IOV_MAX : total_size;
+    while (iov_index > 0)
+      {
+	 int iov_size;
 
-       c_nbytes = writev (fd, c_iov, 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;
+	   }
 
-       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;
-    }
+	 /* 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 @@
 #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 @@
       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 c0245a7..249354c 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 053e9ee..887e3ba 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 <arpa/telnet.h>
 
 /* Vty events */
 enum event 
@@ -50,9 +53,6 @@
 #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 @@
 	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 @@
 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 = vsnprintf (buf, sizeof buf, format, va);
-  if (len < 0)
+    len = snprintf(buf, sizeof(buf), "%s: ", proto_str);
+  if ((len < 0) || ((size_t)len >= sizeof(buf)))
     return -1;
-  write (vty->fd, (u_char *)buf, len);
 
-  snprintf (buf, sizeof buf, "\r\n");
-  write (vty->fd, buf, 2);
+  if (((ret = vsnprintf(buf+len, sizeof(buf)-len, format, va)) < 0) ||
+      ((size_t)((len += ret)+2) > sizeof(buf)))
+    return -1;
 
-  return len;
+  buf[len++] = '\r';
+  buf[len++] = '\n';
+
+  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 @@
 }
 
 /* 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 @@
 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 @@
 }
 
 /* Command execution over the vty interface. */
-int
+static int
 vty_command (struct vty *vty, char *buf)
 {
   int ret;
@@ -385,18 +397,18 @@
   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 @@
 }
 
 /* Show next command line history. */
-void
+static void
 vty_next_line (struct vty *vty)
 {
   int try_index;
@@ -542,7 +554,7 @@
 }
 
 /* Show previous command line history. */
-void
+static void
 vty_previous_line (struct vty *vty)
 {
   int try_index;
@@ -611,7 +623,7 @@
 }
 
 /* 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 @@
     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_redraw_line (vty);
 }
 
-void
+static void
 vty_clear_buf (struct vty *vty)
 {
   memset (vty->buf, 0, vty->max);
@@ -1146,36 +1158,41 @@
   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->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 @@
       
       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 @@
 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;
+  erase = ((vty->status == VTY_MORE || vty->status == VTY_MORELINE));
 
-      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);
-	    }
-	}
+  /* 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->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 @@
       zlog_warn ("can't accept vty socket : %s", safe_strerror (errno));
       return -1;
     }
+  set_nonblocking(vty_sock);
 
   p = sockunion2hostprefix (&su);
 
@@ -1670,7 +1708,7 @@
 }
 
 #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 @@
 #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 @@
 #include <sys/un.h>
 
 /* VTY shell UNIX domain socket. */
-void
+static void
 vty_serv_un (const char *path)
 {
   int ret;
@@ -1879,7 +1917,6 @@
   int accept_sock;
   int sock;
   int client_len;
-  int flags;
   struct sockaddr_un client;
   struct vty *vty;
   
@@ -1899,12 +1936,10 @@
       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;
     }
@@ -1924,6 +1959,26 @@
 }
 
 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)
 {
   int ret;
@@ -1931,15 +1986,26 @@
   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 @@
     }
 
 #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);
+  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. */
 
-  vty_clear_buf (vty);
-
-  /* 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 && buffer_flush_available(vty->obuf, vty->fd))
-    vty_event (VTYSH_WRITE, vty->fd, vty);
+	  if (!vty->t_write && (vtysh_flush(vty) < 0))
+	    /* Try to flush results; exit if a write error occurs. */
+	    return 0;
+	}
+    }
+
   vty_event (VTYSH_READ, sock, vty);
 
   return 0;
@@ -1981,8 +2054,7 @@
   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 @@
     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_close (vty);
 }
 
-FILE *
+static FILE *
 vty_use_backup_config (char *fullpath)
 {
   char *fullpath_sav, *fullpath_tmp;
@@ -2298,16 +2365,20 @@
 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 @@
 }
 
 /* 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 @@
 }
 
 /* Display current configuration. */
-int
+static int
 vty_config_write (struct vty *vty)
 {
   vty_out (vty, "line vty%s", VTY_NEWLINE);
@@ -2749,8 +2820,8 @@
     }
 }
 
-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 8cd2b4e..0290a83 100644
--- a/lib/vty.h
+++ b/lib/vty.h
@@ -81,12 +81,21 @@
   /* 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):
+       <NAWS char> <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 b952ee4..ac236e5 100644
--- a/lib/zebra.h
+++ b/lib/zebra.h
@@ -161,7 +161,6 @@
 #endif /* HAVE_NETDB_H */
 
 #include <arpa/inet.h>
-#include <arpa/telnet.h>
 
 #ifdef HAVE_INET_ND_H
 #include <inet/nd.h>