mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-11-04 03:29:06 +00:00 
			
		
		
		
	This is a rather large mechanical commit that splits up the memory types defined in lib/memtypes.c and distributes them into *_memory.[ch] files in the individual daemons. The zebra change is slightly annoying because there is no nice place to put the #include "zebra_memory.h" statement. bgpd, ospf6d, isisd and some tests were reusing MTYPEs defined in the library for its own use. This is bad practice and would break when the memtype are made static. Acked-by: Vincent JARDIN <vincent.jardin@6wind.com> Acked-by: Donald Sharp <sharpd@cumulusnetworks.com> [CF: rebased for cmaster-next] Signed-off-by: David Lamparter <equinox@opensourcerouting.org> Signed-off-by: Christian Franke <chris@opensourcerouting.org>
		
			
				
	
	
		
			499 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			499 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Buffering of output and input. 
 | 
						|
 * Copyright (C) 1998 Kunihiro Ishiguro
 | 
						|
 *
 | 
						|
 * This file is part of GNU Zebra.
 | 
						|
 *
 | 
						|
 * GNU Zebra is free software; you can redistribute it and/or modify
 | 
						|
 * it under the terms of the GNU General Public License as published
 | 
						|
 * by the Free Software Foundation; either version 2, or (at your
 | 
						|
 * option) any later version.
 | 
						|
 * 
 | 
						|
 * GNU Zebra is distributed in the hope that it will be useful, but
 | 
						|
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
 * General Public License for more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU General Public License
 | 
						|
 * along with GNU Zebra; see the file COPYING.  If not, write to the
 | 
						|
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 | 
						|
 * Boston, MA 02111-1307, USA. 
 | 
						|
 */
 | 
						|
 | 
						|
#include <zebra.h>
 | 
						|
 | 
						|
#include "memory.h"
 | 
						|
#include "buffer.h"
 | 
						|
#include "log.h"
 | 
						|
#include "network.h"
 | 
						|
#include <stddef.h>
 | 
						|
 | 
						|
DEFINE_MTYPE_STATIC(LIB, BUFFER,      "Buffer")
 | 
						|
DEFINE_MTYPE_STATIC(LIB, BUFFER_DATA, "Buffer data")
 | 
						|
 | 
						|
/* Buffer master. */
 | 
						|
struct buffer
 | 
						|
{
 | 
						|
  /* Data list. */
 | 
						|
  struct buffer_data *head;
 | 
						|
  struct buffer_data *tail;
 | 
						|
  
 | 
						|
  /* Size of each buffer_data chunk. */
 | 
						|
  size_t size;
 | 
						|
};
 | 
						|
 | 
						|
/* Data container. */
 | 
						|
struct buffer_data
 | 
						|
{
 | 
						|
  struct buffer_data *next;
 | 
						|
 | 
						|
  /* Location to add new data. */
 | 
						|
  size_t cp;
 | 
						|
 | 
						|
  /* Pointer to data not yet flushed. */
 | 
						|
  size_t sp;
 | 
						|
 | 
						|
  /* Actual data stream (variable length). */
 | 
						|
  unsigned char data[];  /* real dimension is buffer->size */
 | 
						|
};
 | 
						|
 | 
						|
/* It should always be true that: 0 <= sp <= cp <= size */
 | 
						|
 | 
						|
/* Default buffer size (used if none specified).  It is rounded up to the
 | 
						|
   next page boundery. */
 | 
						|
#define BUFFER_SIZE_DEFAULT		4096
 | 
						|
 | 
						|
 | 
						|
#define BUFFER_DATA_FREE(D) XFREE(MTYPE_BUFFER_DATA, (D))
 | 
						|
 | 
						|
/* Make new buffer. */
 | 
						|
struct buffer *
 | 
						|
buffer_new (size_t size)
 | 
						|
{
 | 
						|
  struct buffer *b;
 | 
						|
 | 
						|
  b = XCALLOC (MTYPE_BUFFER, sizeof (struct buffer));
 | 
						|
 | 
						|
  if (size)
 | 
						|
    b->size = size;
 | 
						|
  else
 | 
						|
    {
 | 
						|
      static size_t default_size;
 | 
						|
      if (!default_size)
 | 
						|
        {
 | 
						|
	  long pgsz = sysconf(_SC_PAGESIZE);
 | 
						|
	  default_size = ((((BUFFER_SIZE_DEFAULT-1)/pgsz)+1)*pgsz);
 | 
						|
	}
 | 
						|
      b->size = default_size;
 | 
						|
    }
 | 
						|
 | 
						|
  return b;
 | 
						|
}
 | 
						|
 | 
						|
