From 294bf0c34a53caa25709b794bfeee6a00a2b6ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 20 Nov 2018 15:42:57 +0100 Subject: Split out pretty-print.c and move pager.c and main-func.h to shared/ This is high-level functionality, and fits better in shared/ (which is for our executables), than in basic/ (which is also for libraries). --- src/activate/activate.c | 1 + src/analyze/analyze.c | 3 +- src/ask-password/ask-password.c | 2 +- src/basic/conf-files.c | 33 --- src/basic/conf-files.h | 1 - src/basic/main-func.h | 27 --- src/basic/meson.build | 2 - src/basic/pager.c | 270 --------------------- src/basic/pager.h | 17 -- src/basic/terminal-util.c | 182 -------------- src/basic/terminal-util.h | 12 - src/binfmt/binfmt.c | 2 +- src/boot/bootctl.c | 1 + src/busctl/busctl.c | 1 + src/cgls/cgls.c | 2 +- src/cgtop/cgtop.c | 1 + src/core/main.c | 1 + src/coredump/coredumpctl.c | 1 + src/cryptsetup/cryptsetup.c | 2 +- src/delta/delta.c | 1 + src/detect-virt/detect-virt.c | 2 +- src/escape/escape.c | 2 +- src/firstboot/firstboot.c | 1 + src/hostname/hostnamectl.c | 2 +- src/hwdb/hwdb.c | 2 +- src/id128/id128.c | 2 +- src/journal-remote/journal-gatewayd.c | 2 +- src/journal-remote/journal-remote-main.c | 2 +- src/journal-remote/journal-upload.c | 2 +- src/journal/cat.c | 2 +- src/journal/journalctl.c | 1 + src/locale/localectl.c | 2 +- src/login/inhibit.c | 2 +- src/login/loginctl.c | 1 + src/machine-id-setup/machine-id-setup-main.c | 2 +- src/machine/machinectl.c | 1 + src/modules-load/modules-load.c | 2 +- src/mount/mount-tool.c | 1 + src/network/networkctl.c | 1 + src/network/wait-online/wait-online.c | 2 +- src/notify/notify.c | 2 +- src/nspawn/nspawn.c | 1 + src/partition/growfs.c | 2 +- src/path/path.c | 2 +- src/portable/portablectl.c | 1 + src/resolve/resolvconf-compat.c | 1 + src/resolve/resolvectl.c | 1 + src/run/run.c | 1 + src/shared/id128-print.c | 1 + src/shared/main-func.h | 27 +++ src/shared/meson.build | 5 + src/shared/pager.c | 270 +++++++++++++++++++++ src/shared/pager.h | 17 ++ src/shared/pretty-print.c | 233 ++++++++++++++++++ src/shared/pretty-print.h | 15 ++ src/sleep/sleep.c | 2 +- src/socket-proxy/socket-proxyd.c | 2 +- src/sysctl/sysctl.c | 2 +- src/systemctl/systemctl.c | 1 + src/sysusers/sysusers.c | 2 +- src/test/meson.build | 4 + src/test/test-pretty-print.c | 40 +++ src/test/test-strip-tab-ansi.c | 1 + src/test/test-terminal-util.c | 29 +-- src/timedate/timedatectl.c | 1 + src/tmpfiles/tmpfiles.c | 2 +- .../tty-ask-password-agent.c | 1 + src/udev/udevadm.c | 2 +- src/udev/udevd.c | 2 +- src/veritysetup/veritysetup.c | 1 + 70 files changed, 667 insertions(+), 600 deletions(-) delete mode 100644 src/basic/main-func.h delete mode 100644 src/basic/pager.c delete mode 100644 src/basic/pager.h create mode 100644 src/shared/main-func.h create mode 100644 src/shared/pager.c create mode 100644 src/shared/pager.h create mode 100644 src/shared/pretty-print.c create mode 100644 src/shared/pretty-print.h create mode 100644 src/test/test-pretty-print.c diff --git a/src/activate/activate.c b/src/activate/activate.c index 25e2ec5aa7..4bc133a639 100644 --- a/src/activate/activate.c +++ b/src/activate/activate.c @@ -14,6 +14,7 @@ #include "fd-util.h" #include "log.h" #include "macro.h" +#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "socket-util.h" diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 959c078c5c..5e0e03f53d 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -30,8 +30,9 @@ #include "pager.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #if HAVE_SECCOMP -#include "seccomp-util.h" +# include "seccomp-util.h" #endif #include "special.h" #include "strv.h" diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index 571148f99b..530dc1ee6e 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -10,8 +10,8 @@ #include "log.h" #include "macro.h" #include "main-func.h" +#include "pretty-print.h" #include "strv.h" -#include "terminal-util.h" static const char *arg_icon = NULL; static const char *arg_id = NULL; diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index d03366077d..7b44ae277d 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -328,36 +328,3 @@ int conf_files_list_with_replacement( *replace_file = TAKE_PTR(p); return 0; } - -int conf_files_cat(const char *root, const char *name) { - _cleanup_strv_free_ char **dirs = NULL, **files = NULL; - _cleanup_free_ char *path = NULL; - const char *dir; - char **t; - int r; - - NULSTR_FOREACH(dir, CONF_PATHS_NULSTR("")) { - assert(endswith(dir, "/")); - r = strv_extendf(&dirs, "%s%s.d", dir, name); - if (r < 0) - return log_error_errno(r, "Failed to build directory list: %m"); - } - - r = conf_files_list_strv(&files, ".conf", root, 0, (const char* const*) dirs); - if (r < 0) - return log_error_errno(r, "Failed to query file list: %m"); - - path = path_join(root, "/etc", name); - if (!path) - return log_oom(); - - if (DEBUG_LOGGING) { - log_debug("Looking for configuration in:"); - log_debug(" %s", path); - STRV_FOREACH(t, dirs) - log_debug(" %s/*.conf", *t); - } - - /* show */ - return cat_files(path, files, CAT_FLAGS_MAIN_FILE_OPTIONAL); -} diff --git a/src/basic/conf-files.h b/src/basic/conf-files.h index 81ebcd46b6..f31f17de9d 100644 --- a/src/basic/conf-files.h +++ b/src/basic/conf-files.h @@ -22,4 +22,3 @@ int conf_files_list_with_replacement( const char *replacement, char ***files, char **replace_file); -int conf_files_cat(const char *root, const char *name); diff --git a/src/basic/main-func.h b/src/basic/main-func.h deleted file mode 100644 index 24bf6c99bf..0000000000 --- a/src/basic/main-func.h +++ /dev/null @@ -1,27 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1+ */ -#pragma once - -#include - -#include "pager.h" -#include "static-destruct.h" - -#define _DEFINE_MAIN_FUNCTION(impl, ret) \ - int main(int argc, char *argv[]) { \ - int r; \ - r = impl(argc, argv); \ - static_destruct(); \ - pager_close(); \ - return ret; \ - } - -/* Negative return values from impl are mapped to EXIT_FAILURE, and - * everything else means success! */ -#define DEFINE_MAIN_FUNCTION(impl) \ - _DEFINE_MAIN_FUNCTION(impl, r < 0 ? EXIT_FAILURE : EXIT_SUCCESS) - -/* Zero is mapped to EXIT_SUCCESS, negative values are mapped to EXIT_FAILURE, - * and postive values are propagated. - * Note: "true" means failure! */ -#define DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(impl) \ - _DEFINE_MAIN_FUNCTION(impl, r < 0 ? EXIT_FAILURE : r) diff --git a/src/basic/meson.build b/src/basic/meson.build index abd54db8cc..0b27ffda7d 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -99,8 +99,6 @@ basic_sources = files(''' nss-util.h ordered-set.c ordered-set.h - pager.c - pager.h parse-util.c parse-util.h path-util.c diff --git a/src/basic/pager.c b/src/basic/pager.c deleted file mode 100644 index 88d9ef349e..0000000000 --- a/src/basic/pager.c +++ /dev/null @@ -1,270 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1+ */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "copy.h" -#include "fd-util.h" -#include "fileio.h" -#include "io-util.h" -#include "locale-util.h" -#include "log.h" -#include "macro.h" -#include "pager.h" -#include "process-util.h" -#include "signal-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" - -static pid_t pager_pid = 0; - -static int stored_stdout = -1; -static int stored_stderr = -1; -static bool stdout_redirected = false; -static bool stderr_redirected = false; - -_noreturn_ static void pager_fallback(void) { - int r; - - r = copy_bytes(STDIN_FILENO, STDOUT_FILENO, (uint64_t) -1, 0); - if (r < 0) { - log_error_errno(r, "Internal pager failed: %m"); - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); -} - -static int no_quit_on_interrupt(int exe_name_fd, const char *less_opts) { - _cleanup_fclose_ FILE *file = NULL; - _cleanup_free_ char *line = NULL; - int r; - - assert(exe_name_fd >= 0); - assert(less_opts); - - /* This takes ownership of exe_name_fd */ - file = fdopen(exe_name_fd, "r"); - if (!file) { - safe_close(exe_name_fd); - return log_debug_errno(errno, "Failed to create FILE object: %m"); - } - - /* Find the last line */ - for (;;) { - _cleanup_free_ char *t = NULL; - - r = read_line(file, LONG_LINE_MAX, &t); - if (r < 0) - return r; - if (r == 0) - break; - - free_and_replace(line, t); - } - - /* We only treat "less" specially. - * Return true whenever option K is *not* set. */ - r = streq_ptr(line, "less") && !strchr(less_opts, 'K'); - - log_debug("Pager executable is \"%s\", options \"%s\", quit_on_interrupt: %s", - strnull(line), less_opts, yes_no(!r)); - return r; -} - -int pager_open(PagerFlags flags) { - _cleanup_close_pair_ int fd[2] = { -1, -1 }, exe_name_pipe[2] = { -1, -1 }; - _cleanup_strv_free_ char **pager_args = NULL; - const char *pager, *less_opts; - int r; - - if (flags & PAGER_DISABLE) - return 0; - - if (pager_pid > 0) - return 1; - - if (terminal_is_dumb()) - return 0; - - if (!is_main_thread()) - return -EPERM; - - pager = getenv("SYSTEMD_PAGER"); - if (!pager) - pager = getenv("PAGER"); - - if (pager) { - pager_args = strv_split(pager, WHITESPACE); - if (!pager_args) - return -ENOMEM; - - /* If the pager is explicitly turned off, honour it */ - if (strv_isempty(pager_args) || strv_equal(pager_args, STRV_MAKE("cat"))) - return 0; - } - - /* Determine and cache number of columns/lines before we spawn the pager so that we get the value from the - * actual tty */ - (void) columns(); - (void) lines(); - - if (pipe2(fd, O_CLOEXEC) < 0) - return log_error_errno(errno, "Failed to create pager pipe: %m"); - - /* This is a pipe to feed the name of the executed pager binary into the parent */ - if (pipe2(exe_name_pipe, O_CLOEXEC) < 0) - return log_error_errno(errno, "Failed to create exe_name pipe: %m"); - - /* Initialize a good set of less options */ - less_opts = getenv("SYSTEMD_LESS"); - if (!less_opts) - less_opts = "FRSXMK"; - if (flags & PAGER_JUMP_TO_END) - less_opts = strjoina(less_opts, " +G"); - - r = safe_fork("(pager)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pager_pid); - if (r < 0) - return r; - if (r == 0) { - const char *less_charset, *exe; - - /* In the child start the pager */ - - (void) dup2(fd[0], STDIN_FILENO); - safe_close_pair(fd); - - if (setenv("LESS", less_opts, 1) < 0) - _exit(EXIT_FAILURE); - - /* Initialize a good charset for less. This is - * particularly important if we output UTF-8 - * characters. */ - less_charset = getenv("SYSTEMD_LESSCHARSET"); - if (!less_charset && is_locale_utf8()) - less_charset = "utf-8"; - if (less_charset && - setenv("LESSCHARSET", less_charset, 1) < 0) - _exit(EXIT_FAILURE); - - if (pager_args) { - if (loop_write(exe_name_pipe[1], pager_args[0], strlen(pager_args[0]) + 1, false) < 0) - _exit(EXIT_FAILURE); - - execvp(pager_args[0], pager_args); - } - - /* Debian's alternatives command for pagers is - * called 'pager'. Note that we do not call - * sensible-pagers here, since that is just a - * shell script that implements a logic that - * is similar to this one anyway, but is - * Debian-specific. */ - FOREACH_STRING(exe, "pager", "less", "more") { - if (loop_write(exe_name_pipe[1], exe, strlen(exe) + 1, false) < 0) - _exit(EXIT_FAILURE); - execlp(exe, exe, NULL); - } - - if (loop_write(exe_name_pipe[1], "(built-in)", strlen("(built-in") + 1, false) < 0) - _exit(EXIT_FAILURE); - pager_fallback(); - /* not reached */ - } - - /* Return in the parent */ - stored_stdout = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 3); - if (dup2(fd[1], STDOUT_FILENO) < 0) { - stored_stdout = safe_close(stored_stdout); - return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); - } - stdout_redirected = true; - - stored_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3); - if (dup2(fd[1], STDERR_FILENO) < 0) { - stored_stderr = safe_close(stored_stderr); - return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); - } - stderr_redirected = true; - - exe_name_pipe[1] = safe_close(exe_name_pipe[1]); - - r = no_quit_on_interrupt(TAKE_FD(exe_name_pipe[0]), less_opts); - if (r < 0) - return r; - if (r > 0) - (void) ignore_signals(SIGINT, -1); - - return 1; -} - -void pager_close(void) { - - if (pager_pid <= 0) - return; - - /* Inform pager that we are done */ - (void) fflush(stdout); - if (stdout_redirected) - if (stored_stdout < 0 || dup2(stored_stdout, STDOUT_FILENO) < 0) - (void) close(STDOUT_FILENO); - stored_stdout = safe_close(stored_stdout); - (void) fflush(stderr); - if (stderr_redirected) - if (stored_stderr < 0 || dup2(stored_stderr, STDERR_FILENO) < 0) - (void) close(STDERR_FILENO); - stored_stderr = safe_close(stored_stderr); - stdout_redirected = stderr_redirected = false; - - (void) kill(pager_pid, SIGCONT); - (void) wait_for_terminate(pager_pid, NULL); - pager_pid = 0; -} - -bool pager_have(void) { - return pager_pid > 0; -} - -int show_man_page(const char *desc, bool null_stdio) { - const char *args[4] = { "man", NULL, NULL, NULL }; - char *e = NULL; - pid_t pid; - size_t k; - int r; - - k = strlen(desc); - - if (desc[k-1] == ')') - e = strrchr(desc, '('); - - if (e) { - char *page = NULL, *section = NULL; - - page = strndupa(desc, e - desc); - section = strndupa(e + 1, desc + k - e - 2); - - args[1] = section; - args[2] = page; - } else - args[1] = desc; - - r = safe_fork("(man)", FORK_RESET_SIGNALS|FORK_DEATHSIG|(null_stdio ? FORK_NULL_STDIO : 0)|FORK_LOG, &pid); - if (r < 0) - return r; - if (r == 0) { - /* Child */ - execvp(args[0], (char**) args); - log_error_errno(errno, "Failed to execute man: %m"); - _exit(EXIT_FAILURE); - } - - return wait_for_terminate_and_check(NULL, pid, 0); -} diff --git a/src/basic/pager.h b/src/basic/pager.h deleted file mode 100644 index 8299e23856..0000000000 --- a/src/basic/pager.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1+ */ -#pragma once - -#include - -#include "macro.h" - -typedef enum PagerFlags { - PAGER_DISABLE = 1 << 0, - PAGER_JUMP_TO_END = 1 << 1, -} PagerFlags; - -int pager_open(PagerFlags flags); -void pager_close(void); -bool pager_have(void) _pure_; - -int show_man_page(const char *page, bool null_stdio); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 41789598f7..2c7b4508ce 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -32,7 +32,6 @@ #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" @@ -1272,184 +1271,3 @@ 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 proper 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); -} - -int terminal_urlify_man(const char *page, const char *section, char **ret) { - const char *url, *text; - - url = strjoina("man:", page, "(", section, ")"); - text = strjoina(page, "(", section, ") man page"); - - return terminal_urlify(url, text, ret); -} - -static int cat_file(const char *filename, bool newline) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *urlified = NULL; - int r; - - f = fopen(filename, "re"); - if (!f) - return -errno; - - r = terminal_urlify_path(filename, NULL, &urlified); - if (r < 0) - return r; - - printf("%s%s# %s%s\n", - newline ? "\n" : "", - ansi_highlight_blue(), - urlified, - ansi_normal()); - fflush(stdout); - - for (;;) { - _cleanup_free_ char *line = NULL; - - r = read_line(f, LONG_LINE_MAX, &line); - if (r < 0) - return log_error_errno(r, "Failed to read \"%s\": %m", filename); - if (r == 0) - break; - - puts(line); - } - - return 0; -} - -int cat_files(const char *file, char **dropins, CatFlags flags) { - char **path; - int r; - - if (file) { - r = cat_file(file, false); - if (r == -ENOENT && (flags & CAT_FLAGS_MAIN_FILE_OPTIONAL)) - printf("%s# config file %s not found%s\n", - ansi_highlight_magenta(), - file, - ansi_normal()); - else if (r < 0) - return log_warning_errno(r, "Failed to cat %s: %m", file); - } - - STRV_FOREACH(path, dropins) { - r = cat_file(*path, file || path != dropins); - if (r < 0) - return log_warning_errno(r, "Failed to cat %s: %m", *path); - } - - return 0; -} - -void print_separator(void) { - - /* Outputs a separator line that resolves to whitespace when copied from the terminal. We do that by outputting - * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */ - - if (underline_enabled()) { - size_t i, c; - - c = columns(); - - flockfile(stdout); - fputs_unlocked(ANSI_UNDERLINE, stdout); - - for (i = 0; i < c; i++) - fputc_unlocked(' ', stdout); - - fputs_unlocked(ANSI_NORMAL "\n\n", stdout); - funlockfile(stdout); - } else - fputs("\n\n", stdout); -} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 436951be98..2d64afaee6 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -154,15 +154,3 @@ 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); -int terminal_urlify_man(const char *page, const char *section, char **ret); - -typedef enum CatFlags { - CAT_FLAGS_MAIN_FILE_OPTIONAL = 1 << 0, -} CatFlags; - -int cat_files(const char *file, char **dropins, CatFlags flags); - -void print_separator(void); diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c index b6f53a9bb8..c3219d0873 100644 --- a/src/binfmt/binfmt.c +++ b/src/binfmt/binfmt.c @@ -17,9 +17,9 @@ #include "main-func.h" #include "pager.h" #include "path-util.h" +#include "pretty-print.h" #include "string-util.h" #include "strv.h" -#include "terminal-util.h" #include "util.h" static bool arg_cat_config = false; diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 59241e5ee4..3ff3b3ffa3 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -32,6 +32,7 @@ #include "main-func.h" #include "pager.h" #include "parse-util.h" +#include "pretty-print.h" #include "rm-rf.h" #include "stat-util.h" #include "string-util.h" diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 0a325f9638..5ad2a7d12f 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -22,6 +22,7 @@ #include "pager.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "set.h" #include "strv.h" #include "terminal-util.h" diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index b135dd6d51..3cdadc8fad 100644 --- a/src/cgls/cgls.c +++ b/src/cgls/cgls.c @@ -18,8 +18,8 @@ #include "output-mode.h" #include "pager.h" #include "path-util.h" +#include "pretty-print.h" #include "strv.h" -#include "terminal-util.h" #include "unit-name.h" #include "util.h" diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index cad274a633..562e6ece5b 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -22,6 +22,7 @@ #include "main-func.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "process-util.h" #include "procfs-util.h" #include "stdio-util.h" diff --git a/src/core/main.c b/src/core/main.c index 70227da4b7..f65206625d 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -58,6 +58,7 @@ #include "pager.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "proc-cmdline.h" #include "process-util.h" #include "raw-clone.h" diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 9323e9d028..9f867bb1e7 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -27,6 +27,7 @@ #include "pager.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "process-util.h" #include "rlimit-util.h" #include "sigbus.h" diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index e1ceae06fa..8afff73990 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -20,7 +20,7 @@ #include "path-util.h" #include "string-util.h" #include "strv.h" -#include "terminal-util.h" +#include "pretty-print.h" #include "util.h" /* internal helper */ diff --git a/src/delta/delta.c b/src/delta/delta.c index a2aa28c75b..f3a23f95d4 100644 --- a/src/delta/delta.c +++ b/src/delta/delta.c @@ -17,6 +17,7 @@ #include "pager.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "stat-util.h" diff --git a/src/detect-virt/detect-virt.c b/src/detect-virt/detect-virt.c index f1259fdcd5..1812218daf 100644 --- a/src/detect-virt/detect-virt.c +++ b/src/detect-virt/detect-virt.c @@ -7,8 +7,8 @@ #include "alloc-util.h" #include "main-func.h" +#include "pretty-print.h" #include "string-table.h" -#include "terminal-util.h" #include "util.h" #include "virt.h" diff --git a/src/escape/escape.c b/src/escape/escape.c index b03c9e661d..2b3cff0f3d 100644 --- a/src/escape/escape.c +++ b/src/escape/escape.c @@ -7,9 +7,9 @@ #include "alloc-util.h" #include "log.h" #include "main-func.h" +#include "pretty-print.h" #include "string-util.h" #include "strv.h" -#include "terminal-util.h" #include "unit-name.h" static enum { diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 5b08f34fec..27dd494980 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -32,6 +32,7 @@ #include "os-util.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "proc-cmdline.h" #include "random-util.h" #include "string-util.h" diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 0d40d17a55..7f9bb49e0c 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -15,8 +15,8 @@ #include "bus-util.h" #include "hostname-util.h" #include "main-func.h" +#include "pretty-print.h" #include "spawn-polkit-agent.h" -#include "terminal-util.h" #include "util.h" #include "verbs.h" diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c index 89e3138542..b069a3eb8a 100644 --- a/src/hwdb/hwdb.c +++ b/src/hwdb/hwdb.c @@ -7,8 +7,8 @@ #include "alloc-util.h" #include "hwdb-util.h" #include "main-func.h" +#include "pretty-print.h" #include "selinux-util.h" -#include "terminal-util.h" #include "util.h" #include "verbs.h" diff --git a/src/id128/id128.c b/src/id128/id128.c index cbd7464d2c..3c40dd6e5d 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -6,7 +6,7 @@ #include "alloc-util.h" #include "id128-print.h" #include "main-func.h" -#include "terminal-util.h" +#include "pretty-print.h" #include "util.h" #include "verbs.h" diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 71553083d9..b39c7e3388 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -21,8 +21,8 @@ #include "microhttpd-util.h" #include "os-util.h" #include "parse-util.h" +#include "pretty-print.h" #include "sigbus.h" -#include "terminal-util.h" #include "util.h" #define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC) diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 930871b13b..af927046c6 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -11,6 +11,7 @@ #include "fileio.h" #include "journal-remote-write.h" #include "journal-remote.h" +#include "pretty-print.h" #include "process-util.h" #include "rlimit-util.h" #include "signal-util.h" @@ -18,7 +19,6 @@ #include "stat-util.h" #include "string-table.h" #include "strv.h" -#include "terminal-util.h" #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem" #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem" diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index b91d520cf0..b9f98cda0a 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -19,12 +19,12 @@ #include "log.h" #include "mkdir.h" #include "parse-util.h" +#include "pretty-print.h" #include "process-util.h" #include "rlimit-util.h" #include "sigbus.h" #include "signal-util.h" #include "string-util.h" -#include "terminal-util.h" #include "util.h" #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem" diff --git a/src/journal/cat.c b/src/journal/cat.c index 3198d3261b..570515783a 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -13,9 +13,9 @@ #include "fd-util.h" #include "main-func.h" #include "parse-util.h" +#include "pretty-print.h" #include "string-util.h" #include "syslog-util.h" -#include "terminal-util.h" #include "util.h" static const char *arg_identifier = NULL; diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index e5a5acde8d..a2c812ba57 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -54,6 +54,7 @@ #include "pager.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "rlimit-util.h" #include "set.h" #include "sigbus.h" diff --git a/src/locale/localectl.c b/src/locale/localectl.c index af1d27f9c7..9d76518bf1 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -17,11 +17,11 @@ #include "locale-util.h" #include "main-func.h" #include "pager.h" +#include "pretty-print.h" #include "proc-cmdline.h" #include "set.h" #include "spawn-polkit-agent.h" #include "strv.h" -#include "terminal-util.h" #include "util.h" #include "verbs.h" #include "virt.h" diff --git a/src/login/inhibit.c b/src/login/inhibit.c index 7a63eb6efc..332cfdbba8 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -16,10 +16,10 @@ #include "format-util.h" #include "main-func.h" #include "pager.h" +#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "strv.h" -#include "terminal-util.h" #include "user-util.h" #include "util.h" diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 1f05753dff..427bb431cf 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -21,6 +21,7 @@ #include "main-func.h" #include "pager.h" #include "parse-util.h" +#include "pretty-print.h" #include "process-util.h" #include "rlimit-util.h" #include "sigbus.h" diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index c6a77cc901..bfadd5881d 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -11,7 +11,7 @@ #include "machine-id-setup.h" #include "main-func.h" #include "path-util.h" -#include "terminal-util.h" +#include "pretty-print.h" #include "util.h" static char *arg_root = NULL; diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 5dd46fc4a7..241e175ee6 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -36,6 +36,7 @@ #include "pager.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "process-util.h" #include "ptyfwd.h" #include "rlimit-util.h" diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index 0f7f4c384e..acb39516eb 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -13,10 +13,10 @@ #include "fileio.h" #include "log.h" #include "module-util.h" +#include "pretty-print.h" #include "proc-cmdline.h" #include "string-util.h" #include "strv.h" -#include "terminal-util.h" #include "util.h" static char **arg_proc_cmdline_modules = NULL; diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index 62b910b487..ce9473d60d 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -19,6 +19,7 @@ #include "pager.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "spawn-polkit-agent.h" #include "stat-util.h" #include "strv.h" diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 5fd67ad484..76629aa507 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -24,6 +24,7 @@ #include "netlink-util.h" #include "pager.h" #include "parse-util.h" +#include "pretty-print.h" #include "socket-util.h" #include "sparse-endian.h" #include "stdio-util.h" diff --git a/src/network/wait-online/wait-online.c b/src/network/wait-online/wait-online.c index 2d74593326..7f30862bbf 100644 --- a/src/network/wait-online/wait-online.c +++ b/src/network/wait-online/wait-online.c @@ -5,9 +5,9 @@ #include "sd-daemon.h" #include "manager.h" +#include "pretty-print.h" #include "signal-util.h" #include "strv.h" -#include "terminal-util.h" static bool arg_quiet = false; static usec_t arg_timeout = 120 * USEC_PER_SEC; diff --git a/src/notify/notify.c b/src/notify/notify.c index fdf4598941..07ad97a490 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -14,9 +14,9 @@ #include "log.h" #include "main-func.h" #include "parse-util.h" +#include "pretty-print.h" #include "string-util.h" #include "strv.h" -#include "terminal-util.h" #include "user-util.h" #include "util.h" diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index cd986379b6..3369dd18c9 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -76,6 +76,7 @@ #include "pager.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "process-util.h" #include "ptyfwd.h" #include "random-util.h" diff --git a/src/partition/growfs.c b/src/partition/growfs.c index 673b651da6..c089830b0d 100644 --- a/src/partition/growfs.c +++ b/src/partition/growfs.c @@ -22,8 +22,8 @@ #include "mount-util.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "strv.h" -#include "terminal-util.h" static const char *arg_target = NULL; static bool arg_dry_run = false; diff --git a/src/path/path.c b/src/path/path.c index 4a2e2ca3f6..2ac5f002a3 100644 --- a/src/path/path.c +++ b/src/path/path.c @@ -11,8 +11,8 @@ #include "log.h" #include "macro.h" #include "main-func.h" +#include "pretty-print.h" #include "string-util.h" -#include "terminal-util.h" #include "util.h" static const char *arg_suffix = NULL; diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index af2f84a84c..beed2a8c1c 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -20,6 +20,7 @@ #include "pager.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "spawn-polkit-agent.h" #include "string-util.h" #include "strv.h" diff --git a/src/resolve/resolvconf-compat.c b/src/resolve/resolvconf-compat.c index 013b215a80..340767634f 100644 --- a/src/resolve/resolvconf-compat.c +++ b/src/resolve/resolvconf-compat.c @@ -9,6 +9,7 @@ #include "extract-word.h" #include "fileio.h" #include "parse-util.h" +#include "pretty-print.h" #include "resolvconf-compat.h" #include "resolvectl.h" #include "resolved-def.h" diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 44fa8ee62c..d4a4b3f40e 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -18,6 +18,7 @@ #include "netlink-util.h" #include "pager.h" #include "parse-util.h" +#include "pretty-print.h" #include "resolvconf-compat.h" #include "resolvectl.h" #include "resolved-def.h" diff --git a/src/run/run.c b/src/run/run.c index 697b517c65..dbd437593e 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -16,6 +16,7 @@ #include "format-util.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "process-util.h" #include "ptyfwd.h" #include "signal-util.h" diff --git a/src/shared/id128-print.c b/src/shared/id128-print.c index eea3ee5ca8..1b20b8f644 100644 --- a/src/shared/id128-print.c +++ b/src/shared/id128-print.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "id128-print.h" #include "log.h" +#include "pretty-print.h" #include "terminal-util.h" int id128_pretty_print(sd_id128_t id, bool pretty) { diff --git a/src/shared/main-func.h b/src/shared/main-func.h new file mode 100644 index 0000000000..24bf6c99bf --- /dev/null +++ b/src/shared/main-func.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include + +#include "pager.h" +#include "static-destruct.h" + +#define _DEFINE_MAIN_FUNCTION(impl, ret) \ + int main(int argc, char *argv[]) { \ + int r; \ + r = impl(argc, argv); \ + static_destruct(); \ + pager_close(); \ + return ret; \ + } + +/* Negative return values from impl are mapped to EXIT_FAILURE, and + * everything else means success! */ +#define DEFINE_MAIN_FUNCTION(impl) \ + _DEFINE_MAIN_FUNCTION(impl, r < 0 ? EXIT_FAILURE : EXIT_SUCCESS) + +/* Zero is mapped to EXIT_SUCCESS, negative values are mapped to EXIT_FAILURE, + * and postive values are propagated. + * Note: "true" means failure! */ +#define DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(impl) \ + _DEFINE_MAIN_FUNCTION(impl, r < 0 ? EXIT_FAILURE : r) diff --git a/src/shared/meson.build b/src/shared/meson.build index f7f63a5404..f7c2c7f560 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -96,6 +96,7 @@ shared_sources = files(''' machine-image.h machine-pool.c machine-pool.h + main-func.h module-util.h nsflags.c nsflags.h @@ -103,8 +104,12 @@ shared_sources = files(''' os-util.h output-mode.c output-mode.h + pager.c + pager.h path-lookup.c path-lookup.h + pretty-print.c + pretty-print.h ptyfwd.c ptyfwd.h reboot-util.c diff --git a/src/shared/pager.c b/src/shared/pager.c new file mode 100644 index 0000000000..88d9ef349e --- /dev/null +++ b/src/shared/pager.c @@ -0,0 +1,270 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "copy.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "locale-util.h" +#include "log.h" +#include "macro.h" +#include "pager.h" +#include "process-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" + +static pid_t pager_pid = 0; + +static int stored_stdout = -1; +static int stored_stderr = -1; +static bool stdout_redirected = false; +static bool stderr_redirected = false; + +_noreturn_ static void pager_fallback(void) { + int r; + + r = copy_bytes(STDIN_FILENO, STDOUT_FILENO, (uint64_t) -1, 0); + if (r < 0) { + log_error_errno(r, "Internal pager failed: %m"); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); +} + +static int no_quit_on_interrupt(int exe_name_fd, const char *less_opts) { + _cleanup_fclose_ FILE *file = NULL; + _cleanup_free_ char *line = NULL; + int r; + + assert(exe_name_fd >= 0); + assert(less_opts); + + /* This takes ownership of exe_name_fd */ + file = fdopen(exe_name_fd, "r"); + if (!file) { + safe_close(exe_name_fd); + return log_debug_errno(errno, "Failed to create FILE object: %m"); + } + + /* Find the last line */ + for (;;) { + _cleanup_free_ char *t = NULL; + + r = read_line(file, LONG_LINE_MAX, &t); + if (r < 0) + return r; + if (r == 0) + break; + + free_and_replace(line, t); + } + + /* We only treat "less" specially. + * Return true whenever option K is *not* set. */ + r = streq_ptr(line, "less") && !strchr(less_opts, 'K'); + + log_debug("Pager executable is \"%s\", options \"%s\", quit_on_interrupt: %s", + strnull(line), less_opts, yes_no(!r)); + return r; +} + +int pager_open(PagerFlags flags) { + _cleanup_close_pair_ int fd[2] = { -1, -1 }, exe_name_pipe[2] = { -1, -1 }; + _cleanup_strv_free_ char **pager_args = NULL; + const char *pager, *less_opts; + int r; + + if (flags & PAGER_DISABLE) + return 0; + + if (pager_pid > 0) + return 1; + + if (terminal_is_dumb()) + return 0; + + if (!is_main_thread()) + return -EPERM; + + pager = getenv("SYSTEMD_PAGER"); + if (!pager) + pager = getenv("PAGER"); + + if (pager) { + pager_args = strv_split(pager, WHITESPACE); + if (!pager_args) + return -ENOMEM; + + /* If the pager is explicitly turned off, honour it */ + if (strv_isempty(pager_args) || strv_equal(pager_args, STRV_MAKE("cat"))) + return 0; + } + + /* Determine and cache number of columns/lines before we spawn the pager so that we get the value from the + * actual tty */ + (void) columns(); + (void) lines(); + + if (pipe2(fd, O_CLOEXEC) < 0) + return log_error_errno(errno, "Failed to create pager pipe: %m"); + + /* This is a pipe to feed the name of the executed pager binary into the parent */ + if (pipe2(exe_name_pipe, O_CLOEXEC) < 0) + return log_error_errno(errno, "Failed to create exe_name pipe: %m"); + + /* Initialize a good set of less options */ + less_opts = getenv("SYSTEMD_LESS"); + if (!less_opts) + less_opts = "FRSXMK"; + if (flags & PAGER_JUMP_TO_END) + less_opts = strjoina(less_opts, " +G"); + + r = safe_fork("(pager)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pager_pid); + if (r < 0) + return r; + if (r == 0) { + const char *less_charset, *exe; + + /* In the child start the pager */ + + (void) dup2(fd[0], STDIN_FILENO); + safe_close_pair(fd); + + if (setenv("LESS", less_opts, 1) < 0) + _exit(EXIT_FAILURE); + + /* Initialize a good charset for less. This is + * particularly important if we output UTF-8 + * characters. */ + less_charset = getenv("SYSTEMD_LESSCHARSET"); + if (!less_charset && is_locale_utf8()) + less_charset = "utf-8"; + if (less_charset && + setenv("LESSCHARSET", less_charset, 1) < 0) + _exit(EXIT_FAILURE); + + if (pager_args) { + if (loop_write(exe_name_pipe[1], pager_args[0], strlen(pager_args[0]) + 1, false) < 0) + _exit(EXIT_FAILURE); + + execvp(pager_args[0], pager_args); + } + + /* Debian's alternatives command for pagers is + * called 'pager'. Note that we do not call + * sensible-pagers here, since that is just a + * shell script that implements a logic that + * is similar to this one anyway, but is + * Debian-specific. */ + FOREACH_STRING(exe, "pager", "less", "more") { + if (loop_write(exe_name_pipe[1], exe, strlen(exe) + 1, false) < 0) + _exit(EXIT_FAILURE); + execlp(exe, exe, NULL); + } + + if (loop_write(exe_name_pipe[1], "(built-in)", strlen("(built-in") + 1, false) < 0) + _exit(EXIT_FAILURE); + pager_fallback(); + /* not reached */ + } + + /* Return in the parent */ + stored_stdout = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 3); + if (dup2(fd[1], STDOUT_FILENO) < 0) { + stored_stdout = safe_close(stored_stdout); + return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); + } + stdout_redirected = true; + + stored_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3); + if (dup2(fd[1], STDERR_FILENO) < 0) { + stored_stderr = safe_close(stored_stderr); + return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); + } + stderr_redirected = true; + + exe_name_pipe[1] = safe_close(exe_name_pipe[1]); + + r = no_quit_on_interrupt(TAKE_FD(exe_name_pipe[0]), less_opts); + if (r < 0) + return r; + if (r > 0) + (void) ignore_signals(SIGINT, -1); + + return 1; +} + +void pager_close(void) { + + if (pager_pid <= 0) + return; + + /* Inform pager that we are done */ + (void) fflush(stdout); + if (stdout_redirected) + if (stored_stdout < 0 || dup2(stored_stdout, STDOUT_FILENO) < 0) + (void) close(STDOUT_FILENO); + stored_stdout = safe_close(stored_stdout); + (void) fflush(stderr); + if (stderr_redirected) + if (stored_stderr < 0 || dup2(stored_stderr, STDERR_FILENO) < 0) + (void) close(STDERR_FILENO); + stored_stderr = safe_close(stored_stderr); + stdout_redirected = stderr_redirected = false; + + (void) kill(pager_pid, SIGCONT); + (void) wait_for_terminate(pager_pid, NULL); + pager_pid = 0; +} + +bool pager_have(void) { + return pager_pid > 0; +} + +int show_man_page(const char *desc, bool null_stdio) { + const char *args[4] = { "man", NULL, NULL, NULL }; + char *e = NULL; + pid_t pid; + size_t k; + int r; + + k = strlen(desc); + + if (desc[k-1] == ')') + e = strrchr(desc, '('); + + if (e) { + char *page = NULL, *section = NULL; + + page = strndupa(desc, e - desc); + section = strndupa(e + 1, desc + k - e - 2); + + args[1] = section; + args[2] = page; + } else + args[1] = desc; + + r = safe_fork("(man)", FORK_RESET_SIGNALS|FORK_DEATHSIG|(null_stdio ? FORK_NULL_STDIO : 0)|FORK_LOG, &pid); + if (r < 0) + return r; + if (r == 0) { + /* Child */ + execvp(args[0], (char**) args); + log_error_errno(errno, "Failed to execute man: %m"); + _exit(EXIT_FAILURE); + } + + return wait_for_terminate_and_check(NULL, pid, 0); +} diff --git a/src/shared/pager.h b/src/shared/pager.h new file mode 100644 index 0000000000..8299e23856 --- /dev/null +++ b/src/shared/pager.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include + +#include "macro.h" + +typedef enum PagerFlags { + PAGER_DISABLE = 1 << 0, + PAGER_JUMP_TO_END = 1 << 1, +} PagerFlags; + +int pager_open(PagerFlags flags); +void pager_close(void); +bool pager_have(void) _pure_; + +int show_man_page(const char *page, bool null_stdio); diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c new file mode 100644 index 0000000000..1086018645 --- /dev/null +++ b/src/shared/pretty-print.c @@ -0,0 +1,233 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include +#include +#include + +#include "alloc-util.h" +#include "conf-files.h" +#include "def.h" +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "pager.h" +#include "path-util.h" +#include "pretty-print.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "util.h" + +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 proper 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); +} + +int terminal_urlify_man(const char *page, const char *section, char **ret) { + const char *url, *text; + + url = strjoina("man:", page, "(", section, ")"); + text = strjoina(page, "(", section, ") man page"); + + return terminal_urlify(url, text, ret); +} + +static int cat_file(const char *filename, bool newline) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *urlified = NULL; + int r; + + f = fopen(filename, "re"); + if (!f) + return -errno; + + r = terminal_urlify_path(filename, NULL, &urlified); + if (r < 0) + return r; + + printf("%s%s# %s%s\n", + newline ? "\n" : "", + ansi_highlight_blue(), + urlified, + ansi_normal()); + fflush(stdout); + + for (;;) { + _cleanup_free_ char *line = NULL; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read \"%s\": %m", filename); + if (r == 0) + break; + + puts(line); + } + + return 0; +} + +int cat_files(const char *file, char **dropins, CatFlags flags) { + char **path; + int r; + + if (file) { + r = cat_file(file, false); + if (r == -ENOENT && (flags & CAT_FLAGS_MAIN_FILE_OPTIONAL)) + printf("%s# config file %s not found%s\n", + ansi_highlight_magenta(), + file, + ansi_normal()); + else if (r < 0) + return log_warning_errno(r, "Failed to cat %s: %m", file); + } + + STRV_FOREACH(path, dropins) { + r = cat_file(*path, file || path != dropins); + if (r < 0) + return log_warning_errno(r, "Failed to cat %s: %m", *path); + } + + return 0; +} + +void print_separator(void) { + + /* Outputs a separator line that resolves to whitespace when copied from the terminal. We do that by outputting + * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */ + + if (underline_enabled()) { + size_t i, c; + + c = columns(); + + flockfile(stdout); + fputs_unlocked(ANSI_UNDERLINE, stdout); + + for (i = 0; i < c; i++) + fputc_unlocked(' ', stdout); + + fputs_unlocked(ANSI_NORMAL "\n\n", stdout); + funlockfile(stdout); + } else + fputs("\n\n", stdout); +} + +int conf_files_cat(const char *root, const char *name) { + _cleanup_strv_free_ char **dirs = NULL, **files = NULL; + _cleanup_free_ char *path = NULL; + const char *dir; + char **t; + int r; + + NULSTR_FOREACH(dir, CONF_PATHS_NULSTR("")) { + assert(endswith(dir, "/")); + r = strv_extendf(&dirs, "%s%s.d", dir, name); + if (r < 0) + return log_error_errno(r, "Failed to build directory list: %m"); + } + + r = conf_files_list_strv(&files, ".conf", root, 0, (const char* const*) dirs); + if (r < 0) + return log_error_errno(r, "Failed to query file list: %m"); + + path = path_join(root, "/etc", name); + if (!path) + return log_oom(); + + if (DEBUG_LOGGING) { + log_debug("Looking for configuration in:"); + log_debug(" %s", path); + STRV_FOREACH(t, dirs) + log_debug(" %s/*.conf", *t); + } + + /* show */ + return cat_files(path, files, CAT_FLAGS_MAIN_FILE_OPTIONAL); +} diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h new file mode 100644 index 0000000000..cf9a70dd9e --- /dev/null +++ b/src/shared/pretty-print.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +void print_separator(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); + +typedef enum CatFlags { + CAT_FLAGS_MAIN_FILE_OPTIONAL = 1 << 0, +} CatFlags; + +int cat_files(const char *file, char **dropins, CatFlags flags); +int conf_files_cat(const char *root, const char *name); diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index f86d4c628f..9e4831e35f 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -18,11 +18,11 @@ #include "log.h" #include "main-func.h" #include "parse-util.h" +#include "pretty-print.h" #include "sleep-config.h" #include "stdio-util.h" #include "string-util.h" #include "strv.h" -#include "terminal-util.h" #include "util.h" static char* arg_verb = NULL; diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index 227378edaa..36ba5422ed 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -20,10 +20,10 @@ #include "log.h" #include "parse-util.h" #include "path-util.h" +#include "pretty-print.h" #include "set.h" #include "socket-util.h" #include "string-util.h" -#include "terminal-util.h" #include "util.h" #define BUFFER_SIZE (256 * 1024) diff --git a/src/sysctl/sysctl.c b/src/sysctl/sysctl.c index d04a2cff15..adfb546756 100644 --- a/src/sysctl/sysctl.c +++ b/src/sysctl/sysctl.c @@ -17,10 +17,10 @@ #include "main-func.h" #include "pager.h" #include "path-util.h" +#include "pretty-print.h" #include "string-util.h" #include "strv.h" #include "sysctl-util.h" -#include "terminal-util.h" #include "util.h" static char **arg_prefixes = NULL; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 304dd1bbfb..a54d5abebd 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -56,6 +56,7 @@ #include "parse-util.h" #include "path-lookup.h" #include "path-util.h" +#include "pretty-print.h" #include "proc-cmdline.h" #include "process-util.h" #include "reboot-util.h" diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index dfaff66389..a348ec497a 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -14,12 +14,12 @@ #include "hashmap.h" #include "pager.h" #include "path-util.h" +#include "pretty-print.h" #include "selinux-util.h" #include "smack-util.h" #include "specifier.h" #include "string-util.h" #include "strv.h" -#include "terminal-util.h" #include "uid-range.h" #include "user-util.h" #include "utf8.h" diff --git a/src/test/meson.build b/src/test/meson.build index 24c52a3579..ade905733e 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -305,6 +305,10 @@ tests += [ [], []], + [['src/test/test-pretty-print.c'], + [], + []], + [['src/test/test-uid-range.c'], [], []], diff --git a/src/test/test-pretty-print.c b/src/test/test-pretty-print.c new file mode 100644 index 0000000000..53ec512ec3 --- /dev/null +++ b/src/test/test-pretty-print.c @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include + +#include "alloc-util.h" +#include "macro.h" +#include "pretty-print.h" +#include "strv.h" +#include "tests.h" + +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); +} + +static void test_cat_files(void) { + assert_se(cat_files("/no/such/file", NULL, 0) == -ENOENT); + assert_se(cat_files("/no/such/file", NULL, CAT_FLAGS_MAIN_FILE_OPTIONAL) == 0); + + if (access("/etc/fstab", R_OK) >= 0) + assert_se(cat_files("/etc/fstab", STRV_MAKE("/etc/fstab", "/etc/fstab"), 0) == 0); +} + +int main(int argc, char *argv[]) { + test_setup_logging(LOG_INFO); + + test_terminal_urlify(); + test_cat_files(); + + print_separator(); + + return 0; +} diff --git a/src/test/test-strip-tab-ansi.c b/src/test/test-strip-tab-ansi.c index 362f862221..fae384ef9b 100644 --- a/src/test/test-strip-tab-ansi.c +++ b/src/test/test-strip-tab-ansi.c @@ -3,6 +3,7 @@ #include #include "alloc-util.h" +#include "pretty-print.h" #include "string-util.h" #include "terminal-util.h" #include "util.h" diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index fbe8133b10..7edf7681c2 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -6,7 +6,7 @@ #include "alloc-util.h" #include "fd-util.h" #include "fileio.h" -#include "log.h" +#include "tests.h" #include "macro.h" #include "strv.h" #include "terminal-util.h" @@ -55,36 +55,11 @@ 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); -} - -static void test_cat_files(void) { - assert_se(cat_files("/no/such/file", NULL, 0) == -ENOENT); - assert_se(cat_files("/no/such/file", NULL, CAT_FLAGS_MAIN_FILE_OPTIONAL) == 0); - - if (access("/etc/fstab", R_OK) >= 0) - assert_se(cat_files("/etc/fstab", STRV_MAKE("/etc/fstab", "/etc/fstab"), 0) == 0); -} - int main(int argc, char *argv[]) { - log_parse_environment(); - log_open(); + test_setup_logging(LOG_INFO); test_default_term_for_tty(); test_read_one_char(); - test_terminal_urlify(); - test_cat_files(); - - print_separator(); return 0; } diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index c6d9a23bcf..46712b71fc 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -14,6 +14,7 @@ #include "main-func.h" #include "pager.h" #include "parse-util.h" +#include "pretty-print.h" #include "spawn-polkit-agent.h" #include "sparse-endian.h" #include "string-table.h" diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 9de9498d48..253d3af15a 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -46,6 +46,7 @@ #include "parse-util.h" #include "path-lookup.h" #include "path-util.h" +#include "pretty-print.h" #include "rm-rf.h" #include "selinux-util.h" #include "set.h" @@ -55,7 +56,6 @@ #include "string-table.h" #include "string-util.h" #include "strv.h" -#include "terminal-util.h" #include "umask-util.h" #include "user-util.h" #include "util.h" diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c index 0c966af499..c147493bf8 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -33,6 +33,7 @@ #include "main-func.h" #include "mkdir.h" #include "path-util.h" +#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "socket-util.h" diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 8ed679698a..1e1a92e138 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -6,9 +6,9 @@ #include #include "alloc-util.h" +#include "pretty-print.h" #include "selinux-util.h" #include "string-util.h" -#include "terminal-util.h" #include "udevadm.h" #include "udev-util.h" #include "verbs.h" diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 516453877f..c1c266e8c6 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -49,6 +49,7 @@ #include "mkdir.h" #include "netlink-util.h" #include "parse-util.h" +#include "pretty-print.h" #include "proc-cmdline.h" #include "process-util.h" #include "selinux-util.h" @@ -57,7 +58,6 @@ #include "string-util.h" #include "strxcpyx.h" #include "syslog-util.h" -#include "terminal-util.h" #include "udev-builtin.h" #include "udev-ctrl.h" #include "udev-util.h" diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index a10baf2bfd..dbfd361d87 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -8,6 +8,7 @@ #include "crypt-util.h" #include "hexdecoct.h" #include "log.h" +#include "pretty-print.h" #include "string-util.h" #include "terminal-util.h" -- cgit v1.2.1