├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── src ├── buffer.c ├── buffer.h └── dbg.h └── tests ├── libbuffer_tests.c ├── minunit.h └── runtests.sh /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | src/*.o 3 | tests/*.log 4 | tests/*_tests 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Patrick Reagan 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 14 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 15 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 16 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 17 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 18 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 19 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-g -O2 -Wall -Wall -Isrc -rdynamic $(OPTFLAGS) 2 | LIBS=-ldl $(OPTLIBS) 3 | PREFIX?=/usr/local 4 | 5 | SOURCES=$(wildcard src/**/*.c src/*.c) 6 | OBJECTS=$(patsubst %.c,%.o,$(SOURCES)) 7 | 8 | TEST_SRC=$(wildcard tests/*_tests.c) 9 | TESTS=$(patsubst %.c,%,$(TEST_SRC)) 10 | 11 | TARGET=build/libbuffer.a 12 | 13 | # The target build 14 | all: $(TARGET) tests 15 | 16 | dev:CFLAGS+=-DNDEBUG -Wextra 17 | dev: all 18 | 19 | $(TARGET): CFLAGS += -fPIC 20 | $(TARGET): build $(OBJECTS) 21 | ar rcs $@ $(OBJECTS) 22 | ranlib $@ 23 | 24 | build: 25 | @mkdir -p build 26 | @mkdir -p bin 27 | 28 | # The Unit Tests 29 | .PHONY: tests 30 | tests: CFLAGS += $(TARGET) 31 | tests: $(TESTS) 32 | sh ./tests/runtests.sh 33 | 34 | valgrind: 35 | VALGRIND="valgrind --leak-check=full --log-file=/tmp/valgrind-%p.log" $(MAKE) 36 | 37 | # The cleaner 38 | clean: 39 | rm -rf build $(OBJECTS) $(TESTS) 40 | rm -rf tests/tests.log 41 | find . -name "*.gc*" -exec rm {} \; 42 | rm -rf 'find . -name "*.dSYM" -print' 43 | 44 | # The install 45 | install: all 46 | install -d $(DESTDIR)/$(PREFIX)/lib/ 47 | install $(TARGET) $(DESTDIR)/$(PREFIX)/lib/ 48 | 49 | # The checker 50 | BADFUNCS='[^_.>a-zA-Z0-9](str(n?cpy|n?cat|xfrm|n?dup|str|pbrk|tok|_)|stpn?cpy|a?sn?printf|byte_)' 51 | check: 52 | @echo Files with potentially dangerous functions 53 | @egrep $(BADFUNCS) $(SOURCES) || true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libbuffer 2 | 3 | This is a simple C library that implements a growable buffer. 4 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | #include "dbg.h" 3 | 4 | Buffer * 5 | buffer_alloc(int initial_size) 6 | { 7 | Buffer *buf = malloc(sizeof(Buffer)); 8 | char *tmp = calloc(1, initial_size * sizeof(char)); 9 | 10 | jump_to_error_if(buf == NULL || tmp == NULL); 11 | 12 | buf->contents = tmp; 13 | buf->bytes_used = 0; 14 | buf->total_size = initial_size; 15 | 16 | return buf; 17 | error: 18 | if (buf) { buffer_free(buf); } 19 | if (tmp) { free(tmp); } 20 | 21 | return NULL; 22 | } 23 | 24 | int 25 | buffer_strlen(Buffer *buf) 26 | { 27 | return buf->bytes_used; 28 | } 29 | 30 | void 31 | buffer_free(Buffer *buf) 32 | { 33 | free(buf->contents); 34 | free(buf); 35 | } 36 | 37 | int 38 | buffer_has_space(Buffer *buf, int desired_length) 39 | { 40 | int bytes_remaining = buf->total_size - buf->bytes_used; 41 | 42 | debug("Requesting %d bytes, %d available", desired_length, bytes_remaining); 43 | 44 | return desired_length <= bytes_remaining; 45 | } 46 | 47 | int 48 | buffer_grow(Buffer *buf, int minimum_size) 49 | { 50 | int factor = buf->total_size; 51 | 52 | if (factor < minimum_size) { 53 | factor = minimum_size; 54 | } 55 | 56 | int new_size = factor * 2; 57 | 58 | debug("Growing buffer from %d to %d bytes", buf->total_size, new_size); 59 | 60 | char *tmp = realloc(buf->contents, new_size * sizeof(char)); 61 | jump_to_error_if(tmp == NULL); 62 | 63 | buf->contents = tmp; 64 | buf->total_size = new_size; 65 | 66 | return 0; 67 | error: 68 | return -1; 69 | } 70 | 71 | void 72 | buffer_cat(Buffer *buf, char *append, int length) 73 | { 74 | int i = 0; 75 | int bytes_copied = 0; 76 | int buffer_position = 0; 77 | 78 | for (i = 0; i < length; i++) { 79 | if (append[i] == '\0') { break; } 80 | 81 | buffer_position = buf->bytes_used + i; 82 | *(buf->contents + buffer_position) = append[i]; 83 | 84 | bytes_copied++; 85 | } 86 | 87 | buf->bytes_used += bytes_copied; 88 | *(buf->contents + buf->bytes_used) = '\0'; 89 | } 90 | 91 | int 92 | buffer_append(Buffer *buf, char *append, int length) 93 | { 94 | int status = 0; 95 | int desired_length = length + 1; // Space for NUL byte 96 | 97 | if (!buffer_has_space(buf, desired_length)) { 98 | status = buffer_grow(buf, desired_length); 99 | jump_to_error_unless(status == 0) 100 | } 101 | 102 | buffer_cat(buf, append, length); 103 | 104 | return 0; 105 | error: 106 | return -1; 107 | } 108 | 109 | int 110 | buffer_appendf(Buffer *buf, const char *format, ...) 111 | { 112 | char *tmp = NULL; 113 | int bytes_written, status; 114 | 115 | va_list argp; 116 | va_start(argp, format); 117 | 118 | bytes_written = vasprintf(&tmp, format, argp); 119 | jump_to_error_if(bytes_written < 0); 120 | 121 | va_end(argp); 122 | 123 | status = buffer_append(buf, tmp, bytes_written); 124 | jump_to_error_unless(status == 0); 125 | 126 | free(tmp); 127 | 128 | return 0; 129 | error: 130 | if (tmp != NULL) { free(tmp); } 131 | return -1; 132 | } 133 | 134 | int 135 | buffer_nappendf(Buffer *buf, size_t length, const char *format, ...) 136 | { 137 | int status = 0, 138 | printf_length = length + 1; 139 | 140 | char *tmp = calloc(1, printf_length * sizeof(char)); 141 | 142 | jump_to_error_if(tmp == NULL); 143 | 144 | va_list argp; 145 | va_start(argp, format); 146 | 147 | status = vsnprintf(tmp, printf_length, format, argp); 148 | jump_to_error_if(status < 0); 149 | 150 | va_end(argp); 151 | 152 | status = buffer_append(buf, tmp, length); 153 | jump_to_error_unless(status == 0); 154 | 155 | free(tmp); 156 | 157 | return 0; 158 | error: 159 | if (tmp != NULL) { free(tmp); } 160 | return -1; 161 | 162 | } 163 | 164 | char * 165 | buffer_to_s(Buffer *buf) 166 | { 167 | char *result = calloc(1, buf->bytes_used + 1); 168 | strncpy(result, buf->contents, buffer_strlen(buf)); 169 | 170 | return result; 171 | } -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_H 2 | #define BUFFER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct Buffer { 9 | char *contents; 10 | int bytes_used; 11 | int total_size; 12 | }; 13 | 14 | typedef struct Buffer Buffer; 15 | 16 | Buffer * buffer_alloc(int initial_size); 17 | int buffer_strlen(Buffer *buf); 18 | void buffer_free(Buffer *buf); 19 | int buffer_append(Buffer *buf, char *append, int length); 20 | int buffer_appendf(Buffer *buf, const char *format, ...); 21 | int buffer_nappendf(Buffer *buf, size_t length, const char *format, ...); 22 | char *buffer_to_s(Buffer *buf); 23 | 24 | #endif -------------------------------------------------------------------------------- /src/dbg.h: -------------------------------------------------------------------------------- 1 | #ifndef __dbg_h 2 | #define __dbg_h 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef NDEBUG 9 | #define debug(M, ...) 10 | #else 11 | #define debug(M, ...) fprintf(stderr, "DEBUG %s (in function '%s'):%d: " M "\n", __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) 12 | #endif 13 | 14 | #define clean_errno() (errno == 0 ? "None" : strerror(errno)) 15 | 16 | #define log_err(M, ...) fprintf(stderr, "[ERROR] (%s (in function '%s'):%d: errno: %s) " M "\n", __FILE__, __FUNCTION__, __LINE__, clean_errno(), ##__VA_ARGS__) 17 | 18 | #define jump_to_error_if(A) if (A) { goto error; } 19 | #define jump_to_error_unless(A) if (!(A)) { goto error; } 20 | 21 | #endif -------------------------------------------------------------------------------- /tests/libbuffer_tests.c: -------------------------------------------------------------------------------- 1 | #include "minunit.h" 2 | #include 3 | 4 | int 5 | string_compare_full(char *str, char *comparison, int bytes) 6 | { 7 | int same = 1, 8 | i = 0; 9 | 10 | for (i = 0; i < bytes; i++) { 11 | same = same && (str[i] == comparison[i]); 12 | } 13 | 14 | return same; 15 | } 16 | 17 | int 18 | buffer_compare_full(Buffer *buf, char *comparison, int bytes) 19 | { 20 | return string_compare_full(buf->contents, comparison, bytes); 21 | } 22 | 23 | int 24 | buffer_compare(Buffer *buf, char *comparison) 25 | { 26 | return buffer_compare_full(buf, comparison, strlen(comparison) + 1); 27 | } 28 | 29 | char * 30 | test_grow() 31 | { 32 | Buffer *buf = buffer_alloc(1); 33 | 34 | mu_assert(buf->total_size == 1, "Allocated size should be 1."); 35 | 36 | buffer_append(buf, "A", 1); 37 | mu_assert(buf->total_size == 4, "Allocated size should grow to 4."); 38 | 39 | buffer_append(buf, "BC", 2); 40 | mu_assert(buf->total_size == 4, "Does not grow when there is room."); 41 | 42 | buffer_append(buf, "D", 1); 43 | mu_assert(buf->total_size == 8, "Grows when out of space."); 44 | 45 | buffer_append(buf, "EFGH", 4); 46 | mu_assert(buf->total_size == 16, "Allocated size should grow to 16."); 47 | 48 | buffer_append(buf, "IJKLMNOPQRSTUVWXYZ", 18); 49 | mu_assert(buf->total_size == 38, "Allocated size should grow to 38."); 50 | 51 | buffer_free(buf); 52 | 53 | return NULL; 54 | } 55 | 56 | char * 57 | test_buffer_length() 58 | { 59 | Buffer *buf = buffer_alloc(8); 60 | 61 | mu_assert(buffer_strlen(buf) == 0, "Empty buffer length should be 0."); 62 | 63 | buffer_append(buf, "ABC", 3); 64 | mu_assert(buffer_strlen(buf) == 3, "Buffer length with 'ABC' should be 3."); 65 | 66 | buffer_free(buf); 67 | 68 | return NULL; 69 | } 70 | 71 | char * 72 | test_buffer_append() 73 | { 74 | int cmp_size = 4; 75 | char cmp[cmp_size]; 76 | 77 | Buffer *buf = buffer_alloc(16); 78 | 79 | memset(&cmp, 0, cmp_size); 80 | 81 | cmp[0] = '\0'; 82 | mu_assert(buffer_compare_full(buf, cmp, 1) == 1, "Buffer should be empty."); 83 | 84 | memset(&cmp, 0, cmp_size); 85 | buffer_append(buf, "A", 1); 86 | 87 | cmp[0] = 'A'; 88 | cmp[1] = '\0'; 89 | mu_assert(buffer_compare_full(buf, cmp, 2) == 1, "Appending C-style string should work."); 90 | 91 | memset(&cmp, 0, cmp_size); 92 | cmp[0] = 'A'; 93 | cmp[1] = 'A'; 94 | cmp[2] = '\0'; 95 | 96 | buffer_append(buf, "A", 3); 97 | mu_assert(buffer_compare_full(buf, cmp, 3) == 1, "Appending C-style string stops at NUL."); 98 | 99 | memset(&cmp, 0, cmp_size); 100 | cmp[0] = 'A'; 101 | cmp[1] = 'A'; 102 | cmp[2] = 'B'; 103 | cmp[3] = '\0'; 104 | 105 | char no_nul[1] = {'B'}; 106 | 107 | buffer_append(buf, no_nul, 1); 108 | mu_assert(buffer_compare_full(buf, cmp, 4) == 1, "Appending string without NUL byte stops at specified length."); 109 | 110 | buffer_free(buf); 111 | 112 | return NULL; 113 | } 114 | 115 | char * 116 | test_buffer_appendf() 117 | { 118 | Buffer *buf = buffer_alloc(64); 119 | 120 | buffer_appendf(buf, "%s, #%d", "Hello", 2); 121 | mu_assert(buffer_compare(buf, "Hello, #2") == 1, "Printing interprets string and decimal characters"); 122 | 123 | buffer_appendf(buf, ". %s, #%d?", "Yes", 1); 124 | mu_assert(buffer_compare(buf, "Hello, #2. Yes, #1?") == 1, "Printing appends to existing buffer"); 125 | 126 | buffer_free(buf); 127 | 128 | return NULL; 129 | } 130 | 131 | char * 132 | test_buffer_nappendf() 133 | { 134 | Buffer *buf = buffer_alloc(64); 135 | 136 | char test1[3] = {'H', 'e', '\0'}; 137 | 138 | buffer_nappendf(buf, 2, "%s", "Hello"); 139 | mu_assert(buffer_compare_full(buf, test1, 3) == 1, "Invalid characters after append."); 140 | 141 | buffer_free(buf); 142 | 143 | return NULL; 144 | } 145 | 146 | char * 147 | test_buffer_to_s() 148 | { 149 | Buffer *buf = buffer_alloc(64); 150 | char *result = NULL; 151 | 152 | buffer_append(buf, "Hi there.", 9); 153 | char compare[10] = {'H', 'i', ' ', 't', 'h', 'e', 'r', 'e', '.', '\0'}; 154 | 155 | result = buffer_to_s(buf); 156 | mu_assert(string_compare_full(result, compare, 10) == 1, "Invalid string value."); 157 | 158 | free(result); 159 | buffer_free(buf); 160 | 161 | return NULL; 162 | } 163 | 164 | char * 165 | all_tests() 166 | { 167 | mu_suite_start(); 168 | 169 | mu_run_test(test_grow); 170 | mu_run_test(test_buffer_length); 171 | mu_run_test(test_buffer_append); 172 | mu_run_test(test_buffer_appendf); 173 | mu_run_test(test_buffer_nappendf); 174 | mu_run_test(test_buffer_to_s); 175 | 176 | return NULL; 177 | } 178 | 179 | RUN_TESTS(all_tests); -------------------------------------------------------------------------------- /tests/minunit.h: -------------------------------------------------------------------------------- 1 | #undef NDEBUG 2 | #ifndef _minunit_h 3 | #define _minunit_h 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define mu_suite_start() char *message = NULL 10 | 11 | #define mu_assert(test, message) if (!(test)) { log_err(message); return message; } 12 | 13 | #define mu_run_test(test) debug("\n-----%s", " ", #test); \ 14 | message = test(); tests_run++; if (message) return message; 15 | 16 | #define RUN_TESTS(name) int main (int argc, char *argv[]) {\ 17 | argc = 1;\ 18 | debug("----- RUNNING: %s", argv[0]);\ 19 | printf("-----\nRUNNING: %s\n", argv[0]);\ 20 | char *result = name();\ 21 | if (result != 0) {\ 22 | printf("FAILED: %s\n", result);\ 23 | }\ 24 | else {\ 25 | printf("ALL TESTS PASSED\n");\ 26 | }\ 27 | printf("Tests run: %d\n", tests_run);\ 28 | exit(result != 0);\ 29 | } 30 | 31 | int tests_run; 32 | 33 | #endif -------------------------------------------------------------------------------- /tests/runtests.sh: -------------------------------------------------------------------------------- 1 | echo "Running unit tests:" 2 | 3 | for i in tests/*_tests 4 | do 5 | if test -f $i 6 | then 7 | if $VALGRIND ./$i 2>> tests/tests.log 8 | then 9 | echo $i PASS 10 | else 11 | echo "ERROR in test $i: here's tests/tests.log" 12 | echo "------" 13 | tail tests/tests.log 14 | exit 1 15 | fi 16 | fi 17 | done 18 | 19 | echo "" --------------------------------------------------------------------------------