/* Free buffer. */
 | 
						|
void
 | 
						|
buffer_free (struct buffer *b)
 | 
						|
{
 | 
						|
  buffer_reset(b);
 | 
						|
  XFREE (MTYPE_BUFFER, b);
 | 
						|
}
 | 
						|
 | 
						|
/* Make string clone. */
 | 
						|
char *
 | 
						|
buffer_getstr (struct buffer *b)
 | 
						|
{
 | 
						|
  size_t totlen = 0;
 | 
						|
  struct buffer_data *data;
 | 
						|
  char *s;
 | 
						|
  char *p;
 | 
						|
 | 
						|
  for (data = b->head; data; data = data->next)
 | 
						|
    totlen += data->cp - data->sp;
 | 
						|
  if (!(s = XMALLOC(MTYPE_TMP, totlen+1)))
 | 
						|
    return NULL;
 | 
						|
  p = s;
 | 
						|
  for (data = b->head; data; data = data->next)
 | 
						|
    {
 | 
						|
      memcpy(p, data->data + data->sp, data->cp - data->sp);
 | 
						|
      p += data->cp - data->sp;
 | 
						|
    }
 | 
						|
  *p = '\0';
 | 
						|
  return s;
 | 
						|
}
 | 
						|
 | 
						|
/* Return 1 if buffer is empty. */
 | 
						|
int
 | 
						|
buffer_empty (struct buffer *b)
 | 
						|
{
 | 
						|
  return (b->head == NULL);
 | 
						|
}
 | 
						|
 | 
						|
/* Clear and free all allocated data. */
 | 
						|
void
 | 
						|
buffer_reset (struct buffer *b)
 | 
						|
{
 | 
						|
  struct buffer_data *data;
 | 
						|
  struct buffer_data *next;
 | 
						|
  
 | 
						|
  for (data = b->head; data; data = next)
 | 
						|
    {
 | 
						|
      next = data->next;
 | 
						|
      BUFFER_DATA_FREE(data);
 | 
						|
    }
 | 
						|
  b->head = b->tail = NULL;
 | 
						|
}
 | 
						|
 | 
						|
/* Add buffer_data to the end of buffer. */
 | 
						|
static struct buffer_data *
 | 
						|
buffer_add (struct buffer *b)
 | 
						|
{
 | 
						|
  struct buffer_data *d;
 | 
						|
 | 
						|
  d = XMALLOC(MTYPE_BUFFER_DATA, offsetof(struct buffer_data, data) + b->size);
 | 
						|
  d->cp = d->sp = 0;
 | 
						|
  d->next = NULL;
 | 
						|
 | 
						|
  if (b->tail)
 | 
						|
    b->tail->next = d;
 | 
						|
  else
 | 
						|
    b->head = d;
 | 
						|
  b->tail = d;
 | 
						|
 | 
						|
  return d;
 | 
						|
}
 | 
						|
 | 
						|
/* Write data to buffer. */
 | 
						|
void
 | 
						|
