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