summaryrefslogtreecommitdiff
path: root/usr/src/common/bootbanner/bootbanner.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/common/bootbanner/bootbanner.c')
-rw-r--r--usr/src/common/bootbanner/bootbanner.c354
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);
+}