xref: /illumos-gate/usr/src/common/ilstr/ilstr.c (revision 15cacbd5271ba21ede8d4c34e75a5547374b3e71)
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