buffer_put(struct buffer *b, const void *p, size_t size)
 | 
						|
{
 | 
						|
  struct buffer_data *data = b->tail;
 | 
						|
  const char *ptr = p;
 | 
						|
 | 
						|
  /* We use even last one byte of data buffer. */
 | 
						|
  while (size)    
 | 
						|
    {
 | 
						|
      size_t chunk;
 | 
						|
 | 
						|
      /* If there is no data buffer add it. */
 | 
						|
      if (data == NULL || data->cp == b->size)
 | 
						|
	data = buffer_add (b);
 | 
						|
 | 
						|
      chunk = ((size <= (b->size - data->cp)) ? size : (b->size - data->cp));
 | 
						|
      memcpy ((data->data + data->cp), ptr, chunk);
 | 
						|
      size -= chunk;
 | 
						|
      ptr += chunk;
 | 
						|
      data->cp += chunk;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/* Insert character into the buffer. */
 | 
						|
void
 | 
						|
buffer_putc (struct buffer *b, u_char c)
 | 
						|
{
 | 
						|
  buffer_put(b, &c, 1);
 | 
						|
}
 | 
						|
 | 
						|
/* Put string to the buffer. */
 | 
						|
void
 | 
						|
buffer_putstr (struct buffer *b, const char *c)
 | 
						|
{
 | 
						|
  buffer_put(b, c, strlen(c));
 | 
						|
}
 | 
						|
 | 
						|
/* Keep flushing data to the fd until the buffer is empty or an error is
 | 
						|
   encountered or the operation would block. */
 | 
						|
buffer_status_t
 | 
						|
buffer_flush_all (struct buffer *b, int fd)
 | 
						|
{
 | 
						|
  buffer_status_t ret;
 | 
						|
  struct buffer_data *head;
 | 
						|
  size_t head_sp;
 | 
						|
 | 
						|
  if (!b->head)
 | 
						|
    return BUFFER_EMPTY;
 | 
						|
  head_sp = (head = b->head)->sp;
 | 
						|
  /* Flush all data. */
 | 
						|
  while ((ret = buffer_flush_available(b, fd)) == BUFFER_PENDING)
 | 
						|
    {
 | 
						|
      if ((b->head == head) && (head_sp == head->sp) && (errno != EINTR))
 | 
						|
        /* No data was flushed, so kernel buffer must be full. */
 | 
						|
	return ret;
 | 
						|
      head_sp = (head = b->head)->sp;
 | 
						|
    }
 | 
						|
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* Flush enough data to fill a terminal window of the given scene (used only
 | 
						|
   by vty telnet interface). */
 | 
						|
buffer_status_t
 | 
						|
buffer_flush_window (struct buffer *b, int fd, int width, int height, 
 | 
						|
		     int erase_flag, int no_more_flag)
 | 
						|
{
 | 
						|
  int nbytes;
 | 
						|
  int iov_alloc;
 | 
						|
  int iov_index;
 | 
						|
  struct iovec *iov;
 | 
						|
  struct iovec small_iov[3];
 | 
						|
  char more[] = " --More-- ";
 | 
						|
  char erase[] = { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
 | 
						|
		   ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
 | 
						|
		   0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08};
 | 
						|
  struct buffer_data *data;
 | 
						|
  int column;
 | 
						|
 | 
						|
  if (!b->head)
 | 
						|
    return BUFFER_EMPTY;
 | 
						|
 | 
						|
  if (height < 1)
 | 
						|
    {
 | 
						|
      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->head->next == NULL)
 | 
						|
    {
 | 
						|
      iov_alloc = array_size(small_iov);
 | 
						|
      iov = small_iov;
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
      iov_alloc = ((height*(width+2))/b->size)+10;
 | 
						|
      iov = XMALLOC(MTYPE_TMP, iov_alloc*sizeof(*iov));
 | 
						|
    }
 | 
						|
  iov_index = 0;
 | 
						|
 | 
						|
  /* Previously print out is performed. */
 | 
						|
  if (erase_flag)
 | 
						|
    {
 | 
						|
      iov[iov_index].iov_base = erase;
 | 
						|
      iov[iov_index].iov_len = sizeof erase;
 | 
						|
      iov_index++;
 | 
						|
    }
 | 
						|
 | 
						|
  /* Output data. */
 | 
						|
  column = 1;  /* Column position of next character displayed. */
 | 
						|
  for (data = b->head; data && (height > 0); data = data->next)
 | 
						|
    {
 | 
						|
      size_t cp;
 | 
						|
 | 
						|
      cp = data->sp;
 | 
						|
      while ((cp < data->cp) && (height > 0))
 | 
						|
        {
 | 
						|
	  /* Calculate lines remaining and column position after displaying
 | 
						|
	     this character. */
 | 
						|
	  if (data->data[cp] == '\r')
 | 
						|
	    column = 1;
 | 
						|
	  else if ((data->data[cp] == '\n') || (column == width))
 | 
						|
	    {
 | 
						|
	      column = 1;
 | 
						|
	      height--;
 | 
						|
	    }
 | 
						|
	  else
 | 
						|
	    column++;
 | 
						|
	  cp++;
 | 
						|
        }
 | 
						|
      iov[iov_index].iov_base = (char *)(data->data + data->sp);
 | 
						|
      iov[iov_index++].iov_len = cp-data->sp;
 | 
						|
      data->sp = cp;
 | 
						|
 | 
						|
      if (iov_index == iov_alloc)
 | 
						|
	/* This should not ordinarily happen. */
 | 
						|
        {
 | 
						|
	  iov_alloc *= 2;
 | 
						|
	  if (iov != small_iov)
 | 
						|
	    {
 | 
						|
	      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__, (void *)b->head, (void *)b->tail,
 | 
						|
		       (void *)b->head->next);
 | 
						|
	      iov = XMALLOC(MTYPE_TMP, iov_alloc*sizeof(*iov));
 | 
						|
	      memcpy(iov, small_iov, sizeof(small_iov));
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  /* In case of `more' display need. */
 | 
						|
  if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag)
 | 
						|
    {
 | 
						|
      iov[iov_index].iov_base = more;
 | 
						|
      iov[iov_index].iov_len = sizeof more;
 | 
						|
      iov_index++;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
#ifdef IOV_MAX
 | 
						|
  /* IOV_MAX are normally defined in <sys/uio.h> , Posix.1g.
 | 
						|
     example: Solaris2.6 are defined IOV_MAX size at 16.     */
 | 
						|
  {
 | 
						|
    struct iovec *c_iov = iov;
 | 
						|
    nbytes = 0; /* Make sure it's initialized. */
 | 
						|
 | 
						|
    while (iov_index > 0)
 | 
						|
      {
 | 
						|
	 int iov_size;
 | 
						|
 | 
						|
	 iov_size = ((iov_index > IOV_MAX) ? IOV_MAX : iov_index);
 | 
						|
	 if ((nbytes = writev(fd, c_iov, iov_size)) < 0)
 | 
						|
	   {
 | 
						|
	     zlog_warn("%s: writev to fd %d failed: %s",
 | 
						|
		       __func__, fd, safe_strerror(errno));
 | 
						|
	     break;
 | 
						|
	   }
 | 
						|
 | 
						|
	 /* move pointer io-vector */
 | 
						|
	 c_iov += iov_size;
 | 
						|
	 iov_index -= iov_size;
 | 
						|
      }
 | 
						|
  }
 | 
						|
#else  /* IOV_MAX */
 | 
						|
   if ((nbytes = writev (fd, iov, iov_index)) < 0)
 | 
						|
     zlog_warn("%s: writev to fd %d failed: %s",
 | 
						|
	       __func__, fd, safe_strerror(errno));
 | 
						|
#endif /* IOV_MAX */
 | 
						|
 | 
						|
  /* Free printed buffer data. */
 | 
						|
  while (b->head && (b->head->sp == b->head->cp))
 | 
						|
    {
 | 
						|
      struct buffer_data *del;
 | 
						|
      if (!(b->head = (del = b->head)->next))
 | 
						|
        b->tail = NULL;
 | 
						|
      BUFFER_DATA_FREE(del);
 | 
						|
    }
 | 
						|
 | 
						|
  if (iov != small_iov)
 | 
						|
    XFREE (MTYPE_TMP, iov);
 | 
						|
 | 
						|
  return (nbytes < 0) ? BUFFER_ERROR :
 | 
						|
  			(b->head ? BUFFER_PENDING : BUFFER_EMPTY);
 | 
						|
}
 | 
						|
 | 
						|
/* This function (unlike other buffer_flush* functions above) is designed
 | 
						|
to work with non-blocking sockets.  It does not attempt to write out
 | 
						|
all of the queued data, just a "big" chunk.  It returns 0 if it was
 | 
						|
able to empty out the buffers completely, 1 if more flushing is
 | 
						|
required later, or -1 on a fatal write error. */
 | 
						|
buffer_status_t
 | 
						|
buffer_flush_available(struct buffer *b, int fd)
 | 
						|
{
 | 
						|
 | 
						|
/* These are just reasonable values to make sure a significant amount of
 | 
						|
data is written.  There's no need to go crazy and try to write it all
 | 
						|
in one shot. */
 | 
						|
#ifdef IOV_MAX
 | 
						|
#define MAX_CHUNKS ((IOV_MAX >= 16) ? 16 : IOV_MAX)
 | 
						|
#else
 | 
						|
#define MAX_CHUNKS 16
 | 
						|
#endif
 | 
						|
#define MAX_FLUSH 131072
 | 
						|
 | 
						|
  struct buffer_data *d;
 | 
						|
  size_t written;
 | 
						|
  struct iovec iov[MAX_CHUNKS];
 | 
						|
  size_t iovcnt = 0;
 | 
						|
  size_t nbyte = 0;
 | 
						|
 | 
						|
  for (d = b->head; d && (iovcnt < MAX_CHUNKS) && (nbyte < MAX_FLUSH);
 | 
						|
       d = d->next, iovcnt++)
 | 
						|
    {
 | 
						|
      iov[iovcnt].iov_base = d->data+d->sp;
 | 
						|
      nbyte += (iov[iovcnt].iov_len = d->cp-d->sp);
 | 
						|
    }
 | 
						|
 | 
						|
  if (!nbyte)
 | 
						|
    /* No data to flush: should we issue a warning message? */
 | 
						|
    return BUFFER_EMPTY;
 | 
						|
 | 
						|
  /* only place where written should be sign compared */
 | 
						|
  if ((ssize_t)(written = writev(fd,iov,iovcnt)) < 0)
 | 
						|
    {
 | 
						|
      if (ERRNO_IO_RETRY(errno))
 | 
						|
	/* Calling code should try again later. */
 | 
						|
        return BUFFER_PENDING;
 | 
						|
      zlog_warn("%s: write error on fd %d: %s",
 | 
						|
		__func__, fd, safe_strerror(errno));
 | 
						|
      return BUFFER_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
  /* Free printed buffer data. */
 | 
						|
  while (written > 0)
 | 
						|
    {
 | 
						|
      struct buffer_data *d;
 | 
						|
      if (!(d = b->head))
 | 
						|
        {
 | 
						|
          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;
 | 
						|
	  return BUFFER_PENDING;
 | 
						|
	}
 | 
						|
 | 
						|
      written -= (d->cp-d->sp);
 | 
						|
      if (!(b->head = d->next))
 | 
						|
        b->tail = NULL;
 | 
						|
      BUFFER_DATA_FREE(d);
 | 
						|
    }
 | 
						|
 | 
						|
  return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
 | 
						|
 | 
						|
#undef MAX_CHUNKS
 | 
						|
#undef MAX_FLUSH
 | 
						|
}
 | 
						|
 | 
						|
buffer_status_t
 | 
						|
buffer_write(struct buffer *b, int fd, const void *p, size_t size)
 | 
						|
{
 | 
						|
  ssize_t nbytes;
 | 
						|
 | 
						|
#if 0
 | 
						|
  /* Should we attempt to drain any previously buffered data?  This could help
 | 
						|
     reduce latency in pushing out the data if we are stuck in a long-running
 | 
						|
     thread that is preventing the main select loop from calling the flush
 | 
						|
     thread... */
 | 
						|
  if (b->head && (buffer_flush_available(b, fd) == BUFFER_ERROR))
 | 
						|
    return BUFFER_ERROR;
 | 
						|
#endif
 | 
						|
  if (b->head)
 | 
						|
    /* Buffer is not empty, so do not attempt to write the new data. */
 | 
						|
    nbytes = 0;
 | 
						|
  else if ((nbytes = write(fd, p, size)) < 0)
 | 
						|
    {
 | 
						|
      if (ERRNO_IO_RETRY(errno))
 | 
						|
        nbytes = 0;
 | 
						|
      else
 | 
						|
        {
 | 
						|
	  zlog_warn("%s: write error on fd %d: %s",
 | 
						|
		    __func__, fd, safe_strerror(errno));
 | 
						|
	  return BUFFER_ERROR;
 | 
						|
	}
 | 
						|
    }
 | 
						|
  /* Add any remaining data to the buffer. */
 | 
						|
  {
 | 
						|
    size_t written = nbytes;
 | 
						|
    if (written < size)
 | 
						|
      buffer_put(b, ((const char *)p)+written, size-written);
 | 
						|
  }
 | 
						|
  return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
 | 
						|
}
 |