/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2020 Oxide Computer Company */ #ifdef _KERNEL #include #include #else #include #include #include #include #include #endif #include /* * Rendering of the boot banner, used on the system and zone consoles. */ typedef enum ilstr_errno { ILSTR_ERROR_OK = 0, ILSTR_ERROR_NOMEM, ILSTR_ERROR_OVERFLOW, } ilstr_errno_t; typedef struct ilstr { char *ils_data; size_t ils_datalen; size_t ils_strlen; uint_t ils_errno; int ils_kmflag; } ilstr_t; static void ilstr_init(ilstr_t *ils, int kmflag) { bzero(ils, sizeof (*ils)); ils->ils_kmflag = kmflag; } static void ilstr_reset(ilstr_t *ils) { if (ils->ils_strlen > 0) { /* * Truncate the string but do not free the buffer so that we * can use it again without further allocation. */ ils->ils_data[0] = '\0'; ils->ils_strlen = 0; } ils->ils_errno = ILSTR_ERROR_OK; } static void ilstr_fini(ilstr_t *ils) { if (ils->ils_data != NULL) { #ifdef _KERNEL kmem_free(ils->ils_data, ils->ils_datalen); #else free(ils->ils_data); #endif } } static void ilstr_append_str(ilstr_t *ils, const char *s) { size_t len; size_t chunksz = 64; if (ils->ils_errno != ILSTR_ERROR_OK) { return; } if ((len = strlen(s)) < 1) { return; } /* * Check to ensure that the new string length does not overflow, * leaving room for the termination byte: */ if (len >= SIZE_MAX - ils->ils_strlen - 1) { ils->ils_errno = ILSTR_ERROR_OVERFLOW; return; } size_t new_strlen = ils->ils_strlen + len; if (new_strlen + 1 >= ils->ils_datalen) { size_t new_datalen = ils->ils_datalen; char *new_data; /* * Grow the string buffer to make room for the new string. */ while (new_datalen < new_strlen + 1) { if (chunksz >= SIZE_MAX - new_datalen) { ils->ils_errno = ILSTR_ERROR_OVERFLOW; return; } new_datalen += chunksz; } #ifdef _KERNEL new_data = kmem_alloc(new_datalen, ils->ils_kmflag); #else new_data = malloc(new_datalen); #endif if (new_data == NULL) { ils->ils_errno = ILSTR_ERROR_NOMEM; return; } if (ils->ils_data != NULL) { bcopy(ils->ils_data, new_data, ils->ils_strlen + 1); #ifdef _KERNEL kmem_free(ils->ils_data, ils->ils_datalen); #else free(ils->ils_data); #endif } ils->ils_data = new_data; ils->ils_datalen = new_datalen; } bcopy(s, ils->ils_data + ils->ils_strlen, len + 1); ils->ils_strlen = new_strlen; } #ifdef _KERNEL static void ilstr_append_uint(ilstr_t *ils, uint_t n) { char buf[64]; if (ils->ils_errno != ILSTR_ERROR_OK) { return; } VERIFY3U(snprintf(buf, sizeof (buf), "%u", n), <, sizeof (buf)); ilstr_append_str(ils, buf); } #endif static void ilstr_append_char(ilstr_t *ils, char c) { char buf[2]; if (ils->ils_errno != ILSTR_ERROR_OK) { return; } buf[0] = c; buf[1] = '\0'; ilstr_append_str(ils, buf); } static ilstr_errno_t ilstr_errno(ilstr_t *ils) { return (ils->ils_errno); } static const char * ilstr_cstr(ilstr_t *ils) { return (ils->ils_data); } static size_t ilstr_len(ilstr_t *ils) { return (ils->ils_strlen); } static const char * ilstr_errstr(ilstr_t *ils) { switch (ils->ils_errno) { case ILSTR_ERROR_OK: return ("ok"); case ILSTR_ERROR_NOMEM: return ("could not allocate memory"); case ILSTR_ERROR_OVERFLOW: return ("tried to construct too large a string"); default: return ("unknown error"); } } /* * Expand a boot banner template string. The following expansion tokens * are supported: * * ^^ a literal caret * ^s the base kernel name (utsname.sysname) * ^o the operating system name ("illumos") * ^v the operating system version (utsname.version) * ^r the operating system release (utsname.release) * ^w the native address width in bits (e.g., "32" or "64") */ static void bootbanner_expand_template(const char *input, ilstr_t *output) { size_t pos = 0; enum { ST_REST, ST_CARET, } state = ST_REST; #ifndef _KERNEL struct utsname utsname; bzero(&utsname, sizeof (utsname)); (void) uname(&utsname); #endif for (;;) { char c = input[pos]; if (c == '\0') { /* * Even if the template came to an end mid way through * a caret expansion, it seems best to just print what * we have and drive on. The onus will be on the * distributor to ensure their templates are * well-formed at build time. */ break; } switch (state) { case ST_REST: if (c == '^') { state = ST_CARET; } else { ilstr_append_char(output, c); } pos++; continue; case ST_CARET: if (c == '^') { ilstr_append_char(output, c); } else if (c == 's') { ilstr_append_str(output, utsname.sysname); } else if (c == 'o') { ilstr_append_str(output, "illumos"); } else if (c == 'r') { ilstr_append_str(output, utsname.release); } else if (c == 'v') { ilstr_append_str(output, utsname.version); } else if (c == 'w') { #ifdef _KERNEL ilstr_append_uint(output, NBBY * (uint_t)sizeof (void *)); #else char *bits; char buf[32]; int r; if ((r = sysinfo(SI_ADDRESS_WIDTH, buf, sizeof (buf))) > 0 && r < (int)sizeof (buf)) { bits = buf; } else { bits = "64"; } ilstr_append_str(output, bits); #endif } else { /* * Try to make it obvious what went wrong: */ ilstr_append_str(output, "!^"); ilstr_append_char(output, c); ilstr_append_str(output, " UNKNOWN!"); } state = ST_REST; pos++; continue; } } } static void bootbanner_print_one(ilstr_t *s, void (*printfunc)(const char *, uint_t), const char *template, uint_t *nump) { ilstr_reset(s); bootbanner_expand_template(template, s); if (ilstr_errno(s) == ILSTR_ERROR_OK) { if (ilstr_len(s) > 0) { printfunc(ilstr_cstr(s), *nump); *nump += 1; } } else { char ebuf[128]; snprintf(ebuf, sizeof (ebuf), "boot banner error: %s", ilstr_errstr(s)); printfunc(ebuf, *nump); *nump += 1; } } /* * This routine should be called during early system boot to render the boot * banner on the system console, and during zone boot to do so on the zone * console. * * The "printfunc" argument is a callback function. When passed a string, the * function must print it in a fashion appropriate for the context. The * callback will only be called while within the call to bootbanner_print(). * The "kmflag" value accepts the same values as kmem_alloc(9F) in the kernel, * and is ignored otherwise. */ void bootbanner_print(void (*printfunc)(const char *, uint_t), int kmflag) { ilstr_t s; uint_t num = 0; ilstr_init(&s, kmflag); bootbanner_print_one(&s, printfunc, BOOTBANNER1, &num); bootbanner_print_one(&s, printfunc, BOOTBANNER2, &num); bootbanner_print_one(&s, printfunc, BOOTBANNER3, &num); bootbanner_print_one(&s, printfunc, BOOTBANNER4, &num); bootbanner_print_one(&s, printfunc, BOOTBANNER5, &num); ilstr_fini(&s); }