From b965ec7a63298675aeccb32376c236f1a68801bb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 May 2020 19:32:33 +0200 Subject: [PATCH 1/6] catalog: normalize how we link to documentation --- catalog/systemd.catalog.in | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in index b29ba6d65b..9ba5656f16 100644 --- a/catalog/systemd.catalog.in +++ b/catalog/systemd.catalog.in @@ -302,7 +302,8 @@ shut down. Subject: DNSSEC mode has been turned off, as server doesn't support it Defined-By: systemd Support: %SUPPORT_URL% -Documentation: man:systemd-resolved.service(8) resolved.conf(5) +Documentation: man:systemd-resolved.service(8) +Documentation: man:resolved.conf(5) The resolver service (systemd-resolved.service) has detected that the configured DNS server does not support DNSSEC, and DNSSEC validation has been @@ -417,6 +418,7 @@ Note that the memory pressure might or might not have been caused by @UNIT@. Subject: Accepting user/group name @USER_GROUP_NAME@, which does not match strict user/group name rules. Defined-By: systemd Support: %SUPPORT_URL% +Documentation: https://systemd.io/USER_NAMES The user/group name @USER_GROUP_NAME@ has been specified, which is accepted according the relaxed user/group name rules, but does not qualify under the @@ -432,10 +434,6 @@ characters; names not valid UTF-8; names with leading or trailing whitespace; the strings "." or ".."; fully numeric strings, or strings beginning in a hyphen and otherwise fully numeric. -For further details on strict and relaxed user/group name rules, see: - -https://systemd.io/USER_NAMES - -- 1b3bb94037f04bbf81028e135a12d293 Subject: Failed to generate valid unit name from path '@MOUNT_POINT@'. Defined-By: systemd From a90fb08c814513924728a00d88a77338750e192a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 May 2020 19:32:55 +0200 Subject: [PATCH 2/6] locale-util: add glyph for external links --- src/basic/locale-util.c | 4 ++++ src/basic/locale-util.h | 1 + src/test/test-locale-util.c | 1 + 3 files changed, 6 insertions(+) diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index ab5f86f54a..01af759a7d 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -365,6 +365,7 @@ const char *special_glyph(SpecialGlyph code) { [SPECIAL_GLYPH_SIGMA] = "S", [SPECIAL_GLYPH_ARROW] = "->", [SPECIAL_GLYPH_ELLIPSIS] = "...", + [SPECIAL_GLYPH_EXTERNAL_LINK] = "[LNK]", [SPECIAL_GLYPH_ECSTATIC_SMILEY] = ":-]", [SPECIAL_GLYPH_HAPPY_SMILEY] = ":-}", [SPECIAL_GLYPH_SLIGHTLY_HAPPY_SMILEY] = ":-)", @@ -400,6 +401,9 @@ const char *special_glyph(SpecialGlyph code) { /* Single glyph in Unicode, three in ASCII */ [SPECIAL_GLYPH_ELLIPSIS] = "\342\200\246", /* … (actually called: HORIZONTAL ELLIPSIS) */ + /* Three glyphs in Unicode, five in ASCII */ + [SPECIAL_GLYPH_EXTERNAL_LINK] = "[\360\237\241\225]", /* 🡕 (actually called: NORTH EAST SANS-SERIF ARROW, enclosed in []) */ + /* These smileys are a single glyph in Unicode, and three in ASCII */ [SPECIAL_GLYPH_ECSTATIC_SMILEY] = "\360\237\230\207", /* 😇 (actually called: SMILING FACE WITH HALO) */ [SPECIAL_GLYPH_HAPPY_SMILEY] = "\360\237\230\200", /* 😀 (actually called: GRINNING FACE) */ diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h index 6bd3772059..e4f9711b08 100644 --- a/src/basic/locale-util.h +++ b/src/basic/locale-util.h @@ -55,6 +55,7 @@ typedef enum { SPECIAL_GLYPH_LIGHT_SHADE, SPECIAL_GLYPH_DARK_SHADE, SPECIAL_GLYPH_SIGMA, + SPECIAL_GLYPH_EXTERNAL_LINK, _SPECIAL_GLYPH_FIRST_EMOJI, SPECIAL_GLYPH_ECSTATIC_SMILEY = _SPECIAL_GLYPH_FIRST_EMOJI, SPECIAL_GLYPH_HAPPY_SMILEY, diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c index 728196be08..45b6782145 100644 --- a/src/test/test-locale-util.c +++ b/src/test/test-locale-util.c @@ -107,6 +107,7 @@ static void dump_special_glyphs(void) { dump_glyph(SPECIAL_GLYPH_MU); dump_glyph(SPECIAL_GLYPH_CHECK_MARK); dump_glyph(SPECIAL_GLYPH_CROSS_MARK); + dump_glyph(SPECIAL_GLYPH_EXTERNAL_LINK); dump_glyph(SPECIAL_GLYPH_ECSTATIC_SMILEY); dump_glyph(SPECIAL_GLYPH_HAPPY_SMILEY); dump_glyph(SPECIAL_GLYPH_SLIGHTLY_HAPPY_SMILEY); From 422c8251e5e74071768da5a712fcb0c971ab4d12 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 May 2020 19:33:53 +0200 Subject: [PATCH 3/6] pretty-print: export urlify_enabled() --- src/shared/pretty-print.c | 2 +- src/shared/pretty-print.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index 5772918c37..69bae6883c 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -18,7 +18,7 @@ #include "terminal-util.h" #include "util.h" -static bool urlify_enabled(void) { +bool urlify_enabled(void) { static int cached_urlify_enabled = -1; /* Unfortunately 'less' doesn't support links like this yet 😭, hence let's disable this as long as there's a diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h index 12ab9acf58..b3057ae6b8 100644 --- a/src/shared/pretty-print.h +++ b/src/shared/pretty-print.h @@ -5,6 +5,8 @@ void print_separator(void); int file_url_from_path(const char *path, char **ret); +bool urlify_enabled(void); + int terminal_urlify(const char *url, const char *text, char **ret); int terminal_urlify_path(const char *path, const char *text, char **ret); int terminal_urlify_man(const char *page, const char *section, char **ret); From 2108b56749ebb8d17f06d08b6ada2f79ae4f097a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 May 2020 19:34:40 +0200 Subject: [PATCH 4/6] journal: show documentation hyperlink if known in log output --- src/shared/logs-show.c | 103 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 10 deletions(-) diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index f7fe3b3220..f83c0d5524 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -22,6 +22,7 @@ #include "journal-internal.h" #include "journal-util.h" #include "json.h" +#include "locale-util.h" #include "log.h" #include "logs-show.h" #include "macro.h" @@ -39,6 +40,7 @@ #include "time-util.h" #include "utf8.h" #include "util.h" +#include "web-util.h" /* up to three lines (each up to 100 characters) or 300 characters, whichever is less */ #define PRINT_LINE_THRESHOLD 3 @@ -47,12 +49,16 @@ #define JSON_THRESHOLD 4096U static int print_catalog(FILE *f, sd_journal *j) { - int r; _cleanup_free_ char *t = NULL, *z = NULL; + int r; + + assert(j); r = sd_journal_get_catalog(j, &t); + if (r == -ENOENT) + return 0; if (r < 0) - return r; + return log_error_errno(r, "Failed to find catalog entry: %m"); z = strreplace(strstrip(t), "\n", "\n-- "); if (!z) @@ -62,6 +68,48 @@ static int print_catalog(FILE *f, sd_journal *j) { fputs(z, f); fputc('\n', f); + return 1; +} + +static int url_from_catalog(sd_journal *j, char **ret) { + _cleanup_free_ char *t = NULL, *url = NULL; + const char *weblink; + int r; + + assert(j); + assert(ret); + + r = sd_journal_get_catalog(j, &t); + if (r == -ENOENT) + goto notfound; + if (r < 0) + return log_error_errno(r, "Failed to find catalog entry: %m"); + + weblink = startswith(t, "Documentation:"); + if (!weblink) { + weblink = strstr(t + 1, "\nDocumentation:"); + if (!weblink) + goto notfound; + + weblink += 15; + } + + /* Skip whitespace to value */ + weblink += strspn(weblink, " \t"); + + /* Cut out till next whitespace/newline */ + url = strndup(weblink, strcspn(weblink, WHITESPACE)); + if (!url) + return log_oom(); + + if (!documentation_url_is_valid(url)) + goto notfound; + + *ret = TAKE_PTR(url); + return 1; + +notfound: + *ret = NULL; return 0; } @@ -374,10 +422,13 @@ static int output_short( int r; const void *data; - size_t length; - size_t n = 0; - _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL, *transport = NULL, *config_file = NULL, *unit = NULL, *user_unit = NULL; - size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0, priority_len = 0, transport_len = 0, config_file_len = 0, unit_len = 0, user_unit_len = 0; + size_t length, n = 0; + _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, + *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL, *transport = NULL, + *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL; + size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, + realtime_len = 0, monotonic_len = 0, priority_len = 0, transport_len = 0, config_file_len = 0, + unit_len = 0, user_unit_len = 0, documentation_url_len = 0; int p = LOG_INFO; bool ellipsized = false, audit; const ParseFieldVec fields[] = { @@ -394,6 +445,7 @@ static int output_short( PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=", &config_file, &config_file_len), PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len), PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len), + PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=", &documentation_url, &documentation_url_len), }; size_t highlight_shifted[] = {highlight ? highlight[0] : 0, highlight ? highlight[1] : 0}; @@ -482,11 +534,42 @@ static int output_short( n += fake_pid_len + 2; } + fputs(": ", f); + + if (urlify_enabled()) { + _cleanup_free_ char *c = NULL; + + /* Insert a hyperlink to a documentation URL before the message. Note that we don't make the + * whole message a hyperlink, since otherwise the whole screen might end up being just + * hyperlinks. Moreover, we want to be able to highlight parts of the message (such as the + * config file, see below) hence let's keep the documentation URL link separate. */ + + if (documentation_url && shall_print(documentation_url, documentation_url_len, flags)) { + c = strndup(documentation_url, documentation_url_len); + if (!c) + return log_oom(); + + if (!documentation_url_is_valid(c)) /* Eat up invalid links */ + c = mfree(c); + } + + if (!c) + (void) url_from_catalog(j, &c); /* Acquire from catalog if not embedded in log message itself */ + + if (c) { + _cleanup_free_ char *urlified = NULL; + + if (terminal_urlify(c, special_glyph(SPECIAL_GLYPH_EXTERNAL_LINK), &urlified) >= 0) { + fputs(urlified, f); + fputc(' ', f); + } + } + } + if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(message, message_len)) { char bytes[FORMAT_BYTES_MAX]; - fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len)); + fprintf(f, "[%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len)); } else { - fputs(": ", f); /* URLify config_file string in message, if the message starts with it. * Skip URLification if the highlighted pattern overlaps. */ @@ -522,7 +605,7 @@ static int output_short( } if (flags & OUTPUT_CATALOG) - print_catalog(f, j); + (void) print_catalog(f, j); return ellipsized; } @@ -641,7 +724,7 @@ static int output_verbose( return r; if (flags & OUTPUT_CATALOG) - print_catalog(f, j); + (void) print_catalog(f, j); return 0; } From 2fac514fa943bed52d1e2c8607e133917cd310c3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 May 2020 19:34:51 +0200 Subject: [PATCH 5/6] logs: strndupa() cannot fail --- src/shared/logs-show.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index f83c0d5524..570377dc76 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -176,9 +176,6 @@ static int field_set_test(const Set *fields, const char *name, size_t n) { return 1; s = strndupa(name, n); - if (!s) - return log_oom(); - return set_contains(fields, s); } From dd587c3a881ad695031c01eb825e7371747d5a53 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 May 2020 19:39:50 +0200 Subject: [PATCH 6/6] man: briefly document the DOCUMENTATION= structure log field --- man/systemd.journal-fields.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/man/systemd.journal-fields.xml b/man/systemd.journal-fields.xml index 4d35cfb6dc..31ad143113 100644 --- a/man/systemd.journal-fields.xml +++ b/man/systemd.journal-fields.xml @@ -139,6 +139,16 @@ + + + DOCUMENTATION= + + A documentation URL with further information about the topic of the log message. Tools such + as journalctl will include a hyperlink to an URL specified this way in their + output. Should be a http://, https://, + file:/, man: or info: URL. + +