summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Herrmann <dh.herrmann@gmail.com>2013-11-27 10:40:31 +0100
committerDavid Herrmann <dh.herrmann@gmail.com>2013-11-30 12:13:16 +0100
commitf8c48d8f46c52a688db3c203dbbeeacd1ed2176d (patch)
tree8b6b8b239c54018c86c78a420e604e3c441e0c44
parent875af16dbb9b4ed73d184b4fed428d3d131c3978 (diff)
ring: add basic ring-buffer helper
This adds a very straightforward ring-buffer implementation to libsystemd-shared. Ring-buffers allow pushing data to, and pulling data from, both ends of a buffer without modifying existing/remaining buffer content. This is very useful for network buffers and asynchronous writes. This implementation only contains the most basic functions to push data onto the end of the buffer and pull data from the front. More helpers may be added once needed.
-rw-r--r--Makefile.am2
-rw-r--r--src/shared/ring.c213
-rw-r--r--src/shared/ring.h54
3 files changed, 269 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index 7a45029a0..bde9f8526 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -768,6 +768,8 @@ libsystemd_shared_la_SOURCES = \
src/shared/net-util.h \
src/shared/errno-list.c \
src/shared/errno-list.h \
+ src/shared/ring.h \
+ src/shared/ring.c \
src/shared/syscall-list.c \
src/shared/syscall-list.h \
src/shared/audit.c \
diff --git a/src/shared/ring.c b/src/shared/ring.c
new file mode 100644
index 000000000..f60f09899
--- /dev/null
+++ b/src/shared/ring.c
@@ -0,0 +1,213 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <dh.herrmann@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+
+#include "ring.h"
+#include "util.h"
+
+#define RING_MASK(_r, _v) ((_v) & ((_r)->size - 1))
+
+void ring_flush(Ring *r) {
+ r->start = 0;
+ r->end = 0;
+}
+
+void ring_clear(Ring *r) {
+ free(r->buf);
+ memset(r, 0, sizeof(*r));
+}
+
+/*
+ * Resize ring-buffer to size @nsize. @nsize must be a power-of-2, otherwise
+ * ring operations will behave incorrectly.
+ */
+static int ring_resize(Ring *r, size_t nsize) {
+ char *buf;
+
+ buf = malloc(nsize);
+ if (!buf)
+ return -ENOMEM;
+
+ if (r->end == r->start) {
+ r->end = 0;
+ r->start = 0;
+ } else if (r->end > r->start) {
+ memcpy(buf, &r->buf[r->start], r->end - r->start);
+
+ r->end -= r->start;
+ r->start = 0;
+ } else {
+ memcpy(buf, &r->buf[r->start], r->size - r->start);
+ memcpy(&buf[r->size - r->start], r->buf, r->end);
+
+ r->end += r->size - r->start;
+ r->start = 0;
+ }
+
+ free(r->buf);
+ r->buf = buf;
+ r->size = nsize;
+
+ return 0;
+}
+
+/* Compute next higher power-of-2 of @v. Returns 4096 in case v is 0. */
+static size_t ring_pow2(size_t v) {
+ size_t i;
+
+ if (!v)
+ return 4096;
+
+ --v;
+
+ for (i = 1; i < 8 * sizeof(size_t); i *= 2)
+ v |= v >> i;
+
+ return ++v;
+}
+
+/*
+ * Resize ring-buffer to provide enough room for @add bytes of new data. This
+ * resizes the buffer if it is too small. It returns -ENOMEM on OOM and 0 on
+ * success.
+ */
+static int ring_grow(Ring *r, size_t add) {
+ size_t len;
+
+ /*
+ * Note that "end == start" means "empty buffer". Hence, we can never
+ * fill the last byte of a buffer. That means, we must account for an
+ * additional byte here ("end == start"-byte).
+ */
+
+ if (r->end < r->start)
+ len = r->start - r->end;
+ else
+ len = r->start + r->size - r->end;
+
+ /* don't use ">=" as "end == start" would be ambigious */
+ if (len > add)
+ return 0;
+
+ /* +1 for additional "end == start" byte */
+ len = r->size + add - len + 1;
+ len = ring_pow2(len);
+
+ if (len <= r->size)
+ return -ENOMEM;
+
+ return ring_resize(r, len);
+}
+
+/*
+ * Push @len bytes from @u8 into the ring buffer. The buffer is resized if it
+ * is too small. -ENOMEM is returned on OOM, 0 on success.
+ */
+int ring_push(Ring *r, const char *u8, size_t len) {
+ int err;
+ size_t l;
+
+ err = ring_grow(r, len);
+ if (err < 0)
+ return err;
+
+ if (r->start <= r->end) {
+ l = r->size - r->end;
+ if (l > len)
+ l = len;
+
+ memcpy(&r->buf[r->end], u8, l);
+ r->end = RING_MASK(r, r->end + l);
+
+ len -= l;
+ u8 += l;
+ }
+
+ if (!len)
+ return 0;
+
+ memcpy(&r->buf[r->end], u8, len);
+ r->end = RING_MASK(r, r->end + len);
+
+ return 0;
+}
+
+/*
+ * Remove @len bytes from the start of the ring-buffer. Note that we protect
+ * against overflows so removing more bytes than available is safe.
+ */
+void ring_pull(Ring *r, size_t len) {
+ size_t l;
+
+ if (r->start > r->end) {
+ l = r->size - r->start;
+ if (l > len)
+ l = len;
+
+ r->start = RING_MASK(r, r->start + l);
+ len -= l;
+ }
+
+ if (!len)
+ return;
+
+ l = r->end - r->start;
+ if (l > len)
+ l = len;
+
+ r->start = RING_MASK(r, r->start + l);
+}
+
+/*
+ * Get data pointers for current ring-buffer data. @vec must be an array of 2
+ * iovec objects. They are filled according to the data available in the
+ * ring-buffer. 0, 1 or 2 is returned according to the number of iovec objects
+ * that were filled (0 meaning buffer is empty).
+ *
+ * Hint: "struct iovec" is defined in <sys/uio.h> and looks like this:
+ * struct iovec {
+ * void *iov_base;
+ * size_t iov_len;
+ * };
+ */
+size_t ring_peek(Ring *r, struct iovec *vec) {
+ if (r->end > r->start) {
+ if (vec) {
+ vec[0].iov_base = &r->buf[r->start];
+ vec[0].iov_len = r->end - r->start;
+ }
+ return 1;
+ } else if (r->end < r->start) {
+ if (vec) {
+ vec[0].iov_base = &r->buf[r->start];
+ vec[0].iov_len = r->size - r->start;
+ vec[1].iov_base = r->buf;
+ vec[1].iov_len = r->end;
+ }
+ return r->end ? 2 : 1;
+ } else {
+ return 0;
+ }
+}
diff --git a/src/shared/ring.h b/src/shared/ring.h
new file mode 100644
index 000000000..d2cd01667
--- /dev/null
+++ b/src/shared/ring.h
@@ -0,0 +1,54 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2013 David Herrmann <dh.herrmann@gmail.com>
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+/* Pretty straightforward ring-buffer implementation. */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include "util.h"
+
+typedef struct Ring Ring;
+
+struct Ring {
+ char *buf;
+ size_t size;
+ size_t start;
+ size_t end;
+};
+
+void ring_flush(Ring *r);
+void ring_clear(Ring *r);
+
+int ring_push(Ring *r, const char *u8, size_t len);
+void ring_pull(Ring *r, size_t len);
+size_t ring_peek(Ring *r, struct iovec *vec);
+
+static inline size_t ring_length(Ring *r) {
+ if (r->end > r->start)
+ return r->end - r->start;
+ else if (r->end < r->start)
+ return (r->size - r->start) + r->end;
+ else
+ return 0;
+}