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 2025 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 #include <sys/systm.h> 24 #else 25 #include <stdio.h> 26 #include <stdbool.h> 27 #include <stdlib.h> 28 #include <stdarg.h> 29 #include <strings.h> 30 #endif 31 #include <sys/debug.h> 32 #include <sys/sysmacros.h> 33 34 #include <sys/ilstr.h> 35 36 #ifdef _KERNEL 37 /* 38 * Like a strategically placed banana peel, bcopy(9F) is documented as being 39 * _unsafe_ for overlapping copies: the opposite of the well-known and 40 * standardised behaviour of bcopy(3C)! 41 */ 42 #define bmove(src, dst, len) ovbcopy(src, dst, len) 43 #else 44 #define bmove(src, dst, len) bcopy(src, dst, len) 45 #endif 46 47 static bool ilstr_have_space(ilstr_t *, size_t); 48 49 void 50 ilstr_init(ilstr_t *ils, int kmflag) 51 { 52 #ifdef _KERNEL 53 /* 54 * The kernel version of ilstr is available in "unix", and could thus 55 * be used relatively early in boot. We want a crisp failure in the 56 * case that somebody accidentally uses ilstr_init() prior to kmem 57 * being brought online. If ilstr is required before kmem is ready, 58 * use ilstr_init_prealloc() instead. 59 */ 60 if (!kmem_ready) { 61 panic("ilstr_init() cannot be used before kmem is ready"); 62 } 63 #endif 64 65 bzero(ils, sizeof (*ils)); 66 ils->ils_kmflag = kmflag; 67 } 68 69 /* 70 * Wrap an ilstr_t object around an existing buffer. This is useful if you are 71 * using stack storage, or you have a pre-allocated error buffer for best 72 * effort error message construction in the face of memory exhaustion. 73 * 74 * This routine also allows ilstr to be used to assemble a string in a buffer 75 * provided by a caller. In this case it is safe to return without calling 76 * ilstr_fini(), as ilstr does not allocate any resources that need to be 77 * cleaned up. 78 */ 79 void 80 ilstr_init_prealloc(ilstr_t *ils, char *buf, size_t buflen) 81 { 82 bzero(ils, sizeof (*ils)); 83 ils->ils_data = buf; 84 ils->ils_datalen = buflen; 85 ils->ils_data[0] = '\0'; 86 ils->ils_flag |= ILSTR_FLAG_PREALLOC; 87 } 88 89 void 90 ilstr_reset(ilstr_t *ils) 91 { 92 if (ils->ils_strlen > 0) { 93 /* 94 * Truncate the string but do not free the buffer so that we 95 * can use it again without further allocation. 96 */ 97 ils->ils_data[0] = '\0'; 98 ils->ils_strlen = 0; 99 } 100 ils->ils_errno = ILSTR_ERROR_OK; 101 } 102 103 /* 104 * This function frees any resources allocated by ilstr_init(), and must be 105 * called prior to the ilstr_t object going out of scope. If 106 * ilstr_init_prealloc() is used, calling this function is optional but 107 * harmless. 108 */ 109 void 110 ilstr_fini(ilstr_t *ils) 111 { 112 /* 113 * Take care not to disturb the string buffer for a preallocated 114 * string. The caller needs to be able to use the assembled string 115 * after the buffer is released. 116 */ 117 if (!(ils->ils_flag & ILSTR_FLAG_PREALLOC)) { 118 if (ils->ils_data != NULL) { 119 #ifdef _KERNEL 120 kmem_free(ils->ils_data, ils->ils_datalen); 121 #else 122 free(ils->ils_data); 123 #endif 124 } 125 } 126 127 bzero(ils, sizeof (*ils)); 128 } 129 130 void 131 ilstr_prepend_str(ilstr_t *ils, const char *s) 132 { 133 size_t len; 134 135 if (ils->ils_errno != ILSTR_ERROR_OK) { 136 return; 137 } 138 139 if ((len = strlen(s)) < 1) { 140 return; 141 } 142 143 if (!ilstr_have_space(ils, len)) { 144 return; 145 } 146 147 /* 148 * Move the existing string, including the terminating byte, to make 149 * room for the incoming prefix: 150 */ 151 bmove(ils->ils_data, ils->ils_data + len, ils->ils_strlen + 1); 152 153 /* 154 * Copy the incoming prefix, without copying the terminating byte over 155 * the top of the existing string: 156 */ 157 bcopy(s, ils->ils_data, len); 158 ils->ils_strlen += len; 159 } 160 161 void 162 ilstr_append_str(ilstr_t *ils, const char *s) 163 { 164 size_t len; 165 166 if (ils->ils_errno != ILSTR_ERROR_OK) { 167 return; 168 } 169 170 if ((len = strlen(s)) < 1) { 171 return; 172 } 173 174 if (!ilstr_have_space(ils, len)) { 175 return; 176 } 177 178 /* 179 * Copy the string, including the terminating byte: 180 */ 181 bcopy(s, ils->ils_data + ils->ils_strlen, len + 1); 182 ils->ils_strlen += len; 183 } 184 185 /* 186 * Confirm that there are needbytes free bytes for string characters left in 187 * the buffer. If there are not, try to grow the buffer unless this string is 188 * backed by preallocated memory. Note that, like the return from strlen(), 189 * needbytes does not include the extra byte required for null termination. 190 */ 191 static bool 192 ilstr_have_space(ilstr_t *ils, size_t needbytes) 193 { 194 /* 195 * Make a guess at a useful allocation chunk size. We want small 196 * strings to remain small, but very large strings should not incur the 197 * penalty of constant small allocations. 198 */ 199 size_t chunksz = 64; 200 if (ils->ils_datalen > 3 * chunksz) { 201 chunksz = P2ROUNDUP(ils->ils_datalen / 3, 64); 202 } 203 204 /* 205 * Check to ensure that the new string length does not overflow, 206 * leaving room for the termination byte: 207 */ 208 if (needbytes >= SIZE_MAX - ils->ils_strlen - 1) { 209 ils->ils_errno = ILSTR_ERROR_OVERFLOW; 210 return (false); 211 } 212 size_t new_strlen = ils->ils_strlen + needbytes; 213 214 if (new_strlen + 1 > ils->ils_datalen) { 215 size_t new_datalen = ils->ils_datalen; 216 char *new_data; 217 218 if (ils->ils_flag & ILSTR_FLAG_PREALLOC) { 219 /* 220 * We cannot grow a preallocated string. 221 */ 222 ils->ils_errno = ILSTR_ERROR_NOMEM; 223 return (false); 224 } 225 226 /* 227 * Grow the string buffer to make room for the new string. 228 */ 229 while (new_datalen < new_strlen + 1) { 230 if (chunksz >= SIZE_MAX - new_datalen) { 231 ils->ils_errno = ILSTR_ERROR_OVERFLOW; 232 return (false); 233 } 234 new_datalen += chunksz; 235 } 236 237 #ifdef _KERNEL 238 new_data = kmem_alloc(new_datalen, ils->ils_kmflag); 239 #else 240 new_data = malloc(new_datalen); 241 #endif 242 if (new_data == NULL) { 243 ils->ils_errno = ILSTR_ERROR_NOMEM; 244 return (false); 245 } 246 247 if (ils->ils_data != NULL) { 248 bcopy(ils->ils_data, new_data, ils->ils_strlen + 1); 249 #ifdef _KERNEL 250 kmem_free(ils->ils_data, ils->ils_datalen); 251 #else 252 free(ils->ils_data); 253 #endif 254 } else { 255 /* 256 * Ensure that the first chunk we allocate begins as a 257 * valid zero-length string: 258 */ 259 new_data[0] = '\0'; 260 } 261 262 ils->ils_data = new_data; 263 ils->ils_datalen = new_datalen; 264 } 265 266 return (true); 267 } 268 269 void 270 ilstr_aprintf(ilstr_t *ils, const char *fmt, ...) 271 { 272 va_list ap; 273 274 if (ils->ils_errno != ILSTR_ERROR_OK) { 275 return; 276 } 277 278 va_start(ap, fmt); 279 ilstr_vaprintf(ils, fmt, ap); 280 va_end(ap); 281 } 282 283 void 284 ilstr_vaprintf(ilstr_t *ils, const char *fmt, va_list ap) 285 { 286 if (ils->ils_errno != ILSTR_ERROR_OK) { 287 return; 288 } 289 290 /* 291 * First, determine the length of the string we need to construct: 292 */ 293 va_list tap; 294 va_copy(tap, ap); 295 #ifdef _KERNEL 296 size_t len; 297 #else 298 int len; 299 #endif 300 301 len = vsnprintf(NULL, 0, fmt, tap); 302 #ifndef _KERNEL 303 if (len < 0) { 304 ils->ils_errno = ILSTR_ERROR_PRINTF; 305 return; 306 } 307 #endif 308 309 /* 310 * Grow the buffer to hold the string: 311 */ 312 if (!ilstr_have_space(ils, len)) { 313 return; 314 } 315 316 /* 317 * Now, render the string into the buffer space we have made available: 318 */ 319 len = vsnprintf(ils->ils_data + ils->ils_strlen, len + 1, fmt, ap); 320 #ifndef _KERNEL 321 if (len < 0) { 322 ils->ils_errno = ILSTR_ERROR_PRINTF; 323 return; 324 } 325 #endif 326 ils->ils_strlen += len; 327 } 328 329 void 330 ilstr_append_char(ilstr_t *ils, char c) 331 { 332 char buf[2]; 333 334 if (ils->ils_errno != ILSTR_ERROR_OK) { 335 return; 336 } 337 338 buf[0] = c; 339 buf[1] = '\0'; 340 341 ilstr_append_str(ils, buf); 342 } 343 344 void 345 ilstr_prepend_char(ilstr_t *ils, char c) 346 { 347 char buf[2]; 348 349 if (ils->ils_errno != ILSTR_ERROR_OK) { 350 return; 351 } 352 353 buf[0] = c; 354 buf[1] = '\0'; 355 356 ilstr_prepend_str(ils, buf); 357 } 358 359 ilstr_errno_t 360 ilstr_errno(ilstr_t *ils) 361 { 362 return (ils->ils_errno); 363 } 364 365 const char * 366 ilstr_cstr(ilstr_t *ils) 367 { 368 if (ils->ils_data == NULL) { 369 VERIFY3U(ils->ils_datalen, ==, 0); 370 VERIFY3U(ils->ils_strlen, ==, 0); 371 372 /* 373 * This function should never return NULL. If no buffer has 374 * been allocated, return a pointer to a zero-length string. 375 */ 376 return (""); 377 } 378 379 return (ils->ils_data); 380 } 381 382 size_t 383 ilstr_len(ilstr_t *ils) 384 { 385 return (ils->ils_strlen); 386 } 387 388 bool 389 ilstr_is_empty(ilstr_t *ils) 390 { 391 return (ilstr_len(ils) == 0); 392 } 393 394 const char * 395 ilstr_errstr(ilstr_t *ils) 396 { 397 switch (ils->ils_errno) { 398 case ILSTR_ERROR_OK: 399 return ("ok"); 400 case ILSTR_ERROR_NOMEM: 401 return ("could not allocate memory"); 402 case ILSTR_ERROR_OVERFLOW: 403 return ("tried to construct too large a string"); 404 case ILSTR_ERROR_PRINTF: 405 return ("invalid printf arguments"); 406 default: 407 return ("unknown error"); 408 } 409 } 410