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 2020 Oxide Computer Company
14  */
15 
16 #ifdef _KERNEL
17 #include <sys/types.h>
18 #include <sys/sunddi.h>
19 #else
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <strings.h>
23 #include <sys/utsname.h>
24 #include <sys/systeminfo.h>
25 #endif
26 #include <sys/debug.h>
27 
28 /*
29  * Rendering of the boot banner, used on the system and zone consoles.
30  */
31 
32 typedef enum ilstr_errno {
33 	ILSTR_ERROR_OK = 0,
34 	ILSTR_ERROR_NOMEM,
35 	ILSTR_ERROR_OVERFLOW,
36 } ilstr_errno_t;
37 
38 typedef struct ilstr {
39 	char *ils_data;
40 	size_t ils_datalen;
41 	size_t ils_strlen;
42 	uint_t ils_errno;
43 	int ils_kmflag;
44 } ilstr_t;
45 
46 static void
47 ilstr_init(ilstr_t *ils, int kmflag)
48 {
49 	bzero(ils, sizeof (*ils));
50 	ils->ils_kmflag = kmflag;
51 }
52 
53 static void
54 ilstr_reset(ilstr_t *ils)
55 {
56 	if (ils->ils_strlen > 0) {
57 		/*
58 		 * Truncate the string but do not free the buffer so that we
59 		 * can use it again without further allocation.
60 		 */
61 		ils->ils_data[0] = '\0';
62 		ils->ils_strlen = 0;
63 	}
64 	ils->ils_errno = ILSTR_ERROR_OK;
65 }
66 
67 static void
68 ilstr_fini(ilstr_t *ils)
69 {
70 	if (ils->ils_data != NULL) {
71 #ifdef _KERNEL
72 		kmem_free(ils->ils_data, ils->ils_datalen);
73 #else
74 		free(ils->ils_data);
75 #endif
76 	}
77 }
78 
79 static void
80 ilstr_append_str(ilstr_t *ils, const char *s)
81 {
82 	size_t len;
83 	size_t chunksz = 64;
84 
85 	if (ils->ils_errno != ILSTR_ERROR_OK) {
86 		return;
87 	}
88 
89 	if ((len = strlen(s)) < 1) {
90 		return;
91 	}
92 
93 	/*
94 	 * Check to ensure that the new string length does not overflow,
95 	 * leaving room for the termination byte:
96 	 */
97 	if (len >= SIZE_MAX - ils->ils_strlen - 1) {
98 		ils->ils_errno = ILSTR_ERROR_OVERFLOW;
99 		return;
100 	}
101 	size_t new_strlen = ils->ils_strlen + len;
102 
103 	if (new_strlen + 1 >= ils->ils_datalen) {
104 		size_t new_datalen = ils->ils_datalen;
105 		char *new_data;
106 
107 		/*
108 		 * Grow the string buffer to make room for the new string.
109 		 */
110 		while (new_datalen < new_strlen + 1) {
111 			if (chunksz >= SIZE_MAX - new_datalen) {
112 				ils->ils_errno = ILSTR_ERROR_OVERFLOW;
113 				return;
114 			}
115 			new_datalen += chunksz;
116 		}
117 
118 #ifdef _KERNEL
119 		new_data = kmem_alloc(new_datalen, ils->ils_kmflag);
120 #else
121 		new_data = malloc(new_datalen);
122 #endif
123 		if (new_data == NULL) {
124 			ils->ils_errno = ILSTR_ERROR_NOMEM;
125 			return;
126 		}
127 
128 		if (ils->ils_data != NULL) {
129 			bcopy(ils->ils_data, new_data, ils->ils_strlen + 1);
130 #ifdef _KERNEL
131 			kmem_free(ils->ils_data, ils->ils_datalen);
132 #else
133 			free(ils->ils_data);
134 #endif
135 		}
136 
137 		ils->ils_data = new_data;
138 		ils->ils_datalen = new_datalen;
139 	}
140 
141 	bcopy(s, ils->ils_data + ils->ils_strlen, len + 1);
142 	ils->ils_strlen = new_strlen;
143 }
144 
145 #ifdef _KERNEL
146 static void
147 ilstr_append_uint(ilstr_t *ils, uint_t n)
148 {
149 	char buf[64];
150 
151 	if (ils->ils_errno != ILSTR_ERROR_OK) {
152 		return;
153 	}
154 
155 	VERIFY3U(snprintf(buf, sizeof (buf), "%u", n), <, sizeof (buf));
156 
157 	ilstr_append_str(ils, buf);
158 }
159 #endif
160 
161 static void
162 ilstr_append_char(ilstr_t *ils, char c)
163 {
164 	char buf[2];
165 
166 	if (ils->ils_errno != ILSTR_ERROR_OK) {
167 		return;
168 	}
169 
170 	buf[0] = c;
171 	buf[1] = '\0';
172 
173 	ilstr_append_str(ils, buf);
174 }
175 
176 static ilstr_errno_t
177 ilstr_errno(ilstr_t *ils)
178 {
179 	return (ils->ils_errno);
180 }
181 
182 static const char *
183 ilstr_cstr(ilstr_t *ils)
184 {
185 	return (ils->ils_data);
186 }
187 
188 static size_t
189 ilstr_len(ilstr_t *ils)
190 {
191 	return (ils->ils_strlen);
192 }
193 
194 static const char *
195 ilstr_errstr(ilstr_t *ils)
196 {
197 	switch (ils->ils_errno) {
198 	case ILSTR_ERROR_OK:
199 		return ("ok");
200 	case ILSTR_ERROR_NOMEM:
201 		return ("could not allocate memory");
202 	case ILSTR_ERROR_OVERFLOW:
203 		return ("tried to construct too large a string");
204 	default:
205 		return ("unknown error");
206 	}
207 }
208 
209 /*
210  * Expand a boot banner template string.  The following expansion tokens
211  * are supported:
212  *
213  *	^^	a literal caret
214  *	^s	the base kernel name (utsname.sysname)
215  *	^o	the operating system name ("illumos")
216  *	^v	the operating system version (utsname.version)
217  *	^r	the operating system release (utsname.release)
218  *	^w	the native address width in bits (e.g., "32" or "64")
219  */
220 static void
221 bootbanner_expand_template(const char *input, ilstr_t *output)
222 {
223 	size_t pos = 0;
224 	enum {
225 		ST_REST,
226 		ST_CARET,
227 	} state = ST_REST;
228 
229 #ifndef _KERNEL
230 	struct utsname utsname;
231 	bzero(&utsname, sizeof (utsname));
232 	(void) uname(&utsname);
233 #endif
234 
235 	for (;;) {
236 		char c = input[pos];
237 
238 		if (c == '\0') {
239 			/*
240 			 * Even if the template came to an end mid way through
241 			 * a caret expansion, it seems best to just print what
242 			 * we have and drive on.  The onus will be on the
243 			 * distributor to ensure their templates are
244 			 * well-formed at build time.
245 			 */
246 			break;
247 		}
248 
249 		switch (state) {
250 		case ST_REST:
251 			if (c == '^') {
252 				state = ST_CARET;
253 			} else {
254 				ilstr_append_char(output, c);
255 			}
256 			pos++;
257 			continue;
258 
259 		case ST_CARET:
260 			if (c == '^') {
261 				ilstr_append_char(output, c);
262 			} else if (c == 's') {
263 				ilstr_append_str(output, utsname.sysname);
264 			} else if (c == 'o') {
265 				ilstr_append_str(output, "illumos");
266 			} else if (c == 'r') {
267 				ilstr_append_str(output, utsname.release);
268 			} else if (c == 'v') {
269 				ilstr_append_str(output, utsname.version);
270 			} else if (c == 'w') {
271 #ifdef _KERNEL
272 				ilstr_append_uint(output,
273 				    NBBY * (uint_t)sizeof (void *));
274 #else
275 				char *bits;
276 				char buf[32];
277 				int r;
278 
279 				if ((r = sysinfo(SI_ADDRESS_WIDTH, buf,
280 				    sizeof (buf))) > 0 &&
281 				    r < (int)sizeof (buf)) {
282 					bits = buf;
283 				} else {
284 					bits = "64";
285 				}
286 
287 				ilstr_append_str(output, bits);
288 #endif
289 			} else {
290 				/*
291 				 * Try to make it obvious what went wrong:
292 				 */
293 				ilstr_append_str(output, "!^");
294 				ilstr_append_char(output, c);
295 				ilstr_append_str(output, " UNKNOWN!");
296 			}
297 			state = ST_REST;
298 			pos++;
299 			continue;
300 		}
301 	}
302 }
303 
304 static void
305 bootbanner_print_one(ilstr_t *s, void (*printfunc)(const char *, uint_t),
306     const char *template, uint_t *nump)
307 {
308 	ilstr_reset(s);
309 
310 	bootbanner_expand_template(template, s);
311 
312 	if (ilstr_errno(s) == ILSTR_ERROR_OK) {
313 		if (ilstr_len(s) > 0) {
314 			printfunc(ilstr_cstr(s), *nump);
315 			*nump += 1;
316 		}
317 	} else {
318 		char ebuf[128];
319 
320 		snprintf(ebuf, sizeof (ebuf), "boot banner error: %s",
321 		    ilstr_errstr(s));
322 
323 		printfunc(ebuf, *nump);
324 		*nump += 1;
325 	}
326 }
327 
328 /*
329  * This routine should be called during early system boot to render the boot
330  * banner on the system console, and during zone boot to do so on the zone
331  * console.
332  *
333  * The "printfunc" argument is a callback function.  When passed a string, the
334  * function must print it in a fashion appropriate for the context.  The
335  * callback will only be called while within the call to bootbanner_print().
336  * The "kmflag" value accepts the same values as kmem_alloc(9F) in the kernel,
337  * and is ignored otherwise.
338  */
339 void
340 bootbanner_print(void (*printfunc)(const char *, uint_t), int kmflag)
341 {
342 	ilstr_t s;
343 	uint_t num = 0;
344 
345 	ilstr_init(&s, kmflag);
346 
347 	bootbanner_print_one(&s, printfunc, BOOTBANNER1, &num);
348 	bootbanner_print_one(&s, printfunc, BOOTBANNER2, &num);
349 	bootbanner_print_one(&s, printfunc, BOOTBANNER3, &num);
350 	bootbanner_print_one(&s, printfunc, BOOTBANNER4, &num);
351 	bootbanner_print_one(&s, printfunc, BOOTBANNER5, &num);
352 
353 	ilstr_fini(&s);
354 }
355