Add tool to display emergency log message full-screen on boot failure.

This commit is contained in:
OMOJOLA JOSHUA 2023-06-19 15:16:23 +01:00 committed by Luca Boccassi
parent 5a087ba25c
commit fc7eb1325b
8 changed files with 505 additions and 35 deletions

View file

@ -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', [], ''],

62
man/systemd-bsod.xml Normal file
View file

@ -0,0 +1,62 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-bsod" conditional='HAVE_QRENCODE' xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-bsod</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-bsod</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-bsod</refname>
<refpurpose>Displays boot-time emergency log message full-screen.</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>systemd-bsod</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-bsod</command> 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.</para>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success (displaying the journal message successfully), 0 is returned, a non-zero failure code otherwise.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View file

@ -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;
}

View file

@ -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);

271
src/journal/bsod.c Normal file
View file

@ -0,0 +1,271 @@
/* SPDX-License-Identifier: LPGL-2.1-or-later */
#include <errno.h>
#include <getopt.h>
#include <linux/vt.h>
#include <stdio.h>
#include <sys/ioctl.h>
#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);

View file

@ -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',

View file

@ -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);
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 (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");
fputs(ANSI_WHITE_ON_BLACK, 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");
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);
if (a && b)
fputc(' ', output);
else if (a)
fputs(UNICODE_LOWER_HALF_BLOCK, output);
else if (b)
fputs(UNICODE_UPPER_HALF_BLOCK, output);
else
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);
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 < 4; x++)
fputs(UNICODE_FULL_BLOCK, output);
fputs(ANSI_NORMAL "\n", output);
print_border(output, qr->width, row + move_down, column);
} else {
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;
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);
}
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;
write_qrcode(out, qr);
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, row, column);
fputc('\n', out);

View file

@ -3,12 +3,19 @@
#pragma once
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#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;
}