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