1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include <sys/utsname.h>
7 #include "alloc-util.h"
8 #include "color-util.h"
9 #include "conf-files.h"
10 #include "constants.h"
15 #include "path-util.h"
16 #include "pretty-print.h"
17 #include "string-util.h"
19 #include "terminal-util.h"
21 void draw_cylon(char buffer
[], size_t buflen
, unsigned width
, unsigned pos
) {
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 */
29 p
= mempset(p
, ' ', pos
-2);
30 if (log_get_show_color())
31 p
= stpcpy(p
, ANSI_RED
);
35 if (pos
> 0 && pos
<= width
) {
36 if (log_get_show_color())
37 p
= stpcpy(p
, ANSI_HIGHLIGHT_RED
);
41 if (log_get_show_color())
42 p
= stpcpy(p
, ANSI_NORMAL
);
45 if (log_get_show_color())
46 p
= stpcpy(p
, ANSI_RED
);
49 p
= mempset(p
, ' ', width
-1-pos
);
50 if (log_get_show_color())
51 p
= stpcpy(p
, ANSI_NORMAL
);
57 bool urlify_enabled(void) {
59 static int cached_urlify_enabled
= -1;
61 if (cached_urlify_enabled
< 0) {
64 val
= getenv_bool("SYSTEMD_URLIFY");
66 cached_urlify_enabled
= val
;
68 cached_urlify_enabled
= colors_enabled();
71 return cached_urlify_enabled
;
77 int terminal_urlify(const char *url
, const char *text
, char **ret
) {
82 /* Takes a URL and a pretty string and formats it as clickable link for the terminal. See
83 * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */
89 n
= strjoin("\x1B]8;;", url
, "\a", text
, "\x1B]8;;\a");
99 int file_url_from_path(const char *path
, char **ret
) {
100 _cleanup_free_
char *absolute
= NULL
;
108 if (!path_is_absolute(path
)) {
109 r
= path_make_absolute_cwd(path
, &absolute
);
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. */
121 url
= strjoin("file://", u
.nodename
, path
);
129 int terminal_urlify_path(const char *path
, const char *text
, char **ret
) {
130 _cleanup_free_
char *url
= NULL
;
135 /* Much like terminal_urlify() above, but takes a file system path as input
136 * and turns it into a proper file:// URL first. */
144 if (!urlify_enabled()) {
155 r
= file_url_from_path(path
, &url
);
159 return terminal_urlify(url
, text
, ret
);
162 int terminal_urlify_man(const char *page
, const char *section
, char **ret
) {
163 const char *url
, *text
;
165 url
= strjoina("man:", page
, "(", section
, ")");
166 text
= strjoina(page
, "(", section
, ") man page");
168 return terminal_urlify(url
, text
, ret
);
177 static LineType
classify_line_type(const char *line
, CatFlags flags
) {
178 const char *t
= skip_leading_chars(line
, WHITESPACE
);
180 if ((flags
& CAT_FORMAT_HAS_SECTIONS
) && *t
== '[')
182 if (IN_SET(*t
, '#', ';', '\0'))
187 static int cat_file(const char *filename
, bool newline
, CatFlags flags
) {
188 _cleanup_fclose_
FILE *f
= NULL
;
189 _cleanup_free_
char *urlified
= NULL
, *section
= NULL
, *old_section
= NULL
;
192 f
= fopen(filename
, "re");
196 r
= terminal_urlify_path(filename
, NULL
, &urlified
);
200 printf("%s%s# %s%s\n",
202 ansi_highlight_blue(),
208 _cleanup_free_
char *line
= NULL
;
210 r
= read_line(f
, LONG_LINE_MAX
, &line
);
212 return log_error_errno(r
, "Failed to read \"%s\": %m", filename
);
216 LineType line_type
= classify_line_type(line
, flags
);
217 if (FLAGS_SET(flags
, CAT_TLDR
)) {
218 if (line_type
== LINE_SECTION
) {
219 /* The start of a section, let's not print it yet. */
220 free_and_replace(section
, line
);
224 if (line_type
== LINE_COMMENT
)
227 /* Before we print the actual line, print the last section header */
229 /* Do not print redundant section headers */
230 if (!streq_ptr(section
, old_section
))
232 ansi_highlight_cyan(),
236 free_and_replace(old_section
, section
);
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
, '=');
244 _cleanup_free_
char *highlighted
= NULL
, *directive
= NULL
;
246 directive
= strndup(line
, p
- line
);
250 highlighted
= strjoin(ansi_highlight_green(),
258 free_and_replace(line
, highlighted
);
263 line_type
== LINE_SECTION
? ansi_highlight_cyan() :
264 line_type
== LINE_COMMENT
? ansi_highlight_grey() :
267 line_type
!= LINE_NORMAL
? ansi_normal() : "");
273 int cat_files(const char *file
, char **dropins
, CatFlags flags
) {
277 r
= cat_file(file
, /* newline= */ false, flags
);
279 return log_warning_errno(r
, "Failed to cat %s: %m", file
);
282 STRV_FOREACH(path
, dropins
) {
283 r
= cat_file(*path
, /* newline= */ file
|| path
!= dropins
, flags
);
285 return log_warning_errno(r
, "Failed to cat %s: %m", *path
);
291 void print_separator(void) {
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. */
296 if (underline_enabled()) {
297 size_t c
= columns();
300 fputs_unlocked(ANSI_UNDERLINE
, stdout
);
302 for (size_t i
= 0; i
< c
; i
++)
303 fputc_unlocked(' ', stdout
);
305 fputs_unlocked(ANSI_NORMAL
"\n\n", stdout
);
308 fputs("\n\n", stdout
);
311 static int guess_type(const char **name
, char ***ret_prefixes
, bool *ret_is_collection
, const char **ret_extension
) {
312 /* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/,
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.
318 _cleanup_free_
char *n
= NULL
;
319 bool run
= false, coll
= false;
320 const char *ext
= ".conf";
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 run_prefixes
[] = { "/run/", NULL
};
325 if (path_equal(*name
, "environment.d"))
326 /* Special case: we need to include /etc/environment in the search path, even
327 * though the whole concept is called environment.d. */
328 *name
= "environment";
334 delete_trailing_chars(n
, "/");
336 /* We assume systemd-style config files support the /usr-/run-/etc split and dropins. */
338 if (endswith(n
, ".d"))
341 if (path_equal(n
, "udev/hwdb.d"))
343 else if (path_equal(n
, "udev/rules.d"))
345 else if (path_equal(n
, "kernel/install.d"))
347 else if (path_equal(n
, "systemd/ntp-units.d")) {
350 } else if (path_equal(n
, "systemd/relabel-extra.d")) {
353 } else if (PATH_IN_SET(n
, "systemd/system-preset", "systemd/user-preset")) {
358 *ret_prefixes
= (char**) (run
? run_prefixes
: std_prefixes
);
359 *ret_is_collection
= coll
;
360 *ret_extension
= ext
;
364 int conf_files_cat(const char *root
, const char *name
, CatFlags flags
) {
365 _cleanup_strv_free_
char **dirs
= NULL
, **files
= NULL
;
366 _cleanup_free_
char *path
= NULL
;
367 char **prefixes
= NULL
; /* explicit initialization to appease gcc */
369 const char *extension
;
372 r
= guess_type(&name
, &prefixes
, &is_collection
, &extension
);
378 STRV_FOREACH(prefix
, prefixes
) {
379 assert(endswith(*prefix
, "/"));
380 r
= strv_extendf(&dirs
, "%s%s%s", *prefix
, name
,
381 is_collection
? "" : ".d");
383 return log_error_errno(r
, "Failed to build directory list: %m");
387 log_debug("Looking for configuration in:");
389 STRV_FOREACH(prefix
, prefixes
)
390 log_debug(" %s%s%s", strempty(root
), *prefix
, name
);
392 STRV_FOREACH(t
, dirs
)
393 log_debug(" %s%s/*%s", strempty(root
), *t
, extension
);
396 /* First locate the main config file, if any */
397 if (!is_collection
) {
398 STRV_FOREACH(prefix
, prefixes
) {
399 path
= path_join(root
, *prefix
, name
);
402 if (access(path
, F_OK
) == 0)
408 printf("%s# Main configuration file %s not found%s\n",
409 ansi_highlight_magenta(),
414 /* Then locate the drop-ins, if any */
415 r
= conf_files_list_strv(&files
, extension
, root
, 0, (const char* const*) dirs
);
417 return log_error_errno(r
, "Failed to query file list: %m");
421 flags
|= CAT_FORMAT_HAS_SECTIONS
;
423 return cat_files(path
, files
, flags
);
426 int terminal_tint_color(double hue
, char **ret
) {
427 double red
, green
, blue
;
432 r
= get_default_background_color(&red
, &green
, &blue
);
434 return log_debug_errno(r
, "Unable to get terminal background color: %m");
437 rgb_to_hsv(red
, green
, blue
, /* h= */ NULL
, &s
, &v
);
439 if (v
> 50) /* If the background is bright, then pull down saturation */
441 else /* otherwise pump it up */
444 v
= MAX(20, v
); /* Make sure we don't hide the color in black */
447 hsv_to_rgb(hue
, s
, v
, &r8
, &g8
, &b8
);
449 if (asprintf(ret
, "48;2;%u;%u;%u", r8
, g8
, b8
) < 0)
455 void draw_progress_bar(const char *prefix
, double percentage
) {
459 fputs(prefix
, stderr
);
461 if (!terminal_is_dumb()) {
462 size_t cols
= columns();
463 size_t prefix_length
= strlen_ptr(prefix
);
464 size_t length
= cols
> prefix_length
+ 6 ? cols
- prefix_length
- 6 : 0;
466 if (length
> 5 && percentage
>= 0.0 && percentage
<= 100.0) {
467 size_t p
= (size_t) (length
* percentage
/ 100.0);
468 bool separator_done
= false;
470 fputs(ansi_highlight_green(), stderr
);
472 for (size_t i
= 0; i
< length
; i
++) {
475 if (get_color_mode() == COLOR_24BIT
) {
477 double z
= i
== 0 ? 0 : (((double) i
/ p
) * 100);
478 hsv_to_rgb(145 /* green */, z
, 33 + z
*2/3, &r8
, &g8
, &b8
);
479 fprintf(stderr
, "\x1B[38;2;%u;%u;%um", r8
, g8
, b8
);
482 fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_FAT
), stderr
);
483 } else if (i
+1 < length
&& !separator_done
) {
484 fputs(ansi_normal(), stderr
);
486 separator_done
= true;
487 fputs(ansi_grey(), stderr
);
489 fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_DOTTED
), stderr
);
492 fputs(ansi_normal(), stderr
);
503 if (!terminal_is_dumb())
504 fputs(ANSI_ERASE_TO_END_OF_LINE
, stderr
);
510 void clear_progress_bar(const char *prefix
) {
514 if (terminal_is_dumb())
515 fputs(strrepa(" ", strlen_ptr(prefix
) + 4), /* 4: %3.0f%% */
518 fputs(ANSI_ERASE_TO_END_OF_LINE
, stderr
);