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