summaryrefslogtreecommitdiffstats
path: root/src/journal/bsod.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/journal/bsod.c')
-rw-r--r--src/journal/bsod.c311
1 files changed, 311 insertions, 0 deletions
diff --git a/src/journal/bsod.c b/src/journal/bsod.c
new file mode 100644
index 0000000..a88cb66
--- /dev/null
+++ b/src/journal/bsod.c
@@ -0,0 +1,311 @@
+/* SPDX-License-Identifier: LGPL-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 "sigbus.h"
+#include "signal-util.h"
+#include "sysctl-util.h"
+#include "terminal-util.h"
+
+static bool arg_continuous = false;
+
+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"
+ " -c --continuous Make systemd-bsod wait continuously\n"
+ " for changes in the journal\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 journal: %m");
+
+ for (;;) {
+ r = sd_journal_next(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read next journal entry: %m");
+ if (r > 0)
+ break;
+
+ if (!arg_continuous) {
+ log_debug("No emergency level entries in the journal");
+ *ret = NULL;
+ return 0;
+ }
+
+ r = sd_journal_wait(j, UINT64_MAX);
+ if (r < 0)
+ return log_error_errno(r, "Failed to wait for changes: %m");
+ }
+
+ 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 >= 0);
+ 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, ret = 0, 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);
+ 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);
+ if (r < 0) {
+ ret = log_warning_errno(r, "Failed to write to terminal: %m");
+ goto cleanup;
+ }
+
+ 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);
+ if (r < 0) {
+ ret = log_warning_errno(r, "Failed to write emergency message to terminal: %m");
+ goto cleanup;
+ }
+
+ r = fdopen_independent(fd, "r+", &stream);
+ if (r < 0) {
+ ret = log_error_errno(errno, "Failed to open output file: %m");
+ goto cleanup;
+ }
+
+ 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);
+ if (r < 0) {
+ ret = log_warning_errno(r, "Failed to write to terminal: %m");
+ goto cleanup;
+ }
+
+ r = read_one_char(stream, &read_character_buffer, USEC_INFINITY, NULL);
+ if (r < 0 && r != -EINTR)
+ ret = log_error_errno(r, "Failed to read character: %m");
+
+cleanup:
+ if (ioctl(fd, VT_ACTIVATE, original_vt) < 0)
+ return log_error_errno(errno, "Failed to switch back to original VT: %m");
+
+ return ret;
+}
+
+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 },
+ { "continuous", no_argument, NULL, 'c' },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hc", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case 'c':
+ arg_continuous = true;
+ break;
+
+ 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[]) {
+ /* Don't use SA_RESTART here, as we don't want to restart syscalls on signal
+ * to get out of read_one_char() when needed */
+ static const struct sigaction nop_sigaction = {
+ .sa_handler = nop_signal_handler,
+ .sa_flags = 0,
+ };
+ _cleanup_free_ char *message = NULL;
+ int r;
+
+ log_open();
+ log_parse_environment();
+
+ sigbus_install();
+
+ 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;
+ }
+
+ assert_se(sigaction_many(&nop_sigaction, SIGTERM, SIGINT) >= 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);