]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/pretty-print.c
Merge pull request #31000 from flatcar-hub/krnowak/mutable-overlays
[thirdparty/systemd.git] / src / shared / pretty-print.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
294bf0c3
ZJS
2
3#include <sys/utsname.h>
4#include <errno.h>
5#include <stdio.h>
6
7#include "alloc-util.h"
3ef072ee 8#include "color-util.h"
294bf0c3 9#include "conf-files.h"
28db6fbf 10#include "constants.h"
294bf0c3
ZJS
11#include "env-util.h"
12#include "fd-util.h"
13#include "fileio.h"
14#include "pager.h"
15#include "path-util.h"
16#include "pretty-print.h"
17#include "string-util.h"
18#include "strv.h"
19#include "terminal-util.h"
294bf0c3 20
d61a4dbb
YW
21void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) {
22 char *p = buffer;
23
24 assert(buflen >= CYLON_BUFFER_EXTRA + width + 1);
25 assert(pos <= width+1); /* 0 or width+1 mean that the center light is behind the corner */
26
27 if (pos > 1) {
28 if (pos > 2)
29 p = mempset(p, ' ', pos-2);
30 if (log_get_show_color())
31 p = stpcpy(p, ANSI_RED);
32 *p++ = '*';
33 }
34
35 if (pos > 0 && pos <= width) {
36 if (log_get_show_color())
37 p = stpcpy(p, ANSI_HIGHLIGHT_RED);
38 *p++ = '*';
39 }
40
41 if (log_get_show_color())
42 p = stpcpy(p, ANSI_NORMAL);
43
44 if (pos < width) {
45 if (log_get_show_color())
46 p = stpcpy(p, ANSI_RED);
47 *p++ = '*';
48 if (pos < width-1)
49 p = mempset(p, ' ', width-1-pos);
50 if (log_get_show_color())
51 p = stpcpy(p, ANSI_NORMAL);
52 }
53
54 *p = '\0';
55}
56
422c8251 57bool urlify_enabled(void) {
e5d86ebe 58#if ENABLE_URLIFY
294bf0c3
ZJS
59 static int cached_urlify_enabled = -1;
60
294bf0c3
ZJS
61 if (cached_urlify_enabled < 0) {
62 int val;
63
64 val = getenv_bool("SYSTEMD_URLIFY");
65 if (val >= 0)
66 cached_urlify_enabled = val;
67 else
ebef02dd 68 cached_urlify_enabled = colors_enabled();
294bf0c3
ZJS
69 }
70
71 return cached_urlify_enabled;
e5d86ebe
JH
72#else
73 return 0;
74#endif
294bf0c3
ZJS
75}
76
77int terminal_urlify(const char *url, const char *text, char **ret) {
78 char *n;
79
80 assert(url);
81
5bc9ea07 82 /* Takes a URL and a pretty string and formats it as clickable link for the terminal. See
294bf0c3
ZJS
83 * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */
84
85 if (isempty(text))
86 text = url;
87
88 if (urlify_enabled())
89 n = strjoin("\x1B]8;;", url, "\a", text, "\x1B]8;;\a");
90 else
91 n = strdup(text);
92 if (!n)
93 return -ENOMEM;
94
95 *ret = n;
96 return 0;
97}
98
62d6a1cc 99int file_url_from_path(const char *path, char **ret) {
294bf0c3
ZJS
100 _cleanup_free_ char *absolute = NULL;
101 struct utsname u;
62d6a1cc
LP
102 char *url = NULL;
103 int r;
104
105 if (uname(&u) < 0)
106 return -errno;
107
108 if (!path_is_absolute(path)) {
109 r = path_make_absolute_cwd(path, &absolute);
110 if (r < 0)
111 return r;
112
113 path = absolute;
114 }
115
116 /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local
117 * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested
118 * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly
119 * careful with validating the strings either. */
120
121 url = strjoin("file://", u.nodename, path);
122 if (!url)
123 return -ENOMEM;
124
125 *ret = url;
126 return 0;
127}
128
129int terminal_urlify_path(const char *path, const char *text, char **ret) {
130 _cleanup_free_ char *url = NULL;
294bf0c3
ZJS
131 int r;
132
133 assert(path);
134
135 /* Much like terminal_urlify() above, but takes a file system path as input
136 * and turns it into a proper file:// URL first. */
137
138 if (isempty(path))
139 return -EINVAL;
140
141 if (isempty(text))
142 text = path;
143
144 if (!urlify_enabled()) {
145 char *n;
146
147 n = strdup(text);
148 if (!n)
149 return -ENOMEM;
150
151 *ret = n;
152 return 0;
153 }
154
62d6a1cc
LP
155 r = file_url_from_path(path, &url);
156 if (r < 0)
157 return r;
294bf0c3
ZJS
158
159 return terminal_urlify(url, text, ret);
160}
161
162int terminal_urlify_man(const char *page, const char *section, char **ret) {
163 const char *url, *text;
164
165 url = strjoina("man:", page, "(", section, ")");
166 text = strjoina(page, "(", section, ") man page");
167
168 return terminal_urlify(url, text, ret);
169}
170
a9e68035
ZJS
171typedef enum {
172 LINE_SECTION,
173 LINE_COMMENT,
174 LINE_NORMAL,
175} LineType;
176
177static LineType classify_line_type(const char *line, CatFlags flags) {
178 const char *t = skip_leading_chars(line, WHITESPACE);
179
180 if ((flags & CAT_FORMAT_HAS_SECTIONS) && *t == '[')
181 return LINE_SECTION;
182 if (IN_SET(*t, '#', ';', '\0'))
183 return LINE_COMMENT;
184 return LINE_NORMAL;
185}
186
063c8382 187static int cat_file(const char *filename, bool newline, CatFlags flags) {
294bf0c3 188 _cleanup_fclose_ FILE *f = NULL;
c04cec12 189 _cleanup_free_ char *urlified = NULL, *section = NULL, *old_section = NULL;
294bf0c3
ZJS
190 int r;
191
192 f = fopen(filename, "re");
193 if (!f)
194 return -errno;
195
196 r = terminal_urlify_path(filename, NULL, &urlified);
197 if (r < 0)
198 return r;
199
200 printf("%s%s# %s%s\n",
201 newline ? "\n" : "",
202 ansi_highlight_blue(),
203 urlified,
204 ansi_normal());
205 fflush(stdout);
206
207 for (;;) {
208 _cleanup_free_ char *line = NULL;
209
210 r = read_line(f, LONG_LINE_MAX, &line);
211 if (r < 0)
212 return log_error_errno(r, "Failed to read \"%s\": %m", filename);
213 if (r == 0)
214 break;
215
a9e68035 216 LineType line_type = classify_line_type(line, flags);
22b0b7bf 217 if (FLAGS_SET(flags, CAT_TLDR)) {
a9e68035 218 if (line_type == LINE_SECTION) {
063c8382
ZJS
219 /* The start of a section, let's not print it yet. */
220 free_and_replace(section, line);
221 continue;
222 }
223
a9e68035 224 if (line_type == LINE_COMMENT)
063c8382
ZJS
225 continue;
226
227 /* Before we print the actual line, print the last section header */
228 if (section) {
c04cec12
ZJS
229 /* Do not print redundant section headers */
230 if (!streq_ptr(section, old_section))
231 printf("%s%s%s\n",
232 ansi_highlight_cyan(),
233 section,
234 ansi_normal());
235
236 free_and_replace(old_section, section);
063c8382
ZJS
237 }
238 }
239
22b0b7bf
FS
240 /* Highlight the left side (directive) of a Foo=bar assignment */
241 if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && line_type == LINE_NORMAL) {
242 const char *p = strchr(line, '=');
243 if (p) {
244 _cleanup_free_ char *highlighted = NULL, *directive = NULL;
245
246 directive = strndup(line, p - line);
247 if (!directive)
248 return log_oom();
249
250 highlighted = strjoin(ansi_highlight_green(),
251 directive,
252 "=",
253 ansi_normal(),
254 p + 1);
255 if (!highlighted)
256 return log_oom();
257
258 free_and_replace(line, highlighted);
259 }
260 }
261
a9e68035
ZJS
262 printf("%s%s%s\n",
263 line_type == LINE_SECTION ? ansi_highlight_cyan() :
264 line_type == LINE_COMMENT ? ansi_highlight_grey() :
265 "",
266 line,
267 line_type != LINE_NORMAL ? ansi_normal() : "");
294bf0c3
ZJS
268 }
269
270 return 0;
271}
272
273int cat_files(const char *file, char **dropins, CatFlags flags) {
294bf0c3
ZJS
274 int r;
275
276 if (file) {
063c8382 277 r = cat_file(file, /* newline= */ false, flags);
80788a0b 278 if (r < 0)
294bf0c3
ZJS
279 return log_warning_errno(r, "Failed to cat %s: %m", file);
280 }
281
282 STRV_FOREACH(path, dropins) {
063c8382 283 r = cat_file(*path, /* newline= */ file || path != dropins, flags);
294bf0c3
ZJS
284 if (r < 0)
285 return log_warning_errno(r, "Failed to cat %s: %m", *path);
286 }
287
288 return 0;
289}
290
291void print_separator(void) {
292
293 /* Outputs a separator line that resolves to whitespace when copied from the terminal. We do that by outputting
294 * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */
295
296 if (underline_enabled()) {
2673d6fa 297 size_t c = columns();
294bf0c3
ZJS
298
299 flockfile(stdout);
300 fputs_unlocked(ANSI_UNDERLINE, stdout);
301
2673d6fa 302 for (size_t i = 0; i < c; i++)
294bf0c3
ZJS
303 fputc_unlocked(' ', stdout);
304
305 fputs_unlocked(ANSI_NORMAL "\n\n", stdout);
306 funlockfile(stdout);
307 } else
308 fputs("\n\n", stdout);
309}
310
81d791f1 311static int guess_type(const char **name, char ***prefixes, bool *is_collection, const char **extension) {
f1d9d36a 312 /* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/,
063c8382
ZJS
313 * i.e. a collection of directories without a main config file.
314 * Incidentally, all those formats don't use sections. So we return a single
315 * is_collection boolean, which also means that the format doesn't use sections.
316 */
f1d9d36a
ZJS
317
318 _cleanup_free_ char *n = NULL;
81d791f1 319 bool usr = false, run = false, coll = false;
f1d9d36a 320 const char *ext = ".conf";
81d791f1
ZJS
321 /* This is static so that the array doesn't get deallocated when we exit the function */
322 static const char* const std_prefixes[] = { CONF_PATHS(""), NULL };
323 static const char* const usr_prefixes[] = { CONF_PATHS_USR(""), NULL };
324 static const char* const run_prefixes[] = { "/run/", NULL };
f1d9d36a
ZJS
325
326 if (path_equal(*name, "environment.d"))
327 /* Special case: we need to include /etc/environment in the search path, even
328 * though the whole concept is called environment.d. */
329 *name = "environment";
330
331 n = strdup(*name);
332 if (!n)
333 return log_oom();
334
28365e88
ZJS
335 /* All systemd-style config files should support the /usr-/etc-/run split and
336 * dropins. Let's add a blanket rule that allows us to support them without keeping
337 * an explicit list. */
338 if (path_startswith(n, "systemd") && endswith(n, ".conf"))
339 usr = true;
340
f1d9d36a
ZJS
341 delete_trailing_chars(n, "/");
342
343 if (endswith(n, ".d"))
344 coll = true;
345
346 if (path_equal(n, "environment"))
347 usr = true;
348
349 if (path_equal(n, "udev/hwdb.d"))
350 ext = ".hwdb";
f3663c0e 351 else if (path_equal(n, "udev/rules.d"))
f1d9d36a 352 ext = ".rules";
f3663c0e 353 else if (path_equal(n, "kernel/install.d"))
ea39de2f 354 ext = ".install";
f3663c0e 355 else if (path_equal(n, "systemd/ntp-units.d")) {
afaae43b
ZJS
356 coll = true;
357 ext = ".list";
f3663c0e 358 } else if (path_equal(n, "systemd/relabel-extra.d")) {
81d791f1
ZJS
359 coll = run = true;
360 ext = ".relabel";
f3663c0e 361 } else if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) {
f1d9d36a
ZJS
362 coll = true;
363 ext = ".preset";
364 }
365
366 if (path_equal(n, "systemd/user-preset"))
367 usr = true;
368
81d791f1 369 *prefixes = (char**) (usr ? usr_prefixes : run ? run_prefixes : std_prefixes);
f1d9d36a
ZJS
370 *is_collection = coll;
371 *extension = ext;
372 return 0;
373}
374
063c8382 375int conf_files_cat(const char *root, const char *name, CatFlags flags) {
294bf0c3
ZJS
376 _cleanup_strv_free_ char **dirs = NULL, **files = NULL;
377 _cleanup_free_ char *path = NULL;
de010b0b 378 char **prefixes = NULL; /* explicit initialization to appease gcc */
81d791f1 379 bool is_collection;
f1d9d36a 380 const char *extension;
294bf0c3
ZJS
381 int r;
382
81d791f1 383 r = guess_type(&name, &prefixes, &is_collection, &extension);
f1d9d36a
ZJS
384 if (r < 0)
385 return r;
1c93632e
ZJS
386 assert(prefixes);
387 assert(extension);
f1d9d36a 388
81d791f1
ZJS
389 STRV_FOREACH(prefix, prefixes) {
390 assert(endswith(*prefix, "/"));
391 r = strv_extendf(&dirs, "%s%s%s", *prefix, name,
f1d9d36a 392 is_collection ? "" : ".d");
294bf0c3
ZJS
393 if (r < 0)
394 return log_error_errno(r, "Failed to build directory list: %m");
395 }
396
0895e873
ZJS
397 if (DEBUG_LOGGING) {
398 log_debug("Looking for configuration in:");
399 if (!is_collection)
400 STRV_FOREACH(prefix, prefixes)
401 log_debug(" %s%s%s", strempty(root), *prefix, name);
294bf0c3 402
0895e873
ZJS
403 STRV_FOREACH(t, dirs)
404 log_debug(" %s%s/*%s", strempty(root), *t, extension);
405 }
406
407 /* First locate the main config file, if any */
f1d9d36a 408 if (!is_collection) {
0895e873
ZJS
409 STRV_FOREACH(prefix, prefixes) {
410 path = path_join(root, *prefix, name);
411 if (!path)
412 return log_oom();
413 if (access(path, F_OK) == 0)
414 break;
415 path = mfree(path);
416 }
417
f1d9d36a 418 if (!path)
0895e873
ZJS
419 printf("%s# Main configuration file %s not found%s\n",
420 ansi_highlight_magenta(),
421 name,
422 ansi_normal());
f1d9d36a 423 }
294bf0c3 424
0895e873
ZJS
425 /* Then locate the drop-ins, if any */
426 r = conf_files_list_strv(&files, extension, root, 0, (const char* const*) dirs);
427 if (r < 0)
428 return log_error_errno(r, "Failed to query file list: %m");
294bf0c3 429
0895e873 430 /* Show */
063c8382
ZJS
431 if (is_collection)
432 flags |= CAT_FORMAT_HAS_SECTIONS;
433
434 return cat_files(path, files, flags);
294bf0c3 435}
3ef072ee
LP
436
437int terminal_tint_color(double hue, char **ret) {
438 double red, green, blue;
439 int r;
440
441 assert(ret);
442
443 r = get_default_background_color(&red, &green, &blue);
444 if (r < 0)
445 return log_debug_errno(r, "Unable to get terminal background color: %m");
446
447 double s, v;
448 rgb_to_hsv(red, green, blue, /* h= */ NULL, &s, &v);
449
450 if (v > 50) /* If the background is bright, then pull down saturation */
451 s = 25;
452 else /* otherwise pump it up */
453 s = 75;
454
2f7f0800 455 v = MAX(20, v); /* Make sure we don't hide the color in black */
3ef072ee
LP
456
457 uint8_t r8, g8, b8;
458 hsv_to_rgb(hue, s, v, &r8, &g8, &b8);
459
460 if (asprintf(ret, "48;2;%u;%u;%u", r8, g8, b8) < 0)
461 return -ENOMEM;
462
463 return 0;
464}