1 /* strbuf - String buffer routines 2 * 3 * Copyright (c) 2010-2012 Mark Pulford <[email protected]> 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining 6 * a copy of this software and associated documentation files (the 7 * "Software"), to deal in the Software without restriction, including 8 * without limitation the rights to use, copy, modify, merge, publish, 9 * distribute, sublicense, and/or sell copies of the Software, and to 10 * permit persons to whom the Software is furnished to do so, subject to 11 * the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be 14 * included in all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 */ 24 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <stdarg.h> 28 #include <string.h> 29 30 #include "strbuf.h" 31 32 static void die(const char *fmt, ...) 33 { 34 va_list arg; 35 36 va_start(arg, fmt); 37 vfprintf(stderr, fmt, arg); 38 va_end(arg); 39 fprintf(stderr, "\n"); 40 41 exit(-1); 42 } 43 44 void strbuf_init(strbuf_t *s, int len) 45 { 46 int size; 47 48 if (len <= 0) 49 size = STRBUF_DEFAULT_SIZE; 50 else 51 size = len + 1; /* \0 terminator */ 52 53 s->buf = NULL; 54 s->size = size; 55 s->length = 0; 56 s->increment = STRBUF_DEFAULT_INCREMENT; 57 s->dynamic = 0; 58 s->reallocs = 0; 59 s->debug = 0; 60 61 s->buf = malloc(size); 62 if (!s->buf) 63 die("Out of memory"); 64 65 strbuf_ensure_null(s); 66 } 67 68 strbuf_t *strbuf_new(int len) 69 { 70 strbuf_t *s; 71 72 s = malloc(sizeof(strbuf_t)); 73 if (!s) 74 die("Out of memory"); 75 76 strbuf_init(s, len); 77 78 /* Dynamic strbuf allocation / deallocation */ 79 s->dynamic = 1; 80 81 return s; 82 } 83 84 void strbuf_set_increment(strbuf_t *s, int increment) 85 { 86 /* Increment > 0: Linear buffer growth rate 87 * Increment < -1: Exponential buffer growth rate */ 88 if (increment == 0 || increment == -1) 89 die("BUG: Invalid string increment"); 90 91 s->increment = increment; 92 } 93 94 static inline void debug_stats(strbuf_t *s) 95 { 96 if (s->debug) { 97 fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n", 98 (long)s, s->reallocs, s->length, s->size); 99 } 100 } 101 102 /* If strbuf_t has not been dynamically allocated, strbuf_free() can 103 * be called any number of times strbuf_init() */ 104 void strbuf_free(strbuf_t *s) 105 { 106 debug_stats(s); 107 108 if (s->buf) { 109 free(s->buf); 110 s->buf = NULL; 111 } 112 if (s->dynamic) 113 free(s); 114 } 115 116 char *strbuf_free_to_string(strbuf_t *s, int *len) 117 { 118 char *buf; 119 120 debug_stats(s); 121 122 strbuf_ensure_null(s); 123 124 buf = s->buf; 125 if (len) 126 *len = s->length; 127 128 if (s->dynamic) 129 free(s); 130 131 return buf; 132 } 133 134 static int calculate_new_size(strbuf_t *s, int len) 135 { 136 int reqsize, newsize; 137 138 if (len <= 0) 139 die("BUG: Invalid strbuf length requested"); 140 141 /* Ensure there is room for optional NULL termination */ 142 reqsize = len + 1; 143 144 /* If the user has requested to shrink the buffer, do it exactly */ 145 if (s->size > reqsize) 146 return reqsize; 147 148 newsize = s->size; 149 if (s->increment < 0) { 150 /* Exponential sizing */ 151 while (newsize < reqsize) 152 newsize *= -s->increment; 153 } else { 154 /* Linear sizing */ 155 newsize = ((newsize + s->increment - 1) / s->increment) * s->increment; 156 } 157 158 return newsize; 159 } 160 161 162 /* Ensure strbuf can handle a string length bytes long (ignoring NULL 163 * optional termination). */ 164 void strbuf_resize(strbuf_t *s, int len) 165 { 166 int newsize; 167 168 newsize = calculate_new_size(s, len); 169 170 if (s->debug > 1) { 171 fprintf(stderr, "strbuf(%lx) resize: %d => %d\n", 172 (long)s, s->size, newsize); 173 } 174 175 s->size = newsize; 176 s->buf = realloc(s->buf, s->size); 177 if (!s->buf) 178 die("Out of memory"); 179 s->reallocs++; 180 } 181 182 void strbuf_append_string(strbuf_t *s, const char *str) 183 { 184 int space, i; 185 186 space = strbuf_empty_length(s); 187 188 for (i = 0; str[i]; i++) { 189 if (space < 1) { 190 strbuf_resize(s, s->length + 1); 191 space = strbuf_empty_length(s); 192 } 193 194 s->buf[s->length] = str[i]; 195 s->length++; 196 space--; 197 } 198 } 199 200 /* strbuf_append_fmt() should only be used when an upper bound 201 * is known for the output string. */ 202 void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...) 203 { 204 va_list arg; 205 int fmt_len; 206 207 strbuf_ensure_empty_length(s, len); 208 209 va_start(arg, fmt); 210 fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg); 211 va_end(arg); 212 213 if (fmt_len < 0) 214 die("BUG: Unable to convert number"); /* This should never happen.. */ 215 216 s->length += fmt_len; 217 } 218 219 /* strbuf_append_fmt_retry() can be used when the there is no known 220 * upper bound for the output string. */ 221 void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...) 222 { 223 va_list arg; 224 int fmt_len, try; 225 int empty_len; 226 227 /* If the first attempt to append fails, resize the buffer appropriately 228 * and try again */ 229 for (try = 0; ; try++) { 230 va_start(arg, fmt); 231 /* Append the new formatted string */ 232 /* fmt_len is the length of the string required, excluding the 233 * trailing NULL */ 234 empty_len = strbuf_empty_length(s); 235 /* Add 1 since there is also space to store the terminating NULL. */ 236 fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg); 237 va_end(arg); 238 239 if (fmt_len <= empty_len) 240 break; /* SUCCESS */ 241 if (try > 0) 242 die("BUG: length of formatted string changed"); 243 244 strbuf_resize(s, s->length + fmt_len); 245 } 246 247 s->length += fmt_len; 248 } 249 250 /* vi:ai et sw=4 ts=4: 251 */ 252