changeset 0:3e042207d646

first batch of patches
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Sat, 17 Nov 2018 12:02:16 -0500
parents
children 2df0a7f85837
files 2018-11-17-1-WIP__rope.patch 2018-11-17-2-array__reimplement_as_an_explicitly_wrapped_array.patch 2018-11-17-3-WIP__buffer__remove_byte_slice.patch 2018-11-17-4-WIP__array__implement_a_remove_element_function.patch 2018-11-17-5-exlist__a_non_intrusive_circular_doubly_linked_list.patch
diffstat 5 files changed, 1129 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2018-11-17-1-WIP__rope.patch	Sat Nov 17 12:02:16 2018 -0500
@@ -0,0 +1,278 @@
+# HG changeset patch
+# User Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+# Date 1492309700 14400
+#      Sat Apr 15 22:28:20 2017 -0400
+# Node ID fe935c48619d33820fda3c20c3548be5516e3351
+# Parent  a25ff7b067d556fe3f0af72133f2bf2f87633a7e
+# EXP-Topic rope
+WIP: rope
+
+Sometimes a string is just not good enough.  For some of those times, a rope
+will work much better.
+
+Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -105,6 +105,7 @@ add_library(jeffpc SHARED
+ 	padding.c
+ 	qstring.c
+ 	rand.c
++	rope.c
+ 	sexpr.c
+ 	sexpr_dump.c
+ 	sexpr_eval.c
+diff --git a/include/jeffpc/rope.h b/include/jeffpc/rope.h
+new file mode 100644
+--- /dev/null
++++ b/include/jeffpc/rope.h
+@@ -0,0 +1,51 @@
++/*
++ * Copyright (c) 2016-2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy
++ * of this software and associated documentation files (the "Software"), to deal
++ * in the Software without restriction, including without limitation the rights
++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++ * copies of the Software, and to permit persons to whom the Software is
++ * furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included in
++ * all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ */
++
++#ifndef __JEFFPC_ROPE_H
++#define __JEFFPC_ROPE_H
++
++#include <jeffpc/refcnt.h>
++#include <jeffpc/str.h>
++#include <jeffpc/list.h>
++
++/* scatter-gather string */
++
++struct rope_node {
++	struct list_node node;
++	struct str *str;
++};
++
++struct rope {
++	struct list_node list;
++	size_t nodes;		/* length of list */
++	size_t len;		/* length of string */
++};
++
++extern struct rope *rope_alloc(void);
++extern void rope_free(struct rope *rope);
++extern int rope_append_rope(struct rope *a, struct rope *b);
++extern int rope_append_str(struct rope *a, struct str *b);
++extern int rope_append_cstr(struct rope *a, const char *b);
++extern char *rope_cstr(struct rope *a);
++extern int write_rope(struct rope *a);
++
++#endif
+diff --git a/rope.c b/rope.c
+new file mode 100644
+--- /dev/null
++++ b/rope.c
+@@ -0,0 +1,192 @@
++/*
++ * Copyright (c) 2016-2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy
++ * of this software and associated documentation files (the "Software"), to deal
++ * in the Software without restriction, including without limitation the rights
++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++ * copies of the Software, and to permit persons to whom the Software is
++ * furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included in
++ * all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ */
++
++#include <jeffpc/rope.h>
++#include <jeffpc/jeffpc.h>
++#include <jeffpc/io.h>
++
++static struct rope_node *rope_node_alloc(struct str *str)
++{
++	struct rope_node *rn;
++
++	rn = malloc(sizeof(struct rope_node));
++	if (!rn)
++		return NULL;
++
++	rn->str = str;
++	slist_init(&rn->node);
++
++	return rn;
++}
++
++static void rope_node_free(struct rope_node *rn)
++{
++	str_putref(rn->str);
++	free(rn);
++}
++
++struct rope *rope_alloc(void)
++{
++	struct rope *rope;
++
++	rope = malloc(sizeof(struct rope));
++	if (!rope)
++		return NULL;
++
++	slist_init(&rope->list);
++	rope->nodes = 0;
++	rope->len = 0;
++
++	return rope;
++}
++
++void rope_free(struct rope *rope)
++{
++	struct rope_node *rn, *tmp;
++
++	slist_for_each_entry_safe(rn, tmp, &rope->list, node)
++		rope_node_free(rn);
++
++	free(rope);
++}
++
++/* move contents of b at the end of a */
++int rope_append_rope(struct rope *a, struct rope *b)
++{
++#warning "move slist contents"
++
++	a->nodes += b->nodes;
++	b->nodes = 0;
++	a->len += b->len;
++	b->len = 0;
++
++	rope_free(b);
++
++	return 0;
++}
++
++int rope_append_str(struct rope *a, struct str *b)
++{
++	struct rope_node *rn;
++
++	rn = rope_node_alloc(b);
++	if (!rn)
++		return -ENOMEM;
++
++	slist_insert_tail(&a->list, &rn->node);
++	a->nodes++;
++	a->len += str_len(b);
++
++	return 0;
++}
++
++int rope_append_cstr(struct rope *a, const char *b)
++{
++	struct str *str;
++
++	str = str_dup(b);
++	if (IS_ERR(str))
++		return PTR_ERR(str);
++
++	return rope_append_str(a, str);
++}
++
++char *rope_cstr(struct rope *a)
++{
++	int ret;
++
++	ret = rope_flatten(a);
++	if (ret)
++		return ERR_PTR(ret);
++
++	/* no node - empty string */
++	if (a->nodes == 0)
++		return NULL;
++
++	VERIFY3U(a->nodes, ==, 1);
++
++	/* one node - just return it */
++	return str_cstr(slist_head_entry(&a->list, struct rope_node, node)->str);
++}
++
++int rope_flatten(struct rope *rope)
++{
++	struct rope_node *rn;
++	struct str *str;
++	const char *tmp;
++
++	if (rope->nodes <= 1)
++		return 0;
++
++	tmp = malloc(rope->len + 1);
++	if (!tmp)
++		goto err;
++
++	off = 0;
++	slist_for_each_entry(rn, &a->list, node) {
++		size_t slen = str_len(rn->str);
++
++		memcpy(tmp + off, str_cstr(rn->str), slen);
++
++		off += slen;
++	}
++
++	ret[a->len] = '\0';
++
++	str = str_alloc(ret);
++	if (!str)
++		goto err_cstr;
++
++	rn = rope_node_alloc(str);
++	if (!rn)
++		goto err_str;
++
++	slist_for_each_entry_safe(rn, safe, &rope->list, node)
++		rope_node_free(rn);
++
++	slist_
++
++	rope->nodes = 1;
++
++	return ret;
++
++err_str:
++	str_putref(str);
++err_cstr:
++	free(tmp);
++err:
++	return -ENOMEM;
++}
++
++int xwrite_rope(int fd, struct rope *a)
++{
++	struct rope_node *rn;
++	int ret;
++
++	slist_for_each_entry(rn, &a->list, node) {
++		ret = xwrite_str(fd, str_cstr(rn->str));
++		if (ret)
++			return ret;
++	}
++
++	return 0;
++}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2018-11-17-2-array__reimplement_as_an_explicitly_wrapped_array.patch	Sat Nov 17 12:02:16 2018 -0500
@@ -0,0 +1,484 @@
+# HG changeset patch
+# User Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+# Date 1541776905 18000
+#      Fri Nov 09 10:21:45 2018 -0500
+# Node ID 7b73a1dd0a54f89713008239cf69188e32ea80b9
+# Parent  dfbcf2a05902cd768c8cf28c518c76b5606e0a56
+array: reimplement as an explicitly wrapped array
+
+Instead of being a glorified realloc wrapper, this commit rewrites the whole
+array API to work on typed array structures.  This maintains the type safety
+we had before, but it is now more obvious that we must not free the array by
+calling free(3) but rather array_free.
+
+Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -147,6 +147,7 @@ install(TARGETS	jeffpc
+ 	PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
+ install(FILES	include/jeffpc/atomic.h
+ 		include/jeffpc/array.h
++		include/jeffpc/array_private.h
+ 		include/jeffpc/bst.h
+ 		include/jeffpc/buffer.h
+ 		include/jeffpc/cstr.h
+diff --git a/array.c b/array.c
+--- a/array.c
++++ b/array.c
+@@ -25,96 +25,70 @@
+ #include <jeffpc/array.h>
+ #include <jeffpc/error.h>
+ 
+-struct array {
+-	size_t elem_size;		/* size of each element */
+-	size_t elem_count;		/* number of visible elements */
+-	size_t preallocated;		/* number of allocated elements */
+-	uint8_t raw[];
+-};
++static inline size_t current_num_elem(struct array *array)
++{
++	return buffer_used(array->data) / array->elem_size;
++}
+ 
+ static inline size_t total_size(size_t elem_size, size_t count)
+ {
+-	return sizeof(struct array) + elem_size * count;
+-}
+-
+-static inline void *get_ptr(struct array *array, size_t idx)
+-{
+-	return &array->raw[array->elem_size * idx];
+-}
+-
+-void *array_alloc(size_t elem_size, size_t prealloc_count)
+-{
+-	struct array *array;
+-
+-	array = malloc(total_size(elem_size, prealloc_count));
+-	if (!array)
+-		return NULL;
+-
+-	array->elem_size = elem_size;
+-	array->elem_count = 0;
+-	array->preallocated = prealloc_count;
+-
+-	return array->raw;
++	/* TODO: check for overflows */
++	return elem_size * count;
+ }
+ 
+-void array_free(void *raw)
+-{
+-	if (!raw)
+-		return;
+-
+-	free(container_of(raw, struct array, raw));
+-}
+-
+-static struct array *__array_truncate(struct array *array,
+-				      size_t new_elem_count)
++struct array *array_alloc_i(size_t elem_size, size_t prealloc_count)
+ {
+-	struct array *newarray;
+-
+-	if (new_elem_count <= array->elem_count)
+-		goto done; /* shrinking or no change */
++	struct array *array;
++	struct buffer *buf;
+ 
+-	if (new_elem_count <= array->preallocated)
+-		goto clear; /* growing into preallocated space */
+-
+-	/* grow the allocation */
+-
+-	newarray = realloc(array, total_size(array->elem_size, new_elem_count));
+-	if (!newarray)
++	array = malloc(sizeof(struct array));
++	if (!array)
+ 		return ERR_PTR(-ENOMEM);
+ 
+-	array = newarray;
+-
+-	array->preallocated = new_elem_count;
++	buf = buffer_alloc(total_size(elem_size, prealloc_count));
++	if (IS_ERR(buf)) {
++		free(array);
++		return ERR_CAST(buf);
++	}
+ 
+-clear:
+-	memset(get_ptr(array, array->elem_count), 0,
+-	       total_size(array->elem_size, new_elem_count) -
+-	       total_size(array->elem_size, array->elem_count));
+-
+-done:
+-	array->elem_count = new_elem_count;
++	array->elem_size = elem_size;
++	array->data = buf;
+ 
+ 	return array;
+ }
+ 
+-#undef array_truncate
+-int array_truncate(void **raw, size_t idx)
++void array_free_i(struct array *array)
+ {
+-	struct array *array = container_of(*raw, struct array, raw);
++	if (!array)
++		return;
+ 
+-	array = __array_truncate(array, idx);
+-	if (IS_ERR(array))
+-		return PTR_ERR(array);
++	buffer_free(array->data);
++	free(array);
++}
+ 
+-	*raw = array->raw;
+-
+-	return 0;
++int array_truncate_i(struct array *array, size_t new_elem_count)
++{
++	return buffer_truncate(array->data,
++			       total_size(array->elem_size, new_elem_count));
+ }
+ 
+-size_t array_size(void *raw)
++size_t array_size_i(struct array *array)
+ {
+-	if (!raw)
++	if (!array)
+ 		return 0;
+ 
+-	return container_of(raw, struct array, raw)->elem_count;
++	return current_num_elem(array);
+ }
++
++int array_set_i(struct array *array, size_t idx, void *v, bool fatal)
++{
++	int ret;
++
++	ret = buffer_pwrite(array->data, v, array->elem_size,
++			    idx * array->elem_size);
++	if ((ret < 0) && fatal)
++		panic("failed to set array[%zu] to value at %p: %s",
++		      idx, v, xstrerror(ret));
++
++	return (ret < 0) ? ret : 0;
++}
+diff --git a/include/jeffpc/array.h b/include/jeffpc/array.h
+--- a/include/jeffpc/array.h
++++ b/include/jeffpc/array.h
+@@ -1,5 +1,5 @@
+ /*
+- * Copyright (c) 2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
++ * Copyright (c) 2017-2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+  *
+  * Permission is hereby granted, free of charge, to any person obtaining a copy
+  * of this software and associated documentation files (the "Software"), to deal
+@@ -23,25 +23,111 @@
+ #ifndef __JEFFPC_ARRAY_H
+ #define __JEFFPC_ARRAY_H
+ 
+-#include <jeffpc/types.h>
++#include <jeffpc/array_private.h>
+ 
+-extern void *array_alloc(size_t elem_size, size_t prealloc_count);
+-extern void array_free(void *raw);
++#define array_alloc(type_name, elem_count)				\
++	({								\
++		union {							\
++			ARRAY_TYPE(type_name) *typed;			\
++			struct array *plain;				\
++		} u;							\
++									\
++		u.plain = array_alloc_i(sizeof(*u.typed->dummy),	\
++					(elem_count));			\
++									\
++		u.typed;						\
++	})
+ 
+-extern int array_truncate(void **raw, size_t idx);
+-extern size_t array_size(void *raw);
++#define array_free(typed)						\
++	array_free_i(&(typed)->array)
++#define array_truncate(typed, new_elem_count)				\
++	array_truncate_i(&(typed)->array, (new_elem_count))
++#define array_size(typed)						\
++	array_size_i(&(typed)->array)
++#define array_get_ptr(typed, idx)					\
++	((typeof((typed)->dummy)) array_get_void_ptr_i(&(typed)->array, \
++						       (idx)))
++#define array_get(typed, idx)						\
++	(*array_get_ptr((typed), (idx)))
+ 
+ /*
+- * This silly wrapper lets us have a void ** argument, yet users can pass in
+- * a struct foo **.
++ * There are multiple ways to set and append elements:
++ *
++ *   array_{set,append}{,_by_ptr}{,_fatal}(...)
++ *
++ * The three parts are defined as:
++ *
++ *   Operation:
++ *     - "set" sets an element at an index
++ *     - "append" appends a new element at the end of the array
++ *
++ *   Value type:
++ *     - "" passes the new element by value
++ *     - "by_ptr" passes the new element by reference
++ *
++ *   Error handling:
++ *     - "" returns a negated errno
++ *     - "fatal" panics on error
+  */
+-#define array_truncate(raw, idx)				\
+-	({							\
+-		void *_tmp = *(raw);				\
+-		int ret;					\
+-		ret = array_truncate(&_tmp, (idx));		\
+-		*(raw) = _tmp;					\
+-		ret;						\
+-	})
++#define array_set_by_ptr(typed, idx, v)					\
++	array_set_helper_i((typed)->dummy, false, false,		\
++			   &(typed)->array, (idx), (v))
++#define array_set_by_ptr_fatal(typed, idx, v)				\
++	do {								\
++		array_set_helper_i((typed)->dummy, false, true,		\
++				   &(typed)->array, (idx), (v));	\
++	} while (0)
++#define array_append_by_ptr(typed, v)					\
++	array_set_helper_i((typed)->dummy, true, false,			\
++			   &(typed)->array, 0, (v))
++#define array_append_by_ptr_fatal(typed, v)				\
++	do {								\
++		array_set_helper_i((typed)->dummy, true, true,		\
++				   &(typed)->array, 0, (v));		\
++	} while (0)
++
++#define array_set(typed, idx, v)					\
++	array_set_helper_i(*(typed)->dummy, false, false,		\
++			   &(typed)->array, (idx), (v))
++#define array_set_fatal(typed, idx, v)					\
++	do {								\
++		array_set_helper_i(*(typed)->dummy, false, true,	\
++				   &(typed)->array, (idx), (v));	\
++	} while (0)
++#define array_append(typed, v)						\
++	array_set_helper_i(*(typed)->dummy, true, false,		\
++			   &(typed)->array, 0, (v))
++#define array_append_fatal(typed, v)					\
++	do {								\
++		array_set_helper_i(*(typed)->dummy, true, true,		\
++				   &(typed)->array, 0, (v));		\
++	} while (0)
++
++ARRAY_DECL_TYPE(ulonglong, unsigned long long);
++ARRAY_DECL_TYPE(ulong, unsigned long);
++ARRAY_DECL_TYPE(uint, unsigned int);
++ARRAY_DECL_TYPE(ushort, unsigned short);
++ARRAY_DECL_TYPE(uchar, unsigned char);
++
++ARRAY_DECL_TYPE(longlong, long long);
++ARRAY_DECL_TYPE(long, long);
++ARRAY_DECL_TYPE(int, int);
++ARRAY_DECL_TYPE(short, short);
++ARRAY_DECL_TYPE(char, char);
++
++ARRAY_DECL_TYPE(size_t, size_t);
++ARRAY_DECL_TYPE(ssize_t, ssize_t);
++
++ARRAY_DECL_TYPE(uintptr_t, uintptr_t);
++ARRAY_DECL_TYPE(uint64_t, uint64_t);
++ARRAY_DECL_TYPE(uint32_t, uint32_t);
++ARRAY_DECL_TYPE(uint16_t, uint16_t);
++ARRAY_DECL_TYPE(uint8_t, uint8_t);
++
++ARRAY_DECL_TYPE(intptr_t, intptr_t);
++ARRAY_DECL_TYPE(int64_t, int64_t);
++ARRAY_DECL_TYPE(int32_t, int32_t);
++ARRAY_DECL_TYPE(int16_t, int16_t);
++ARRAY_DECL_TYPE(int8_t, int8_t);
+ 
+ #endif
+diff --git a/include/jeffpc/array_private.h b/include/jeffpc/array_private.h
+new file mode 100644
+--- /dev/null
++++ b/include/jeffpc/array_private.h
+@@ -0,0 +1,80 @@
++/*
++ * Copyright (c) 2017-2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy
++ * of this software and associated documentation files (the "Software"), to deal
++ * in the Software without restriction, including without limitation the rights
++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++ * copies of the Software, and to permit persons to whom the Software is
++ * furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included in
++ * all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ */
++
++#ifndef __JEFFPC_ARRAY_IMPL_H
++#define __JEFFPC_ARRAY_IMPL_H
++
++#include <jeffpc/types.h>
++#include <jeffpc/buffer.h>
++
++struct array {
++	struct buffer *data;
++	size_t elem_size;
++};
++
++#define ARRAY_TYPE(name) \
++	union array_##name
++
++#define ARRAY_DECL_TYPE(name, type) \
++	union array_##name { \
++		struct array array; \
++		type *dummy; \
++	}
++
++#define array_set_helper_i(dummy, append, fatal, array, idx, v)		\
++	({								\
++		/* this type checks the passed in value */		\
++		typeof(dummy) value = (v);				\
++		int ret;						\
++									\
++		if (append)						\
++			ret = array_append_i((array), &value, fatal);	\
++		else							\
++			ret = array_set_i((array), (idx), &value, fatal);\
++									\
++		ret;							\
++	})
++
++extern struct array *array_alloc_i(size_t elem_size, size_t prealloc_count);
++extern void array_free_i(struct array *array);
++
++extern int array_truncate_i(struct array *array, size_t idx);
++extern size_t array_size_i(struct array *array);
++
++extern int array_set_i(struct array *array, size_t idx, void *v, bool fatal);
++
++static inline const void *array_get_void_ptr_i(struct array *array, size_t idx)
++{
++	const size_t off = idx * array->elem_size;
++	const char *data;
++
++	data = buffer_data(array->data);
++
++	return &data[off];
++}
++
++static inline int array_append_i(struct array *array, void *v, bool fatal)
++{
++	return array_set_i(array, array_size_i(array), v, fatal);
++}
++
++#endif
+diff --git a/mapfile-vers b/mapfile-vers
+--- a/mapfile-vers
++++ b/mapfile-vers
+@@ -23,10 +23,11 @@
+ JEFFPC_0.10 {
+ 	global:
+ 		# array
+-		array_alloc;
+-		array_free;
+-		array_size;
+-		array_truncate;
++		array_alloc_i;
++		array_free_i;
++		array_set_i;
++		array_size_i;
++		array_truncate_i;
+ 
+ 		# bst
+ 		bst_add;
+diff --git a/tests/test_array.c b/tests/test_array.c
+--- a/tests/test_array.c
++++ b/tests/test_array.c
+@@ -1,5 +1,5 @@
+ /*
+- * Copyright (c) 2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
++ * Copyright (c) 2017-2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+  *
+  * Permission is hereby granted, free of charge, to any person obtaining a copy
+  * of this software and associated documentation files (the "Software"), to deal
+@@ -27,9 +27,9 @@
+ 
+ static void test_alloc_free(void)
+ {
+-	int *arr;
++	ARRAY_TYPE(int) *arr;
+ 
+-	arr = array_alloc(sizeof(int), 0);
++	arr = array_alloc(int, 0);
+ 	if (!arr)
+ 		fail("array_alloc returned NULL");
+ 
+@@ -58,13 +58,14 @@ static void test_alloc_free(void)
+ #define CHECK_VAL(arr, idx, expected_val)				\
+ 	do {								\
+ 		size_t exp = (expected_val);				\
++		int got = array_get((arr), (idx));			\
+ 									\
+ 		fprintf(stderr, "checking array idx %zu; expect %zu...",\
+ 			(idx), exp);					\
+ 									\
+-		if (arr[idx] != (expected_val))				\
++		if (got != exp)						\
+ 			fail("value mismatch! expected %zu, got %u",	\
+-			     exp, arr[i]);				\
++			     exp, got);					\
+ 									\
+ 		fprintf(stderr, "ok\n");				\
+ 	} while (0)
+@@ -73,13 +74,13 @@ static void test_alloc_free(void)
+ 
+ static void test_size(void)
+ {
+-	unsigned int *arr;
++	ARRAY_TYPE(int) *arr;
+ 	size_t i, j, k;
+ 
+ 	for (i = 0; i < 3; i++) {
+ 		fprintf(stderr, "prealloc %zu\n", i * 10);
+ 
+-		arr = array_alloc(sizeof(int), i * 10);
++		arr = array_alloc(int, i * 10);
+ 		if (!arr)
+ 			fail("array_alloc_returned NULL");
+ 
+@@ -90,7 +91,7 @@ static void test_size(void)
+ 
+ 			fprintf(stderr, "truncating to %zu\n", j);
+ 
+-			ret = array_truncate(&arr, j);
++			ret = array_truncate(arr, j);
+ 			if (ret)
+ 				fail("truncate failed: %s", xstrerror(ret));
+ 
+@@ -101,7 +102,7 @@ static void test_size(void)
+ 				CHECK_VAL(arr, j - 1, 0);
+ 
+ 				/* set the newly allocated value */
+-				arr[j - 1] = GEN_VAL(j - 1);
++				array_set_fatal(arr, j - 1, GEN_VAL(j - 1));
+ 			}
+ 
+ 			/* check that the previous values are still there */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2018-11-17-3-WIP__buffer__remove_byte_slice.patch	Sat Nov 17 12:02:16 2018 -0500
@@ -0,0 +1,51 @@
+# HG changeset patch
+# User Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+# Date 1541776712 18000
+#      Fri Nov 09 10:18:32 2018 -0500
+# Node ID f5d73784e3be072001b5dacf11ef37a6570dade7
+# Parent  7b73a1dd0a54f89713008239cf69188e32ea80b9
+WIP: buffer: remove byte slice
+
+diff --git a/buffer.c b/buffer.c
+--- a/buffer.c
++++ b/buffer.c
+@@ -217,6 +217,17 @@ int buffer_truncate(struct buffer *buffe
+ 	return 0;
+ }
+ 
++int buffer_remove(struct buffer *buffer, size_t off, size_t len)
++{
++	if (!buffer)
++		return -EINVAL;
++
++	if (!len)
++		return 0;
++
++	XXX;
++}
++
+ ssize_t buffer_pread(struct buffer *buffer, void *buf, size_t len, size_t off)
+ {
+ 	ssize_t ret;
+diff --git a/include/jeffpc/buffer.h b/include/jeffpc/buffer.h
+--- a/include/jeffpc/buffer.h
++++ b/include/jeffpc/buffer.h
+@@ -76,6 +76,7 @@ extern void buffer_init_stdio(struct buf
+ extern int buffer_append(struct buffer *buffer, const void *data, size_t size);
+ extern ssize_t buffer_seek(struct buffer *buffer, off_t offset, int whence);
+ extern int buffer_truncate(struct buffer *buffer, size_t size);
++extern int buffer_remove(struct buffer *buffer, size_t off, size_t len);
+ extern ssize_t buffer_pread(struct buffer *buffer, void *data, size_t len,
+ 			    size_t off);
+ extern ssize_t buffer_pwrite(struct buffer *buffer, const void *data, size_t len,
+diff --git a/mapfile-vers b/mapfile-vers
+--- a/mapfile-vers
++++ b/mapfile-vers
+@@ -45,6 +45,7 @@ JEFFPC_0.10 {
+ 		buffer_init_stdio;
+ 		buffer_pread;
+ 		buffer_pwrite;
++		buffer_remove;
+ 		buffer_seek;
+ 		buffer_truncate;
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2018-11-17-4-WIP__array__implement_a_remove_element_function.patch	Sat Nov 17 12:02:16 2018 -0500
@@ -0,0 +1,37 @@
+# HG changeset patch
+# User Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+# Date 1541776930 18000
+#      Fri Nov 09 10:22:10 2018 -0500
+# Node ID 1dd108ab152cacaf7676755282844c1673b5f064
+# Parent  f5d73784e3be072001b5dacf11ef37a6570dade7
+WIP: array: implement a remove-element function
+
+Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+
+diff --git a/include/jeffpc/array.h b/include/jeffpc/array.h
+--- a/include/jeffpc/array.h
++++ b/include/jeffpc/array.h
+@@ -42,6 +42,8 @@
+ 	array_free_i(&(typed)->array)
+ #define array_truncate(typed, new_elem_count)				\
+ 	array_truncate_i(&(typed)->array, (new_elem_count))
++#define array_remove(typed, idx)					\
++	array_remove_i(&(typed)->array, (idx))
+ #define array_size(typed)						\
+ 	array_size_i(&(typed)->array)
+ #define array_get_ptr(typed, idx)					\
+diff --git a/include/jeffpc/array_private.h b/include/jeffpc/array_private.h
+--- a/include/jeffpc/array_private.h
++++ b/include/jeffpc/array_private.h
+@@ -77,4 +77,11 @@ static inline int array_append_i(struct 
+ 	return array_set_i(array, array_size_i(array), v, fatal);
+ }
+ 
++static inline int array_remove_i(struct array *array, size_t idx)
++{
++	const size_t off = idx * array->elem_size;
++
++	return buffer_remove(array->data, off, array->elem_size);
++}
++
+ #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2018-11-17-5-exlist__a_non_intrusive_circular_doubly_linked_list.patch	Sat Nov 17 12:02:16 2018 -0500
@@ -0,0 +1,279 @@
+# HG changeset patch
+# User Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+# Date 1542072280 18000
+#      Mon Nov 12 20:24:40 2018 -0500
+# Node ID f086439ec8a832b724b41837cd34c0619b9e4eed
+# Parent  1dd108ab152cacaf7676755282844c1673b5f064
+exlist: a non-intrusive circular doubly-linked list
+
+Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -93,6 +93,7 @@ add_library(jeffpc SHARED
+ 	buffer_stdio.c
+ 	cstr.c
+ 	error.c
++	exlist.c
+ 	fmt_cbor.c
+ 	hexdump.c
+ 	init.c
+@@ -152,6 +153,7 @@ install(FILES	include/jeffpc/atomic.h
+ 		include/jeffpc/buffer.h
+ 		include/jeffpc/cstr.h
+ 		include/jeffpc/error.h
++		include/jeffpc/exlist.h
+ 		include/jeffpc/hexdump.h
+ 		include/jeffpc/int.h
+ 		include/jeffpc/io.h
+diff --git a/exlist.c b/exlist.c
+new file mode 100644
+--- /dev/null
++++ b/exlist.c
+@@ -0,0 +1,109 @@
++/*
++ * Copyright (c) 2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy
++ * of this software and associated documentation files (the "Software"), to deal
++ * in the Software without restriction, including without limitation the rights
++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++ * copies of the Software, and to permit persons to whom the Software is
++ * furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included in
++ * all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ */
++
++#include <jeffpc/exlist.h>
++
++struct exlist_node {
++	struct list_node node;
++	void *data;
++};
++
++void exlist_create(struct exlist *list)
++{
++	list_create(&list->list, sizeof(struct exlist_node),
++		    offsetof(struct exlist_node, node));
++}
++
++int _exlist_insert(struct exlist *list, void *new, bool head)
++{
++	struct exlist_node *node;
++
++	node = malloc(sizeof(struct exlist_node));
++	if (!node)
++		return -ENOMEM;
++
++	node->data = new;
++
++	if (head)
++		list_insert_head(&list->list, node);
++	else
++		list_insert_tail(&list->list, node);
++
++	return 0;
++}
++
++void *_exlist_headtail(struct exlist *list, bool head)
++{
++	struct exlist_node *node;
++
++	node = head ? list_head(&list->list) : list_tail(&list->list);
++
++	return node ? node->data : NULL;
++}
++
++void *_exlist_nextprev(struct exlist *list,
++		       struct exlist_iter_cookie *cookie, bool fwd)
++{
++	struct exlist_node *node;
++
++	if (fwd)
++		node = list_next(&list->list, (void *) cookie->private);
++	else
++		node = list_prev(&list->list, (void *) cookie->private);
++
++	cookie->private = (uintptr_t) node;
++
++	return node ? node->data : NULL;
++}
++
++bool exlist_remove(struct exlist *list, void *item)
++{
++	struct exlist_node *node;
++
++	list_for_each(node, &list->list) {
++		if (node->data != item)
++			continue;
++
++		list_remove(&list->list, node);
++		free(node);
++		return true;
++	}
++
++	return false;
++}
++
++void *_exlist_remove_headtail(struct exlist *list, bool head)
++{
++	struct exlist_node *node;
++	void *item;
++
++	if (head)
++		node = list_remove_head(&list->list);
++	else
++		node = list_remove_tail(&list->list);
++
++	item = node->data;
++
++	free(node);
++
++	return item;
++}
+diff --git a/include/jeffpc/exlist.h b/include/jeffpc/exlist.h
+new file mode 100644
+--- /dev/null
++++ b/include/jeffpc/exlist.h
+@@ -0,0 +1,113 @@
++/*
++ * Copyright (c) 2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy
++ * of this software and associated documentation files (the "Software"), to deal
++ * in the Software without restriction, including without limitation the rights
++ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++ * copies of the Software, and to permit persons to whom the Software is
++ * furnished to do so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included in
++ * all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ */
++
++#ifndef __JEFFPC_EXLIST_H
++#define __JEFFPC_EXLIST_H
++
++/*
++ * An external/non-intrusive circular doubly-linked list.
++ */
++
++#include <jeffpc/list.h>
++
++struct exlist {
++	struct list list;
++};
++
++struct exlist_iter_cookie {
++	uintptr_t private;
++};
++
++/* internal helpers */
++extern int _exlist_insert(struct exlist *list, void *new, bool head);
++extern void *_exlist_headtail(struct exlist *list, bool head);
++extern void *_exlist_nextprev(struct exlist *list,
++			      struct exlist_iter_cookie *cookie, bool fwd);
++extern void *_exlist_remove_headtail(struct exlist *list, bool head);
++
++/* API */
++extern void exlist_create(struct exlist *list);
++extern bool exlist_remove(struct exlist *list, void *item);
++
++static inline void exlist_destroy(struct exlist *list)
++{
++	list_destroy(&list->list);
++}
++
++static inline void exlist_move_tail(struct exlist *dst, struct exlist *src)
++{
++	list_move_tail(&dst->list, &src->list);
++}
++
++static inline bool exlist_is_empty(struct exlist *list)
++{
++	return list_is_empty(&list->list);
++}
++
++static inline void exlist_insert_head(struct exlist *list, void *new)
++{
++	_exlist_insert(list, new, true);
++}
++
++static inline void exlist_insert_tail(struct exlist *list, void *new)
++{
++	_exlist_insert(list, new, false);
++}
++
++static inline void *exlist_head(struct exlist *list)
++{
++	return _exlist_headtail(list, true);
++}
++
++static inline void *exlist_tail(struct exlist *list)
++{
++	return _exlist_headtail(list, false);
++}
++
++static inline void *exlist_next(struct exlist *list,
++				struct exlist_iter_cookie *cookie)
++{
++	return _exlist_nextprev(list, cookie, true);
++}
++
++static inline void *exlist_prev(struct exlist *list,
++				struct exlist_iter_cookie *cookie)
++{
++	return _exlist_nextprev(list, cookie, false);
++}
++
++static inline void *exlist_remove_head(struct exlist *list)
++{
++	return _exlist_remove_headtail(list, true);
++}
++
++static inline void *exlist_remove_tail(struct exlist *list)
++{
++	return _exlist_remove_headtail(list, false);
++}
++
++#define exlist_for_each(node, list) \
++	for (node = exlist_head(list); \
++	     node != NULL; \
++	     node = exlist_next(list, node))
++
++#endif
+diff --git a/mapfile-vers b/mapfile-vers
+--- a/mapfile-vers
++++ b/mapfile-vers
+@@ -82,6 +82,14 @@ JEFFPC_0.10 {
+ 		save_stacktrace;
+ 		xstrerror;
+ 
++		# exlist
++		_exlist_headtail;
++		_exlist_insert;
++		_exlist_nextprev;
++		_exlist_remove_headtail;
++		exlist_create;
++		exlist_remove;
++
+ 		# hexdump
+ 		hexdump;
+ 		hexdumpz;