diff --git a/docs/changelog.rst b/docs/changelog.rst index 7f21f2219..84bee5e45 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -52,6 +52,9 @@ To update |kitty|, :doc:`follow the instructions `. - Add a ``right`` option for :opt:`tab_switch_strategy` (:pull:`3155`) +- Fix a regression in 0.19.0 that caused a rare crash when using the optional + :opt:`scrollback_pager_history_size` (:iss:`3049`) + 0.19.2 [2020-11-13] ------------------- diff --git a/kitty/data-types.h b/kitty/data-types.h index bb90483e1..f042513b6 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -193,9 +193,8 @@ typedef struct { } HistoryBufSegment; typedef struct { - uint8_t *buffer; - size_t buffer_size, max_sz; - size_t start, length; + void *ringbuf; + size_t maximum_size; bool rewrap_needed; } PagerHistoryBuf; diff --git a/kitty/history.c b/kitty/history.c index 5ea9953bf..64510f857 100644 --- a/kitty/history.c +++ b/kitty/history.c @@ -9,6 +9,7 @@ #include "lineops.h" #include "charsets.h" #include +#include "ringbuf.h" extern PyTypeObject Line_Type; #define SEGMENT_SIZE 2048 @@ -61,42 +62,37 @@ alloc_pagerhist(size_t pagerhist_sz) { if (!pagerhist_sz) return NULL; ph = PyMem_Calloc(1, sizeof(PagerHistoryBuf)); if (!ph) return NULL; - ph->max_sz = pagerhist_sz; - ph->buffer_size = MIN(1024u*1024u, ph->max_sz); - ph->buffer = PyMem_Malloc(ph->buffer_size); - if (!ph->buffer) { PyMem_Free(ph); return NULL; } + size_t sz = MIN(1024u * 1024u, pagerhist_sz); + ph->ringbuf = ringbuf_new(sz); + if (!ph->ringbuf) { PyMem_Free(ph); return NULL; } + ph->maximum_size = pagerhist_sz; return ph; } static inline void free_pagerhist(HistoryBuf *self) { - if (self->pagerhist) PyMem_Free(self->pagerhist->buffer); + if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_free((ringbuf_t*)&self->pagerhist->ringbuf); PyMem_Free(self->pagerhist); self->pagerhist = NULL; } static inline bool pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) { - if (ph->buffer_size >= ph->max_sz) return false; - size_t newsz = MIN(ph->max_sz, ph->buffer_size + MAX(1024u * 1024u, minsz)); - uint8_t *newbuf = PyMem_Malloc(newsz); + size_t buffer_size = ringbuf_capacity(ph->ringbuf); + if (buffer_size >= ph->maximum_size) return false; + size_t newsz = MIN(ph->maximum_size, buffer_size + MAX(1024u * 1024u, minsz)); + ringbuf_t newbuf = ringbuf_new(newsz); if (!newbuf) return false; - size_t copied = MIN(ph->length, ph->buffer_size - ph->start); - if (copied) memcpy(newbuf, ph->buffer + ph->start, copied); - if (copied < ph->length) memcpy(newbuf + copied, ph->buffer, (ph->length - copied)); - PyMem_Free(ph->buffer); - ph->start = 0; - ph->buffer = newbuf; - ph->buffer_size = newsz; + size_t count = ringbuf_bytes_used(ph->ringbuf); + if (count) ringbuf_copy(newbuf, ph->ringbuf, count); + ringbuf_free((ringbuf_t*)&ph->ringbuf); + ph->ringbuf = newbuf; return true; } static inline void pagerhist_clear(HistoryBuf *self) { - if (!self->pagerhist || !self->pagerhist->max_sz) return; - index_type pagerhist_sz = self->pagerhist->max_sz; - free_pagerhist(self); - self->pagerhist = alloc_pagerhist(pagerhist_sz); + if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_reset(self->pagerhist->ringbuf); } static HistoryBuf* @@ -188,36 +184,28 @@ historybuf_clear(HistoryBuf *self) { static inline bool pagerhist_write_bytes(PagerHistoryBuf *ph, const uint8_t *buf, size_t sz) { - if (sz > ph->max_sz) return false; + if (sz > ph->maximum_size) return false; if (!sz) return true; - if (sz > ph->buffer_size - ph->length) pagerhist_extend(ph, sz); - if (sz > ph->buffer_size) return false; - size_t start_writing_at = (ph->start + ph->length) % ph->buffer_size; - size_t available_space = ph->buffer_size - ph->length; - size_t overlap = available_space < sz ? sz - available_space : 0; - size_t copied = MIN(sz, ph->buffer_size - start_writing_at); - ph->length += sz - overlap; - ph->start = (ph->start + overlap) % ph->buffer_size; - if (copied) memcpy(ph->buffer + start_writing_at, buf, copied); - if (copied < sz) memcpy(ph->buffer, buf + copied, (sz - copied)); + size_t space_in_ringbuf = ringbuf_bytes_free(ph->ringbuf); + if (sz > space_in_ringbuf) pagerhist_extend(ph, sz); + ringbuf_memcpy_into(ph->ringbuf, buf, sz); return true; } static inline bool pagerhist_ensure_start_is_valid_utf8(PagerHistoryBuf *ph) { + uint8_t scratch[8]; + size_t num = ringbuf_memcpy_from(scratch, ph->ringbuf, arraysz(scratch)); uint32_t state = UTF8_ACCEPT, codep; - size_t pos = ph->start, count = 0; + size_t count = 0; size_t last_reject_at = 0; - while (count < ph->length) { - decode_utf8(&state, &codep, ph->buffer[pos]); - count++; + while (count < num) { + decode_utf8(&state, &codep, scratch[count++]); if (state == UTF8_ACCEPT) break; if (state == UTF8_REJECT) { state = UTF8_ACCEPT; last_reject_at = count; } - pos = pos == ph->buffer_size - 1 ? 0: pos + 1; } if (last_reject_at) { - ph->start = (ph->start + last_reject_at) % ph->buffer_size; - ph->length -= last_reject_at; + ringbuf_memmove_from(scratch, ph->ringbuf, last_reject_at); return true; } return false; @@ -241,7 +229,7 @@ pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) { Line l = {.xnum=self->xnum}; init_line(self, self->start_of_data, &l); line_as_ansi(&l, as_ansi_buf, &prev_cell); - if (ph->length != 0 && !l.continued) pagerhist_write_bytes(ph, (const uint8_t*)"\n", 1); + if (ringbuf_bytes_used(ph->ringbuf) && !l.continued) pagerhist_write_bytes(ph, (const uint8_t*)"\n", 1); pagerhist_write_bytes(ph, (const uint8_t*)"\x1b[m", 3); if (pagerhist_write_ucs4(ph, as_ansi_buf->buf, as_ansi_buf->len)) pagerhist_write_bytes(ph, (const uint8_t*)"\r", 1); } @@ -335,15 +323,15 @@ static inline Line* get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; } static inline char_type -pagerhist_read_char(PagerHistoryBuf *ph, size_t pos, unsigned *count, uint8_t record[8]) { +pagerhist_remove_char(PagerHistoryBuf *ph, unsigned *count, uint8_t record[8]) { uint32_t codep, state = UTF8_ACCEPT; *count = 0; - while (true) { - decode_utf8(&state, &codep, ph->buffer[pos]); - record[(*count)++] = ph->buffer[pos]; + while (ringbuf_bytes_used(ph->ringbuf)) { + ringbuf_memmove_from(&record[*count], ph->ringbuf, 1); + decode_utf8(&state, &codep, record[*count]); + *count += 1; if (state == UTF8_REJECT) { codep = 0; break; } if (state == UTF8_ACCEPT) break; - pos = pos == ph->buffer_size - 1 ? 0 : (pos + 1); } return codep; } @@ -351,14 +339,12 @@ pagerhist_read_char(PagerHistoryBuf *ph, size_t pos, unsigned *count, uint8_t re static void pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) { PagerHistoryBuf *ph = self->pagerhist; - if (!ph->length) return; + if (!ph->ringbuf || !ringbuf_bytes_used(ph->ringbuf)) return; PagerHistoryBuf *nph = PyMem_Calloc(sizeof(PagerHistoryBuf), 1); if (!nph) return; - nph->buffer_size = ph->buffer_size; - nph->max_sz = ph->max_sz; - nph->buffer = PyMem_Malloc(nph->buffer_size); - if (!nph->buffer) { PyMem_Free(nph); return ; } - size_t i = 0, pos; + nph->maximum_size = ph->maximum_size; + nph->ringbuf = ringbuf_new(MIN(ph->maximum_size, ringbuf_capacity(ph->ringbuf) + 4096)); + if (!nph->ringbuf) { PyMem_Free(nph); return ; } ssize_t ch_width = 0; unsigned count; uint8_t record[8]; @@ -367,11 +353,6 @@ pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) { WCSState wcs_state; initialize_wcs_state(&wcs_state); -#define READ_CHAR(ch) { \ - ch = pagerhist_read_char(ph, pos, &count, record); \ - i += count; pos += count; \ - if (pos >= ph->buffer_size) pos = pos - ph->buffer_size; \ -} #define WRITE_CHAR() { \ if (num_in_current_line + ch_width > cells_in_line) { \ pagerhist_write_bytes(nph, (const uint8_t*)"\r", 1); \ @@ -381,10 +362,8 @@ pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) { pagerhist_write_bytes(nph, record, count); \ } - for (i = 0; i < ph->length;) { - pos = ph->start + i; - if (pos >= ph->buffer_size) pos = pos - ph->buffer_size; - READ_CHAR(ch); + while (ringbuf_bytes_used(ph->ringbuf)) { + ch = pagerhist_remove_char(ph, &count, record); if (ch == '\n') { initialize_wcs_state(&wcs_state); ch_width = 1; @@ -397,12 +376,12 @@ pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) { } free_pagerhist(self); self->pagerhist = nph; -#undef READ_CHAR +#undef WRITE_CHAR } static PyObject* pagerhist_write(HistoryBuf *self, PyObject *what) { - if (self->pagerhist && self->pagerhist->max_sz) { + if (self->pagerhist && self->pagerhist->maximum_size) { if (PyBytes_Check(what)) pagerhist_write_bytes(self->pagerhist, (const uint8_t*)PyBytes_AS_STRING(what), PyBytes_GET_SIZE(what)); else if (PyUnicode_Check(what) && PyUnicode_READY(what) == 0) { Py_UCS4 *buf = PyUnicode_AsUCS4Copy(what); @@ -418,19 +397,17 @@ pagerhist_write(HistoryBuf *self, PyObject *what) { static PyObject* pagerhist_as_bytes(HistoryBuf *self, PyObject *args UNUSED) { PagerHistoryBuf *ph = self->pagerhist; - if (!ph || !ph->length) return PyBytes_FromStringAndSize("", 0); + if (!ph || !ringbuf_bytes_used(ph->ringbuf)) return PyBytes_FromStringAndSize("", 0); pagerhist_ensure_start_is_valid_utf8(ph); if (ph->rewrap_needed) pagerhist_rewrap_to(self, self->xnum); Line l = {.xnum=self->xnum}; get_line(self, 0, &l); - size_t sz = ph->length; + size_t sz = ringbuf_bytes_used(ph->ringbuf); if (!l.continued) sz += 1; PyObject *ans = PyBytes_FromStringAndSize(NULL, sz); if (!ans) return NULL; uint8_t *buf = (uint8_t*)PyBytes_AS_STRING(ans); - size_t copied = MIN(ph->length, ph->buffer_size - ph->start); - if (copied) memcpy(buf, ph->buffer + ph->start, copied); - if (copied < ph->length) memcpy(buf + copied, ph->buffer, (ph->length - copied)); + ringbuf_memcpy_from(buf, ph->ringbuf, sz); if (!l.continued) buf[sz-1] = '\n'; return ans; } @@ -561,7 +538,7 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other, ANSIBuf *as_ansi_buf other->count = self->count; other->start_of_data = self->start_of_data; return; } - if (other->pagerhist && other->xnum != self->xnum && other->pagerhist->length) + if (other->pagerhist && other->xnum != self->xnum && ringbuf_bytes_used(other->pagerhist->ringbuf)) other->pagerhist->rewrap_needed = true; other->count = 0; other->start_of_data = 0; index_type x = 0, y = 0; diff --git a/kitty/ringbuf.c b/kitty/ringbuf.c new file mode 100644 index 000000000..3c138adb2 --- /dev/null +++ b/kitty/ringbuf.c @@ -0,0 +1,380 @@ +/* + * ringbuf.c - C ring buffer (FIFO) implementation. + * + * Written in 2011 by Drew Hess . + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to + * the public domain worldwide. This software is distributed without + * any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication + * along with this software. If not, see + * . + */ + +#define NDEBUG 1 +#include "ringbuf.h" + +#include +#include +#include +#include +#include +#include +#include + +static inline size_t +size_t_min(size_t x, size_t y) { + return x > y ? y : x; +} + +/* + * The code is written for clarity, not cleverness or performance, and + * contains many assert()s to enforce invariant assumptions and catch + * bugs. Feel free to optimize the code and to remove asserts for use + * in your own projects, once you're comfortable that it functions as + * intended. + */ + +struct ringbuf_t +{ + uint8_t *buf; + uint8_t *head, *tail; + size_t size; +}; + +ringbuf_t +ringbuf_new(size_t capacity) +{ + ringbuf_t rb = malloc(sizeof(struct ringbuf_t)); + if (rb) { + + /* One byte is used for detecting the full condition. */ + rb->size = capacity + 1; + rb->buf = malloc(rb->size); + if (rb->buf) + ringbuf_reset(rb); + else { + free(rb); + return 0; + } + } + return rb; +} + +size_t +ringbuf_buffer_size(const struct ringbuf_t *rb) +{ + return rb->size; +} + +void +ringbuf_reset(ringbuf_t rb) +{ + rb->head = rb->tail = rb->buf; +} + +void +ringbuf_free(ringbuf_t *rb) +{ + assert(rb && *rb); + free((*rb)->buf); + free(*rb); + *rb = 0; +} + +size_t +ringbuf_capacity(const struct ringbuf_t *rb) +{ + return ringbuf_buffer_size(rb) - 1; +} + +/* + * Return a pointer to one-past-the-end of the ring buffer's + * contiguous buffer. You shouldn't normally need to use this function + * unless you're writing a new ringbuf_* function. + */ +static const uint8_t * +ringbuf_end(const struct ringbuf_t *rb) +{ + return rb->buf + ringbuf_buffer_size(rb); +} + +size_t +ringbuf_bytes_free(const struct ringbuf_t *rb) +{ + if (rb->head >= rb->tail) + return ringbuf_capacity(rb) - (rb->head - rb->tail); + else + return rb->tail - rb->head - 1; +} + +size_t +ringbuf_bytes_used(const struct ringbuf_t *rb) +{ + return ringbuf_capacity(rb) - ringbuf_bytes_free(rb); +} + +int +ringbuf_is_full(const struct ringbuf_t *rb) +{ + return ringbuf_bytes_free(rb) == 0; +} + +int +ringbuf_is_empty(const struct ringbuf_t *rb) +{ + return ringbuf_bytes_free(rb) == ringbuf_capacity(rb); +} + +const void * +ringbuf_tail(const struct ringbuf_t *rb) +{ + return rb->tail; +} + +const void * +ringbuf_head(const struct ringbuf_t *rb) +{ + return rb->head; +} + +/* + * Given a ring buffer rb and a pointer to a location within its + * contiguous buffer, return the a pointer to the next logical + * location in the ring buffer. + */ +static uint8_t * +ringbuf_nextp(ringbuf_t rb, const uint8_t *p) +{ + /* + * The assert guarantees the expression (++p - rb->buf) is + * non-negative; therefore, the modulus operation is safe and + * portable. + */ + assert((p >= rb->buf) && (p < ringbuf_end(rb))); + return rb->buf + ((++p - rb->buf) % ringbuf_buffer_size(rb)); +} + +size_t +ringbuf_findchr(const struct ringbuf_t *rb, int c, size_t offset) +{ + const uint8_t *bufend = ringbuf_end(rb); + size_t bytes_used = ringbuf_bytes_used(rb); + if (offset >= bytes_used) + return bytes_used; + + const uint8_t *start = rb->buf + + (((rb->tail - rb->buf) + offset) % ringbuf_buffer_size(rb)); + assert(bufend > start); + size_t n = size_t_min(bufend - start, bytes_used - offset); + const uint8_t *found = memchr(start, c, n); + if (found) + return offset + (found - start); + else + return ringbuf_findchr(rb, c, offset + n); +} + +size_t +ringbuf_memset(ringbuf_t dst, int c, size_t len) +{ + const uint8_t *bufend = ringbuf_end(dst); + size_t nwritten = 0; + size_t count = size_t_min(len, ringbuf_buffer_size(dst)); + int overflow = count > ringbuf_bytes_free(dst); + + while (nwritten != count) { + + /* don't copy beyond the end of the buffer */ + assert(bufend > dst->head); + size_t n = size_t_min(bufend - dst->head, count - nwritten); + memset(dst->head, c, n); + dst->head += n; + nwritten += n; + + /* wrap? */ + if (dst->head == bufend) + dst->head = dst->buf; + } + + if (overflow) { + dst->tail = ringbuf_nextp(dst, dst->head); + assert(ringbuf_is_full(dst)); + } + + return nwritten; +} + +void * +ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count) +{ + const uint8_t *u8src = src; + const uint8_t *bufend = ringbuf_end(dst); + int overflow = count > ringbuf_bytes_free(dst); + size_t nread = 0; + + while (nread != count) { + /* don't copy beyond the end of the buffer */ + assert(bufend > dst->head); + size_t n = size_t_min(bufend - dst->head, count - nread); + memcpy(dst->head, u8src + nread, n); + dst->head += n; + nread += n; + + /* wrap? */ + if (dst->head == bufend) + dst->head = dst->buf; + } + + if (overflow) { + dst->tail = ringbuf_nextp(dst, dst->head); + assert(ringbuf_is_full(dst)); + } + + return dst->head; +} + +ssize_t +ringbuf_read(int fd, ringbuf_t rb, size_t count) +{ + const uint8_t *bufend = ringbuf_end(rb); + size_t nfree = ringbuf_bytes_free(rb); + + /* don't write beyond the end of the buffer */ + assert(bufend > rb->head); + count = size_t_min(bufend - rb->head, count); + ssize_t n = read(fd, rb->head, count); + if (n > 0) { + assert(rb->head + n <= bufend); + rb->head += n; + + /* wrap? */ + if (rb->head == bufend) + rb->head = rb->buf; + + /* fix up the tail pointer if an overflow occurred */ + if ((size_t)n > nfree) { + rb->tail = ringbuf_nextp(rb, rb->head); + assert(ringbuf_is_full(rb)); + } + } + + return n; +} + +void * +ringbuf_memmove_from(void *dst, ringbuf_t src, size_t count) +{ + size_t bytes_used = ringbuf_bytes_used(src); + if (count > bytes_used) + return 0; + + uint8_t *u8dst = dst; + const uint8_t *bufend = ringbuf_end(src); + size_t nwritten = 0; + while (nwritten != count) { + assert(bufend > src->tail); + size_t n = size_t_min(bufend - src->tail, count - nwritten); + memcpy(u8dst + nwritten, src->tail, n); + src->tail += n; + nwritten += n; + + /* wrap ? */ + if (src->tail == bufend) + src->tail = src->buf; + } + + assert(count + ringbuf_bytes_used(src) == bytes_used); + return src->tail; +} + +size_t +ringbuf_memcpy_from(void *dst, const ringbuf_t src, size_t count) +{ + size_t bytes_used = ringbuf_bytes_used(src); + if (count > bytes_used) count = bytes_used; + + uint8_t *u8dst = dst; + const uint8_t *bufend = ringbuf_end(src); + size_t nwritten = 0; + const uint8_t* tail = src->tail; + while (nwritten != count) { + assert(bufend > tail); + size_t n = size_t_min(bufend - tail, count - nwritten); + memcpy(u8dst + nwritten, tail, n); + tail += n; + nwritten += n; + + /* wrap ? */ + if (tail == bufend) + tail = src->buf; + } + + assert(count + ringbuf_bytes_used(src) == bytes_used); + return count; +} + + +ssize_t +ringbuf_write(int fd, ringbuf_t rb, size_t count) +{ + size_t bytes_used = ringbuf_bytes_used(rb); + if (count > bytes_used) + return 0; + + const uint8_t *bufend = ringbuf_end(rb); + assert(bufend > rb->head); + count = size_t_min(bufend - rb->tail, count); + ssize_t n = write(fd, rb->tail, count); + if (n > 0) { + assert(rb->tail + n <= bufend); + rb->tail += n; + + /* wrap? */ + if (rb->tail == bufend) + rb->tail = rb->buf; + + assert(n + ringbuf_bytes_used(rb) == bytes_used); + } + + return n; +} + +void * +ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count) +{ + size_t src_bytes_used = ringbuf_bytes_used(src); + if (count > src_bytes_used) + return 0; + int overflow = count > ringbuf_bytes_free(dst); + + const uint8_t *src_bufend = ringbuf_end(src); + const uint8_t *dst_bufend = ringbuf_end(dst); + size_t ncopied = 0; + while (ncopied != count) { + assert(src_bufend > src->tail); + size_t nsrc = size_t_min(src_bufend - src->tail, count - ncopied); + assert(dst_bufend > dst->head); + size_t n = size_t_min(dst_bufend - dst->head, nsrc); + memcpy(dst->head, src->tail, n); + src->tail += n; + dst->head += n; + ncopied += n; + + /* wrap ? */ + if (src->tail == src_bufend) + src->tail = src->buf; + if (dst->head == dst_bufend) + dst->head = dst->buf; + } + + assert(count + ringbuf_bytes_used(src) == src_bytes_used); + + if (overflow) { + dst->tail = ringbuf_nextp(dst, dst->head); + assert(ringbuf_is_full(dst)); + } + + return dst->head; +} diff --git a/kitty/ringbuf.h b/kitty/ringbuf.h new file mode 100644 index 000000000..5dc31806f --- /dev/null +++ b/kitty/ringbuf.h @@ -0,0 +1,247 @@ +#pragma once +/* + * ringbuf.h - C ring buffer (FIFO) interface. + * + * Written in 2011 by Drew Hess . + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to + * the public domain worldwide. This software is distributed without + * any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication + * along with this software. If not, see + * . + */ + +/* + * A byte-addressable ring buffer FIFO implementation. + * + * The ring buffer's head pointer points to the starting location + * where data should be written when copying data *into* the buffer + * (e.g., with ringbuf_read). The ring buffer's tail pointer points to + * the starting location where data should be read when copying data + * *from* the buffer (e.g., with ringbuf_write). + */ + +#include +#include + +typedef struct ringbuf_t *ringbuf_t; + +/* + * Create a new ring buffer with the given capacity (usable + * bytes). Note that the actual internal buffer size may be one or + * more bytes larger than the usable capacity, for bookkeeping. + * + * Returns the new ring buffer object, or 0 if there's not enough + * memory to fulfill the request for the given capacity. + */ +ringbuf_t +ringbuf_new(size_t capacity); + +/* + * The size of the internal buffer, in bytes. One or more bytes may be + * unusable in order to distinguish the "buffer full" state from the + * "buffer empty" state. + * + * For the usable capacity of the ring buffer, use the + * ringbuf_capacity function. + */ +size_t +ringbuf_buffer_size(const struct ringbuf_t *rb); + +/* + * Deallocate a ring buffer, and, as a side effect, set the pointer to + * 0. + */ +void +ringbuf_free(ringbuf_t *rb); + +/* + * Reset a ring buffer to its initial state (empty). + */ +void +ringbuf_reset(ringbuf_t rb); + +/* + * The usable capacity of the ring buffer, in bytes. Note that this + * value may be less than the ring buffer's internal buffer size, as + * returned by ringbuf_buffer_size. + */ +size_t +ringbuf_capacity(const struct ringbuf_t *rb); + +/* + * The number of free/available bytes in the ring buffer. This value + * is never larger than the ring buffer's usable capacity. + */ +size_t +ringbuf_bytes_free(const struct ringbuf_t *rb); + +/* + * The number of bytes currently being used in the ring buffer. This + * value is never larger than the ring buffer's usable capacity. + */ +size_t +ringbuf_bytes_used(const struct ringbuf_t *rb); + +int +ringbuf_is_full(const struct ringbuf_t *rb); + +int +ringbuf_is_empty(const struct ringbuf_t *rb); + +/* + * Const access to the head and tail pointers of the ring buffer. + */ +const void * +ringbuf_tail(const struct ringbuf_t *rb); + +const void * +ringbuf_head(const struct ringbuf_t *rb); + +/* + * Locate the first occurrence of character c (converted to an + * unsigned char) in ring buffer rb, beginning the search at offset + * bytes from the ring buffer's tail pointer. The function returns the + * offset of the character from the ring buffer's tail pointer, if + * found. If c does not occur in the ring buffer, the function returns + * the number of bytes used in the ring buffer. + * + * Note that the offset parameter and the returned offset are logical + * offsets from the tail pointer, not necessarily linear offsets. + */ +size_t +ringbuf_findchr(const struct ringbuf_t *rb, int c, size_t offset); + +/* + * Beginning at ring buffer dst's head pointer, fill the ring buffer + * with a repeating sequence of len bytes, each of value c (converted + * to an unsigned char). len can be as large as you like, but the + * function will never write more than ringbuf_buffer_size(dst) bytes + * in a single invocation, since that size will cause all bytes in the + * ring buffer to be written exactly once each. + * + * Note that if len is greater than the number of free bytes in dst, + * the ring buffer will overflow. When an overflow occurs, the state + * of the ring buffer is guaranteed to be consistent, including the + * head and tail pointers; old data will simply be overwritten in FIFO + * fashion, as needed. However, note that, if calling the function + * results in an overflow, the value of the ring buffer's tail pointer + * may be different than it was before the function was called. + * + * Returns the actual number of bytes written to dst: len, if + * len < ringbuf_buffer_size(dst), else ringbuf_buffer_size(dst). + */ +size_t +ringbuf_memset(ringbuf_t dst, int c, size_t len); + +/* + * Copy n bytes from a contiguous memory area src into the ring buffer + * dst. Returns the ring buffer's new head pointer. + * + * It is possible to copy more data from src than is available in the + * buffer; i.e., it's possible to overflow the ring buffer using this + * function. When an overflow occurs, the state of the ring buffer is + * guaranteed to be consistent, including the head and tail pointers; + * old data will simply be overwritten in FIFO fashion, as + * needed. However, note that, if calling the function results in an + * overflow, the value of the ring buffer's tail pointer may be + * different than it was before the function was called. + */ +void * +ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count); + +/* + * This convenience function calls read(2) on the file descriptor fd, + * using the ring buffer rb as the destination buffer for the read, + * and returns the value returned by read(2). It will only call + * read(2) once, and may return a short count. + * + * It is possible to read more data from the file descriptor than is + * available in the buffer; i.e., it's possible to overflow the ring + * buffer using this function. When an overflow occurs, the state of + * the ring buffer is guaranteed to be consistent, including the head + * and tail pointers: old data will simply be overwritten in FIFO + * fashion, as needed. However, note that, if calling the function + * results in an overflow, the value of the ring buffer's tail pointer + * may be different than it was before the function was called. + */ +ssize_t +ringbuf_read(int fd, ringbuf_t rb, size_t count); + +/* + * Copy n bytes from the ring buffer src, starting from its tail + * pointer, into a contiguous memory area dst. Returns the value of + * src's tail pointer after the copy is finished. + * + * Note that this copy is destructive with respect to the ring buffer: + * the n bytes copied from the ring buffer are no longer available in + * the ring buffer after the copy is complete, and the ring buffer + * will have n more free bytes than it did before the function was + * called. + * + * This function will *not* allow the ring buffer to underflow. If + * count is greater than the number of bytes used in the ring buffer, + * no bytes are copied, and the function will return 0. + */ +void * +ringbuf_memmove_from(void *dst, ringbuf_t src, size_t count); + +/* + * Same as ringbuf_memmove_from() except that it does not change the ringbuffer + * and returns the actual number of bytes copied, which is the minimum of ringbuf_bytes_used + * and count. + */ +size_t +ringbuf_memcpy_from(void *dst, const ringbuf_t src, size_t count); + +/* + * This convenience function calls write(2) on the file descriptor fd, + * using the ring buffer rb as the source buffer for writing (starting + * at the ring buffer's tail pointer), and returns the value returned + * by write(2). It will only call write(2) once, and may return a + * short count. + * + * Note that this copy is destructive with respect to the ring buffer: + * any bytes written from the ring buffer to the file descriptor are + * no longer available in the ring buffer after the copy is complete, + * and the ring buffer will have N more free bytes than it did before + * the function was called, where N is the value returned by the + * function (unless N is < 0, in which case an error occurred and no + * bytes were written). + * + * This function will *not* allow the ring buffer to underflow. If + * count is greater than the number of bytes used in the ring buffer, + * no bytes are written to the file descriptor, and the function will + * return 0. + */ +ssize_t +ringbuf_write(int fd, ringbuf_t rb, size_t count); + +/* + * Copy count bytes from ring buffer src, starting from its tail + * pointer, into ring buffer dst. Returns dst's new head pointer after + * the copy is finished. + * + * Note that this copy is destructive with respect to the ring buffer + * src: any bytes copied from src into dst are no longer available in + * src after the copy is complete, and src will have 'count' more free + * bytes than it did before the function was called. + * + * It is possible to copy more data from src than is available in dst; + * i.e., it's possible to overflow dst using this function. When an + * overflow occurs, the state of dst is guaranteed to be consistent, + * including the head and tail pointers; old data will simply be + * overwritten in FIFO fashion, as needed. However, note that, if + * calling the function results in an overflow, the value dst's tail + * pointer may be different than it was before the function was + * called. + * + * It is *not* possible to underflow src; if count is greater than the + * number of bytes used in src, no bytes are copied, and the function + * returns 0. + */ +void * +ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count);