From: OMOJOLA JOSHUA Date: Mon, 19 Jun 2023 14:16:23 +0000 (+0100) Subject: Add tool to display emergency log message full-screen on boot failure. X-Git-Tag: v255-rc1~828 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fc7eb1325bd297634568528fb934698a68855121;p=thirdparty%2Fsystemd.git Add tool to display emergency log message full-screen on boot failure. --- diff --git a/man/rules/meson.build b/man/rules/meson.build index a450fedb090..f03cde876d7 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -902,6 +902,7 @@ manpages = [ ''], ['systemd-boot-random-seed.service', '8', [], 'ENABLE_BOOTLOADER'], ['systemd-boot', '7', ['sd-boot'], 'ENABLE_BOOTLOADER'], + ['systemd-bsod', '8', [], 'HAVE_QRENCODE'], ['systemd-cat', '1', [], ''], ['systemd-cgls', '1', [], ''], ['systemd-cgtop', '1', [], ''], diff --git a/man/systemd-bsod.xml b/man/systemd-bsod.xml new file mode 100644 index 00000000000..7a64f4f004a --- /dev/null +++ b/man/systemd-bsod.xml @@ -0,0 +1,62 @@ + + + + + + + + systemd-bsod + systemd + + + + systemd-bsod + 8 + + + + systemd-bsod + Displays boot-time emergency log message full-screen. + + + + + systemd-bsod + OPTIONS + + + + + Description + + systemd-bsod is used to display a blue screen which contains a message relating to + a boot failure, including a QR code which users can scan with their phones to get helpful information + about the cause of their boot failure. + + + + Options + + The following options are understood: + + + + + + + + + Exit status + + On success (displaying the journal message successfully), 0 is returned, a non-zero failure code otherwise. + + + + See Also + + systemd1 + + + + diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index d1cfb161de3..2575a658b16 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1535,3 +1535,18 @@ void get_log_colors(int priority, const char **on, const char **off, const char *highlight = ansi_highlight_red(); } } + +int set_terminal_cursor_position(int fd, unsigned int row, unsigned int column) { + int r; + char cursor_position[STRLEN("\x1B[") + DECIMAL_STR_MAX(int) * 2 + STRLEN(";H") + 1]; + + assert(fd >= 0); + + xsprintf(cursor_position, "\x1B[%u;%uH", row, column); + + r = loop_write(fd, cursor_position, SIZE_MAX, /* do_poll = */false); + if (r < 0) + return log_warning_errno(r, "Failed to set cursor position, ignoring: %m"); + + return 0; +} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 945deb5c661..2a7d48b95da 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -68,6 +68,9 @@ #define ANSI_HIGHLIGHT_YELLOW_FALLBACK "\x1B[0;1;33m" #define ANSI_HIGHLIGHT_YELLOW_FALLBACK_UNDERLINE "\x1B[0;1;4;33m" +/* Background colors */ +#define ANSI_BACKGROUND_BLUE "\x1B[44m" + /* Reset/clear ANSI styles */ #define ANSI_NORMAL "\x1B[0m" @@ -82,6 +85,7 @@ int reset_terminal_fd(int fd, bool switch_to_text); int reset_terminal(const char *name); +int set_terminal_cursor_position(int fd, unsigned int row, unsigned int column); int open_terminal(const char *name, int mode); diff --git a/src/journal/bsod.c b/src/journal/bsod.c new file mode 100644 index 00000000000..7d106eb48ab --- /dev/null +++ b/src/journal/bsod.c @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: LPGL-2.1-or-later */ + +#include +#include +#include +#include +#include + +#include "sd-id128.h" +#include "sd-journal.h" + +#include "alloc-util.h" +#include "build.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "log.h" +#include "logs-show.h" +#include "main-func.h" +#include "pretty-print.h" +#include "qrcode-util.h" +#include "sysctl-util.h" +#include "terminal-util.h" + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-bsod", "8", &link); + if (r < 0) + return log_oom(); + + printf("%s\n\n" + "%sFilter the journal to fetch the first message from the\n" + "current boot with an emergency log level and displays it\n" + "as a string and a QR code.\n\n%s" + " -h --help Show this help\n" + " --version Show package version\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int acquire_first_emergency_log_message(char **ret) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + _cleanup_free_ char *message = NULL; + const void *d; + size_t l; + int r; + + assert(ret); + + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + if (r < 0) + return log_error_errno(r, "Failed to open journal: %m"); + + r = add_match_this_boot(j, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to add boot ID filter: %m"); + + r = sd_journal_add_match(j, "_UID=0", 0); + if (r < 0) + return log_warning_errno(r, "Failed to add User ID filter: %m"); + + assert_cc(0 == LOG_EMERG); + r = sd_journal_add_match(j, "PRIORITY=0", 0); + if (r < 0) + return log_warning_errno(r, "Failed to add Emergency filter: %m"); + + r = sd_journal_seek_head(j); + if (r < 0) + return log_error_errno(r, "Failed to seek to start of jornal: %m"); + + r = sd_journal_next(j); + if (r < 0) + return log_error_errno(r, "Failed to read next journal entry: %m"); + if (r == 0) { + log_debug("No emergency level entries in the journal"); + *ret = NULL; + return 0; + } + + r = sd_journal_get_data(j, "MESSAGE", &d, &l); + if (r < 0) + return log_error_errno(r, "Failed to read journal message: %m"); + + message = memdup_suffix0((const char*)d + STRLEN("MESSAGE="), l - STRLEN("MESSAGE=")); + if (!message) + return log_oom(); + + *ret = TAKE_PTR(message); + + return 0; +} + +static int find_next_free_vt(int fd, int *ret_free_vt, int *ret_original_vt) { + struct vt_stat terminal_status; + + assert(fd); + assert(ret_free_vt); + assert(ret_original_vt); + + if (ioctl(fd, VT_GETSTATE, &terminal_status) < 0) + return -errno; + + for (size_t i = 0; i < sizeof(terminal_status.v_state) * 8; i++) + if ((terminal_status.v_state & (1 << i)) == 0) { + *ret_free_vt = i; + *ret_original_vt = terminal_status.v_active; + return 0; + } + + return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "No free VT found: %m"); +} + +static int display_emergency_message_fullscreen(const char *message) { + int r, free_vt = 0, original_vt = 0; + unsigned qr_code_start_row = 1, qr_code_start_column = 1; + char tty[STRLEN("/dev/tty") + DECIMAL_STR_MAX(int) + 1]; + _cleanup_close_ int fd = -EBADF; + _cleanup_fclose_ FILE *stream = NULL; + char read_character_buffer = '\0'; + struct winsize w = { + .ws_col = 80, + .ws_row = 25, + }; + + assert(message); + + fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(fd, "Failed to open tty1: %m"); + + r = find_next_free_vt(fd, &free_vt, &original_vt); + if (r < 0) + return log_error_errno(r, "Failed to find a free VT: %m"); + + xsprintf(tty, "/dev/tty%d", free_vt + 1); + + r = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (r < 0) + return log_error_errno(fd, "Failed to open tty: %m"); + + close_and_replace(fd, r); + + if (ioctl(fd, TIOCGWINSZ, &w) < 0) + log_warning_errno(errno, "Failed to fetch tty size, ignoring: %m"); + + if (ioctl(fd, VT_ACTIVATE, free_vt + 1) < 0) + return log_error_errno(errno, "Failed to activate tty: %m"); + + r = loop_write(fd, ANSI_BACKGROUND_BLUE ANSI_HOME_CLEAR, SIZE_MAX, /* do_poll = */ false); + if (r < 0) + log_warning_errno(r, "Failed to clear terminal, ignoring: %m"); + + r = set_terminal_cursor_position(fd, 2, 4); + if (r < 0) + log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); + + r = loop_write(fd, "The current boot has failed!", SIZE_MAX, /* do_poll = */false); + if (r < 0) + return log_warning_errno(r, "Failed to write to terminal: %m"); + + qr_code_start_row = w.ws_row * 3U / 5U; + qr_code_start_column = w.ws_col * 3U / 4U; + r = set_terminal_cursor_position(fd, 4, 4); + if (r < 0) + log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); + + r = loop_write(fd, message, SIZE_MAX, /* do_poll = */false); + if (r < 0) + return log_warning_errno(r, "Failed to write emergency message to terminal: %m"); + + r = fdopen_independent(fd, "r+", &stream); + if (r < 0) + return log_error_errno(errno, "Failed to open output file: %m"); + + r = print_qrcode_full(stream, "Scan the QR code", message, qr_code_start_row, qr_code_start_column, w.ws_col, w.ws_row); + if (r < 0) + log_warning_errno(r, "QR code could not be printed, ignoring: %m"); + + r = set_terminal_cursor_position(fd, w.ws_row - 1, w.ws_col * 2U / 5U); + if (r < 0) + log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); + + r = loop_write(fd, "Press any key to exit...", SIZE_MAX, /* do_poll = */false); + if (r < 0) + return log_warning_errno(r, "Failed to write to terminal: %m"); + + r = read_one_char(stream, &read_character_buffer, USEC_INFINITY, NULL); + if (r < 0) + return log_error_errno(r, "Failed to read character: %m"); + + if (ioctl(fd, VT_ACTIVATE, original_vt) < 0) + return log_error_errno(errno, "Failed to switch back to original VT: %m"); + + return 0; +} + +static int parse_argv(int argc, char * argv[]) { + + enum { + ARG_VERSION = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + if (optind < argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s takes no argument.", + program_invocation_short_name); + return 1; +} + +static int run(int argc, char *argv[]) { + int r; + _cleanup_free_ char *message = NULL; + + log_open(); + log_parse_environment(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = acquire_first_emergency_log_message(&message); + if (r < 0) + return log_error_errno(r, "Failed to acquire first emergency log message: %m"); + + if (!message) { + log_debug("No emergency-level entries"); + return 0; + } + + r = display_emergency_message_fullscreen((const char*) message); + if (r < 0) + return log_error_errno(r, "Failed to display emergency message on terminal: %m"); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/journal/meson.build b/src/journal/meson.build index 65adb2ccba4..3412e273e51 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -95,6 +95,14 @@ executables += [ threads, ], }, + executable_template + { + 'name' : 'systemd-bsod', + 'conditions' : ['HAVE_QRENCODE'], + 'public' : true, + 'sources' : files('bsod.c'), + 'link_with' : libshared, + 'dependencies' : libqrencode, + }, journal_test_template + { 'sources' : files('test-journal-append.c'), 'type' : 'manual', diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c index 6d2cf58fa31..b0dd90acd1a 100644 --- a/src/shared/qrcode-util.c +++ b/src/shared/qrcode-util.c @@ -36,60 +36,138 @@ int dlopen_qrencode(void) { return r; } -static void print_border(FILE *output, unsigned width) { - /* Four rows of border */ - for (unsigned y = 0; y < 4; y += 2) { - fputs(ANSI_WHITE_ON_BLACK, output); +static void print_border(FILE *output, unsigned width, unsigned row, unsigned column) { + assert(output); + assert(width); - for (unsigned x = 0; x < 4 + width + 4; x++) - fputs(UNICODE_FULL_BLOCK, output); + if (row != UINT_MAX && column != UINT_MAX) { + int r, fd; - fputs(ANSI_NORMAL "\n", output); + fd = fileno(output); + if (fd < 0) + return (void)log_debug_errno(errno, "Failed to get file descriptor from the file stream: %m"); + + r = set_terminal_cursor_position(fd, row, column); + if (r < 0) + log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); + + /* Four rows of border */ + for (unsigned y = 0; y < 4; y += 2) { + fputs(ANSI_WHITE_ON_BLACK, output); + + for (unsigned x = 0; x < 4 + width + 4; x++) + fputs(UNICODE_FULL_BLOCK, output); + + fputs(ANSI_NORMAL "\n", output); + r = set_terminal_cursor_position(fd, row + 1, column); + if (r < 0) + log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); + } + } else { + /* Four rows of border */ + for (unsigned y = 0; y < 4; y += 2) { + fputs(ANSI_WHITE_ON_BLACK, output); + + for (unsigned x = 0; x < 4 + width + 4; x++) + fputs(UNICODE_FULL_BLOCK, output); + + fputs(ANSI_NORMAL "\n", output); + } } } -static void write_qrcode(FILE *output, QRcode *qr) { +static void write_qrcode(FILE *output, QRcode *qr, unsigned int row, unsigned int column) { assert(qr); if (!output) output = stdout; - print_border(output, qr->width); + print_border(output, qr->width, row, column); + + if (row != UINT_MAX && column != UINT_MAX) { + /* After printing two rows of top border, we need to move the cursor down two rows before starting to print the actual QR code */ + int r, fd, move_down = 2; + fd = fileno(output); + if (fd < 0) + return (void)log_debug_errno(errno, "Failed to get file descriptor from the file stream: %m"); + + r = set_terminal_cursor_position(fd, row + move_down, column); + if (r < 0) + log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); + + for (unsigned y = 0; y < (unsigned) qr->width; y += 2) { + const uint8_t *row1 = qr->data + qr->width * y; + const uint8_t *row2 = row1 + qr->width; + + fputs(ANSI_WHITE_ON_BLACK, output); + + for (unsigned x = 0; x < 4; x++) + fputs(UNICODE_FULL_BLOCK, output); + + for (unsigned x = 0; x < (unsigned) qr->width; x++) { + bool a, b; - for (unsigned y = 0; y < (unsigned) qr->width; y += 2) { - const uint8_t *row1 = qr->data + qr->width * y; - const uint8_t *row2 = row1 + qr->width; + a = row1[x] & 1; + b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false; - fputs(ANSI_WHITE_ON_BLACK, output); - for (unsigned x = 0; x < 4; x++) - fputs(UNICODE_FULL_BLOCK, output); + if (a && b) + fputc(' ', output); + else if (a) + fputs(UNICODE_LOWER_HALF_BLOCK, output); + else if (b) + fputs(UNICODE_UPPER_HALF_BLOCK, output); + else + fputs(UNICODE_FULL_BLOCK, output); + } + + for (unsigned x = 0; x < 4; x++) + fputs(UNICODE_FULL_BLOCK, output); + r = set_terminal_cursor_position(fd, row + move_down, column); + if (r < 0) + log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); + move_down += 1; + fputs(ANSI_NORMAL "\n", output); + } - for (unsigned x = 0; x < (unsigned) qr->width; x++) { - bool a, b; + print_border(output, qr->width, row + move_down, column); + } else { - a = row1[x] & 1; - b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false; + for (unsigned y = 0; y < (unsigned) qr->width; y += 2) { + const uint8_t *row1 = qr->data + qr->width * y; + const uint8_t *row2 = row1 + qr->width; - if (a && b) - fputc(' ', output); - else if (a) - fputs(UNICODE_LOWER_HALF_BLOCK, output); - else if (b) - fputs(UNICODE_UPPER_HALF_BLOCK, output); - else + fputs(ANSI_WHITE_ON_BLACK, output); + for (unsigned x = 0; x < 4; x++) fputs(UNICODE_FULL_BLOCK, output); + + for (unsigned x = 0; x < (unsigned) qr->width; x++) { + bool a, b; + + a = row1[x] & 1; + b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false; + + if (a && b) + fputc(' ', output); + else if (a) + fputs(UNICODE_LOWER_HALF_BLOCK, output); + else if (b) + fputs(UNICODE_UPPER_HALF_BLOCK, output); + else + fputs(UNICODE_FULL_BLOCK, output); + } + + for (unsigned x = 0; x < 4; x++) + fputs(UNICODE_FULL_BLOCK, output); + fputs(ANSI_NORMAL "\n", output); } - for (unsigned x = 0; x < 4; x++) - fputs(UNICODE_FULL_BLOCK, output); - fputs(ANSI_NORMAL "\n", output); + print_border(output, qr->width, row, column); } - print_border(output, qr->width); fflush(output); } -int print_qrcode(FILE *out, const char *header, const char *string) { +int print_qrcode_full(FILE *out, const char *header, const char *string, unsigned row, unsigned column, unsigned tty_width, unsigned tty_height) { QRcode* qr; int r; @@ -106,10 +184,34 @@ int print_qrcode(FILE *out, const char *header, const char *string) { if (!qr) return -ENOMEM; - if (header) - fprintf(out, "\n%s:\n\n", header); + if (row != UINT_MAX && column != UINT_MAX) { + int fd; + unsigned qr_code_width, qr_code_height; + fd = fileno(out); + if (fd < 0) + return log_debug_errno(errno, "Failed to get file descriptor from the file stream: %m"); + qr_code_width = qr_code_height = qr->width + 8; + + if (column + qr_code_width > tty_width) + column = tty_width - qr_code_width; + + /* Terminal characters are twice as high as they are wide so it's qr_code_height / 2, + * our QR code prints an extra new line, so we have -1 as well */ + if (row + qr_code_height > tty_height) + row = tty_height - (qr_code_height / 2 ) - 1; + + if (header) { + r = set_terminal_cursor_position(fd, row - 2, tty_width - qr_code_width - 2); + if (r < 0) + log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); + + fprintf(out, "%s:\n\n", header); + } + } else + if (header) + fprintf(out, "\n%s:\n\n", header); - write_qrcode(out, qr); + write_qrcode(out, qr, row, column); fputc('\n', out); diff --git a/src/shared/qrcode-util.h b/src/shared/qrcode-util.h index b64ecce80a3..ee58294436b 100644 --- a/src/shared/qrcode-util.h +++ b/src/shared/qrcode-util.h @@ -3,12 +3,19 @@ #pragma once #include #include +#include #if HAVE_QRENCODE int dlopen_qrencode(void); -int print_qrcode(FILE *out, const char *header, const char *string); +int print_qrcode_full(FILE *out, const char *header, const char *string, unsigned row, unsigned column, unsigned tty_width, unsigned tty_height); +static inline int print_qrcode(FILE *out, const char *header, const char *string) { + return print_qrcode_full(out, header, string, UINT_MAX, UINT_MAX, UINT_MAX, UINT_MAX); +} #else +static inline int print_qrcode_full(FILE *out, const char *header, const char *string, unsigned row, unsigned column, unsigned tty_width, unsigned tty_height) { + return -EOPNOTSUPP; +} static inline int print_qrcode(FILE *out, const char *header, const char *string) { return -EOPNOTSUPP; }