]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/subr_sbuf.c
xnu-7195.50.7.100.1.tar.gz
[apple/xnu.git] / bsd / kern / subr_sbuf.c
index e9d175fb4972e147f5ed759aa7d7acb6994386ce..1904851455769e1bfcad2672edb83503e5fac40f 100644 (file)
-/*-
- * Copyright (c) 2000 Poul-Henning Kamp and Dag-Erling Co\95dan Sm¿rgrav
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer
- *    in this position and unchanged.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
- *    derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ *
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
  */
-
-#include <sys/cdefs.h>
-
+#include <kern/kalloc.h>
+#include <libkern/libkern.h>
+#include <os/base.h>
+#include <os/overflow.h>
 #include <sys/param.h>
-
-#ifdef KERNEL
-/* #include <ctype.h> */
-#include <sys/kernel.h>
-#include <sys/malloc.h>
-#include <sys/systm.h>
-#include <sys/uio.h>
-#include <sys/uio_internal.h>
-#include <sys/systm.h>
-#include <stdarg.h>
-#else /* KERNEL */
-#include <ctype.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#endif /* KERNEL */
-
 #include <sys/sbuf.h>
+#include <sys/uio.h>
 
-#ifdef KERNEL
-/* MALLOC_DEFINE(M_SBUF, "sbuf", "string buffers"); */
-#define SBMALLOC(size)          _MALLOC(size, M_SBUF, M_WAITOK)
-#define SBFREE(buf)             FREE(buf, M_SBUF)
-#else /* KERNEL */
-#define KASSERT(e, m)
-#define SBMALLOC(size)          malloc(size)
-#define SBFREE(buf)             free(buf)
-#define min(x, y)                MIN(x,y)
-
-#endif /* KERNEL */
-
-/*
- * Predicates
- */
-#define SBUF_ISDYNAMIC(s)       ((s)->s_flags & SBUF_DYNAMIC)
-#define SBUF_ISDYNSTRUCT(s)     ((s)->s_flags & SBUF_DYNSTRUCT)
-#define SBUF_ISFINISHED(s)      ((s)->s_flags & SBUF_FINISHED)
-#define SBUF_HASOVERFLOWED(s)   ((s)->s_flags & SBUF_OVERFLOWED)
-#define SBUF_HASROOM(s)         ((s)->s_len < (s)->s_size - 1)
-#define SBUF_FREESPACE(s)       ((s)->s_size - (s)->s_len - 1)
-#define SBUF_CANEXTEND(s)       ((s)->s_flags & SBUF_AUTOEXTEND)
+#if DEBUG || DEVELOPMENT
+#include <kern/macro_help.h>
+#include <sys/errno.h>
+#include <sys/sysctl.h>
+#endif /* DEBUG || DEVELOPMENT */
 
-/*
- * Set / clear flags
- */
+#define SBUF_ISSET(s, f)        ((s)->s_flags & (f))
 #define SBUF_SETFLAG(s, f)      do { (s)->s_flags |= (f); } while (0)
 #define SBUF_CLEARFLAG(s, f)    do { (s)->s_flags &= ~(f); } while (0)
 
-#define SBUF_MINEXTENDSIZE      16              /* Should be power of 2. */
+#define SBUF_CANEXTEND(s)       SBUF_ISSET(s, SBUF_AUTOEXTEND)
+#define SBUF_HASOVERFLOWED(s)   SBUF_ISSET(s, SBUF_OVERFLOWED)
+#define SBUF_ISDYNAMIC(s)       SBUF_ISSET(s, SBUF_DYNAMIC)
+#define SBUF_ISDYNSTRUCT(s)     SBUF_ISSET(s, SBUF_DYNSTRUCT)
+#define SBUF_ISFINISHED(s)      SBUF_ISSET(s, SBUF_FINISHED)
+
+#define SBUF_MINEXTENDSIZE      16
 #define SBUF_MAXEXTENDSIZE      PAGE_SIZE
 #define SBUF_MAXEXTENDINCR      PAGE_SIZE
 
-/*
- * Debugging support
+/*!
+ * @function sbuf_delete
+ *
+ * @brief
+ * Destroys an sbuf.  Frees the underlying buffer if it's allocated on the heap
+ * (indicated by SBUF_ISDYNAMIC) and frees the sbuf if it itself is allocated
+ * on the heap (SBUF_ISDYNSTRUCT).
+ *
+ * @param s
+ * The sbuf to destroy.
  */
