mirror of
https://github.com/systemd/systemd.git
synced 2024-10-27 01:55:22 +03:00
Add tool to display emergency log message full-screen on boot failure.
This commit is contained in:
parent
5a087ba25c
commit
fc7eb1325b
@ -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
62
man/systemd-bsod.xml
Normal 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>
|
@ -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;
|
||||
}
|
||||
|
@ -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
271
src/journal/bsod.c
Normal 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);
|
@ -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',
|
||||
|
@ -36,7 +36,34 @@ int dlopen_qrencode(void) {
|
||||
return r;
|
||||
}
|
||||
|
||||
static void print_border(FILE *output, unsigned width) {
|
||||
static void print_border(FILE *output, unsigned width, unsigned row, unsigned column) {
|
||||
assert(output);
|
||||
assert(width);
|
||||
|
||||
if (row != UINT_MAX && column != UINT_MAX) {
|
||||
int r, fd;
|
||||
|
||||
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);
|
||||
@ -47,14 +74,63 @@ static void print_border(FILE *output, unsigned width) {
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
@ -85,11 +161,13 @@ static void write_qrcode(FILE *output, QRcode *qr) {
|
||||
fputs(ANSI_NORMAL "\n", output);
|
||||
}
|
||||
|
||||
print_border(output, qr->width);
|
||||
print_border(output, qr->width, row, column);
|
||||
}
|
||||
|
||||
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 (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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user