Use a separate, re-useable ring-buffer implementation

Not only is it re-useable it also allows for easier debugging by
separating the ring buffer specific logic and the pager history logic.

Hopefully it fixes #3049
This commit is contained in:
Kovid Goyal 2020-12-11 21:17:38 +05:30
parent 3b8be26cc7
commit bffe0f4a6c
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 675 additions and 69 deletions

View File

@ -52,6 +52,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- 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]
-------------------

View File

@ -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;

View File

@ -9,6 +9,7 @@
#include "lineops.h"
#include "charsets.h"
#include <structmember.h>
#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;

380
kitty/ringbuf.c Normal file
View File

@ -0,0 +1,380 @@
/*
* ringbuf.c - C ring buffer (FIFO) implementation.
*
* Written in 2011 by Drew Hess <dhess-src@bothan.net>.
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#define NDEBUG 1
#include "ringbuf.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/param.h>
#include <assert.h>
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;
}

247
kitty/ringbuf.h Normal file
View File

@ -0,0 +1,247 @@
#pragma once
/*
* ringbuf.h - C ring buffer (FIFO) interface.
*
* Written in 2011 by Drew Hess <dhess-src@bothan.net>.
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
/*
* 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 <stddef.h>
#include <sys/types.h>
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);