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
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 void
ilstr_init_prealloc(ilstr_t * ils,char * buf,size_t buflen)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
ilstr_reset(ilstr_t * ils)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
ilstr_fini(ilstr_t * ils)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
ilstr_append_str(ilstr_t * ils,const char * s)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
ilstr_have_space(ilstr_t * ils,size_t needbytes)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
ilstr_aprintf(ilstr_t * ils,const char * fmt,...)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
ilstr_vaprintf(ilstr_t * ils,const char * fmt,va_list ap)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
ilstr_append_char(ilstr_t * ils,char c)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
ilstr_errno(ilstr_t * ils)280 ilstr_errno(ilstr_t *ils)
281 {
282 return (ils->ils_errno);
283 }
284
285 const char *
ilstr_cstr(ilstr_t * ils)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
ilstr_len(ilstr_t * ils)303 ilstr_len(ilstr_t *ils)
304 {
305 return (ils->ils_strlen);
306 }
307
308 const char *
ilstr_errstr(ilstr_t * ils)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