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 2024 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 /* 58 * Wrap an ilstr_t object around an existing buffer. This is useful if you are 59 * using stack storage, or you have a pre-allocated error buffer for best 60 * effort error message construction in the face of memory exhaustion. 61 * 62 * This routine also allows ilstr to be used to assemble a string in a buffer 63 * provided by a caller. In this case it is safe to return without calling 64 * ilstr_fini(), as ilstr does not allocate any resources that need to be 65 * cleaned up. 66 */ 67 void 68 ilstr_init_prealloc(ilstr_t *ils, char *buf, size_t buflen) 69 { 70 bzero(ils, sizeof (*ils)); 71 ils->ils_data = buf; 72 ils->ils_datalen = buflen; 73 ils->ils_data[0] = '\0'; 74 ils->ils_flag |= ILSTR_FLAG_PREALLOC; 75 } 76 77 void 78 ilstr_reset(ilstr_t *ils) 79 { 80 if (ils->ils_strlen > 0) { 81 /* 82 * Truncate the string but do not free the buffer so that we 83 * can use it again without further allocation. 84 */ 85 ils->ils_data[0] = '\0'; 86 ils->ils_strlen = 0; 87 } 88 ils->ils_errno = ILSTR_ERROR_OK; 89 } 90 91 /* 92 * This function frees any resources allocated by ilstr_init(), and must be 93 * called prior to the ilstr_t object going out of scope. If 94 * ilstr_init_prealloc() is used, calling this function is optional but 95 * harmless. 96 */ 97 void 98 ilstr_fini(ilstr_t *ils) 99 { 100 /* 101 * Take care not to disturb the string buffer for a preallocated 102 * string. The caller needs to be able to use the assembled string 103 * after the buffer is released. 104 */ 105 if (!(ils->ils_flag & ILSTR_FLAG_PREALLOC)) { 106 if (ils->ils_data != NULL) { 107 #ifdef _KERNEL 108 kmem_free(ils->ils_data, ils->ils_datalen); 109 #else 110 free(ils->ils_data); 111 #endif 112 } 113 } 114 115 bzero(ils, sizeof (*ils)); 116 } 117 118 void 119 ilstr_append_str(ilstr_t *ils, const char *s) 120 { 121 size_t len; 122 123 if (ils->ils_errno != ILSTR_ERROR_OK) { 124 return; 125 } 126 127 if ((len = strlen(s)) < 1) { 128 return; 129 } 130 131 if (!ilstr_have_space(ils, len)) { 132 return; 133 } 134 135 /* 136 * Copy the string, including the terminating byte: 137 */ 138 bcopy(s, ils->ils_data + ils->ils_strlen, len + 1); 139 ils->ils_strlen += len; 140 } 141 142 /* 143 * Confirm that there are needbytes free bytes for string characters left in 144 * the buffer. If there are not, try to grow the buffer unless this string is 145 * backed by preallocated memory. Note that, like the return from strlen(), 146 * needbytes does not include the extra byte required for null termination. 147 */ 148 static bool 149 ilstr_have_space(ilstr_t *ils, size_t needbytes) 150 { 151 /* 152 * Make a guess at a useful allocation chunk size. We want small 153 * strings to remain small, but very large strings should not incur the 154 * penalty of constant small allocations. 155 */ 156 size_t chunksz = 64; 157 if (ils->ils_datalen > 3 * chunksz) { 158 chunksz = P2ROUNDUP(ils->ils_datalen / 3, 64); 159 } 160 161 /* 162 * Check to ensure that the new string length does not overflow, 163 * leaving room for the termination byte: 164 */ 165 if (needbytes >= SIZE_MAX - ils->ils_strlen - 1) { 166 ils->ils_errno = ILSTR_ERROR_OVERFLOW; 167 return (false); 168 } 169 size_t new_strlen = ils->ils_strlen + needbytes; 170 171 if (new_strlen + 1 > ils->ils_datalen) { 172 size_t new_datalen = ils->ils_datalen; 173 char *new_data; 174 175 if (ils->ils_flag & ILSTR_FLAG_PREALLOC) { 176 /* 177 * We cannot grow a preallocated string. 178 */ 179 ils->ils_errno = ILSTR_ERROR_NOMEM; 180 return (false); 181 } 182 183 /* 184 * Grow the string buffer to make room for the new string. 185 */ 186 while (new_datalen < new_strlen + 1) { 187 if (chunksz >= SIZE_MAX - new_datalen) { 188 ils->ils_errno = ILSTR_ERROR_OVERFLOW; 189 return (false); 190 } 191 new_datalen += chunksz; 192 } 193 194 #ifdef _KERNEL 195 new_data = kmem_alloc(new_datalen, ils->ils_kmflag); 196 #else 197 new_data = malloc(new_datalen); 198 #endif 199 if (new_data == NULL) { 200 ils->ils_errno = ILSTR_ERROR_NOMEM; 201 return (false); 202 } 203 204 if (ils->ils_data != NULL) { 205 bcopy(ils->ils_data, new_data, ils->ils_strlen + 1); 206 #ifdef _KERNEL 207 kmem_free(ils->ils_data, ils->ils_datalen); 208 #else 209 free(ils->ils_data); 210 #endif 211 } 212 213 ils->ils_data = new_data; 214 ils->ils_datalen = new_datalen; 215 } 216 217 return (true); 218 } 219 220 void 221 ilstr_aprintf(ilstr_t *ils, const char *fmt, ...) 222 { 223 va_list ap; 224 225 if (ils->ils_errno != ILSTR_ERROR_OK) { 226 return; 227 } 228 229 va_start(ap, fmt); 230 ilstr_vaprintf(ils, fmt, ap); 231 va_end(ap); 232 } 233 234 void 235 ilstr_vaprintf(ilstr_t *ils, const char *fmt, va_list ap) 236 { 237 if (ils->ils_errno != ILSTR_ERROR_OK) { 238 return; 239 } 240 241 /* 242 * First, determine the length of the string we need to construct: 243 */ 244 va_list tap; 245 va_copy(tap, ap); 246 #ifdef _KERNEL 247 size_t len; 248 #else 249 int len; 250 #endif 251 252 len = vsnprintf(NULL, 0, fmt, tap); 253 #ifndef _KERNEL 254 if (len < 0) { 255 ils->ils_errno = ILSTR_ERROR_PRINTF; 256 return; 257 } 258 #endif 259 260 /* 261 * Grow the buffer to hold the string: 262 */ 263 if (!ilstr_have_space(ils, len)) { 264 return; 265 } 266 267 /* 268 * Now, render the string into the buffer space we have made available: 269 */ 270 len = vsnprintf(ils->ils_data + ils->ils_strlen, len + 1, fmt, ap); 271 #ifndef _KERNEL 272 if (len < 0) { 273 ils->ils_errno = ILSTR_ERROR_PRINTF; 274 return; 275 } 276 #endif 277 ils->ils_strlen += len; 278 } 279 280 void 281 ilstr_append_char(ilstr_t *ils, char c) 282 { 283 char buf[2]; 284 285 if (ils->ils_errno != ILSTR_ERROR_OK) { 286 return; 287 } 288 289 buf[0] = c; 290 buf[1] = '\0'; 291 292 ilstr_append_str(ils, buf); 293 } 294 295 ilstr_errno_t 296 ilstr_errno(ilstr_t *ils) 297 { 298 return (ils->ils_errno); 299 } 300 301 const char * 302 ilstr_cstr(ilstr_t *ils) 303 { 304 if (ils->ils_data == NULL) { 305 VERIFY3U(ils->ils_datalen, ==, 0); 306 VERIFY3U(ils->ils_strlen, ==, 0); 307 308 /* 309 * This function should never return NULL. If no buffer has 310 * been allocated, return a pointer to a zero-length string. 311 */ 312 return (""); 313 } 314 315 return (ils->ils_data); 316 } 317 318 size_t 319 ilstr_len(ilstr_t *ils) 320 { 321 return (ils->ils_strlen); 322 } 323 324 const char * 325 ilstr_errstr(ilstr_t *ils) 326 { 327 switch (ils->ils_errno) { 328 case ILSTR_ERROR_OK: 329 return ("ok"); 330 case ILSTR_ERROR_NOMEM: 331 return ("could not allocate memory"); 332 case ILSTR_ERROR_OVERFLOW: 333 return ("tried to construct too large a string"); 334 case ILSTR_ERROR_PRINTF: 335 return ("invalid printf arguments"); 336 default: 337 return ("unknown error"); 338 } 339 } 340