mirror of
https://github.com/systemd/systemd.git
synced 2026-07-15 03:50:44 +00:00
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).
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#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)
|
||||
@@ -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
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user