-#if defined(KERNEL) && defined(INVARIANTS)
-static void
-_assert_sbuf_integrity(const char *fun, struct sbuf *s)
-{
-       KASSERT(s != NULL,
-           ("%s called with a NULL sbuf pointer", fun));
-       KASSERT(s->s_buf != NULL,
-           ("%s called with uninitialized or corrupt sbuf", fun));
-       KASSERT(s->s_len < s->s_size,
-           ("wrote past end of sbuf (%d >= %d)", s->s_len, s->s_size));
-}
-
-static void
-_assert_sbuf_state(const char *fun, struct sbuf *s, int state)
-{
-       KASSERT((s->s_flags & SBUF_FINISHED) == state,
-           ("%s called with %sfinished or corrupt sbuf", fun,
-           (state ? "un" : "")));
-}
-#define assert_sbuf_integrity(s) _assert_sbuf_integrity(__func__, (s))
-#define assert_sbuf_state(s, i)  _assert_sbuf_state(__func__, (s), (i))
-#else /* KERNEL && INVARIANTS */
-#define assert_sbuf_integrity(s) do { } while (0)
-#define assert_sbuf_state(s, i)  do { } while (0)
-#endif /* KERNEL && INVARIANTS */
-
-static int
-sbuf_extendsize(int size)
+void
+sbuf_delete(struct sbuf *s)
 {
-       int newsize;
-
-       newsize = SBUF_MINEXTENDSIZE;
-       while (newsize < size) {
-               if (newsize < (int)SBUF_MAXEXTENDSIZE) {
-                       newsize *= 2;
-               } else {
-                       newsize += SBUF_MAXEXTENDINCR;
-               }
+       if (SBUF_ISDYNAMIC(s) && s->s_buf) {
+               kheap_free(KHEAP_DATA_BUFFERS, s->s_buf, s->s_size);
+               s->s_buf = NULL;
        }
 
-       return newsize;
+       if (SBUF_ISDYNSTRUCT(s)) {
+               kheap_free(KHEAP_DEFAULT, s, sizeof(*s));
+       }
 }
 
-
-/*
- * Extend an sbuf.
+/*!
+ * @function sbuf_extendsize
+ *
+ * @brief
+ * Attempts to extend the size of an sbuf to the value pointed to by size.
+ *
+ * @param size
+ * Points to a size_t containing the desired size for input and receives the
+ * actual new size on success (which will be greater than or equal to the
+ * requested size).
+ *
+ * @returns
+ * 0 on success, -1 on failure.
  */
 static int
-sbuf_extend(struct sbuf *s, int addlen)
+sbuf_extendsize(size_t *size)
 {
-       char *newbuf;
-       int newsize;
+       size_t target_size = *size;
+       size_t new_size;
 
-       if (!SBUF_CANEXTEND(s)) {
+       if (target_size > INT_MAX) {
                return -1;
        }
 
-       newsize = sbuf_extendsize(s->s_size + addlen);
-       newbuf = (char *)SBMALLOC(newsize);
-       if (newbuf == NULL) {
-               return -1;
-       }
-       bcopy(s->s_buf, newbuf, s->s_size);
-       if (SBUF_ISDYNAMIC(s)) {
-               SBFREE(s->s_buf);
+       if (target_size < SBUF_MAXEXTENDSIZE) {
+               new_size = SBUF_MINEXTENDSIZE;
+               while (new_size < target_size) {
+                       new_size *= 2;
+               }
        } else {
-               SBUF_SETFLAG(s, SBUF_DYNAMIC);
+               /* round up to nearest page: */
+               new_size = (target_size + PAGE_SIZE - 1) & ~PAGE_MASK;
+       }
+
+       if (new_size > INT_MAX) {
+               return -1;
        }
-       s->s_buf = newbuf;
-       s->s_size = newsize;
+
+       *size = new_size;
        return 0;
 }
 
-/*
- * Initialize an sbuf.
- * If buf is non-NULL, it points to a static or already-allocated string
- * big enough to hold at least length characters.
+/*!
+ * @function sbuf_new
+ *
+ * @brief
+ * Allocates and/or initializes an sbuf.
+ *
+ * @param s
+ * An optional existing sbuf to initialize.  If NULL, a new one is allocated on
+ * the heap.
+ *
+ * @param buf
+ * An optional existing backing buffer to assign to the sbuf.  If NULL, a new
+ * one is allocated on the heap.
+ *
+ * @param length_
+ * The initial size of the sbuf.  The actual size may be greater than this
+ * value.
+ *
+ * @param flags
+ * The flags to set on the sbuf.  Accepted values are:
+ *
+ *   - SBUF_FIXEDLEN:   Do not allow the backing buffer to dynamically expand
+ *                      to accommodate appended data.
+ *   - SBUF_AUTOEXPAND: Automatically reallocate the backing buffer using the
+ *                      heap if required.
+ *
+ * @returns
+ * The new and/or initialized sbuf on success, or NULL on failure.
  */
 struct sbuf *
-sbuf_new(struct sbuf *s, char *buf, int length, int flags)
+sbuf_new(struct sbuf *s, char *buf, int length_, int flags)
 {
-       KASSERT(length >= 0,
-           ("attempt to create an sbuf of negative length (%d)", length));
-       KASSERT((flags & ~SBUF_USRFLAGMSK) == 0,
-           ("%s called with invalid flags", __func__));
+       size_t length = (size_t)length_;
+
+       if (length > INT_MAX || flags & ~SBUF_USRFLAGMSK) {
+               return NULL;
+       }
 
-       flags &= SBUF_USRFLAGMSK;
        if (s == NULL) {
-               s = (struct sbuf *)SBMALLOC(sizeof *s);
-               if (s == NULL) {
+               s = (struct sbuf *)kheap_alloc(KHEAP_DEFAULT, sizeof(*s), Z_WAITOK);
+               if (NULL == s) {
                        return NULL;
                }
-               bzero(s, sizeof *s);
+
+               bzero(s, sizeof(*s));
                s->s_flags = flags;
                SBUF_SETFLAG(s, SBUF_DYNSTRUCT);
        } else {
-               bzero(s, sizeof *s);
+               bzero(s, sizeof(*s));
                s->s_flags = flags;
        }
-       s->s_size = length;
+
        if (buf) {
+               s->s_size = (int)length;
                s->s_buf = buf;
                return s;
        }
-       if (flags & SBUF_AUTOEXTEND) {
-               s->s_size = sbuf_extendsize(s->s_size);
+
+       if (SBUF_CANEXTEND(s) && (-1 == sbuf_extendsize(&length))) {
+               goto fail;
        }
-       s->s_buf = (char *)SBMALLOC(s->s_size);
-       if (s->s_buf == NULL) {
-               if (SBUF_ISDYNSTRUCT(s)) {
-                       SBFREE(s);
-               }
-               return NULL;
+
+       /*
+        * we always need at least 1 byte for \0, so s_size of 0 will cause an
+        * underflow in sbuf_capacity.
+        */
+       if (length == 0) {
+               goto fail;
        }
+
+       s->s_buf = (char *)kheap_alloc(KHEAP_DATA_BUFFERS, length, Z_WAITOK);
+       if (NULL == s->s_buf) {
+               goto fail;
+       }
+       bzero(s->s_buf, length);
+       s->s_size = (int)length;
+
        SBUF_SETFLAG(s, SBUF_DYNAMIC);
        return s;
+
+fail:
+       sbuf_delete(s);
+       return NULL;
 }
 
-#ifdef KERNEL
-/*
- * Create an sbuf with uio data
+/*!
+ * @function sbuf_setpos
+ *
+ * @brief
+ * Set the current position of the sbuf.
+ *
+ * @param s
+ * The sbuf to modify.
+ *
+ * @param pos
+ * The new position to set.  Must be less than or equal to the current position.
+ *
+ * @returns
+ * 0 on success, -1 on failure.
  */
-struct sbuf *
-sbuf_uionew(struct sbuf *s, struct uio *uio, int *error)
+int
+sbuf_setpos(struct sbuf *s, int pos)
 {
-       KASSERT(uio != NULL,
-           ("%s called with NULL uio pointer", __func__));
-       KASSERT(error != NULL,
-           ("%s called with NULL error pointer", __func__));
-
-       s = sbuf_new(s, NULL, uio_resid(uio) + 1, 0);
-       if (s == NULL) {
-               *error = ENOMEM;
-               return NULL;
-       }
-       *error = uiomove(s->s_buf, uio_resid(uio), uio);
-       if (*error != 0) {
-               sbuf_delete(s);
-               return NULL;
+       if (pos < 0 || pos > s->s_len) {
+               return -1;
        }
-       s->s_len = s->s_size - 1;
-       *error = 0;
-       return s;
+
+       s->s_len = pos;
+       return 0;
 }
-#endif
 
-/*
- * Clear an sbuf and reset its position.
+/*!
+ * @function sbuf_clear
+ *
+ * @brief
+ * Resets the position/length of the sbuf data to zero and clears the finished
+ * and overflow flags.
+ *
+ * @param s
+ * The sbuf to clear.
  */
 void
 sbuf_clear(struct sbuf *s)
 {
-       assert_sbuf_integrity(s);
-       /* don't care if it's finished or not */
-
        SBUF_CLEARFLAG(s, SBUF_FINISHED);
        SBUF_CLEARFLAG(s, SBUF_OVERFLOWED);
-       s->s_len = 0;
+       sbuf_setpos(s, 0);
 }
 
-/*
- * Set the sbuf's end position to an arbitrary value.
- * Effectively truncates the sbuf at the new position.
+/*!
+ * @function sbuf_extend
+ *
+ * @brief
+ * Attempt to extend the size of an sbuf's backing buffer by @a addlen bytes.
+ *
+ * @param s
+ * The sbuf to extend.
+ *
+ * @param addlen
+ * How many bytes to increase the size by.
+ *
+ * @returns
+ * 0 on success, -1 on failure.
  */
-int
-sbuf_setpos(struct sbuf *s, int pos)
+static int OS_WARN_RESULT
+sbuf_extend(struct sbuf *s, size_t addlen)
 {
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, 0);
+       char *new_buf;
+       size_t new_size;
 
-       KASSERT(pos >= 0,
-           ("attempt to seek to a negative position (%d)", pos));
-       KASSERT(pos < s->s_size,
-           ("attempt to seek past end of sbuf (%d >= %d)", pos, s->s_size));
+       if (addlen == 0) {
+               return 0;
+       }
 
-       if (pos < 0 || pos > s->s_len) {
+       if (!SBUF_CANEXTEND(s)) {
                return -1;
        }
-       s->s_len = pos;
-       return 0;
-}
-
-/*
- * Append a byte string to an sbuf.
- */
-int
-sbuf_bcat(struct sbuf *s, const void *buf, size_t len)
-{
-       const char *str = buf;
-
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, 0);
 
-       if (SBUF_HASOVERFLOWED(s)) {
+       if (os_add_overflow((size_t)s->s_size, addlen, &new_size)) {
                return -1;
        }
 
-       for (; len; len--) {
-               if (!SBUF_HASROOM(s) && sbuf_extend(s, len) < 0) {
-                       break;
-               }
-               s->s_buf[s->s_len++] = *str++;
-       }
-       if (len) {
-               SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+       if (-1 == sbuf_extendsize(&new_size)) {
                return -1;
        }
-       return 0;
-}
-
-#ifdef KERNEL
-/*
- * Copy a byte string from userland into an sbuf.
- */
-int
-sbuf_bcopyin(struct sbuf *s, const void *uaddr, size_t len)
-{
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, 0);
 
-       if (SBUF_HASOVERFLOWED(s)) {
+       new_buf = (char *)kheap_alloc(KHEAP_DATA_BUFFERS, new_size, Z_WAITOK);
+       if (NULL == new_buf) {
                return -1;
        }
 
-       if (len == 0) {
-               return 0;
-       }
-       if (len > (unsigned) SBUF_FREESPACE(s)) {
-               sbuf_extend(s, len - SBUF_FREESPACE(s));
-               len = min(len, SBUF_FREESPACE(s));
-       }
-       if (copyin(CAST_USER_ADDR_T(uaddr), s->s_buf + s->s_len, len) != 0) {
-               return -1;
+       bcopy(s->s_buf, new_buf, (size_t)s->s_size);
+       if (SBUF_ISDYNAMIC(s)) {
+               kheap_free(KHEAP_DATA_BUFFERS, s->s_buf, (size_t)s->s_size);
+       } else {
+               SBUF_SETFLAG(s, SBUF_DYNAMIC);
        }
-       s->s_len += len;
 
+       s->s_buf = new_buf;
+       s->s_size = (int)new_size;
        return 0;
 }
-#endif
 
-/*
- * Copy a byte string into an sbuf.
+/*!
+ * @function sbuf_capacity
+ *
+ * @brief
+ * Get the current capacity of an sbuf: how many more bytes we can append given
+ * the current size and position.
+ *
+ * @param s
+ * The sbuf to get the capacity of.
+ *
+ * @returns
+ * The current sbuf capacity.
  */
-int
-sbuf_bcpy(struct sbuf *s, const void *buf, size_t len)
+static size_t
+sbuf_capacity(const struct sbuf *s)
 {
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, 0);
+       /* 1 byte reserved for \0: */
+       return (size_t)(s->s_size - s->s_len - 1);
+}
 
-       sbuf_clear(s);
-       return sbuf_bcat(s, buf, len);
+/*!
+ * @function sbuf_ensure_capacity
+ *
+ * @brief
+ * Ensure that an sbuf can accommodate @a add_len bytes, reallocating the
+ * backing buffer if necessary.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @param wanted
+ * The minimum capacity to ensure @a s has.
+ *
+ * @returns
+ * 0 if the minimum capacity is met by @a s, or -1 on error.
+ */
+static int
+sbuf_ensure_capacity(struct sbuf *s, size_t wanted)
+{
+       size_t size;
+
+       size = sbuf_capacity(s);
+       if (size >= wanted) {
+               return 0;
+       }
+
+       return sbuf_extend(s, wanted - size);
 }
 
-/*
- * Append a string to an sbuf.
+/*!
+ * @function sbuf_bcat
+ *
+ * @brief
+ * Append data to an sbuf.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @param data
+ * The data to append.
+ *
+ * @param len
+ * The length of the data.
+ *
+ * @returns
+ * 0 on success, -1 on failure.  Will always fail if the sbuf is marked as
+ * overflowed.
  */
 int
-sbuf_cat(struct sbuf *s, const char *str)
+sbuf_bcat(struct sbuf *s, const void *data, size_t len)
 {
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, 0);
-
        if (SBUF_HASOVERFLOWED(s)) {
                return -1;
        }
 
-       while (*str) {
-               if (!SBUF_HASROOM(s) && sbuf_extend(s, strlen(str)) < 0) {
-                       break;
-               }
-               s->s_buf[s->s_len++] = *str++;
-       }
-       if (*str) {
+       if (-1 == sbuf_ensure_capacity(s, len)) {
                SBUF_SETFLAG(s, SBUF_OVERFLOWED);
                return -1;
        }
+
+       bcopy(data, s->s_buf + s->s_len, len);
+       s->s_len += (int)len; /* safe */
+
        return 0;
 }
 
-#ifdef KERNEL
-/*
- * Append a string from userland to an sbuf.
+/*!
+ * @function sbuf_bcpy
+ *
+ * @brief
+ * Set the entire sbuf data, possibly reallocating the backing buffer to
+ * accommodate.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @param data
+ * The data to set.
+ *
+ * @param len
+ * The length of the data to set.
+ *
+ * @returns
+ * 0 on success or -1 on failure.  Will clear the finished/overflowed flags.
  */
 int
-sbuf_copyin(struct sbuf *s, const void *uaddr, size_t len)
+sbuf_bcpy(struct sbuf *s, const void *data, size_t len)
 {
-       size_t done;
-
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, 0);
-
-       if (SBUF_HASOVERFLOWED(s)) {
-               return -1;
-       }
-
-       if (len == 0) {
-               len = SBUF_FREESPACE(s);        /* XXX return 0? */
-       }
-       if (len > (unsigned) SBUF_FREESPACE(s)) {
-               sbuf_extend(s, len);
-               len = min(len, SBUF_FREESPACE(s));
-       }
-       switch (copyinstr(CAST_USER_ADDR_T(uaddr), s->s_buf + s->s_len, len + 1, &done)) {
-       case ENAMETOOLONG:
-               SBUF_SETFLAG(s, SBUF_OVERFLOWED);
-       /* fall through */
-       case 0:
-               s->s_len += done - 1;
-               break;
-       default:
-               return -1;    /* XXX */
-       }
+       sbuf_clear(s);
+       return sbuf_bcat(s, data, len);
+}
 
-       return done;
+/*!
+ * @function sbuf_cat
+ *
+ * @brief
+ * Append a string to an sbuf, possibly expanding the backing buffer to
+ * accommodate.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @param str
+ * The string to append.
+ *
+ * @returns
+ * 0 on success, -1 on failure.  Always fails if the sbuf is marked as
+ * overflowed.
+ */
+int
+sbuf_cat(struct sbuf *s, const char *str)
+{
+       return sbuf_bcat(s, str, strlen(str));
 }
-#endif
 
-/*
- * Copy a string into an sbuf.
+/*!
+ * @function sbuf_cpy
+ *
+ * @brief
+ * Set the entire sbuf data to the given nul-terminated string, possibly
+ * expanding the backing buffer to accommodate it if necessary.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @param str
+ * The string to set the sbuf data to.
+ *
+ * @returns
+ * 0 on success, -1 on failure.  Clears and resets the sbuf first.
  */
 int
 sbuf_cpy(struct sbuf *s, const char *str)
 {
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, 0);
-
        sbuf_clear(s);
        return sbuf_cat(s, str);
 }
 
-/*
- * Format the given argument list and append the resulting string to an sbuf.
+/*!
+ * @function sbuf_vprintf
+ *
+ * @brief
+ * Formatted-print into an sbuf using a va_list.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @param fmt
+ * The format string.
+ *
+ * @param ap
+ * The format string argument data.
+ *
+ * @returns
+ * 0 on success, -1 on failure.  Always fails if the sbuf is marked as
+ * overflowed.
  */
 int
 sbuf_vprintf(struct sbuf *s, const char *fmt, va_list ap)
 {
-       __builtin_va_list ap_copy; /* XXX tduffy - blame on him */
-       int len;
-
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, 0);
-
-       KASSERT(fmt != NULL,
-           ("%s called with a NULL format string", __func__));
+       va_list ap_copy;
+       int result;
+       size_t capacity;
+       size_t len;
 
        if (SBUF_HASOVERFLOWED(s)) {
                return -1;
        }
 
        do {
+               capacity = sbuf_capacity(s);
+
                va_copy(ap_copy, ap);
-               len = vsnprintf(&s->s_buf[s->s_len], SBUF_FREESPACE(s) + 1,
-                   fmt, ap_copy);
+               /* +1 for \0.  safe because we already accommodate this. */
+               result = vsnprintf(&s->s_buf[s->s_len], capacity + 1, fmt, ap_copy);
                va_end(ap_copy);
-       } while (len > SBUF_FREESPACE(s) &&
-           sbuf_extend(s, len - SBUF_FREESPACE(s)) == 0);
 
-       /*
-        * s->s_len is the length of the string, without the terminating nul.
-        * When updating s->s_len, we must subtract 1 from the length that
-        * we passed into vsnprintf() because that length includes the
-        * terminating nul.
-        *
-        * vsnprintf() returns the amount that would have been copied,
-        * given sufficient space, hence the min() calculation below.
-        */
-       s->s_len += min(len, SBUF_FREESPACE(s));
-       if (!SBUF_HASROOM(s) && !SBUF_CANEXTEND(s)) {
-               SBUF_SETFLAG(s, SBUF_OVERFLOWED);
-       }
+               if (result < 0) {
+                       return -1;
+               }
 
-       KASSERT(s->s_len < s->s_size,
-           ("wrote past end of sbuf (%d >= %d)", s->s_len, s->s_size));
+               len = (size_t)result;
+               if (len <= capacity) {
+                       s->s_len += (int)len;
+                       return 0;
+               }
+       } while (-1 != sbuf_ensure_capacity(s, len));
 
-       if (SBUF_HASOVERFLOWED(s)) {
-               return -1;
-       }
-       return 0;
+       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+       return -1;
 }
 
-/*
- * Format the given arguments and append the resulting string to an sbuf.
+/*!
+ * @function sbuf_printf
+ *
+ * @brief
+ * Formatted-print into an sbuf using variadic arguments.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @param fmt
+ * The format string.
+ *
+ * @returns
+ * 0 on success, -1 on failure.  Always fails if the sbuf is marked as
+ * overflowed.
  */
 int
 sbuf_printf(struct sbuf *s, const char *fmt, ...)
@@ -480,26 +542,40 @@ sbuf_printf(struct sbuf *s, const char *fmt, ...)
        return result;
 }
 
-/*
- * Append a character to an sbuf.
+/*!
+ * @function sbuf_putc
+ *
+ * @brief
+ * Append a single character to an sbuf.  Ignores '\0'.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @param c_
+ * The character to append.
+ *
+ * @returns
+ * 0 on success, -1 on failure.  This function will always fail if the sbuf is
+ * marked as overflowed.
  */
 int
-sbuf_putc(struct sbuf *s, int c)
+sbuf_putc(struct sbuf *s, int c_)
 {
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, 0);
+       char c = (char)c_;
 
        if (SBUF_HASOVERFLOWED(s)) {
                return -1;
        }
 
-       if (!SBUF_HASROOM(s) && sbuf_extend(s, 1) < 0) {
+       if (-1 == sbuf_ensure_capacity(s, 1)) {
                SBUF_SETFLAG(s, SBUF_OVERFLOWED);
                return -1;
        }
+
        if (c != '\0') {
                s->s_buf[s->s_len++] = c;
        }
+
        return 0;
 }
 
@@ -509,102 +585,1608 @@ isspace(char ch)
        return ch == ' ' || ch == '\n' || ch == '\t';
 }
 
-/*
- * Trim whitespace characters from end of an sbuf.
+/*!
+ * @function sbuf_trim
+ *
+ * @brief
+ * Removes whitespace from the end of an sbuf.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @returns
+ * 0 on success or -1 if the sbuf is marked as overflowed.
  */
 int
 sbuf_trim(struct sbuf *s)
 {
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, 0);
-
        if (SBUF_HASOVERFLOWED(s)) {
                return -1;
        }
 
-       while (s->s_len && isspace(s->s_buf[s->s_len - 1])) {
+       while (s->s_len > 0 && isspace(s->s_buf[s->s_len - 1])) {
                --s->s_len;
        }
 
        return 0;
 }
 
-/*
- * Check if an sbuf overflowed
+/*!
+ * @function sbuf_overflowed
+ *
+ * @brief
+ * Indicates whether the sbuf is marked as overflowed.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @returns
+ * 1 if the sbuf has overflowed or 0 otherwise.
  */
 int
 sbuf_overflowed(struct sbuf *s)
 {
-       return SBUF_HASOVERFLOWED(s);
+       return !!SBUF_HASOVERFLOWED(s);
 }
 
-/*
- * Finish off an sbuf.
+/*!
+ * @function sbuf_finish
+ *
+ * @brief
+ * Puts a trailing nul byte onto the sbuf data.
+ *
+ * @param s
+ * The sbuf.
  */
 void
 sbuf_finish(struct sbuf *s)
 {
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, 0);
-
+       /* safe because we always reserve a byte at the end for \0: */
        s->s_buf[s->s_len] = '\0';
        SBUF_CLEARFLAG(s, SBUF_OVERFLOWED);
        SBUF_SETFLAG(s, SBUF_FINISHED);
 }
 
-/*
- * Return a pointer to the sbuf data.
+/*!
+ * @function sbuf_data
+ *
+ * @brief
+ * Gets a pointer to the sbuf backing data.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @returns
+ * A pointer to the sbuf data.
  */
 char *
 sbuf_data(struct sbuf *s)
 {
-       assert_sbuf_integrity(s);
-       assert_sbuf_state(s, SBUF_FINISHED);
-
        return s->s_buf;
 }
 
-/*
- * Return the length of the sbuf data.
+/*!
+ * @function sbuf_len
+ *
+ * @brief
+ * Retrieves the current length of the sbuf data.
+ *
+ * @param s
+ * The sbuf
+ *
+ * @returns
+ * The length of the sbuf data or -1 if the sbuf is marked as overflowed.
  */
 int
 sbuf_len(struct sbuf *s)
 {
-       assert_sbuf_integrity(s);
-       /* don't care if it's finished or not */
-
        if (SBUF_HASOVERFLOWED(s)) {
                return -1;
        }
+
        return s->s_len;
 }
 
-/*
- * Clear an sbuf, free its buffer if necessary.
+/*!
+ * @function sbuf_done
+ *
+ * @brief
+ * Tests if the sbuf is marked as finished.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @returns
+ * 1 if the sbuf is marked as finished or 0 if not.
  */
-void
-sbuf_delete(struct sbuf *s)
+int
+sbuf_done(struct sbuf *s)
 {
-       int isdyn;
+       return !!SBUF_ISFINISHED(s);
+}
 
-       assert_sbuf_integrity(s);
-       /* don't care if it's finished or not */
+/*!
+ * @function sbuf_uionew
+ *
+ * @brief
+ * Create a new sbuf and initialize its buffer with data from the given uio.
+ *
+ * @param s
+ * An optional existing sbuf to initialize, or NULL to allocate a new one.
+ *
+ * @param uio
+ * The uio describing the data to populate the sbuf with.
+ *
+ * @param error
+ * An output parameter to report any error to.
+ *
+ * @returns
+ * The new and/or initialized sbuf, or NULL on error.  The error code is
+ * reported back via @a error.
+ */
+struct sbuf *
+sbuf_uionew(struct sbuf *s, struct uio *uio, int *error)
+{
+       int size;
 
-       if (SBUF_ISDYNAMIC(s)) {
-               SBFREE(s->s_buf);
+       if ((user_size_t)uio_resid(uio) > INT_MAX - 1) {
+               *error = EINVAL;
+               return NULL;
        }
-       isdyn = SBUF_ISDYNSTRUCT(s);
-       bzero(s, sizeof *s);
-       if (isdyn) {
-               SBFREE(s);
+
+       size = (int)uio_resid(uio);
+       s = sbuf_new(s, NULL, size + 1, 0);
+       if (s == NULL) {
+               *error = ENOMEM;
+               return NULL;
        }
+
+       *error = uiomove(s->s_buf, size, uio);
+       if (*error != 0) {
+               sbuf_delete(s);
+               return NULL;
+       }
+
+       s->s_len = size;
+       *error = 0;
+
+       return s;
 }
 
-/*
- * Check if an sbuf has been finished.
+/*!
+ * @function sbuf_bcopyin
+ *
+ * @brief
+ * Append userland data to an sbuf.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @param uaddr
+ * The userland address of data to append to the sbuf.
+ *
+ * @param len
+ * The length of the data to copy from userland.
+ *
+ * @returns
+ * 0 on success or -1 on error.  Always returns -1 if the sbuf is marked as
+ * overflowed.
  */
 int
-sbuf_done(struct sbuf *s)
+sbuf_bcopyin(struct sbuf *s, const void *uaddr, size_t len)
+{
+       if (SBUF_HASOVERFLOWED(s)) {
+               return -1;
+       }
+
+       if (len == 0) {
+               return 0;
+       }
+
+       if (-1 == sbuf_ensure_capacity(s, len)) {
+               SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+               return -1;
+       }
+
+       if (copyin(CAST_USER_ADDR_T(uaddr), &s->s_buf[s->s_len], len) != 0) {
+               return -1;
+       }
+
+       s->s_len += (int)len;
+       return 0;
+}
+
+/*!
+ * @function sbuf_copyin
+ *
+ * @brief
+ * Append a userland string to an sbuf.
+ *
+ * @param s
+ * The sbuf.
+ *
+ * @param uaddr
+ * The userland address of the string to append to the sbuf.
+ *
+ * @param len
+ * The maximum length of the string to copy.  If zero, the current capacity of
+ * the sbuf is used.
+ *
+ * @returns
+ * The number of bytes copied or -1 if an error occurred.  Always returns -1 if
+ * the sbuf is marked as overflowed.
+ */
+int
+sbuf_copyin(struct sbuf *s, const void *uaddr, size_t len)
 {
-       return SBUF_ISFINISHED(s);
+       size_t done;
+
+       if (SBUF_HASOVERFLOWED(s)) {
+               return -1;
+       }
+
+       if (len == 0) {
+               len = sbuf_capacity(s);
+       } else if (-1 == sbuf_ensure_capacity(s, len)) {
+               return -1;
+       }
+
+       switch (copyinstr(CAST_USER_ADDR_T(uaddr), &s->s_buf[s->s_len], len + 1, &done)) {
+       case ENAMETOOLONG:
+               SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+               s->s_len += done;
+               return -1;
+       case 0:
+               s->s_len += done - 1;
+               break;
+       default:
+               return -1;
+       }
+
+       return (int)done;
+}
+
+#if DEBUG || DEVELOPMENT
+
+/*
+ * a = assertion string
+ */
+#define SBUF_FAIL(a)                                                             \
+    MACRO_BEGIN                                                                  \
+       printf("sbuf_tests: failed assertion: %s\n", a);                         \
+       if (what != NULL && should != NULL) {                                    \
+           printf("sbuf_tests: while testing: %s should %s\n", what, should);   \
+       }                                                                        \
+       goto fail;                                                               \
+    MACRO_END
+
+#define SBUF_PASS \
+    ++passed
+
+/*
+ * x = expression
+ */
+#define SBUF_ASSERT(x)     \
+    MACRO_BEGIN            \
+       if (x) {           \
+           SBUF_PASS;     \
+       } else {           \
+           SBUF_FAIL(#x); \
+       }                  \
+    MACRO_END
+
+#define SBUF_ASSERT_NOT(x) \
+    SBUF_ASSERT(!(x))
+
+/*
+ * e = expected
+ * a = actual
+ * c = comparator
+ */
+#define SBUF_ASSERT_CMP(e, a, c)         \
+    MACRO_BEGIN                          \
+       if ((a) c (e)) {                 \
+           SBUF_PASS;                   \
+       } else {                         \
+           SBUF_FAIL(#a " " #c " " #e); \
+       }                                \
+    MACRO_END
+
+#define SBUF_ASSERT_EQ(e, a)    SBUF_ASSERT_CMP(e, a, ==)
+#define SBUF_ASSERT_NE(e, a)    SBUF_ASSERT_CMP(e, a, !=)
+#define SBUF_ASSERT_GT(e, a)    SBUF_ASSERT_CMP(e, a, >)
+#define SBUF_ASSERT_GTE(e, a)   SBUF_ASSERT_CMP(e, a, >=)
+#define SBUF_ASSERT_LT(e, a)    SBUF_ASSERT_CMP(e, a, <)
+#define SBUF_ASSERT_LTE(e, a)   SBUF_ASSERT_CMP(e, a, <=)
+
+#define SBUF_TEST_BEGIN      \
+    size_t passed = 0;       \
+    const char *what = NULL; \
+    const char *should = NULL;
+
+/*
+ * include the trailing semi-colons here intentionally to allow for block-like
+ * appearance:
+ */
+#define SBUF_TESTING(f) \
+    MACRO_BEGIN         \
+       what = (f);     \
+    MACRO_END;
+
+#define SBUF_SHOULD(s) \
+    MACRO_BEGIN        \
+       should = (s);  \
+    MACRO_END;
+
+#define SBUF_TEST_END                                      \
+    printf("sbuf_tests: %zu assertions passed\n", passed); \
+    return 0;                                              \
+fail:                                                      \
+    return ENOTRECOVERABLE;
+
+static int
+sysctl_sbuf_tests SYSCTL_HANDLER_ARGS
+{
+#pragma unused(arg1, arg2)
+       int rval = 0;
+       char str[32] = { 'o', 'k', 0 };
+
+       rval = sysctl_handle_string(oidp, str, sizeof(str), req);
+       if (rval != 0 || req->newptr == 0 || req->newlen < 1) {
+               return rval;
+       }
+
+       SBUF_TEST_BEGIN;
+
+       SBUF_TESTING("sbuf_new")
+       {
+               SBUF_SHOULD("fail to allocate >INT_MAX")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, INT_MAX + 1, 0);
+                       SBUF_ASSERT_EQ(NULL, s);
+               }
+
+               SBUF_SHOULD("fail when claiming a backing buffer >INT_MAX")
+               {
+                       struct sbuf *s = NULL;
+                       char buf[4] = { 0 };
+
+                       s = sbuf_new(NULL, buf, INT_MAX + 1, 0);
+                       SBUF_ASSERT_EQ(NULL, s);
+               }
+
+               SBUF_SHOULD("fail to allocate a zero-length sbuf")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 0, 0);
+                       SBUF_ASSERT_EQ(NULL, s);
+               }
+
+               SBUF_SHOULD("not accept invalid flags")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0x10000);
+                       SBUF_ASSERT_EQ(NULL, s);
+               }
+
+               SBUF_SHOULD("succeed when passed an existing sbuf")
+               {
+                       struct sbuf *s = NULL;
+                       struct sbuf existing;
+
+                       memset(&existing, 0x41, sizeof(existing));
+                       s = sbuf_new(&existing, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(&existing, s);
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_AUTOEXTEND));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_DYNAMIC));
+                       SBUF_ASSERT_NE(NULL, s->s_buf);
+                       SBUF_ASSERT_NE(0, s->s_size);
+                       SBUF_ASSERT_EQ(0, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed when passed an existing sbuf and buffer")
+               {
+                       struct sbuf *s = NULL;
+                       struct sbuf existing;
+                       char buf[4] = { 0 };
+
+                       memset(&existing, 0x41, sizeof(existing));
+                       s = sbuf_new(&existing, buf, sizeof(buf), 0);
+                       SBUF_ASSERT_EQ(&existing, s);
+                       SBUF_ASSERT_EQ(buf, s->s_buf);
+                       SBUF_ASSERT_EQ(4, s->s_size);
+                       SBUF_ASSERT_EQ(0, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed without an existing sbuf or buffer")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_NE(NULL, s);
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_DYNAMIC));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_DYNSTRUCT));
+                       SBUF_ASSERT_NE(NULL, s->s_buf);
+                       SBUF_ASSERT_NE(0, s->s_size);
+                       SBUF_ASSERT_EQ(0, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed without an existing sbuf, but with a buffer")
+               {
+                       struct sbuf *s = NULL;
+                       char buf[4] = { 0 };
+
+                       s = sbuf_new(NULL, buf, sizeof(buf), 0);
+                       SBUF_ASSERT_NE(NULL, s);
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_DYNSTRUCT));
+                       SBUF_ASSERT_EQ(buf, s->s_buf);
+                       SBUF_ASSERT_EQ(4, s->s_size);
+                       SBUF_ASSERT_EQ(0, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("round up the requested size if SBUF_AUTOEXTEND")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 1, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_GT(1, s->s_size);
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_clear")
+       {
+               SBUF_SHOULD("clear the overflowed and finished flags")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+                       SBUF_SETFLAG(s, SBUF_FINISHED);
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_FINISHED));
+                       sbuf_clear(s);
+                       SBUF_ASSERT_NOT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+                       SBUF_ASSERT_NOT(SBUF_ISSET(s, SBUF_FINISHED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("reset the position to zero")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+
+                       s->s_len = 1;
+                       sbuf_clear(s);
+                       SBUF_ASSERT_EQ(0, s->s_len);
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_extend")
+       {
+               SBUF_SHOULD("allow zero")
+               {
+                       struct sbuf *s = NULL;
+                       int size_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       size_before = s->s_size;
+                       SBUF_ASSERT_EQ(0, sbuf_extend(s, 0));
+                       SBUF_ASSERT_EQ(size_before, s->s_size);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail for sbuf not marked as SBUF_AUTOEXTEND")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(-1, sbuf_extend(s, 10));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("accommodate reasonable requests")
+               {
+                       struct sbuf *s = NULL;
+                       int size_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       size_before = s->s_size;
+
+                       SBUF_ASSERT_EQ(0, sbuf_extend(s, 10));
+                       SBUF_ASSERT_GTE(10, s->s_size - size_before);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("reject requests that cause overflows")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(-1, sbuf_extend(s, SIZE_MAX));
+                       SBUF_ASSERT_EQ(-1, sbuf_extend(s, INT_MAX));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("transform the sbuf into an SBUF_DYNAMIC one")
+               {
+                       struct sbuf *s = NULL;
+                       char buf[4] = { 0 };
+
+                       s = sbuf_new(NULL, buf, sizeof(buf), SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_NOT(SBUF_ISSET(s, SBUF_DYNAMIC));
+                       SBUF_ASSERT_EQ(0, sbuf_extend(s, 10));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_DYNAMIC));
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_capacity")
+       {
+               SBUF_SHOULD("account for the trailing nul byte")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(s->s_size - s->s_len - 1, sbuf_capacity(s));
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_ensure_capacity")
+       {
+               SBUF_SHOULD("return 0 if the sbuf already has enough capacity")
+               {
+                       struct sbuf *s = NULL;
+                       int size_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       size_before = s->s_size;
+                       SBUF_ASSERT_EQ(0, sbuf_ensure_capacity(s, 5));
+                       SBUF_ASSERT_EQ(size_before, s->s_size);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("extend the buffer as needed")
+               {
+                       struct sbuf *s = NULL;
+                       int size_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       size_before = s->s_size;
+                       SBUF_ASSERT_EQ(0, sbuf_ensure_capacity(s, 30));
+                       SBUF_ASSERT_GT(size_before, s->s_size);
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_bcat")
+       {
+               SBUF_SHOULD("fail if the sbuf is marked as overflowed")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT_EQ(-1, sbuf_bcat(s, "A", 1));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail if len is too big")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(-1, sbuf_bcat(s, "A", INT_MAX));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed for a fixed buf within limits")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_bcat(s, "ABC", 3));
+                       SBUF_ASSERT_EQ(3, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('B', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('C', s->s_buf[2]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed for binary data, even with nul bytes")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_bcat(s, "A\0C", 3));
+                       SBUF_ASSERT_EQ(3, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('\0', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('C', s->s_buf[2]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("append to existing data")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_bcat(s, "ABC", 3));
+                       SBUF_ASSERT_EQ(3, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('B', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('C', s->s_buf[2]);
+
+                       SBUF_ASSERT_EQ(0, sbuf_bcat(s, "DEF", 3));
+                       SBUF_ASSERT_EQ(6, s->s_len);
+                       SBUF_ASSERT_EQ('D', s->s_buf[3]);
+                       SBUF_ASSERT_EQ('E', s->s_buf[4]);
+                       SBUF_ASSERT_EQ('F', s->s_buf[5]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed for a fixed buf right up to the limit")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_bcat(s, "0123456789abcde", 15));
+                       SBUF_ASSERT_EQ(15, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail for a fixed buf if too big")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(-1, sbuf_bcat(s, "0123456789abcdef", 16));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("expand the backing buffer as needed")
+               {
+                       struct sbuf *s = NULL;
+                       int size_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       size_before = s->s_size;
+                       SBUF_ASSERT_EQ(0, sbuf_bcat(s, "0123456789abcdef", 16));
+                       SBUF_ASSERT_GT(size_before, s->s_size);
+                       SBUF_ASSERT_EQ(16, s->s_len);
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_bcpy")
+       {
+               SBUF_SHOULD("overwrite any existing data")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_bcpy(s, "ABC", 3));
+                       SBUF_ASSERT_EQ(3, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('B', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('C', s->s_buf[2]);
+
+                       SBUF_ASSERT_EQ(0, sbuf_bcpy(s, "XYZ123", 6));
+                       SBUF_ASSERT_EQ(6, s->s_len);
+                       SBUF_ASSERT_EQ('X', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('Y', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('Z', s->s_buf[2]);
+                       SBUF_ASSERT_EQ('1', s->s_buf[3]);
+                       SBUF_ASSERT_EQ('2', s->s_buf[4]);
+                       SBUF_ASSERT_EQ('3', s->s_buf[5]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed if the sbuf is marked as overflowed, but there is space")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT_EQ(0, sbuf_bcpy(s, "A", 1));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail if len is too big")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(-1, sbuf_bcpy(s, "A", INT_MAX));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed for a fixed buf within limits")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_bcpy(s, "ABC", 3));
+                       SBUF_ASSERT_EQ(3, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('B', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('C', s->s_buf[2]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed for a fixed buf right up to the limit")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_bcpy(s, "0123456789abcde", 15));
+                       SBUF_ASSERT_EQ(15, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail for a fixed buf if too big")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(-1, sbuf_bcpy(s, "0123456789abcdef", 16));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("expand the backing buffer as needed")
+               {
+                       struct sbuf *s = NULL;
+                       int size_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       size_before = s->s_size;
+                       SBUF_ASSERT_EQ(0, sbuf_bcpy(s, "0123456789abcdef", 16));
+                       SBUF_ASSERT_GT(size_before, s->s_size);
+                       SBUF_ASSERT_EQ(16, s->s_len);
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_cat")
+       {
+               SBUF_SHOULD("fail if the sbuf is marked as overflowed")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT_EQ(-1, sbuf_cat(s, "A"));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed for a fixed buf within limits")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_cat(s, "ABC"));
+                       SBUF_ASSERT_EQ(3, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('B', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('C', s->s_buf[2]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("only copy up to a nul byte")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_cat(s, "A\0C"));
+                       SBUF_ASSERT_EQ(1, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("append to existing data")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_cat(s, "ABC"));
+                       SBUF_ASSERT_EQ(3, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('B', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('C', s->s_buf[2]);
+
+                       SBUF_ASSERT_EQ(0, sbuf_cat(s, "DEF"));
+                       SBUF_ASSERT_EQ(6, s->s_len);
+                       SBUF_ASSERT_EQ('D', s->s_buf[3]);
+                       SBUF_ASSERT_EQ('E', s->s_buf[4]);
+                       SBUF_ASSERT_EQ('F', s->s_buf[5]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed for a fixed buf right up to the limit")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_cat(s, "0123456789abcde"));
+                       SBUF_ASSERT_EQ(15, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail for a fixed buf if too big")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(-1, sbuf_cat(s, "0123456789abcdef"));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("expand the backing buffer as needed")
+               {
+                       struct sbuf *s = NULL;
+                       int size_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       size_before = s->s_size;
+                       SBUF_ASSERT_EQ(0, sbuf_cat(s, "0123456789abcdef"));
+                       SBUF_ASSERT_GT(size_before, s->s_size);
+                       SBUF_ASSERT_EQ(16, s->s_len);
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_cpy")
+       {
+               SBUF_SHOULD("overwrite any existing data")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "ABC"));
+                       SBUF_ASSERT_EQ(3, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('B', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('C', s->s_buf[2]);
+
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "XYZ123"));
+                       SBUF_ASSERT_EQ(6, s->s_len);
+                       SBUF_ASSERT_EQ('X', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('Y', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('Z', s->s_buf[2]);
+                       SBUF_ASSERT_EQ('1', s->s_buf[3]);
+                       SBUF_ASSERT_EQ('2', s->s_buf[4]);
+                       SBUF_ASSERT_EQ('3', s->s_buf[5]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed if the sbuf is marked as overflowed, but there is space")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT_EQ(0, sbuf_bcpy(s, "A", 1));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed for a fixed buf within limits")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "ABC"));
+                       SBUF_ASSERT_EQ(3, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('B', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('C', s->s_buf[2]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed for a fixed buf right up to the limit")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "0123456789abcde"));
+                       SBUF_ASSERT_EQ(15, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail for a fixed buf if too big")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(-1, sbuf_cpy(s, "0123456789abcdef"));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("expand the backing buffer as needed")
+               {
+                       struct sbuf *s = NULL;
+                       int size_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       size_before = s->s_size;
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "0123456789abcdef"));
+                       SBUF_ASSERT_GT(size_before, s->s_size);
+                       SBUF_ASSERT_EQ(16, s->s_len);
+
+                       sbuf_delete(s);
+               }
+       }
+
+       /* also tests sbuf_vprintf: */
+       SBUF_TESTING("sbuf_printf")
+       {
+               SBUF_SHOULD("support simple printing")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(0, sbuf_printf(s, "hello"));
+                       SBUF_ASSERT_EQ(5, s->s_len);
+                       SBUF_ASSERT_EQ('h', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('e', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('l', s->s_buf[2]);
+                       SBUF_ASSERT_EQ('l', s->s_buf[3]);
+                       SBUF_ASSERT_EQ('o', s->s_buf[4]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("support format strings")
+               {
+                       struct sbuf *s = NULL;
+                       char data1 = 'A';
+                       int data2 = 123;
+                       const char *data3 = "foo";
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(0, sbuf_printf(s, "%c %d %s", data1, data2, data3));
+                       SBUF_ASSERT_EQ(9, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+                       SBUF_ASSERT_EQ(' ', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('1', s->s_buf[2]);
+                       SBUF_ASSERT_EQ('2', s->s_buf[3]);
+                       SBUF_ASSERT_EQ('3', s->s_buf[4]);
+                       SBUF_ASSERT_EQ(' ', s->s_buf[5]);
+                       SBUF_ASSERT_EQ('f', s->s_buf[6]);
+                       SBUF_ASSERT_EQ('o', s->s_buf[7]);
+                       SBUF_ASSERT_EQ('o', s->s_buf[8]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("work with the fact we reserve a nul byte at the end")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_printf(s, "0123456789abcde"));
+                       SBUF_ASSERT_NOT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("mark the sbuf as overflowed if we try to write too much")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(-1, sbuf_printf(s, "0123456789abcdef"));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("auto-extend as necessary")
+               {
+                       struct sbuf *s = NULL;
+                       const char *data = "0123456789abcdef";
+                       int size_before;
+                       size_t n;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       size_before = s->s_size;
+                       SBUF_ASSERT_EQ(0, sbuf_printf(s, "%s", data));
+                       SBUF_ASSERT_GT(size_before, s->s_size);
+
+                       for (n = 0; n < strlen(data); ++n) {
+                               SBUF_ASSERT_EQ(data[n], s->s_buf[n]);
+                       }
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail if the sbuf is marked as overflowed")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT_EQ(-1, sbuf_printf(s, "A"));
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_putc")
+       {
+               SBUF_SHOULD("work where we have capacity")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_putc(s, 'a'));
+                       SBUF_ASSERT_EQ(1, s->s_len);
+                       SBUF_ASSERT_EQ('a', s->s_buf[0]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail if we have a full, fixedlen sbuf")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "0123456789abcd"));
+                       SBUF_ASSERT_EQ(0, sbuf_putc(s, 'e'));
+                       SBUF_ASSERT_EQ(-1, sbuf_putc(s, 'f'));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("ignore nul")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(0, sbuf_putc(s, '\0'));
+                       SBUF_ASSERT_EQ(0, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("auto-extend if necessary")
+               {
+                       struct sbuf *s = NULL;
+                       int len_before;
+                       int size_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "0123456789abcde"));
+                       len_before = s->s_len;
+                       size_before = s->s_size;
+                       SBUF_ASSERT_EQ(0, sbuf_putc(s, 'f'));
+                       SBUF_ASSERT_EQ(len_before + 1, s->s_len);
+                       SBUF_ASSERT_GT(size_before, s->s_size);
+                       SBUF_ASSERT_EQ('f', s->s_buf[s->s_len - 1]);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail if the sbuf is overflowed")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT_EQ(-1, sbuf_putc(s, 'a'));
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_trim")
+       {
+               SBUF_SHOULD("remove trailing spaces, tabs and newlines")
+               {
+                       struct sbuf *s = NULL;
+                       const char *test = "foo    \t\t\n\t";
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, test));
+                       SBUF_ASSERT_EQ(strlen(test), s->s_len);
+                       SBUF_ASSERT_EQ(0, sbuf_trim(s));
+                       SBUF_ASSERT_EQ(3, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("do nothing if there is no trailing whitespace")
+               {
+                       struct sbuf *s = NULL;
+                       const char *test = "foo";
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, test));
+                       SBUF_ASSERT_EQ(strlen(test), s->s_len);
+                       SBUF_ASSERT_EQ(0, sbuf_trim(s));
+                       SBUF_ASSERT_EQ(strlen(test), s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail if the sbuf is overflowed")
+               {
+                       struct sbuf *s = NULL;
+                       const char *test = "foo   ";
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, test));
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT_EQ(-1, sbuf_trim(s));
+                       SBUF_ASSERT_EQ(strlen(test), s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("work on empty strings")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_trim(s));
+                       SBUF_ASSERT_EQ(0, s->s_len);
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_overflowed")
+       {
+               SBUF_SHOULD("return false if it hasn't overflowed")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_NOT(sbuf_overflowed(s));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("return true if it has overflowed")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT(sbuf_overflowed(s));
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_finish")
+       {
+               SBUF_SHOULD("insert a nul byte, clear the overflowed flag and set the finished flag")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_putc(s, 'A'));
+                       s->s_buf[s->s_len] = 'x';
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT_NOT(SBUF_ISSET(s, SBUF_FINISHED));
+
+                       sbuf_finish(s);
+
+                       SBUF_ASSERT_EQ(0, s->s_buf[s->s_len]);
+                       SBUF_ASSERT_NOT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_FINISHED));
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_data")
+       {
+               SBUF_SHOULD("return the s_buf pointer")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(s->s_buf, sbuf_data(s));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("return the buffer we gave it")
+               {
+                       struct sbuf *s = NULL;
+                       char buf[4] = { 0 };
+
+                       s = sbuf_new(NULL, buf, sizeof(buf), 0);
+                       SBUF_ASSERT_EQ(buf, sbuf_data(s));
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_len")
+       {
+               SBUF_SHOULD("return the length of the sbuf data")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "hello"));
+                       SBUF_ASSERT_EQ(5, sbuf_len(s));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("return -1 if the sbuf is overflowed")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "hello"));
+                       SBUF_ASSERT_EQ(5, sbuf_len(s));
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT_EQ(-1, sbuf_len(s));
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_done")
+       {
+               SBUF_SHOULD("return false if the sbuf isn't finished")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_NOT(sbuf_done(s));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("return true if the sbuf has finished")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_NOT(sbuf_done(s));
+                       SBUF_SETFLAG(s, SBUF_FINISHED);
+                       SBUF_ASSERT(sbuf_done(s));
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_delete")
+       {
+               SBUF_SHOULD("just free the backing buffer if we supplied an sbuf")
+               {
+                       struct sbuf *s = NULL;
+                       struct sbuf existing = {};
+
+                       s = sbuf_new(&existing, NULL, 16, 0);
+                       SBUF_ASSERT_NE(NULL, s->s_buf);
+
+                       sbuf_delete(s);
+                       SBUF_ASSERT_EQ(NULL, s->s_buf);
+               }
+       }
+
+       SBUF_TESTING("sbuf_uionew")
+       {
+               SBUF_SHOULD("reject residuals that are too large")
+               {
+                       struct sbuf *s = NULL;
+                       uio_t auio = NULL;
+                       char buf[4];
+                       int error = 0;
+
+                       buf[0] = 'A';
+                       buf[1] = 'B';
+                       buf[2] = 'C';
+                       buf[3] = 'D';
+
+                       auio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ);
+                       uio_addiov(auio, (user_addr_t)buf, INT_MAX);
+
+                       s = sbuf_uionew(NULL, auio, &error);
+                       SBUF_ASSERT_EQ(NULL, s);
+                       SBUF_ASSERT_EQ(EINVAL, error);
+
+                       uio_free(auio);
+               }
+
+               SBUF_SHOULD("initialize using data described by the uio")
+               {
+                       struct sbuf *s = NULL;
+                       uio_t auio = NULL;
+                       char buf[4];
+                       int error = 0;
+
+                       buf[0] = 'A';
+                       buf[1] = 'B';
+                       buf[2] = 'C';
+                       buf[3] = 'D';
+
+                       auio = uio_create(1, 0, UIO_SYSSPACE, UIO_WRITE);
+                       uio_addiov(auio, (user_addr_t)buf, sizeof(buf));
+
+                       s = sbuf_uionew(NULL, auio, &error);
+                       SBUF_ASSERT_NE(NULL, s);
+                       SBUF_ASSERT_EQ(0, error);
+                       SBUF_ASSERT_EQ(4, s->s_len);
+                       SBUF_ASSERT_EQ('A', s->s_buf[0]);
+                       SBUF_ASSERT_EQ('B', s->s_buf[1]);
+                       SBUF_ASSERT_EQ('C', s->s_buf[2]);
+                       SBUF_ASSERT_EQ('D', s->s_buf[3]);
+
+                       sbuf_delete(s);
+                       uio_free(auio);
+               }
+
+               SBUF_SHOULD("fail gracefully for bad addresses")
+               {
+                       struct sbuf *s = NULL;
+                       uio_t auio = NULL;
+                       int error = 0;
+
+                       auio = uio_create(1, 0, UIO_USERSPACE, UIO_WRITE);
+                       uio_addiov(auio, (user_addr_t)0xdeadUL, 123);
+
+                       s = sbuf_uionew(NULL, auio, &error);
+                       SBUF_ASSERT_EQ(NULL, s);
+                       SBUF_ASSERT_NE(0, error);
+
+                       uio_free(auio);
+               }
+       }
+
+       SBUF_TESTING("sbuf_bcopyin")
+       {
+               SBUF_SHOULD("succeed when len is zero")
+               {
+                       struct sbuf *s = NULL;
+                       const void *uptr = (const void *)req->newptr;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_bcopyin(s, uptr, 0));
+                       SBUF_ASSERT_EQ(0, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("succeed in the simple case")
+               {
+                       struct sbuf *s = NULL;
+                       const void *uptr = (const void *)req->newptr;
+                       size_t ulen = req->newlen;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(0, sbuf_bcopyin(s, uptr, ulen));
+                       SBUF_ASSERT_EQ(ulen, (size_t)s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail for invalid userland addresses")
+               {
+                       struct sbuf *s = NULL;
+                       const void *uptr = (const void *)0xdeadUL;
+                       size_t ulen = req->newlen;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(-1, sbuf_bcopyin(s, uptr, ulen));
+                       SBUF_ASSERT_EQ(0, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail for kernel addresses")
+               {
+                       struct sbuf *s = NULL;
+                       const void *uptr = "abcd";
+                       size_t ulen = 4;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_ASSERT_EQ(-1, sbuf_bcopyin(s, uptr, ulen));
+                       SBUF_ASSERT_EQ(0, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail if we don't have capacity for a fixed-len sbuf")
+               {
+                       struct sbuf *s = NULL;
+                       const void *uptr = (const void *)req->newptr;
+                       size_t ulen = req->newlen;
+                       int len_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "0123456789abcde"));
+                       len_before = s->s_len;
+                       SBUF_ASSERT_EQ(-1, sbuf_bcopyin(s, uptr, ulen));
+                       SBUF_ASSERT_EQ(len_before, s->s_len);
+                       SBUF_ASSERT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("auto-extend if we don't have capacity for an auto-extend sbuf")
+               {
+                       struct sbuf *s = NULL;
+                       const void *uptr = (const void *)req->newptr;
+                       size_t ulen = req->newlen;
+                       int len_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "0123456789abcde"));
+                       len_before = s->s_len;
+                       SBUF_ASSERT_EQ(0, sbuf_bcopyin(s, uptr, ulen));
+                       SBUF_ASSERT_EQ(len_before + (int)ulen, s->s_len);
+                       SBUF_ASSERT_NOT(SBUF_ISSET(s, SBUF_OVERFLOWED));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail if overflowed")
+               {
+                       struct sbuf *s = NULL;
+                       const void *uptr = (const void *)req->newptr;
+                       size_t ulen = req->newlen;
+
+                       s = sbuf_new(NULL, NULL, 16, 0);
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT_EQ(-1, sbuf_bcopyin(s, uptr, ulen));
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TESTING("sbuf_copyin")
+       {
+               SBUF_SHOULD("succeed in the simple case")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(req->newlen + 1, sbuf_copyin(s, (const void *)req->newptr, req->newlen));
+                       SBUF_ASSERT_EQ(req->newlen, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("use the sbuf capacity if len is zero")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(req->newlen + 1, sbuf_copyin(s, (const void *)req->newptr, 0));
+                       SBUF_ASSERT_EQ(req->newlen, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail if we can't extend the sbuf to accommodate")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_FIXEDLEN);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "0123456789abcde"));
+                       SBUF_ASSERT_EQ(-1, sbuf_copyin(s, (const void *)req->newptr, req->newlen));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("auto-extend the buffer if necessary")
+               {
+                       struct sbuf *s = NULL;
+                       int len_before;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(0, sbuf_cpy(s, "0123456789abcde"));
+                       len_before = s->s_len;
+                       SBUF_ASSERT_NE(-1, sbuf_copyin(s, (const void *)req->newptr, req->newlen));
+                       SBUF_ASSERT_GT(len_before, s->s_len);
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail if the sbuf is overflowed")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_SETFLAG(s, SBUF_OVERFLOWED);
+                       SBUF_ASSERT_EQ(-1, sbuf_copyin(s, (const void *)req->newptr, req->newlen));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail gracefully for an invalid address")
+               {
+                       struct sbuf *s = NULL;
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(-1, sbuf_copyin(s, (void *)0xdeadUL, req->newlen));
+
+                       sbuf_delete(s);
+               }
+
+               SBUF_SHOULD("fail gracefully for a kernel address")
+               {
+                       struct sbuf *s = NULL;
+                       const char *ptr = "abcd";
+
+                       s = sbuf_new(NULL, NULL, 16, SBUF_AUTOEXTEND);
+                       SBUF_ASSERT_EQ(-1, sbuf_copyin(s, ptr, strlen(ptr)));
+
+                       sbuf_delete(s);
+               }
+       }
+
+       SBUF_TEST_END;
 }
+
+SYSCTL_PROC(_kern, OID_AUTO, sbuf_test, CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_KERN | CTLFLAG_MASKED, 0, 0, sysctl_sbuf_tests, "A", "sbuf tests");
+
+#endif /* DEBUG || DEVELOPMENT */