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