]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/pretty-print.c
Merge pull request #13131 from yuwata/pstore-follow-ups
[thirdparty/systemd.git] / src / shared / pretty-print.c
CommitLineData
294bf0c3
ZJS
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
3#include <sys/utsname.h>
4#include <errno.h>
5#include <stdio.h>
6
7#include "alloc-util.h"
8#include "conf-files.h"
9#include "def.h"
10#include "env-util.h"
11#include "fd-util.h"
12#include "fileio.h"
13#include "pager.h"
14#include "path-util.h"
15#include "pretty-print.h"
16#include "string-util.h"
17#include "strv.h"
18#include "terminal-util.h"
19#include "util.h"
20
21static bool urlify_enabled(void) {
22 static int cached_urlify_enabled = -1;
23
24 /* Unfortunately 'less' doesn't support links like this yet 😭, hence let's disable this as long as there's a
25 * pager in effect. Let's drop this check as soon as less got fixed a and enough time passed so that it's safe
26 * to assume that a link-enabled 'less' version has hit most installations. */
27
28 if (cached_urlify_enabled < 0) {
29 int val;
30
31 val = getenv_bool("SYSTEMD_URLIFY");
32 if (val >= 0)
33 cached_urlify_enabled = val;
34 else
35 cached_urlify_enabled = colors_enabled() && !pager_have();
36 }
37
38 return cached_urlify_enabled;
39}
40
41int terminal_urlify(const char *url, const char *text, char **ret) {
42 char *n;
43
44 assert(url);
45
46 /* Takes an URL and a pretty string and formats it as clickable link for the terminal. See
47 * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */
48
49 if (isempty(text))
50 text = url;
51
52 if (urlify_enabled())
53 n = strjoin("\x1B]8;;", url, "\a", text, "\x1B]8;;\a");
54 else
55 n = strdup(text);
56 if (!n)
57 return -ENOMEM;
58
59 *ret = n;
60 return 0;
61}
62
62d6a1cc 63int file_url_from_path(const char *path, char **ret) {
294bf0c3
ZJS
64 _cleanup_free_ char *absolute = NULL;
65 struct utsname u;
62d6a1cc
LP
66 char *url = NULL;
67 int r;
68
69 if (uname(&u) < 0)
70 return -errno;
71
72 if (!path_is_absolute(path)) {
73 r = path_make_absolute_cwd(path, &absolute);
74 if (r < 0)
75 return r;
76
77 path = absolute;
78 }
79
80 /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local
81 * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested
82 * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly
83 * careful with validating the strings either. */
84
85 url = strjoin("file://", u.nodename, path);
86 if (!url)
87 return -ENOMEM;
88
89 *ret = url;
90 return 0;
91}
92
93int terminal_urlify_path(const char *path, const char *text, char **ret) {
94 _cleanup_free_ char *url = NULL;
294bf0c3
ZJS
95 int r;
96
97 assert(path);
98
99 /* Much like terminal_urlify() above, but takes a file system path as input
100 * and turns it into a proper file:// URL first. */
101
102 if (isempty(path))
103 return -EINVAL;
104
105 if (isempty(text))
106 text = path;
107
108 if (!urlify_enabled()) {
109 char *n;
110
111 n = strdup(text);
112 if (!n)
113 return -ENOMEM;
114
115 *ret = n;
116 return 0;
117 }
118
62d6a1cc
LP
119 r = file_url_from_path(path, &url);
120 if (r < 0)
121 return r;
294bf0c3
ZJS
122
123 return terminal_urlify(url, text, ret);
124}
125
126int terminal_urlify_man(const char *page, const char *section, char **ret) {
127 const char *url, *text;
128
129 url = strjoina("man:", page, "(", section, ")");
130 text = strjoina(page, "(", section, ") man page");
131
132 return terminal_urlify(url, text, ret);
133}
134
135static int cat_file(const char *filename, bool newline) {
136 _cleanup_fclose_ FILE *f = NULL;
137 _cleanup_free_ char *urlified = NULL;
138 int r;
139
140 f = fopen(filename, "re");
141 if (!f)
142 return -errno;
143
144 r = terminal_urlify_path(filename, NULL, &urlified);
145 if (r < 0)
146 return r;
147
148 printf("%s%s# %s%s\n",
149 newline ? "\n" : "",
150 ansi_highlight_blue(),
151 urlified,
152 ansi_normal());
153 fflush(stdout);
154
155 for (;;) {
156 _cleanup_free_ char *line = NULL;
157
158 r = read_line(f, LONG_LINE_MAX, &line);
159 if (r < 0)
160 return log_error_errno(r, "Failed to read \"%s\": %m", filename);
161 if (r == 0)
162 break;
163
164 puts(line);
165 }
166
167 return 0;
168}
169
170int cat_files(const char *file, char **dropins, CatFlags flags) {
171 char **path;
172 int r;
173
174 if (file) {
175 r = cat_file(file, false);
176 if (r == -ENOENT && (flags & CAT_FLAGS_MAIN_FILE_OPTIONAL))
177 printf("%s# config file %s not found%s\n",
178 ansi_highlight_magenta(),
179 file,
180 ansi_normal());
181 else if (r < 0)
182 return log_warning_errno(r, "Failed to cat %s: %m", file);
183 }
184
185 STRV_FOREACH(path, dropins) {
186 r = cat_file(*path, file || path != dropins);
187 if (r < 0)
188 return log_warning_errno(r, "Failed to cat %s: %m", *path);
189 }
190
191 return 0;
192}
193
194void print_separator(void) {
195
196 /* Outputs a separator line that resolves to whitespace when copied from the terminal. We do that by outputting
197 * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */
198
199 if (underline_enabled()) {
200 size_t i, c;
201
202 c = columns();
203
204 flockfile(stdout);
205 fputs_unlocked(ANSI_UNDERLINE, stdout);
206
207 for (i = 0; i < c; i++)
208 fputc_unlocked(' ', stdout);
209
210 fputs_unlocked(ANSI_NORMAL "\n\n", stdout);
211 funlockfile(stdout);
212 } else
213 fputs("\n\n", stdout);
214}
215
f1d9d36a
ZJS
216static int guess_type(const char **name, bool *is_usr, bool *is_collection, const char **extension) {
217 /* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/,
218 * i.e. a collection of directories without a main config file. */
219
220 _cleanup_free_ char *n = NULL;
221 bool usr = false, coll = false;
222 const char *ext = ".conf";
223
224 if (path_equal(*name, "environment.d"))
225 /* Special case: we need to include /etc/environment in the search path, even
226 * though the whole concept is called environment.d. */
227 *name = "environment";
228
229 n = strdup(*name);
230 if (!n)
231 return log_oom();
232
233 delete_trailing_chars(n, "/");
234
235 if (endswith(n, ".d"))
236 coll = true;
237
238 if (path_equal(n, "environment"))
239 usr = true;
240
241 if (path_equal(n, "udev/hwdb.d"))
242 ext = ".hwdb";
243
244 if (path_equal(n, "udev/rules.d"))
245 ext = ".rules";
246
ea39de2f
ZJS
247 if (path_equal(n, "kernel/install.d"))
248 ext = ".install";
249
f1d9d36a
ZJS
250 if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) {
251 coll = true;
252 ext = ".preset";
253 }
254
255 if (path_equal(n, "systemd/user-preset"))
256 usr = true;
257
258 *is_usr = usr;
259 *is_collection = coll;
260 *extension = ext;
261 return 0;
262}
263
294bf0c3
ZJS
264int conf_files_cat(const char *root, const char *name) {
265 _cleanup_strv_free_ char **dirs = NULL, **files = NULL;
266 _cleanup_free_ char *path = NULL;
f1d9d36a
ZJS
267 char **dir;
268 bool is_usr, is_collection;
269 const char *extension;
294bf0c3
ZJS
270 char **t;
271 int r;
272
f1d9d36a
ZJS
273 r = guess_type(&name, &is_usr, &is_collection, &extension);
274 if (r < 0)
275 return r;
276
277 STRV_FOREACH(dir, is_usr ? CONF_PATHS_USR_STRV("") : CONF_PATHS_STRV("")) {
278 assert(endswith(*dir, "/"));
279 r = strv_extendf(&dirs, "%s%s%s", *dir, name,
280 is_collection ? "" : ".d");
294bf0c3
ZJS
281 if (r < 0)
282 return log_error_errno(r, "Failed to build directory list: %m");
283 }
284
f1d9d36a 285 r = conf_files_list_strv(&files, extension, root, 0, (const char* const*) dirs);
294bf0c3
ZJS
286 if (r < 0)
287 return log_error_errno(r, "Failed to query file list: %m");
288
f1d9d36a
ZJS
289 if (!is_collection) {
290 path = path_join(root, "/etc", name);
291 if (!path)
292 return log_oom();
293 }
294bf0c3
ZJS
294
295 if (DEBUG_LOGGING) {
296 log_debug("Looking for configuration in:");
f1d9d36a
ZJS
297 if (path)
298 log_debug(" %s", path);
294bf0c3 299 STRV_FOREACH(t, dirs)
f1d9d36a 300 log_debug(" %s/*%s", *t, extension);
294bf0c3
ZJS
301 }
302
303 /* show */
304 return cat_files(path, files, CAT_FLAGS_MAIN_FILE_OPTIONAL);
305}