/* * 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 #include #include #include #include #include #include #if DEBUG || DEVELOPMENT #include #include #include #endif /* DEBUG || DEVELOPMENT */ #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_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 /*! * @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. */ void sbuf_delete(struct sbuf *s) { if (SBUF_ISDYNAMIC(s) && s->s_buf) { kfree_data(s->s_buf, s->s_size); s->s_buf = NULL; } if (SBUF_ISDYNSTRUCT(s)) { kfree_type(struct sbuf, s); } } /*! * @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_extendsize(size_t *size) { size_t target_size = *size; size_t new_size; if (target_size > INT_MAX) { return -1; } if (target_size < SBUF_MAXEXTENDSIZE) { new_size = SBUF_MINEXTENDSIZE; while (new_size < target_size) { new_size *= 2; } } else { /* round up to nearest page: */ new_size = (target_size + PAGE_SIZE - 1) & ~PAGE_MASK; } if (new_size > INT_MAX) { return -1; } *size = new_size; return 0; } /*! * @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) { size_t length = (size_t)length_; if (length > INT_MAX || flags & ~SBUF_USRFLAGMSK) { return NULL; } if (s == NULL) { s = (struct sbuf *)kalloc_type(struct sbuf, Z_WAITOK | Z_ZERO); if (NULL == s) { return NULL; } s->s_flags = flags; SBUF_SETFLAG(s, SBUF_DYNSTRUCT); } else { bzero(s, sizeof(*s)); s->s_flags = flags; } if (buf) { s->s_size = (int)length; s->s_buf = buf; return s; } if (SBUF_CANEXTEND(s) && (-1 == sbuf_extendsize(&length))) { goto fail; } /* * 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 = kalloc_data(length, Z_WAITOK | Z_ZERO); if (NULL == s->s_buf) { goto fail; } s->s_size = (int)length; SBUF_SETFLAG(s, SBUF_DYNAMIC); return s; fail: sbuf_delete(s); return NULL; } /*! * @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. */ int sbuf_setpos(struct sbuf *s, int pos) { if (pos < 0 || pos > s->s_len) { return -1; } s->s_len = pos; return 0; } /*! * @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) { SBUF_CLEARFLAG(s, SBUF_FINISHED); SBUF_CLEARFLAG(s, SBUF_OVERFLOWED); sbuf_setpos(s, 0); } /*! * @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. */ static int OS_WARN_RESULT sbuf_extend(struct sbuf *s, size_t addlen) { char *new_buf; size_t new_size; if (addlen == 0) { return 0; } if (!SBUF_CANEXTEND(s)) { return -1; } if (os_add_overflow((size_t)s->s_size, addlen, &new_size)) { return -1; } if (-1 == sbuf_extendsize(&new_size)) { return -1; } new_buf = (char *) kalloc_data(new_size, Z_WAITOK); if (NULL == new_buf) { return -1; } bcopy(s->s_buf, new_buf, (size_t)s->s_size); if (SBUF_ISDYNAMIC(s)) { kfree_data(s->s_buf, (size_t)s->s_size); } else { SBUF_SETFLAG(s, SBUF_DYNAMIC); } s->s_buf = new_buf; s->s_size = (int)new_size; return 0; } /*! * @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. */ static size_t sbuf_capacity(const struct sbuf *s) { /* 1 byte reserved for \0: */ return (size_t)(s->s_size - s->s_len - 1); } /*! * @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); } /*! * @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_bcat(struct sbuf *s, const void *data, size_t len) { if (SBUF_HASOVERFLOWED(s)) { return -1; } 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; } /*! * @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_bcpy(struct sbuf *s, const void *data, size_t len) { sbuf_clear(s); return sbuf_bcat(s, data, len); } /*! * @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)); } /*! * @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) { sbuf_clear(s); return sbuf_cat(s, str); } /*! * @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) { 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); /* +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); if (result < 0) { return -1; } len = (size_t)result; if (len <= capacity) { s->s_len += (int)len; return 0; } } while (-1 != sbuf_ensure_capacity(s, len)); SBUF_SETFLAG(s, SBUF_OVERFLOWED); return -1; } /*! * @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, ...) { va_list ap; int result; va_start(ap, fmt); result = sbuf_vprintf(s, fmt, ap); va_end(ap); return result; } /*! * @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_) { char c = (char)c_; if (SBUF_HASOVERFLOWED(s)) { return -1; } 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; } static inline int isspace(char ch) { return ch == ' ' || ch == '\n' || ch == '\t'; } /*! * @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) { if (SBUF_HASOVERFLOWED(s)) { return -1; } while (s->s_len > 0 && isspace(s->s_buf[s->s_len - 1])) { --s->s_len; } return 0; } /*! * @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); } /*! * @function sbuf_finish * * @brief * Puts a trailing nul byte onto the sbuf data. * * @param s * The sbuf. */ void sbuf_finish(struct sbuf *s) { /* 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); } /*! * @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) { return s->s_buf; } /*! * @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) { if (SBUF_HASOVERFLOWED(s)) { return -1; } return s->s_len; } /*! * @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. */ int sbuf_done(struct sbuf *s) { return !!SBUF_ISFINISHED(s); } #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_MIN, 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_MIN, 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_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 */