| /***************************************************************************** |
| * cbuf.c |
| ***************************************************************************** |
| * Copyright (C) 2002-2005 The Regents of the University of California. |
| * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). |
| * Written by Chris Dunlap <cdunlap@llnl.gov>. |
| * |
| * This file is from LSD-Tools, the LLNL Software Development Toolbox. |
| * |
| * LSD-Tools 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 of the License, or (at your option) |
| * any later version. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * LSD-Tools 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 LSD-Tools; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| ***************************************************************************** |
| * Refer to "cbuf.h" for documentation on public functions. |
| *****************************************************************************/ |
| |
| #include "config.h" |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <pthread.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "src/common/cbuf.h" |
| #include "src/common/log.h" |
| #include "src/common/xmalloc.h" |
| |
| /*************** |
| * Constants * |
| ***************/ |
| |
| #define CBUF_CHUNK 1000 |
| #define CBUF_MAGIC 0xDEADBEEF |
| #define CBUF_MAGIC_LEN (sizeof(unsigned long)) |
| |
| |
| /**************** |
| * Data Types * |
| ****************/ |
| |
| struct cbuf { |
| |
| #ifndef NDEBUG |
| unsigned long magic; /* cookie for asserting validity */ |
| #endif /* !NDEBUG */ |
| |
| pthread_mutex_t mutex; /* mutex to protect access to cbuf */ |
| |
| int alloc; /* num bytes xmalloc'd/xrealloc'd */ |
| int minsize; /* min bytes of data to allocate */ |
| int maxsize; /* max bytes of data to allocate */ |
| int size; /* num bytes of data allocated */ |
| int used; /* num bytes of unread data */ |
| cbuf_overwrite_t overwrite; /* overwrite option behavior */ |
| int got_wrap; /* true if data has wrapped */ |
| int i_in; /* index to where data is written in */ |
| int i_out; /* index to where data is read out */ |
| int i_rep; /* index to where data is replayable */ |
| unsigned char *data; /* ptr to circular buffer of data */ |
| }; |
| |
| typedef int (*cbuf_iof) (void *cbuf_data, void *arg, int len); |
| |
| |
| /**************** |
| * Prototypes * |
| ****************/ |
| |
| static int cbuf_find_replay_line(cbuf_t *cb, int chars, int *nlines, int *nl); |
| static int cbuf_find_unread_line(cbuf_t *cb, int chars, int *nlines); |
| |
| static int cbuf_get_fd (void *dstbuf, int *psrcfd, int len); |
| static int cbuf_get_mem (void *dstbuf, unsigned char **psrcbuf, int len); |
| static int cbuf_put_fd (void *srcbuf, int *pdstfd, int len); |
| static int cbuf_put_mem (void *srcbuf, unsigned char **pdstbuf, int len); |
| |
| static int cbuf_copier(cbuf_t *src, cbuf_t *dst, int len, int *ndropped); |
| static int cbuf_dropper(cbuf_t *cb, int len); |
| static int cbuf_reader(cbuf_t *src, int len, cbuf_iof putf, void *dst); |
| static int cbuf_replayer(cbuf_t *src, int len, cbuf_iof putf, void *dst); |
| static int cbuf_writer(cbuf_t *dst, int len, cbuf_iof getf, void *src, |
| int *ndropped); |
| |
| static int cbuf_grow(cbuf_t *cb, int n); |
| static int cbuf_shrink(cbuf_t *cb); |
| |
| #ifndef NDEBUG |
| static int _cbuf_is_valid(cbuf_t *cb); |
| static int _cbuf_mutex_is_locked(cbuf_t *cb); |
| #endif /* !NDEBUG */ |
| |
| /*************** |
| * Functions * |
| ***************/ |
| |
| cbuf_t *cbuf_create(int minsize, int maxsize) |
| { |
| cbuf_t *cb; |
| |
| if (minsize <= 0) { |
| errno = EINVAL; |
| return(NULL); |
| } |
| cb = xmalloc(sizeof(struct cbuf)); |
| |
| /* Circular buffer is empty when (i_in == i_out), |
| * so reserve 1 byte for this sentinel. |
| */ |
| cb->alloc = minsize + 1; |
| #ifndef NDEBUG |
| /* Reserve space for the magic cookies used to protect the |
| * cbuf data[] array from underflow and overflow. |
| */ |
| cb->alloc += 2 * CBUF_MAGIC_LEN; |
| #endif /* !NDEBUG */ |
| |
| cb->data = xmalloc(cb->alloc); |
| slurm_mutex_init(&cb->mutex); |
| cb->minsize = minsize; |
| cb->maxsize = (maxsize > minsize) ? maxsize : minsize; |
| cb->size = minsize; |
| cb->used = 0; |
| cb->overwrite = CBUF_WRAP_MANY; |
| cb->got_wrap = 0; |
| cb->i_in = cb->i_out = cb->i_rep = 0; |
| |
| #ifndef NDEBUG |
| /* C is for cookie, that's good enough for me, yeah! |
| * The magic cookies are only defined during DEBUG code. |
| * The first "magic" cookie is at the top of the structure. |
| * Magic cookies are also placed at the top & bottom of the |
| * cbuf data[] array to catch buffer underflow & overflow errors. |
| */ |
| cb->data += CBUF_MAGIC_LEN; /* jump forward past underflow magic */ |
| cb->magic = CBUF_MAGIC; |
| /* |
| * Must use memcpy since overflow cookie may not be word-aligned. |
| */ |
| memcpy(cb->data - CBUF_MAGIC_LEN, (void *) &cb->magic, CBUF_MAGIC_LEN); |
| memcpy(cb->data + cb->size + 1, (void *) &cb->magic, CBUF_MAGIC_LEN); |
| |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| slurm_mutex_unlock(&cb->mutex); |
| #endif /* !NDEBUG */ |
| |
| return(cb); |
| } |
| |
| |
| void cbuf_destroy(cbuf_t *cb) |
| { |
| assert(cb != NULL); |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| |
| #ifndef NDEBUG |
| /* The moon sometimes looks like a C, but you can't eat that. |
| * Munch the magic cookies before xfreeing memory. |
| */ |
| cb->magic = ~CBUF_MAGIC; /* the anti-cookie! */ |
| memcpy(cb->data - CBUF_MAGIC_LEN, (void *) &cb->magic, CBUF_MAGIC_LEN); |
| memcpy(cb->data + cb->size + 1, (void *) &cb->magic, CBUF_MAGIC_LEN); |
| cb->data -= CBUF_MAGIC_LEN; /* jump back to what xmalloc returned */ |
| #endif /* !NDEBUG */ |
| |
| xfree(cb->data); |
| slurm_mutex_unlock(&cb->mutex); |
| slurm_mutex_destroy(&cb->mutex); |
| xfree(cb); |
| return; |
| } |
| |
| |
| void cbuf_flush(cbuf_t *cb) |
| { |
| assert(cb != NULL); |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| /* |
| * FIXME: Shrink buffer back to minimum size. |
| */ |
| cb->used = 0; |
| cb->got_wrap = 0; |
| cb->i_in = cb->i_out = cb->i_rep = 0; |
| assert(_cbuf_is_valid(cb)); |
| slurm_mutex_unlock(&cb->mutex); |
| return; |
| } |
| |
| |
| int cbuf_size(cbuf_t *cb) |
| { |
| int size; |
| |
| assert(cb != NULL); |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| size = cb->maxsize; |
| slurm_mutex_unlock(&cb->mutex); |
| return(size); |
| } |
| |
| |
| int cbuf_free(cbuf_t *cb) |
| { |
| int nfree; |
| |
| assert(cb != NULL); |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| nfree = cb->maxsize - cb->used; |
| slurm_mutex_unlock(&cb->mutex); |
| return(nfree); |
| } |
| |
| |
| int cbuf_used(cbuf_t *cb) |
| { |
| int used; |
| |
| assert(cb != NULL); |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| used = cb->used; |
| slurm_mutex_unlock(&cb->mutex); |
| return(used); |
| } |
| |
| |
| int cbuf_lines_used(cbuf_t *cb) |
| { |
| int lines = -1; |
| |
| assert(cb != NULL); |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| cbuf_find_unread_line(cb, cb->size, &lines); |
| slurm_mutex_unlock(&cb->mutex); |
| return(lines); |
| } |
| |
| |
| int cbuf_reused(cbuf_t *cb) |
| { |
| /* If (O > R) |
| * n = O - R |
| * else |
| * n = (O - 0) + ((S+1) - R). |
| * (S+1) is used since data[] contains 'size' bytes + a 1-byte sentinel. |
| */ |
| int reused; |
| |
| assert(cb != NULL); |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| reused = (cb->i_out - cb->i_rep + (cb->size + 1)) % (cb->size + 1); |
| slurm_mutex_unlock(&cb->mutex); |
| return(reused); |
| } |
| |
| |
| int cbuf_lines_reused(cbuf_t *cb) |
| { |
| int lines = -1; |
| |
| assert(cb != NULL); |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| cbuf_find_replay_line(cb, cb->size, &lines, NULL); |
| slurm_mutex_unlock(&cb->mutex); |
| return(lines); |
| } |
| |
| |
| int cbuf_is_empty(cbuf_t *cb) |
| { |
| int used; |
| |
| assert(cb != NULL); |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| used = cb->used; |
| slurm_mutex_unlock(&cb->mutex); |
| return(used == 0); |
| } |
| |
| |
| int cbuf_opt_get(cbuf_t *cb, cbuf_opt_t name, int *value) |
| { |
| int rc = 0; |
| |
| assert(cb != NULL); |
| |
| if (value == NULL) { |
| errno = EINVAL; |
| return(-1); |
| } |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| if (name == CBUF_OPT_OVERWRITE) { |
| *value = cb->overwrite; |
| } |
| else { |
| errno = EINVAL; |
| rc = -1; |
| } |
| slurm_mutex_unlock(&cb->mutex); |
| return(rc); |
| } |
| |
| |
| int cbuf_opt_set(cbuf_t *cb, cbuf_opt_t name, int value) |
| { |
| int rc = 0; |
| |
| assert(cb != NULL); |
| |
| slurm_mutex_lock(&cb->mutex); |
| assert(_cbuf_is_valid(cb)); |
| if (name == CBUF_OPT_OVERWRITE) { |
| if ( (value == CBUF_NO_DROP) |
| || (value == CBUF_WRAP_ONCE) |
| || (value == CBUF_WRAP_MANY) ) { |
| cb->overwrite = value; |
| } |
| else { |
| errno = EINVAL; |
| rc = -1; |
| } |
| } |
| else { |
| errno = EINVAL; |
| rc = -1; |
| } |
| assert(_cbuf_is_valid(cb)); |
| slurm_mutex_unlock(&cb->mutex); |
| return(rc); |
| } |
| |
| |
| int cbuf_drop(cbuf_t *src, int len) |
| { |
| assert(src != NULL); |
| |
| if (len < -1) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (len == 0) { |
| return(0); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| |
| if (len == -1) { |
| len = src->used; |
| } |
| else { |
| len = MIN(len, src->used); |
| } |
| if (len > 0) { |
| cbuf_dropper(src, len); |
| } |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(len); |
| } |
| |
| |
| int cbuf_peek(cbuf_t *src, void *dstbuf, int len) |
| { |
| int n; |
| |
| assert(src != NULL); |
| |
| if ((dstbuf == NULL) || (len < 0)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (len == 0) { |
| return(0); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| n = cbuf_reader(src, len, (cbuf_iof) cbuf_put_mem, &dstbuf); |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_read(cbuf_t *src, void *dstbuf, int len) |
| { |
| int n; |
| |
| assert(src != NULL); |
| |
| if ((dstbuf == NULL) || (len < 0)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (len == 0) { |
| return(0); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| n = cbuf_reader(src, len, (cbuf_iof) cbuf_put_mem, &dstbuf); |
| if (n > 0) { |
| cbuf_dropper(src, n); |
| } |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_replay(cbuf_t *src, void *dstbuf, int len) |
| { |
| int n; |
| |
| assert(src != NULL); |
| |
| if ((dstbuf == NULL) || (len < 0)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (len == 0) { |
| return(0); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| n = cbuf_replayer(src, len, (cbuf_iof) cbuf_put_mem, &dstbuf); |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_rewind(cbuf_t *src, int len) |
| { |
| int reused; |
| |
| assert(src != NULL); |
| |
| if (len < -1) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (len == 0) { |
| return(0); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| |
| reused = (src->i_out - src->i_rep + (src->size + 1)) % (src->size + 1); |
| if (len == -1) { |
| len = reused; |
| } |
| else { |
| len = MIN(len, reused); |
| } |
| if (len > 0) { |
| src->used += len; |
| src->i_out = (src->i_out - len + (src->size + 1)) % (src->size + 1); |
| } |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(len); |
| } |
| |
| |
| int cbuf_write(cbuf_t *dst, void *srcbuf, int len, int *ndropped) |
| { |
| int n; |
| |
| assert(dst != NULL); |
| |
| if (ndropped) { |
| *ndropped = 0; |
| } |
| if ((srcbuf == NULL) || (len < 0)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (len == 0) { |
| return(0); |
| } |
| slurm_mutex_lock(&dst->mutex); |
| assert(_cbuf_is_valid(dst)); |
| n = cbuf_writer(dst, len, (cbuf_iof) cbuf_get_mem, &srcbuf, ndropped); |
| assert(_cbuf_is_valid(dst)); |
| slurm_mutex_unlock(&dst->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_drop_line(cbuf_t *src, int len, int lines) |
| { |
| int n; |
| |
| assert(src != NULL); |
| |
| if ((len < 0) || (lines < -1)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (lines == 0) { |
| return(0); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| |
| n = cbuf_find_unread_line(src, len, &lines); |
| if (n > 0) { |
| cbuf_dropper(src, n); |
| } |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_peek_line(cbuf_t *src, char *dstbuf, int len, int lines) |
| { |
| int n, m, l; |
| char *pdst; |
| |
| assert(src != NULL); |
| |
| if ((dstbuf == NULL) || (len < 0) || (lines < -1)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (lines == 0) { |
| return(0); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| n = cbuf_find_unread_line(src, len - 1, &lines); |
| if (n > 0) { |
| if (len > 0) { |
| m = MIN(n, len - 1); |
| if (m > 0) { |
| pdst = dstbuf; |
| l = cbuf_reader(src, m, (cbuf_iof) cbuf_put_mem, &pdst); |
| if (l) |
| assert(l == m); |
| } |
| assert(m < len); |
| dstbuf[m] = '\0'; |
| } |
| } |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_read_line(cbuf_t *src, char *dstbuf, int len, int lines) |
| { |
| int n, m, l; |
| char *pdst; |
| |
| assert(src != NULL); |
| |
| if ((dstbuf == NULL) || (len < 0) || (lines < -1)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (lines == 0) { |
| return(0); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| n = cbuf_find_unread_line(src, len - 1, &lines); |
| if (n > 0) { |
| if (len > 0) { |
| m = MIN(n, len - 1); |
| if (m > 0) { |
| pdst = dstbuf; |
| l = cbuf_reader(src, m, (cbuf_iof) cbuf_put_mem, &pdst); |
| if (l) |
| assert(l == m); |
| } |
| assert(m < len); |
| dstbuf[m] = '\0'; |
| } |
| cbuf_dropper(src, n); |
| } |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_replay_line(cbuf_t *src, char *dstbuf, int len, int lines) |
| { |
| int n, m, l; |
| int nl; |
| char *pdst; |
| |
| assert(src != NULL); |
| |
| if ((dstbuf == NULL) || (len < 0) || (lines < -1)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (lines == 0) { |
| return(0); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| n = cbuf_find_replay_line(src, len - 1, &lines, &nl); |
| if (n > 0) { |
| if (len > 0) { |
| assert((nl == 0) || (nl == 1)); |
| m = MIN(n, len - 1 - nl); |
| m = MAX(m, 0); |
| if (m > 0) { |
| pdst = dstbuf; |
| l = cbuf_replayer(src, m, (cbuf_iof) cbuf_put_mem, &pdst); |
| if (l) |
| assert(l == m); |
| } |
| /* Append newline if needed and space allows. |
| */ |
| if ((nl) && (len > 1)) { |
| dstbuf[m++] = '\n'; |
| } |
| assert(m < len); |
| dstbuf[m] = '\0'; |
| n += nl; |
| } |
| } |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_rewind_line(cbuf_t *src, int len, int lines) |
| { |
| int n; |
| |
| assert(src != NULL); |
| |
| if ((len < 0) || (lines < -1)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (lines == 0) { |
| return(0); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| |
| n = cbuf_find_replay_line(src, len, &lines, NULL); |
| if (n > 0) { |
| src->used += n; |
| src->i_out = (src->i_out - n + (src->size + 1)) % (src->size + 1); |
| } |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_write_line(cbuf_t *dst, char *srcbuf, int *ndropped) |
| { |
| int len; |
| int nfree, ncopy, n; |
| int ndrop = 0, d; |
| char *psrc = srcbuf; |
| char *newline = "\n"; |
| |
| assert(dst != NULL); |
| |
| if (ndropped) { |
| *ndropped = 0; |
| } |
| if (srcbuf == NULL) { |
| errno = EINVAL; |
| return(-1); |
| } |
| /* Compute number of bytes to effectively copy to dst cbuf. |
| * Reserve space for the trailing newline if needed. |
| */ |
| len = ncopy = strlen(srcbuf); |
| if ((len == 0) || (srcbuf[len - 1] != '\n')) { |
| len++; |
| } |
| slurm_mutex_lock(&dst->mutex); |
| assert(_cbuf_is_valid(dst)); |
| /* |
| * Attempt to grow dst cbuf if necessary. |
| */ |
| nfree = dst->size - dst->used; |
| if ((len > nfree) && (dst->size < dst->maxsize)) { |
| (void) cbuf_grow(dst, len - nfree); |
| } |
| /* Determine if src will fit (or be made to fit) in dst cbuf. |
| */ |
| if (dst->overwrite == CBUF_NO_DROP) { |
| if (len > dst->size - dst->used) { |
| errno = ENOSPC; |
| len = -1; /* cannot return while mutex locked */ |
| } |
| } |
| else if (dst->overwrite == CBUF_WRAP_ONCE) { |
| if (len > dst->size) { |
| errno = ENOSPC; |
| len = -1; /* cannot return while mutex locked */ |
| } |
| } |
| if (len > 0) { |
| /* |
| * Discard data that won't fit in dst cbuf. |
| */ |
| if (len > dst->size) { |
| ndrop += len - dst->size; |
| ncopy -= ndrop; |
| psrc += ndrop; |
| } |
| /* Copy data from src string to dst cbuf. |
| */ |
| if (ncopy > 0) { |
| n = cbuf_writer(dst, ncopy, (cbuf_iof) cbuf_get_mem, &psrc, &d); |
| if (n) |
| assert(n == ncopy); |
| ndrop += d; |
| } |
| /* Append newline if needed. |
| */ |
| if (srcbuf[len - 1] != '\n') { |
| n = cbuf_writer(dst, 1, (cbuf_iof) cbuf_get_mem, &newline, &d); |
| assert(n == 1); |
| ndrop += d; |
| } |
| } |
| assert(_cbuf_is_valid(dst)); |
| slurm_mutex_unlock(&dst->mutex); |
| if (ndropped) { |
| *ndropped = ndrop; |
| } |
| return(len); |
| } |
| |
| |
| int cbuf_peek_to_fd(cbuf_t *src, int dstfd, int len) |
| { |
| int n = 0; |
| |
| assert(src != NULL); |
| |
| if ((dstfd < 0) || (len < -1)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| if (len == -1) { |
| len = src->used; |
| } |
| if (len > 0) { |
| n = cbuf_reader(src, len, (cbuf_iof) cbuf_put_fd, &dstfd); |
| } |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_read_to_fd(cbuf_t *src, int dstfd, int len) |
| { |
| int n = 0; |
| |
| assert(src != NULL); |
| |
| if ((dstfd < 0) || (len < -1)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| if (len == -1) { |
| len = src->used; |
| } |
| if (len > 0) { |
| n = cbuf_reader(src, len, (cbuf_iof) cbuf_put_fd, &dstfd); |
| if (n > 0) { |
| cbuf_dropper(src, n); |
| } |
| } |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_replay_to_fd(cbuf_t *src, int dstfd, int len) |
| { |
| int n = 0; |
| |
| assert(src != NULL); |
| |
| if ((dstfd < 0) || (len < -1)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| slurm_mutex_lock(&src->mutex); |
| assert(_cbuf_is_valid(src)); |
| if (len == -1) { |
| len = src->size - src->used; |
| } |
| if (len > 0) { |
| n = cbuf_replayer(src, len, (cbuf_iof) cbuf_put_fd, &dstfd); |
| } |
| assert(_cbuf_is_valid(src)); |
| slurm_mutex_unlock(&src->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_write_from_fd(cbuf_t *dst, int srcfd, int len, int *ndropped) |
| { |
| int n = 0; |
| |
| assert(dst != NULL); |
| |
| if (ndropped) { |
| *ndropped = 0; |
| } |
| if ((srcfd < 0) || (len < -1)) { |
| errno = EINVAL; |
| return(-1); |
| } |
| slurm_mutex_lock(&dst->mutex); |
| assert(_cbuf_is_valid(dst)); |
| if (len == -1) { |
| /* |
| * Try to use all of the free buffer space available for writing. |
| * If it is all in use, try to grab another chunk. |
| */ |
| len = dst->size - dst->used; |
| if (len == 0) { |
| len = CBUF_CHUNK; |
| } |
| } |
| if (len > 0) { |
| n = cbuf_writer(dst, len, (cbuf_iof) cbuf_get_fd, &srcfd, ndropped); |
| } |
| assert(_cbuf_is_valid(dst)); |
| slurm_mutex_unlock(&dst->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_copy(cbuf_t *src, cbuf_t *dst, int len, int *ndropped) |
| { |
| int n = 0; |
| |
| assert(src != NULL); |
| assert(dst != NULL); |
| |
| if (ndropped) { |
| *ndropped = 0; |
| } |
| if (src == dst) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (len < -1) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (len == 0) { |
| return(0); |
| } |
| /* Lock cbufs in order of lowest memory address to prevent deadlock. |
| */ |
| if (src < dst) { |
| slurm_mutex_lock(&src->mutex); |
| slurm_mutex_lock(&dst->mutex); |
| } |
| else { |
| slurm_mutex_lock(&dst->mutex); |
| slurm_mutex_lock(&src->mutex); |
| } |
| assert(_cbuf_is_valid(src)); |
| assert(_cbuf_is_valid(dst)); |
| |
| if (len == -1) { |
| len = src->used; |
| } |
| if (len > 0) { |
| n = cbuf_copier(src, dst, len, ndropped); |
| } |
| assert(_cbuf_is_valid(src)); |
| assert(_cbuf_is_valid(dst)); |
| slurm_mutex_unlock(&src->mutex); |
| slurm_mutex_unlock(&dst->mutex); |
| return(n); |
| } |
| |
| |
| int cbuf_move(cbuf_t *src, cbuf_t *dst, int len, int *ndropped) |
| { |
| int n = 0; |
| |
| assert(src != NULL); |
| assert(dst != NULL); |
| |
| if (ndropped) { |
| *ndropped = 0; |
| } |
| if (src == dst) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (len < -1) { |
| errno = EINVAL; |
| return(-1); |
| } |
| if (len == 0) { |
| return(0); |
| } |
| /* Lock cbufs in order of lowest memory address to prevent deadlock. |
| */ |
| if (src < dst) { |
| slurm_mutex_lock(&src->mutex); |
| slurm_mutex_lock(&dst->mutex); |
| } |
| else { |
| slurm_mutex_lock(&dst->mutex); |
| slurm_mutex_lock(&src->mutex); |
| } |
| assert(_cbuf_is_valid(src)); |
| assert(_cbuf_is_valid(dst)); |
| |
| if (len == -1) { |
| len = src->used; |
| } |
| if (len > 0) { |
| n = cbuf_copier(src, dst, len, ndropped); |
| if (n > 0) { |
| cbuf_dropper(src, n); |
| } |
| } |
| assert(_cbuf_is_valid(src)); |
| assert(_cbuf_is_valid(dst)); |
| slurm_mutex_unlock(&src->mutex); |
| slurm_mutex_unlock(&dst->mutex); |
| return(n); |
| } |
| |
| |
| static int cbuf_find_replay_line(cbuf_t *cb, int chars, int *nlines, int *nl) |
| { |
| /* Finds the specified number of lines from the replay region of the buffer. |
| * If ([nlines] > 0), returns the number of bytes comprising the line count, |
| * or 0 if this number of lines is not available (ie, all or none). |
| * If ([nlines] == -1), returns the number of bytes comprising the maximum |
| * line count bounded by the number of characters specified by [chars]. |
| * Only complete lines (ie, those terminated by a newline) are counted, |
| * with once exception: the most recent line of replay data is treated |
| * as a complete line regardless of the presence of a terminating newline. |
| * Sets the value-result parameter [nlines] to the number of lines found. |
| * Sets [nl] to '1' if a newline is required to terminate the replay data. |
| */ |
| int i, n, m, l; |
| int lines; |
| |
| assert(cb != NULL); |
| assert(nlines != NULL); |
| assert(*nlines >= -1); |
| assert(_cbuf_mutex_is_locked(cb)); |
| |
| n = m = l = 0; |
| lines = *nlines; |
| *nlines = 0; |
| |
| if (nl) { |
| *nl = 0; /* init in case of early return */ |
| } |
| if ((lines == 0) || ((lines <= -1) && (chars <= 0))) { |
| return(0); |
| } |
| if (cb->i_out == cb->i_rep) { |
| return(0); /* no replay data available */ |
| } |
| if (lines > 0) { |
| chars = -1; /* chars param not used if lines > 0 */ |
| } |
| else { |
| ++chars; /* incr to allow for preceding '\n' */ |
| } |
| /* Since the most recent line of replay data is considered implicitly |
| * terminated, decrement the char count to account for the newline |
| * if one is not present, or increment the line count if one is. |
| * Note: cb->data[(O - 1 + (S+1)) % (S+1)] is the last replayable char. |
| */ |
| if (cb->data[(cb->i_out + cb->size) % (cb->size + 1)] != '\n') { |
| if (nl) { |
| *nl = 1; |
| } |
| --chars; |
| } |
| else { |
| if (lines > 0) { |
| ++lines; |
| } |
| --l; |
| } |
| i = cb->i_out; |
| while (i != cb->i_rep) { |
| i = (i + cb->size) % (cb->size + 1); /* (i - 1 + (S+1)) % (S+1) */ |
| ++n; |
| if (chars > 0) { |
| --chars; |
| } |
| /* Complete lines are identified by a preceding newline. |
| */ |
| if (cb->data[i] == '\n') { |
| if (lines > 0) { |
| --lines; |
| } |
| m = n - 1; /* do not include preceding '\n' */ |
| ++l; |
| } |
| if ((chars == 0) || (lines == 0)) { |
| break; |
| } |
| } |
| /* But the first line written in does not need a preceding newline. |
| */ |
| if ((!cb->got_wrap) && ((chars > 0) || (lines > 0))) { |
| if (lines > 0) { |
| --lines; |
| } |
| m = n; |
| ++l; |
| } |
| if (lines > 0) { |
| return(0); /* all or none, and not enough found */ |
| } |
| *nlines = l; |
| return(m); |
| } |
| |
| |
| static int cbuf_find_unread_line(cbuf_t *cb, int chars, int *nlines) |
| { |
| /* Finds the specified number of lines from the unread region of the buffer. |
| * If ([nlines] > 0), returns the number of bytes comprising the line count, |
| * or 0 if this number of lines is not available (ie, all or none). |
| * If ([nlines] == -1), returns the number of bytes comprising the maximum |
| * line count bounded by the number of characters specified by [chars]. |
| * Only complete lines (ie, those terminated by a newline) are counted. |
| * Sets the value-result parameter [nlines] to the number of lines found. |
| */ |
| int i, n, m, l; |
| int lines; |
| |
| assert(cb != NULL); |
| assert(nlines != NULL); |
| assert(*nlines >= -1); |
| assert(_cbuf_mutex_is_locked(cb)); |
| |
| n = m = l = 0; |
| lines = *nlines; |
| *nlines = 0; |
| |
| if ((lines == 0) || ((lines <= -1) && (chars <= 0))) { |
| return(0); |
| } |
| if (cb->used == 0) { |
| return(0); /* no unread data available */ |
| } |
| if (lines > 0) { |
| chars = -1; /* chars param not used if lines > 0 */ |
| } |
| i = cb->i_out; |
| while (i != cb->i_in) { |
| ++n; |
| if (chars > 0) { |
| --chars; |
| } |
| if (cb->data[i] == '\n') { |
| if (lines > 0) { |
| --lines; |
| } |
| m = n; |
| ++l; |
| } |
| if ((chars == 0) || (lines == 0)) { |
| break; |
| } |
| i = (i + 1) % (cb->size + 1); |
| } |
| if (lines > 0) { |
| return(0); /* all or none, and not enough found */ |
| } |
| *nlines = l; |
| return(m); |
| } |
| |
| |
| static int |
| cbuf_get_fd (void *dstbuf, int *psrcfd, int len) |
| { |
| /* Copies data from the file referenced by the file descriptor |
| * pointed at by [psrcfd] into cbuf's [dstbuf]. |
| * Returns the number of bytes read from the fd, 0 on EOF, or -1 on error. |
| */ |
| int n; |
| |
| assert(dstbuf != NULL); |
| assert(psrcfd != NULL); |
| assert(*psrcfd >= 0); |
| assert(len > 0); |
| |
| do { |
| n = read(*psrcfd, dstbuf, len); |
| } while ((n < 0) && (errno == EINTR)); |
| return(n); |
| } |
| |
| |
| static int |
| cbuf_get_mem (void *dstbuf, unsigned char **psrcbuf, int len) |
| { |
| /* Copies data from the buffer pointed at by [psrcbuf] into cbuf's [dstbuf]. |
| * Returns the number of bytes copied. |
| */ |
| assert(dstbuf != NULL); |
| assert(psrcbuf != NULL); |
| assert(*psrcbuf != NULL); |
| assert(len > 0); |
| |
| memcpy(dstbuf, *psrcbuf, len); |
| *psrcbuf += len; |
| return(len); |
| } |
| |
| |
| static int |
| cbuf_put_fd (void *srcbuf, int *pdstfd, int len) |
| { |
| /* Copies data from cbuf's [srcbuf] into the file referenced |
| * by the file descriptor pointed at by [pdstfd]. |
| * Returns the number of bytes written to the fd, or -1 on error. |
| */ |
| int n; |
| |
| assert(srcbuf != NULL); |
| assert(pdstfd != NULL); |
| assert(*pdstfd >= 0); |
| assert(len > 0); |
| |
| do { |
| n = write(*pdstfd, srcbuf, len); |
| } while ((n < 0) && (errno == EINTR)); |
| return(n); |
| } |
| |
| |
| static int |
| cbuf_put_mem (void *srcbuf, unsigned char **pdstbuf, int len) |
| { |
| /* Copies data from cbuf's [srcbuf] into the buffer pointed at by [pdstbuf]. |
| * Returns the number of bytes copied. |
| */ |
| assert(srcbuf != NULL); |
| assert(pdstbuf != NULL); |
| assert(*pdstbuf != NULL); |
| assert(len > 0); |
| |
| memcpy(*pdstbuf, srcbuf, len); |
| *pdstbuf += len; |
| return(len); |
| } |
| |
| |
| static int cbuf_copier(cbuf_t *src, cbuf_t *dst, int len, int *ndropped) |
| { |
| /* Copies up to [len] bytes from the [src] cbuf into the [dst] cbuf. |
| * Returns the number of bytes copied, or -1 on error (with errno set). |
| * Sets [ndropped] (if not NULL) to the number of [dst] bytes overwritten. |
| */ |
| int ncopy, nfree, nleft, nrepl, n; |
| int i_src, i_dst; |
| |
| assert(src != NULL); |
| assert(dst != NULL); |
| assert(len > 0); |
| assert(_cbuf_mutex_is_locked(src)); |
| assert(_cbuf_mutex_is_locked(dst)); |
| |
| /* Bound len by the number of bytes available. |
| */ |
| len = MIN(len, src->used); |
| if (len == 0) { |
| return(0); |
| } |
| /* Attempt to grow dst cbuf if necessary. |
| */ |
| nfree = dst->size - dst->used; |
| if ((len > nfree) && (dst->size < dst->maxsize)) { |
| nfree += cbuf_grow(dst, len - nfree); |
| } |
| /* Compute number of bytes to effectively copy to dst cbuf. |
| */ |
| if (dst->overwrite == CBUF_NO_DROP) { |
| len = MIN(len, dst->size - dst->used); |
| if (len == 0) { |
| errno = ENOSPC; |
| return(-1); |
| } |
| } |
| else if (dst->overwrite == CBUF_WRAP_ONCE) { |
| len = MIN(len, dst->size); |
| } |
| /* Compute number of bytes that will be overwritten in dst cbuf. |
| */ |
| if (ndropped) { |
| *ndropped = MAX(0, len - dst->size + dst->used); |
| } |
| /* Compute number of bytes to physically copy to dst cbuf. This prevents |
| * copying data that will overwritten if the cbuf wraps multiple times. |
| */ |
| ncopy = len; |
| i_src = src->i_out; |
| i_dst = dst->i_in; |
| if (ncopy > dst->size) { |
| n = ncopy - dst->size; |
| i_src = (i_src + n) % (src->size + 1); |
| ncopy -= n; |
| } |
| /* Copy data from src cbuf to dst cbuf. |
| */ |
| nleft = ncopy; |
| while (nleft > 0) { |
| n = MIN(((src->size + 1) - i_src), ((dst->size + 1) - i_dst)); |
| n = MIN(n, nleft); |
| memcpy(&dst->data[i_dst], &src->data[i_src], n); |
| i_src = (i_src + n) % (src->size + 1); |
| i_dst = (i_dst + n) % (dst->size + 1); |
| nleft -= n; |
| } |
| /* Update dst cbuf metadata. |
| */ |
| if (ncopy > 0) { |
| nrepl = (dst->i_out - dst->i_rep + (dst->size + 1)) % (dst->size + 1); |
| dst->used = MIN(dst->used + ncopy, dst->size); |
| assert(i_dst == (dst->i_in + ncopy) % (dst->size + 1)); |
| dst->i_in = i_dst; |
| if (ncopy > nfree - nrepl) { |
| dst->got_wrap = 1; |
| dst->i_rep = (dst->i_in + 1) % (dst->size + 1); |
| } |
| if (ncopy > nfree) { |
| dst->i_out = dst->i_rep; |
| } |
| } |
| return(len); |
| } |
| |
| |
| static int cbuf_dropper(cbuf_t *cb, int len) |
| { |
| /* Discards exactly [len] bytes of unread data from [cb]. |
| * Returns the number of bytes dropped. |
| */ |
| assert(cb != NULL); |
| assert(len > 0); |
| assert(len <= cb->used); |
| assert(_cbuf_mutex_is_locked(cb)); |
| |
| cb->used -= len; |
| cb->i_out = (cb->i_out + len) % (cb->size + 1); |
| |
| /* Attempt to shrink cbuf if possible. |
| */ |
| if ((cb->size - cb->used > CBUF_CHUNK) && (cb->size > cb->minsize)) { |
| cbuf_shrink(cb); |
| } |
| /* Don't call me clumsy, don't call me a fool. |
| * When things fall down on me, I'm following the rule. |
| */ |
| return(len); |
| } |
| |
| |
| static int cbuf_reader(cbuf_t *src, int len, cbuf_iof putf, void *dst) |
| { |
| /* Reads up to [len] bytes from [src] into the object pointed at by [dst]. |
| * The I/O function [putf] specifies how data is written into [dst]. |
| * Returns the number of bytes read, or -1 on error (with errno set). |
| * Note that [dst] is a value-result parameter and will be "moved forward" |
| * by the number of bytes written into it. |
| */ |
| int nleft, n, m; |
| int i_src; |
| |
| assert(src != NULL); |
| assert(len > 0); |
| assert(putf != NULL); |
| assert(dst != NULL); |
| assert(_cbuf_mutex_is_locked(src)); |
| |
| /* Bound len by the number of bytes available. |
| */ |
| len = MIN(len, src->used); |
| if (len == 0) { |
| return(0); |
| } |
| /* Copy data from src cbuf to dst obj. Do the cbuf hokey-pokey and |
| * wrap-around the buffer at most once. Break out if putf() returns |
| * either an ERR or a short count. |
| */ |
| i_src = src->i_out; |
| nleft = len; |
| m = 0; |
| while (nleft > 0) { |
| n = MIN(nleft, (src->size + 1) - i_src); |
| m = putf(&src->data[i_src], dst, n); |
| if (m > 0) { |
| nleft -= m; |
| i_src = (i_src + m) % (src->size + 1); |
| } |
| if (n != m) { |
| break; /* got ERR or "short" putf() */ |
| } |
| } |
| /* Compute number of bytes written to dst obj. |
| */ |
| n = len - nleft; |
| assert((n >= 0) && (n <= len)); |
| /* |
| * If no data has been written, return the ERR reported by putf(). |
| */ |
| if (n == 0) { |
| return(m); |
| } |
| return(n); |
| } |
| |
| |
| static int cbuf_replayer(cbuf_t *src, int len, cbuf_iof putf, void *dst) |
| { |
| /* Replays up to [len] bytes from [src] into the object pointed at by [dst]. |
| * The I/O function [putf] specifies how data is written into [dst]. |
| * Returns the number of bytes replayed, or -1 on error (with errno set). |
| * Note that [dst] is a value-result parameter and will be "moved forward" |
| * by the number of bytes written into it. |
| */ |
| int nleft, n, m; |
| int i_src; |
| |
| assert(src != NULL); |
| assert(len > 0); |
| assert(putf != NULL); |
| assert(dst != NULL); |
| assert(_cbuf_mutex_is_locked(src)); |
| |
| /* Bound len by the number of bytes available. |
| */ |
| n = (src->i_out - src->i_rep + (src->size + 1)) % (src->size + 1); |
| len = MIN(len, n); |
| if (len == 0) { |
| return(0); |
| } |
| /* Copy data from src cbuf to dst obj. Do the cbuf hokey-pokey and |
| * wrap-around the buffer at most once. Break out if putf() returns |
| * either an ERR or a short count. |
| */ |
| i_src = (src->i_out - len + (src->size + 1)) % (src->size + 1); |
| nleft = len; |
| m = 0; |
| while (nleft > 0) { |
| n = MIN(nleft, (src->size + 1) - i_src); |
| m = putf(&src->data[i_src], dst, n); |
| if (m > 0) { |
| nleft -= m; |
| i_src = (i_src + m) % (src->size + 1); |
| } |
| if (n != m) { |
| break; /* got ERR or "short" putf() */ |
| } |
| } |
| /* Compute number of bytes written to dst obj. |
| */ |
| n = len - nleft; |
| assert((n >= 0) && (n <= len)); |
| /* |
| * If no data has been written, return the ERR reported by putf(). |
| */ |
| if (n == 0) { |
| return(m); |
| } |
| return(n); |
| } |
| |
| |
| static int cbuf_writer(cbuf_t *dst, int len, cbuf_iof getf, void *src, int *ndropped) |
| { |
| /* Writes up to [len] bytes from the object pointed at by [src] into [dst]. |
| * The I/O function [getf] specifies how data is read from [src]. |
| * Returns the number of bytes written, or -1 on error (with errno set). |
| * Sets [ndropped] (if not NULL) to the number of [dst] bytes overwritten. |
| * Note that [src] is a value-result parameter and will be "moved forward" |
| * by the number of bytes read from it. |
| */ |
| int nfree, nleft, nrepl, n, m; |
| int i_dst; |
| |
| assert(dst != NULL); |
| assert(len > 0); |
| assert(getf != NULL); |
| assert(src != NULL); |
| assert(_cbuf_mutex_is_locked(dst)); |
| |
| /* Attempt to grow dst cbuf if necessary. |
| */ |
| nfree = dst->size - dst->used; |
| if ((len > nfree) && (dst->size < dst->maxsize)) { |
| nfree += cbuf_grow(dst, len - nfree); |
| } |
| /* Compute number of bytes to write to dst cbuf. |
| */ |
| if (dst->overwrite == CBUF_NO_DROP) { |
| len = MIN(len, dst->size - dst->used); |
| if (len == 0) { |
| errno = ENOSPC; |
| return(-1); |
| } |
| } |
| else if (dst->overwrite == CBUF_WRAP_ONCE) { |
| len = MIN(len, dst->size); |
| } |
| /* Copy data from src obj to dst cbuf. Do the cbuf hokey-pokey and |
| * wrap-around the buffer as needed. Break out if getf() returns |
| * either an EOF/ERR or a short count. |
| */ |
| i_dst = dst->i_in; |
| nleft = len; |
| m = 0; |
| while (nleft > 0) { |
| n = MIN(nleft, (dst->size + 1) - i_dst); |
| m = getf(&dst->data[i_dst], src, n); |
| if (m > 0) { |
| nleft -= m; |
| i_dst = (i_dst + m) % (dst->size + 1); |
| } |
| if (n != m) { |
| break; /* got EOF/ERR or "short" getf() */ |
| } |
| } |
| /* Compute number of bytes written to dst cbuf. |
| */ |
| n = len - nleft; |
| assert((n >= 0) && (n <= len)); |
| /* |
| * If no data has been written, return the EOF/ERR reported by getf(). |
| */ |
| if (n == 0) { |
| return(m); |
| } |
| /* Update dst cbuf metadata. |
| */ |
| if (n > 0) { |
| nrepl = (dst->i_out - dst->i_rep + (dst->size + 1)) % (dst->size + 1); |
| dst->used = MIN(dst->used + n, dst->size); |
| assert(i_dst == (dst->i_in + n) % (dst->size + 1)); |
| dst->i_in = i_dst; |
| if (n > nfree - nrepl) { |
| dst->got_wrap = 1; |
| dst->i_rep = (dst->i_in + 1) % (dst->size + 1); |
| } |
| if (n > nfree) { |
| dst->i_out = dst->i_rep; |
| } |
| } |
| if (ndropped) { |
| *ndropped = MAX(0, n - nfree); |
| } |
| return(n); |
| } |
| |
| |
| static int cbuf_grow(cbuf_t *cb, int n) |
| { |
| /* Attempts to grow the circular buffer [cb] by at least [n] bytes. |
| * Returns the number of bytes by which the buffer has grown (which may be |
| * less-than, equal-to, or greater-than the number of bytes requested). |
| */ |
| unsigned char *data; |
| int size_old, size_meta; |
| int m; |
| |
| assert(cb != NULL); |
| assert(n > 0); |
| assert(_cbuf_mutex_is_locked(cb)); |
| |
| if (cb->size == cb->maxsize) { |
| return(0); |
| } |
| size_old = cb->size; |
| size_meta = cb->alloc - cb->size; /* size of sentinel & magic cookies */ |
| assert(size_meta > 0); |
| |
| /* Attempt to grow data buffer by multiples of the chunk-size. |
| */ |
| m = cb->alloc + n; |
| m = m + (CBUF_CHUNK - (m % CBUF_CHUNK)); |
| m = MIN(m, (cb->maxsize + size_meta)); |
| assert(m > cb->alloc); |
| |
| data = cb->data; |
| #ifndef NDEBUG |
| data -= CBUF_MAGIC_LEN; /* jump back to what xmalloc returned */ |
| #endif /* !NDEBUG */ |
| |
| data = xrealloc(data, m); |
| cb->data = data; |
| cb->alloc = m; |
| cb->size = m - size_meta; |
| |
| #ifndef NDEBUG |
| /* A round cookie with one bite out of it looks like a C. |
| * The underflow cookie will have been copied by realloc() if needed. |
| * But the overflow cookie must be rebaked. |
| * Must use memcpy since overflow cookie may not be word-aligned. |
| */ |
| cb->data += CBUF_MAGIC_LEN; /* jump forward past underflow magic */ |
| memcpy(cb->data + cb->size + 1, (void *) &cb->magic, CBUF_MAGIC_LEN); |
| #endif /* !NDEBUG */ |
| |
| /* The memory containing replay and unread data must be contiguous modulo |
| * the buffer size. Additional memory must be inserted between where |
| * new data is written in (i_in) and where replay data starts (i_rep). |
| * If replay data wraps-around the old buffer, move it to the new end |
| * of the buffer so it wraps-around in the same manner. |
| */ |
| if (cb->i_rep > cb->i_in) { |
| n = (size_old + 1) - cb->i_rep; |
| m = (cb->size + 1) - n; |
| memmove(cb->data + m, cb->data + cb->i_rep, n); |
| |
| if (cb->i_out >= cb->i_rep) { |
| cb->i_out += m - cb->i_rep; |
| } |
| cb->i_rep = m; |
| } |
| assert(_cbuf_is_valid(cb)); |
| return(cb->size - size_old); |
| } |
| |
| |
| static int cbuf_shrink(cbuf_t *cb) |
| { |
| /* XXX: DOCUMENT ME. |
| */ |
| assert(cb != NULL); |
| assert(_cbuf_mutex_is_locked(cb)); |
| assert(_cbuf_is_valid(cb)); |
| |
| if (cb->size == cb->minsize) { |
| return(0); |
| } |
| if (cb->size - cb->used <= CBUF_CHUNK) { |
| return(0); |
| } |
| /* FIXME: NOT IMPLEMENTED. |
| */ |
| assert(_cbuf_is_valid(cb)); |
| return(0); |
| } |
| |
| |
| #ifndef NDEBUG |
| static int _cbuf_mutex_is_locked(cbuf_t *cb) |
| { |
| /* Returns true if the mutex is locked; o/w, returns false. |
| */ |
| int rc; |
| |
| assert(cb != NULL); |
| rc = pthread_mutex_trylock(&cb->mutex); |
| return(rc == EBUSY ? 1 : 0); |
| } |
| |
| static int _cbuf_is_valid(cbuf_t *cb) |
| { |
| /* Validates the data structure. All invariants should be tested here. |
| * Returns true if everything is valid; o/w, aborts due to assertion failure. |
| */ |
| int nfree; |
| |
| assert(cb != NULL); |
| assert(_cbuf_mutex_is_locked(cb)); |
| assert(cb->data != NULL); |
| assert(cb->magic == CBUF_MAGIC); |
| /* |
| * Must use memcmp since overflow cookie may not be word-aligned. |
| */ |
| assert(memcmp(cb->data - CBUF_MAGIC_LEN, |
| (void *) &cb->magic, CBUF_MAGIC_LEN) == 0); |
| assert(memcmp(cb->data + cb->size + 1, |
| (void *) &cb->magic, CBUF_MAGIC_LEN) == 0); |
| |
| assert(cb->alloc > 0); |
| assert(cb->alloc > cb->size); |
| assert(cb->size > 0); |
| assert(cb->size >= cb->minsize); |
| assert(cb->size <= cb->maxsize); |
| assert(cb->minsize > 0); |
| assert(cb->maxsize > 0); |
| assert(cb->used >= 0); |
| assert(cb->used <= cb->size); |
| assert(cb->overwrite == CBUF_NO_DROP |
| || cb->overwrite == CBUF_WRAP_ONCE |
| || cb->overwrite == CBUF_WRAP_MANY); |
| assert(cb->got_wrap || !cb->i_rep); /* i_rep = 0 if data has not wrapped */ |
| assert(cb->i_in >= 0); |
| assert(cb->i_in <= cb->size); |
| assert(cb->i_out >= 0); |
| assert(cb->i_out <= cb->size); |
| assert(cb->i_rep >= 0); |
| assert(cb->i_rep <= cb->size); |
| |
| if (cb->i_in >= cb->i_out) { |
| assert((cb->i_rep > cb->i_in) || (cb->i_rep <= cb->i_out)); |
| } |
| else /* if (cb->in < cb->i_out) */ { |
| assert((cb->i_rep > cb->i_in) && (cb->i_rep <= cb->i_out)); |
| } |
| nfree = (cb->i_out - cb->i_in - 1 + (cb->size + 1)) % (cb->size + 1); |
| assert(cb->size - cb->used == nfree); |
| |
| return(1); |
| } |
| #endif /* !NDEBUG */ |