From 23b27b39d2a3a002ad827a2e8a9872a51495d797 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 19 Apr 2018 17:48:53 +0200 Subject: [PATCH 1/5] terminal: add internal API to format URLs for display in capable terminals Newer terminals (in particular gnome-terminal) understand special escape sequence for formatting clickable links. Let's support that to make our tool output more clickable where that's appropriate. For details see this: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda The one big issue is that 'less' currently doesn't grok this, and doesn't ignore sequence like regular terminal implementations do if they don't support it. Hence for now, let's disable URL output if a pager is used. We should revisit that though as soon as less added support for it and enough time passed for it to enter various distributions. --- man/systemd.xml | 9 +++ src/basic/terminal-util.c | 109 +++++++++++++++++++++++++++++++--- src/basic/terminal-util.h | 3 + src/test/test-terminal-util.c | 14 +++++ 4 files changed, 127 insertions(+), 8 deletions(-) diff --git a/man/systemd.xml b/man/systemd.xml index 679851fc90..84aa43723c 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -855,6 +855,15 @@ + + $SYSTEMD_URLIFY + + The value must be a boolean. Controls whether clickable links should be generated in the output + for terminal emulators supporting this. This can be specified to override the decision that + systemd makes based on $TERM and other conditions. + + + $LISTEN_PID $LISTEN_FDS diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index f0405e3c3a..3166145b33 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -8,21 +8,22 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include #include +#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include #include #include @@ -34,6 +35,7 @@ #include "io-util.h" #include "log.h" #include "macro.h" +#include "pager.h" #include "parse-util.h" #include "path-util.h" #include "proc-cmdline.h" @@ -1269,3 +1271,94 @@ int vt_reset_keyboard(int fd) { return 0; } + +static 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 + * pager in effect. Let's drop this check as soon as less got fixed a and enough time passed so that it's safe + * to assume that a link-enabled 'less' version has hit most installations. */ + + if (cached_urlify_enabled < 0) { + int val; + + val = getenv_bool("SYSTEMD_URLIFY"); + if (val >= 0) + cached_urlify_enabled = val; + else + cached_urlify_enabled = colors_enabled() && !pager_have(); + } + + return cached_urlify_enabled; +} + +int terminal_urlify(const char *url, const char *text, char **ret) { + char *n; + + assert(url); + + /* Takes an URL and a pretty string and formats it as clickable link for the terminal. See + * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */ + + if (isempty(text)) + text = url; + + if (urlify_enabled()) + n = strjoin("\x1B]8;;", url, "\a", text, "\x1B]8;;\a"); + else + n = strdup(text); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int terminal_urlify_path(const char *path, const char *text, char **ret) { + _cleanup_free_ char *absolute = NULL; + struct utsname u; + const char *url; + int r; + + assert(path); + + /* Much like terminal_urlify() above, but takes a file system path as input, and turns it into a properl + * file:// URL first. */ + + if (isempty(path)) + return -EINVAL; + + if (isempty(text)) + text = path; + + if (!urlify_enabled()) { + char *n; + + n = strdup(text); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; + } + + if (uname(&u) < 0) + return -errno; + + if (!path_is_absolute(path)) { + r = path_make_absolute_cwd(path, &absolute); + if (r < 0) + return r; + + path = absolute; + } + + /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local + * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested + * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly + * careful with validating the strings either. */ + + url = strjoina("file://", u.nodename, path); + + return terminal_urlify(url, text, ret); +} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index d60956455b..ad6ee338ed 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -156,3 +156,6 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode); int vt_default_utf8(void); int vt_reset_keyboard(int fd); + +int terminal_urlify(const char *url, const char *text, char **ret); +int terminal_urlify_path(const char *path, const char *text, char **ret); diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index 0a240280ea..c83dfa9dcd 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -9,6 +9,7 @@ #include #include +#include "alloc-util.h" #include "fd-util.h" #include "fileio.h" #include "log.h" @@ -63,12 +64,25 @@ static void test_read_one_char(void) { unlink(name); } +static void test_terminal_urlify(void) { + _cleanup_free_ char *formatted = NULL; + + assert_se(terminal_urlify("https://www.freedesktop.org/wiki/Software/systemd/", "systemd homepage", &formatted) >= 0); + printf("Hey, considere visiting the %s right now! It is very good!\n", formatted); + + formatted = mfree(formatted); + + assert_se(terminal_urlify_path("/etc/fstab", "this link to your /etc/fstab", &formatted) >= 0); + printf("Or click on %s to have a look at it!\n", formatted); +} + int main(int argc, char *argv[]) { log_parse_environment(); log_open(); test_default_term_for_tty(); test_read_one_char(); + test_terminal_urlify(); return 0; } From 64928aa54520b8cad3cda14d7aab0f4f59409acf Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 19 Apr 2018 17:52:22 +0200 Subject: [PATCH 2/5] hostnamed: expose HOME_URL os-release field on the bus --- src/hostname/hostnamed.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index c73390cfdf..614f93af73 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -39,6 +39,7 @@ enum { PROP_KERNEL_VERSION, PROP_OS_PRETTY_NAME, PROP_OS_CPE_NAME, + PROP_HOME_URL, _PROP_MAX }; @@ -100,6 +101,7 @@ static int context_read_data(Context *c) { r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME], "CPE_NAME", &c->data[PROP_OS_CPE_NAME], + "HOME_URL", &c->data[PROP_HOME_URL], NULL); if (r == -ENOENT) r = parse_env_file("/usr/lib/os-release", NEWLINE, @@ -640,6 +642,7 @@ static const sd_bus_vtable hostname_vtable[] = { SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("HomeURL", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOME_URL, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED), From 87adb0db27f4c679789271130577f18b62dca936 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 19 Apr 2018 17:52:51 +0200 Subject: [PATCH 3/5] hostnamectl: make the distribution name a pretty clickable link if we can --- src/hostname/hostnamectl.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index cebc7742bf..2cc9324ff3 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -20,6 +20,7 @@ #include "bus-util.h" #include "hostname-util.h" #include "spawn-polkit-agent.h" +#include "terminal-util.h" #include "util.h" #include "verbs.h" @@ -44,6 +45,7 @@ typedef struct StatusInfo { const char *os_cpe_name; const char *virtualization; const char *architecture; + const char *home_url; } StatusInfo; static void print_status_info(StatusInfo *i) { @@ -87,8 +89,17 @@ static void print_status_info(StatusInfo *i) { if (!isempty(i->virtualization)) printf(" Virtualization: %s\n", i->virtualization); - if (!isempty(i->os_pretty_name)) - printf(" Operating System: %s\n", i->os_pretty_name); + if (!isempty(i->os_pretty_name)) { + _cleanup_free_ char *formatted = NULL; + const char *t = i->os_pretty_name; + + if (i->home_url) { + if (terminal_urlify(i->home_url, i->os_pretty_name, &formatted) >= 0) + t = formatted; + } + + printf(" Operating System: %s\n", t); + } if (!isempty(i->os_cpe_name)) printf(" CPE OS Name: %s\n", i->os_cpe_name); @@ -141,6 +152,7 @@ static int show_all_names(sd_bus *bus, sd_bus_error *error) { { "KernelRelease", "s", NULL, offsetof(StatusInfo, kernel_release) }, { "OperatingSystemPrettyName", "s", NULL, offsetof(StatusInfo, os_pretty_name) }, { "OperatingSystemCPEName", "s", NULL, offsetof(StatusInfo, os_cpe_name) }, + { "HomeURL", "s", NULL, offsetof(StatusInfo, home_url) }, {} }; From 78c7d20ebceb1dcb9a60b7c4d2de7a400e80f808 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 19 Apr 2018 17:53:58 +0200 Subject: [PATCH 4/5] systemctl: format documentation links in status output as clickable links --- src/systemctl/systemctl.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 388eee47a1..b5352068bd 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -4143,8 +4143,17 @@ static void print_status_info( if (i->what) printf(" What: %s\n", i->what); - STRV_FOREACH(t, i->documentation) - printf(" %*s %s\n", 9, t == i->documentation ? "Docs:" : "", *t); + STRV_FOREACH(t, i->documentation) { + _cleanup_free_ char *formatted = NULL; + const char *q; + + if (terminal_urlify(*t, NULL, &formatted) >= 0) + q = formatted; + else + q = *t; + + printf(" %*s %s\n", 9, t == i->documentation ? "Docs:" : "", q); + } STRV_FOREACH_PAIR(t, t2, i->listen) printf(" %*s %s (%s)\n", 9, t == i->listen ? "Listen:" : "", *t2, *t); From c708025774af017cb1512d9dd7fe8d2759be481b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 19 Apr 2018 17:54:25 +0200 Subject: [PATCH 5/5] systemctl: format unit file and dropin paths as clickable links in status output --- src/systemctl/systemctl.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index b5352068bd..bcf5babb42 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -3954,11 +3954,11 @@ static void print_status_info( UnitStatusInfo *i, bool *ellipsized) { - ExecStatusInfo *p; + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1, since2[FORMAT_TIMESTAMP_MAX], *s2; const char *active_on, *active_off, *on, *off, *ss; + _cleanup_free_ char *formatted_path = NULL; + ExecStatusInfo *p; usec_t timestamp; - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char since2[FORMAT_TIMESTAMP_MAX], *s2; const char *path; char **t, **t2; int r; @@ -3993,7 +3993,9 @@ static void print_status_info( } else on = off = ""; - path = i->source_path ? i->source_path : i->fragment_path; + path = i->source_path ?: i->fragment_path; + if (path && terminal_urlify_path(path, NULL, &formatted_path) >= 0) + path = formatted_path; if (i->load_error != 0) printf(" Loaded: %s%s%s (Reason: %s)\n", @@ -4021,6 +4023,9 @@ static void print_status_info( char ** dropin; STRV_FOREACH(dropin, i->dropin_paths) { + _cleanup_free_ char *dropin_formatted = NULL; + const char *df; + if (!dir || last) { printf(dir ? " " : " Drop-In: "); @@ -4040,7 +4045,12 @@ static void print_status_info( last = ! (*(dropin + 1) && startswith(*(dropin + 1), dir)); - printf("%s%s", basename(*dropin), last ? "\n" : ", "); + if (terminal_urlify_path(*dropin, basename(*dropin), &dropin_formatted) >= 0) + df = dropin_formatted; + else + df = *dropin; + + printf("%s%s", df, last ? "\n" : ", "); } }