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
ilstr_init(ilstr_t * ils,int kmflag)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
ilstr_init_prealloc(ilstr_t * ils,char * buf,size_t buflen)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
ilstr_reset(ilstr_t * ils)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
ilstr_fini(ilstr_t * ils)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
ilstr_prepend_str(ilstr_t * ils,const char * s)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
ilstr_append_str(ilstr_t * ils,const char * s)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
ilstr_have_space(ilstr_t * ils,size_t needbytes)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
ilstr_aprintf(ilstr_t * ils,const char * fmt,...)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
ilstr_vaprintf(ilstr_t * ils,const char * fmt,va_list ap)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
ilstr_append_char(ilstr_t * ils,char c)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
ilstr_prepend_char(ilstr_t * ils,char c)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
ilstr_errno(ilstr_t * ils)360 ilstr_errno(ilstr_t *ils)
361 {
362 return (ils->ils_errno);
363 }
364
365 const char *
ilstr_cstr(ilstr_t * ils)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
ilstr_len(ilstr_t * ils)383 ilstr_len(ilstr_t *ils)
384 {
385 return (ils->ils_strlen);
386 }
387
388 bool
ilstr_is_empty(ilstr_t * ils)389 ilstr_is_empty(ilstr_t *ils)
390 {
391 return (ilstr_len(ils) == 0);
392 }
393
394 const char *
ilstr_errstr(ilstr_t * ils)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