diff options
Diffstat (limited to 'usr/src/common/bootbanner/bootbanner.c')
-rw-r--r-- | usr/src/common/bootbanner/bootbanner.c | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/usr/src/common/bootbanner/bootbanner.c b/usr/src/common/bootbanner/bootbanner.c new file mode 100644 index 0000000000..90eccd8e0b --- /dev/null +++ b/usr/src/common/bootbanner/bootbanner.c @@ -0,0 +1,354 @@ +/* + * 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 <sys/types.h> +#include <sys/sunddi.h> +#else +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <sys/utsname.h> +#include <sys/systeminfo.h> +#endif +#include <sys/debug.h> + +/* + * 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); +} |