''],
['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', [], ''],
--- /dev/null
+<?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>
*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;
+}
#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"
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);
--- /dev/null
+/* 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);
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',
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;
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);
#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;
}