1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2023 Oxide Computer Company 14 */ 15 16 #ifdef _KERNEL 17 #include <sys/types.h> 18 #include <sys/cmn_err.h> 19 #include <sys/ddi.h> 20 #include <sys/sunddi.h> 21 #include <sys/stdbool.h> 22 #include <sys/varargs.h> 23 #else 24 #include <stdio.h> 25 #include <stdbool.h> 26 #include <stdlib.h> 27 #include <stdarg.h> 28 #include <strings.h> 29 #endif 30 #include <sys/debug.h> 31 #include <sys/sysmacros.h> 32 33 #include <sys/ilstr.h> 34 35 static bool ilstr_have_space(ilstr_t *, size_t); 36 37 void 38 ilstr_init(ilstr_t *ils, int kmflag) 39 { 40 #ifdef _KERNEL 41 /* 42 * The kernel version of ilstr is available in "unix", and could thus 43 * be used relatively early in boot. We want a crisp failure in the 44 * case that somebody accidentally uses ilstr_init() prior to kmem 45 * being brought online. If ilstr is required before kmem is ready, 46 * use ilstr_init_prealloc() instead. 47 */ 48 if (!kmem_ready) { 49 panic("ilstr_init() cannot be used before kmem is ready"); 50 } 51 #endif 52 53 bzero(ils, sizeof (*ils)); 54 ils->ils_kmflag = kmflag; 55 } 56 57 void 58 ilstr_init_prealloc(ilstr_t *ils, char *buf, size_t buflen) 59 { 60 bzero(ils, sizeof (*ils)); 61 ils->ils_data = buf; 62 ils->ils_datalen = buflen; 63 ils->ils_data[0] = '\0'; 64 ils->ils_flag |= ILSTR_FLAG_PREALLOC; 65 } 66 67 void 68 ilstr_reset(ilstr_t *ils) 69 { 70 if (ils->ils_strlen > 0) { 71 /* 72 * Truncate the string but do not free the buffer so that we 73 * can use it again without further allocation. 74 */ 75 ils->ils_data[0] = '\0'; 76 ils->ils_strlen = 0; 77 } 78 ils->ils_errno = ILSTR_ERROR_OK; 79 } 80 81 void 82 ilstr_fini(ilstr_t *ils) 83 { 84 /* 85 * Take care not to disturb the string buffer for a preallocated 86 * string. The caller needs to be able to use the assembled string 87 * after the buffer is released. 88 */ 89 if (!(ils->ils_flag & ILSTR_FLAG_PREALLOC)) { 90 if (ils->ils_data != NULL) { 91 #ifdef _KERNEL 92 kmem_free(ils->ils_data, ils->ils_datalen); 93 #else 94 free(ils->ils_data); 95 #endif 96 } 97 } 98 99 bzero(ils, sizeof (*ils)); 100 } 101 102 void 103 ilstr_append_str(ilstr_t *ils, const char *s) 104 { 105 size_t len; 106 107 if (ils->ils_errno != ILSTR_ERROR_OK) { 108 return; 109 } 110 111 if ((len = strlen(s)) < 1) { 112 return; 113 } 114 115 if (!ilstr_have_space(ils, len)) { 116 return; 117 } 118 119 /* 120 * Copy the string, including the terminating byte: 121 */ 122 bcopy(s, ils->ils_data + ils->ils_strlen, len + 1); 123 ils->ils_strlen += len; 124 } 125 126 /* 127 * Confirm that there are needbytes free bytes for string characters left in 128 * the buffer. If there are not, try to grow the buffer unless this string is 129 * backed by preallocated memory. Note that, like the return from strlen(), 130 * needbytes does not include the extra byte required for null termination. 131 */ 132 static bool 133 ilstr_have_space(ilstr_t *ils, size_t needbytes) 134 { 135 /* 136 * Make a guess at a useful allocation chunk size. We want small 137 * strings to remain small, but very large strings should not incur the 138 * penalty of constant small allocations. 139 */ 140 size_t chunksz = 64; 141 if (ils->ils_datalen > 3 * chunksz) { 142 chunksz = P2ROUNDUP(ils->ils_datalen / 3, 64); 143 } 144 145 /* 146 * Check to ensure that the new string length does not overflow, 147 * leaving room for the termination byte: 148 */ 149 if (needbytes >= SIZE_MAX - ils->ils_strlen - 1) { 150 ils->ils_errno = ILSTR_ERROR_OVERFLOW; 151 return (false); 152 } 153 size_t new_strlen = ils->ils_strlen + needbytes; 154 155 if (new_strlen + 1 > ils->ils_datalen) { 156 size_t new_datalen = ils->ils_datalen; 157 char *new_data; 158 159 if (ils->ils_flag & ILSTR_FLAG_PREALLOC) { 160 /* 161 * We cannot grow a preallocated string. 162 */ 163 ils->ils_errno = ILSTR_ERROR_NOMEM; 164 return (false); 165 } 166 167 /* 168 * Grow the string buffer to make room for the new string. 169 */ 170 while (new_datalen < new_strlen + 1) { 171 if (chunksz >= SIZE_MAX - new_datalen) { 172 ils->ils_errno = ILSTR_ERROR_OVERFLOW; 173 return (false); 174 } 175 new_datalen += chunksz; 176 } 177 178 #ifdef _KERNEL 179 new_data = kmem_alloc(new_datalen, ils->ils_kmflag); 180 #else 181 new_data = malloc(new_datalen); 182 #endif 183 if (new_data == NULL) { 184 ils->ils_errno = ILSTR_ERROR_NOMEM; 185 return (false); 186 } 187 188 if (ils->ils_data != NULL) { 189 bcopy(ils->ils_data, new_data, ils->ils_strlen + 1); 190 #ifdef _KERNEL 191 kmem_free(ils->ils_data, ils->ils_datalen); 192 #else 193 free(ils->ils_data); 194 #endif 195 } 196 197 ils->ils_data = new_data; 198 ils->ils_datalen = new_datalen; 199 } 200 201 return (true); 202 } 203 204 void 205 ilstr_aprintf(ilstr_t *ils, const char *fmt, ...) 206 { 207 va_list ap; 208 209 if (ils->ils_errno != ILSTR_ERROR_OK) { 210 return; 211 } 212 213 va_start(ap, fmt); 214 ilstr_vaprintf(ils, fmt, ap); 215 va_end(ap); 216 } 217 218 void 219 ilstr_vaprintf(ilstr_t *ils, const char *fmt, va_list ap) 220 { 221 if (ils->ils_errno != ILSTR_ERROR_OK) { 222 return; 223 } 224 225 /* 226 * First, determine the length of the string we need to construct: 227 */ 228 va_list tap; 229 va_copy(tap, ap); 230 #ifdef _KERNEL 231 size_t len; 232 #else 233 int len; 234 #endif 235 236 len = vsnprintf(NULL, 0, fmt, tap); 237 #ifndef _KERNEL 238 if (len < 0) { 239 ils->ils_errno = ILSTR_ERROR_PRINTF; 240 return; 241 } 242 #endif 243 244 /* 245 * Grow the buffer to hold the string: 246 */ 247 if (!ilstr_have_space(ils, len)) { 248 return; 249 } 250 251 /* 252 * Now, render the string into the buffer space we have made available: 253 */ 254 len = vsnprintf(ils->ils_data + ils->ils_strlen, len + 1, fmt, ap); 255 #ifndef _KERNEL 256 if (len < 0) { 257 ils->ils_errno = ILSTR_ERROR_PRINTF; 258 return; 259 } 260 #endif 261 ils->ils_strlen += len; 262 } 263 264 void 265 ilstr_append_char(ilstr_t *ils, char c) 266 { 267 char buf[2]; 268 269 if (ils->ils_errno != ILSTR_ERROR_OK) { 270 return; 271 } 272 273 buf[0] = c; 274 buf[1] = '\0'; 275 276 ilstr_append_str(ils, buf); 277 } 278 279 ilstr_errno_t 280 ilstr_errno(ilstr_t *ils) 281 { 282 return (ils->ils_errno); 283 } 284 285 const char * 286 ilstr_cstr(ilstr_t *ils) 287 { 288 if (ils->ils_data == NULL) { 289 VERIFY3U(ils->ils_datalen, ==, 0); 290 VERIFY3U(ils->ils_strlen, ==, 0); 291 292 /* 293 * This function should never return NULL. If no buffer has 294 * been allocated, return a pointer to a zero-length string. 295 */ 296 return (""); 297 } 298 299 return (ils->ils_data); 300 } 301 302 size_t 303 ilstr_len(ilstr_t *ils) 304 { 305 return (ils->ils_strlen); 306 } 307 308 const char * 309 ilstr_errstr(ilstr_t *ils) 310 { 311 switch (ils->ils_errno) { 312 case ILSTR_ERROR_OK: 313 return ("ok"); 314 case ILSTR_ERROR_NOMEM: 315 return ("could not allocate memory"); 316 case ILSTR_ERROR_OVERFLOW: 317 return ("tried to construct too large a string"); 318 case ILSTR_ERROR_PRINTF: 319 return ("invalid printf arguments"); 320 default: 321 return ("unknown error"); 322 } 323 } 324