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/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) }, {} }; 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), diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 388eee47a1..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" : ", "); } } @@ -4143,8 +4153,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); 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; }