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/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 #include <sys/ilstr.h>
28 
29 /*
30  * Rendering of the boot banner, used on the system and zone consoles.
31  */
32 
33 /*
34  * Expand a boot banner template string.  The following expansion tokens
35  * are supported:
36  *
37  *	^^	a literal caret
38  *	^s	the base kernel name (utsname.sysname)
39  *	^o	the operating system name ("illumos")
40  *	^v	the operating system version (utsname.version)
41  *	^r	the operating system release (utsname.release)
42  *	^w	the native address width in bits (e.g., "32" or "64")
43  */
44 static void
bootbanner_expand_template(const char * input,ilstr_t * output)45 bootbanner_expand_template(const char *input, ilstr_t *output)
46 {
47 	size_t pos = 0;
48 	enum {
49 		ST_REST,
50 		ST_CARET,
51 	} state = ST_REST;
52 
53 #ifndef _KERNEL
54 	struct utsname utsname;
55 	bzero(&utsname, sizeof (utsname));
56 	(void) uname(&utsname);
57 #endif
58 
59 	for (;;) {
60 		char c = input[pos];
61 
62 		if (c == '\0') {
63 			/*
64 			 * Even if the template came to an end mid way through
65 			 * a caret expansion, it seems best to just print what
66 			 * we have and drive on.  The onus will be on the
67 			 * distributor to ensure their templates are
68 			 * well-formed at build time.
69 			 */
70 			break;
71 		}
72 
73 		switch (state) {
74 		case ST_REST:
75 			if (c == '^') {
76 				state = ST_CARET;
77 			} else {
78 				ilstr_append_char(output, c);
79 			}
80 			pos++;
81 			continue;
82 
83 		case ST_CARET:
84 			if (c == '^') {
85 				ilstr_append_char(output, c);
86 			} else if (c == 's') {
87 				ilstr_append_str(output, utsname.sysname);
88 			} else if (c == 'o') {
89 				ilstr_append_str(output, "illumos");
90 			} else if (c == 'r') {
91 				ilstr_append_str(output, utsname.release);
92 			} else if (c == 'v') {
93 				ilstr_append_str(output, utsname.version);
94 			} else if (c == 'w') {
95 #ifdef _KERNEL
96 				ilstr_aprintf(output, "%u",
97 				    NBBY * (uint_t)sizeof (void *));
98 #else
99 				char *bits;
100 				char buf[32];
101 				int r;
102 
103 				if ((r = sysinfo(SI_ADDRESS_WIDTH, buf,
104 				    sizeof (buf))) > 0 &&
105 				    r < (int)sizeof (buf)) {
106 					bits = buf;
107 				} else {
108 					bits = "64";
109 				}
110 
111 				ilstr_append_str(output, bits);
112 #endif
113 			} else {
114 				/*
115 				 * Try to make it obvious what went wrong:
116 				 */
117 				ilstr_append_str(output, "!^");
118 				ilstr_append_char(output, c);
119 				ilstr_append_str(output, " UNKNOWN!");
120 			}
121 			state = ST_REST;
122 			pos++;
123 			continue;
124 		}
125 	}
126 }
127 
128 static void
bootbanner_print_one(ilstr_t * s,void (* printfunc)(const char *,uint_t),const char * template,uint_t * nump)129 bootbanner_print_one(ilstr_t *s, void (*printfunc)(const char *, uint_t),
130     const char *template, uint_t *nump)
131 {
132 	ilstr_reset(s);
133 
134 	bootbanner_expand_template(template, s);
135 
136 	if (ilstr_errno(s) == ILSTR_ERROR_OK) {
137 		if (ilstr_len(s) > 0) {
138 			printfunc(ilstr_cstr(s), *nump);
139 			*nump += 1;
140 		}
141 	} else {
142 		char ebuf[128];
143 
144 		snprintf(ebuf, sizeof (ebuf), "boot banner error: %s",
145 		    ilstr_errstr(s));
146 
147 		printfunc(ebuf, *nump);
148 		*nump += 1;
149 	}
150 }
151 
152 /*
153  * This routine should be called during early system boot to render the boot
154  * banner on the system console, and during zone boot to do so on the zone
155  * console.
156  *
157  * The "printfunc" argument is a callback function.  When passed a string, the
158  * function must print it in a fashion appropriate for the context.  The
159  * callback will only be called while within the call to bootbanner_print().
160  */
161 void
bootbanner_print(void (* printfunc)(const char *,uint_t))162 bootbanner_print(void (*printfunc)(const char *, uint_t))
163 {
164 	/*
165 	 * To avoid the need to allocate in early boot, we'll use a static
166 	 * buffer four times the size of a tasteful terminal width.  Note that
167 	 * ilstr will allow us to produce diagnostic output if this buffer
168 	 * would have been overrun.
169 	 */
170 	char sbuf[80 * 4];
171 	ilstr_t s;
172 	uint_t num = 0;
173 
174 	ilstr_init_prealloc(&s, sbuf, sizeof (sbuf));
175 
176 	bootbanner_print_one(&s, printfunc, BOOTBANNER1, &num);
177 	bootbanner_print_one(&s, printfunc, BOOTBANNER2, &num);
178 	bootbanner_print_one(&s, printfunc, BOOTBANNER3, &num);
179 	bootbanner_print_one(&s, printfunc, BOOTBANNER4, &num);
180 	bootbanner_print_one(&s, printfunc, BOOTBANNER5, &num);
181 
182 	ilstr_fini(&s);
183 }
184