/* Copyright (c) 2013 * Mike Gerwitz (mtg@gnu.org) * * This file is part of GNU screen. * * GNU screen 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 3, or (at your option) * any later version. * * This program 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 this program (see the file COPYING); if not, see * . * **************************************************************** */ #include "config.h" #include "winmsgbuf.h" #include #include #include /* Allocate and initialize to the empty string a new window message buffer. The * return value must be freed using wmbc_free. */ WinMsgBuf *wmb_create(void) { WinMsgBuf *w = malloc(sizeof(WinMsgBuf)); if (w == NULL) return NULL; w->buf = malloc(WINMSGBUF_SIZE); if (w->buf == NULL) { free(w); return NULL; } w->size = WINMSGBUF_SIZE; wmb_reset(w); return w; } /* Attempts to expand the buffer to hold at least MIN bytes. The new size of the * buffer is returned, which may be unchanged from the original size if * additional memory could not be allocated. */ size_t wmb_expand(WinMsgBuf *wmb, size_t min) { size_t size = wmb->size; if (size >= min) return size; /* keep doubling the buffer until we reach at least the requested size; this * ensures that we'll always be a power of two (so long as the original * buffer size was) and doubling will help cut down on excessive allocation * requests on large buffers */ while (size < min) { size *= 2; } void *p = realloc(wmb->buf, size); if (p == NULL) { /* reallocation failed; maybe the caller can do without? */ return wmb->size; } /* realloc already handled the free for us */ wmb->buf = p; wmb->size = size; return size; } /* Add a rendition to the buffer */ void wmb_rendadd(WinMsgBuf *wmb, uint64_t r, int offset) { /* TODO: lift arbitrary limit; dynamically allocate */ if (wmb->numrend >= MAX_WINMSG_REND) return; wmb->rend[wmb->numrend] = r; wmb->rendpos[wmb->numrend] = offset; wmb->numrend++; } /* Retrieve buffer size. This returns the total size of the buffer, not how much * has been used. */ size_t wmb_size(const WinMsgBuf *wmb) { return wmb->size; } /* Retrieve a pointer to the raw buffer contents. This should not be used to * modify the buffer. */ const char *wmb_contents(const WinMsgBuf *wmb) { return wmb->buf; } /* Initializes window buffer to the empty string; useful for re-using an * existing buffer without allocating a new one. */ void wmb_reset(WinMsgBuf *w) { *w->buf = '\0'; w->numrend = 0; } /* Deinitialize and free memory allocated to the given window buffer */ void wmb_free(WinMsgBuf *w) { free(w->buf); free(w); } /* Allocate and initialize a buffer context for the given buffer. The return * value must be freed using wmbc_free. */ WinMsgBufContext *wmbc_create(WinMsgBuf *w) { if (w == NULL) return NULL; WinMsgBufContext *c = malloc(sizeof(WinMsgBufContext)); if (c == NULL) return NULL; c->buf = w; c->p = w->buf; return c; } /* Rewind pointer to the first byte of the buffer. */ void wmbc_rewind(WinMsgBufContext *wmbc) { wmbc->p = wmbc->buf->buf; } /* Place pointer at terminating null character. */ void wmbc_fastfw0(WinMsgBufContext *wmbc) { wmbc->p += strlen(wmbc->p); } /* Place pointer just past the last byte in the buffer, ignoring terminating null * characters. The next write will trigger an expansion. */ void wmbc_fastfw_end(WinMsgBufContext *wmbc) { wmbc->p = wmbc->buf->buf + wmbc->buf->size; } /* Attempts buffer expansion and updates context pointers appropriately. The * result is true if expansion succeeded, otherwise false. */ static bool _wmbc_expand(WinMsgBufContext *wmbc, size_t size) { size_t offset = wmbc_offset(wmbc); if (wmb_expand(wmbc->buf, size) < size) { return false; } /* the buffer address may have changed; re-calculate pointer address */ wmbc->p = wmbc->buf->buf + offset; return true; } /* Sets a character at the current buffer position and increments the pointer. * The terminating null character is not retained. The buffer will be * dynamically resized as needed. */ void wmbc_putchar(WinMsgBufContext *wmbc, char c) { /* attempt to accomodate this character, but bail out silenty if it cannot * fit */ if (!wmbc_bytesleft(wmbc)) { if (!_wmbc_expand(wmbc, wmbc->buf->size + 1)) { return; } } *wmbc->p++ = c; } /* Copies a string into the buffer, dynamically resizing the buffer as needed to * accomodate length N. If S is shorter than N characters in length, the * remaining bytes are filled will nulls. The context pointer is adjusted to the * terminating null byte. A pointer to the first copied character in the buffer * is returned; it shall not be used to modify the buffer. */ const char *wmbc_strncpy(WinMsgBufContext *wmbc, const char *s, size_t n) { size_t l = wmbc_bytesleft(wmbc); /* silently fail in the event that we cannot accomodate */ if (l < n) { size_t size = wmbc->buf->size + (n - l); if (!_wmbc_expand(wmbc, size)) { /* TODO: we should copy what can fit. */ return NULL; } } char *p = wmbc->p; strncpy(wmbc->p, s, n); wmbc->p += n; return p; } /* Copies a string into the buffer, dynamically resizing the buffer as needed to * accomodate the length of the string sans its terminating null byte. The * context pointer is adjusted to the the terminiating null byte. A pointer to * the first copied character in the destination buffer is returned; it shall * not be used to modify the buffer. */ const char *wmbc_strcpy(WinMsgBufContext *wmbc, const char *s) { return wmbc_strncpy(wmbc, s, strlen(s)); } /* Write data to the buffer using a printf-style format string. If needed, the * buffer will be automatically expanded to accomodate the resulting string and * is therefore protected against overflows. */ int wmbc_printf(WinMsgBufContext *wmbc, const char *fmt, ...) { va_list ap; size_t n, max; /* to prevent buffer overflows, cap the number of bytes to the remaining * buffer size */ va_start(ap, fmt); max = wmbc_bytesleft(wmbc); n = vsnprintf(wmbc->p, max, fmt, ap); va_end(ap); /* more space is needed if vsnprintf returns a larger number than our max, * in which case we should accomodate by dynamically resizing the buffer and * trying again */ if (n > max) { if (!_wmbc_expand(wmbc, wmb_size(wmbc->buf) + n - max)) { /* failed to allocate additional memory; this will simply have to do */ wmbc_fastfw_end(wmbc); return max; } va_start(ap, fmt); size_t m = vsnprintf(wmbc->p, n + 1, fmt, ap); assert(m == n); /* this should never fail */ va_end(ap); } wmbc_fastfw0(wmbc); return n; } /* Retrieve the 0-indexed offset of the context pointer into the buffer */ size_t wmbc_offset(WinMsgBufContext *wmbc) { ptrdiff_t offset = wmbc->p - wmbc->buf->buf; /* when using wmbc_* functions (as one always should), the offset should * always be within the bounds of the buffer or one byte outside of it * (the latter case would require an expansion before writing) */ assert(offset > -1); assert((size_t)offset <= wmbc->buf->size); return (size_t)offset; } /* Calculate the number of bytes remaining in the buffer relative to the current * position within the buffer */ size_t wmbc_bytesleft(WinMsgBufContext *wmbc) { return wmbc->buf->size - wmbc_offset(wmbc); } /* Merges the contents of another null-terminated buffer and its renditions. The * return value is a pointer to the first character of WMB's buffer. */ const char *wmbc_mergewmb(WinMsgBufContext *wmbc, WinMsgBuf *wmb) { const char *p; size_t offset = wmbc_offset(wmbc); /* import buffer contents into our own at our current position */ assert(wmb); p = wmbc_strcpy(wmbc, wmb->buf); /* merge renditions, adjusting them to reflect their new offset */ for (int i = 0; i < wmb->numrend; i++) { wmb_rendadd(wmbc->buf, wmb->rend[i], offset + wmb->rendpos[i]); } return p; } /* Write a terminating null byte to the buffer and return a pointer to the * buffer contents. This should not be used to modify the buffer. If buffer is * full and expansion fails, then the last byte in the buffer will be replaced * with the null byte. */ const char *wmbc_finish(WinMsgBufContext *wmbc) { if (!wmbc_bytesleft(wmbc)) { size_t size = wmbc->buf->size + 1; if (wmb_expand(wmbc->buf, size) < size) { /* we must terminate the string or we may cause big problems for the * caller; overwrite the last char :x */ wmbc->p--; } } *wmbc->p = '\0'; return wmb_contents(wmbc->buf); } /* Deinitializes and frees previously allocated context. The contained buffer * must be freed separately; this function will not do so for you. */ void wmbc_free(WinMsgBufContext *c) { free(c); }