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
ilstr_init(ilstr_t * ils,int kmflag)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
ilstr_init_prealloc(ilstr_t * ils,char * buf,size_t buflen)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
ilstr_reset(ilstr_t * ils)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
ilstr_fini(ilstr_t * ils)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
ilstr_append_str(ilstr_t * ils,const char * s)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
ilstr_have_space(ilstr_t * ils,size_t needbytes)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
ilstr_aprintf(ilstr_t * ils,const char * fmt,...)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
ilstr_vaprintf(ilstr_t * ils,const char * fmt,va_list ap)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
ilstr_append_char(ilstr_t * ils,char c)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
ilstr_errno(ilstr_t * ils)296 ilstr_errno(ilstr_t *ils)
297 {
298 return (ils->ils_errno);
299 }
300
301 const char *
ilstr_cstr(ilstr_t * ils)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
ilstr_len(ilstr_t * ils)319 ilstr_len(ilstr_t *ils)
320 {
321 return (ils->ils_strlen);
322 }
323
324 const char *
ilstr_errstr(ilstr_t * ils)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