summaryrefslogtreecommitdiffstats
path: root/src/shared/pretty-print.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/shared/pretty-print.c421
1 files changed, 421 insertions, 0 deletions
diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c
new file mode 100644
index 0000000..2833063
--- /dev/null
+++ b/src/shared/pretty-print.c
@@ -0,0 +1,421 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/utsname.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "constants.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"
+
+void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) {
+ char *p = buffer;
+
+ assert(buflen >= CYLON_BUFFER_EXTRA + width + 1);
+ assert(pos <= width+1); /* 0 or width+1 mean that the center light is behind the corner */
+
+ if (pos > 1) {
+ if (pos > 2)
+ p = mempset(p, ' ', pos-2);
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_RED);
+ *p++ = '*';
+ }
+
+ if (pos > 0 && pos <= width) {
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_HIGHLIGHT_RED);
+ *p++ = '*';
+ }
+
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_NORMAL);
+
+ if (pos < width) {
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_RED);
+ *p++ = '*';
+ if (pos < width-1)
+ p = mempset(p, ' ', width-1-pos);
+ if (log_get_show_color())
+ p = stpcpy(p, ANSI_NORMAL);
+ }
+
+ *p = '\0';
+}
+
+bool urlify_enabled(void) {
+#if ENABLE_URLIFY
+ static int cached_urlify_enabled = -1;
+
+ 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();
+ }
+
+ return cached_urlify_enabled;
+#else
+ return 0;
+#endif
+}
+
+int terminal_urlify(const char *url, const char *text, char **ret) {
+ char *n;
+
+ assert(url);
+
+ /* Takes a 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 file_url_from_path(const char *path, char **ret) {
+ _cleanup_free_ char *absolute = NULL;
+ struct utsname u;
+ char *url = NULL;
+ int r;
+
+ 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 = strjoin("file://", u.nodename, path);
+ if (!url)
+ return -ENOMEM;
+
+ *ret = url;
+ return 0;
+}
+
+int terminal_urlify_path(const char *path, const char *text, char **ret) {
+ _cleanup_free_ char *url = NULL;
+ 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;
+ }
+
+ r = file_url_from_path(path, &url);
+ if (r < 0)
+ return r;
+
+ 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);
+}
+
+typedef enum {
+ LINE_SECTION,
+ LINE_COMMENT,
+ LINE_NORMAL,
+} LineType;
+
+static LineType classify_line_type(const char *line, CatFlags flags) {
+ const char *t = skip_leading_chars(line, WHITESPACE);
+
+ if ((flags & CAT_FORMAT_HAS_SECTIONS) && *t == '[')
+ return LINE_SECTION;
+ if (IN_SET(*t, '#', ';', '\0'))
+ return LINE_COMMENT;
+ return LINE_NORMAL;
+}
+
+static int cat_file(const char *filename, bool newline, CatFlags flags) {
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *urlified = NULL, *section = NULL, *old_section = 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;
+
+ LineType line_type = classify_line_type(line, flags);
+ if (flags & CAT_TLDR) {
+ if (line_type == LINE_SECTION) {
+ /* The start of a section, let's not print it yet. */
+ free_and_replace(section, line);
+ continue;
+ }
+
+ if (line_type == LINE_COMMENT)
+ continue;
+
+ /* Before we print the actual line, print the last section header */
+ if (section) {
+ /* Do not print redundant section headers */
+ if (!streq_ptr(section, old_section))
+ printf("%s%s%s\n",
+ ansi_highlight_cyan(),
+ section,
+ ansi_normal());
+
+ free_and_replace(old_section, section);
+ }
+ }
+
+ printf("%s%s%s\n",
+ line_type == LINE_SECTION ? ansi_highlight_cyan() :
+ line_type == LINE_COMMENT ? ansi_highlight_grey() :
+ "",
+ line,
+ line_type != LINE_NORMAL ? ansi_normal() : "");
+ }
+
+ return 0;
+}
+
+int cat_files(const char *file, char **dropins, CatFlags flags) {
+ int r;
+
+ if (file) {
+ r = cat_file(file, /* newline= */ false, flags);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to cat %s: %m", file);
+ }
+
+ STRV_FOREACH(path, dropins) {
+ r = cat_file(*path, /* newline= */ file || path != dropins, flags);
+ 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);
+}
+
+static int guess_type(const char **name, char ***prefixes, bool *is_collection, const char **extension) {
+ /* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/,
+ * i.e. a collection of directories without a main config file.
+ * Incidentally, all those formats don't use sections. So we return a single
+ * is_collection boolean, which also means that the format doesn't use sections.
+ */
+
+ _cleanup_free_ char *n = NULL;
+ bool usr = false, run = false, coll = false;
+ const char *ext = ".conf";
+ /* This is static so that the array doesn't get deallocated when we exit the function */
+ static const char* const std_prefixes[] = { CONF_PATHS(""), NULL };
+ static const char* const usr_prefixes[] = { CONF_PATHS_USR(""), NULL };
+ static const char* const run_prefixes[] = { "/run/", NULL };
+
+ if (path_equal(*name, "environment.d"))
+ /* Special case: we need to include /etc/environment in the search path, even
+ * though the whole concept is called environment.d. */
+ *name = "environment";
+
+ n = strdup(*name);
+ if (!n)
+ return log_oom();
+
+ /* All systemd-style config files should support the /usr-/etc-/run split and
+ * dropins. Let's add a blanket rule that allows us to support them without keeping
+ * an explicit list. */
+ if (path_startswith(n, "systemd") && endswith(n, ".conf"))
+ usr = true;
+
+ delete_trailing_chars(n, "/");
+
+ if (endswith(n, ".d"))
+ coll = true;
+
+ if (path_equal(n, "environment"))
+ usr = true;
+
+ if (path_equal(n, "udev/hwdb.d"))
+ ext = ".hwdb";
+
+ if (path_equal(n, "udev/rules.d"))
+ ext = ".rules";
+
+ if (path_equal(n, "kernel/install.d"))
+ ext = ".install";
+
+ if (path_equal(n, "systemd/ntp-units.d")) {
+ coll = true;
+ ext = ".list";
+ }
+
+ if (path_equal(n, "systemd/relabel-extra.d")) {
+ coll = run = true;
+ ext = ".relabel";
+ }
+
+ if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) {
+ coll = true;
+ ext = ".preset";
+ }
+
+ if (path_equal(n, "systemd/user-preset"))
+ usr = true;
+
+ *prefixes = (char**) (usr ? usr_prefixes : run ? run_prefixes : std_prefixes);
+ *is_collection = coll;
+ *extension = ext;
+ return 0;
+}
+
+int conf_files_cat(const char *root, const char *name, CatFlags flags) {
+ _cleanup_strv_free_ char **dirs = NULL, **files = NULL;
+ _cleanup_free_ char *path = NULL;
+ char **prefixes = NULL; /* explicit initialization to appease gcc */
+ bool is_collection;
+ const char *extension;
+ int r;
+
+ r = guess_type(&name, &prefixes, &is_collection, &extension);
+ if (r < 0)
+ return r;
+ assert(prefixes);
+ assert(extension);
+
+ STRV_FOREACH(prefix, prefixes) {
+ assert(endswith(*prefix, "/"));
+ r = strv_extendf(&dirs, "%s%s%s", *prefix, name,
+ is_collection ? "" : ".d");
+ if (r < 0)
+ return log_error_errno(r, "Failed to build directory list: %m");
+ }
+
+ if (DEBUG_LOGGING) {
+ log_debug("Looking for configuration in:");
+ if (!is_collection)
+ STRV_FOREACH(prefix, prefixes)
+ log_debug(" %s%s%s", strempty(root), *prefix, name);
+
+ STRV_FOREACH(t, dirs)
+ log_debug(" %s%s/*%s", strempty(root), *t, extension);
+ }
+
+ /* First locate the main config file, if any */
+ if (!is_collection) {
+ STRV_FOREACH(prefix, prefixes) {
+ path = path_join(root, *prefix, name);
+ if (!path)
+ return log_oom();
+ if (access(path, F_OK) == 0)
+ break;
+ path = mfree(path);
+ }
+
+ if (!path)
+ printf("%s# Main configuration file %s not found%s\n",
+ ansi_highlight_magenta(),
+ name,
+ ansi_normal());
+ }
+
+ /* Then locate the drop-ins, if any */
+ r = conf_files_list_strv(&files, extension, root, 0, (const char* const*) dirs);
+ if (r < 0)
+ return log_error_errno(r, "Failed to query file list: %m");
+
+ /* Show */
+ if (is_collection)
+ flags |= CAT_FORMAT_HAS_SECTIONS;
+
+ return cat_files(path, files, flags);
+}