]>
| Commit | Line | Data |
|---|---|---|
| db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| 294bf0c3 | 2 | |
| 07b869b9 | 3 | #include <math.h> |
| 294bf0c3 | 4 | #include <stdio.h> |
| 07b869b9 | 5 | #include <sys/utsname.h> |
| 69a283c5 | 6 | #include <unistd.h> |
| 294bf0c3 ZJS |
7 | |
| 8 | #include "alloc-util.h" | |
| 661b5bfd | 9 | #include "chase.h" |
| 3ef072ee | 10 | #include "color-util.h" |
| 294bf0c3 | 11 | #include "conf-files.h" |
| 28db6fbf | 12 | #include "constants.h" |
| 294bf0c3 | 13 | #include "env-util.h" |
| 86c4e423 | 14 | #include "errno-util.h" |
| 294bf0c3 ZJS |
15 | #include "fd-util.h" |
| 16 | #include "fileio.h" | |
| 93a1f792 | 17 | #include "log.h" |
| 294bf0c3 ZJS |
18 | #include "path-util.h" |
| 19 | #include "pretty-print.h" | |
| 20 | #include "string-util.h" | |
| 21 | #include "strv.h" | |
| 22 | #include "terminal-util.h" | |
| 1ad4e37d | 23 | #include "utf8.h" |
| 294bf0c3 | 24 | |
| d61a4dbb YW |
25 | void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) { |
| 26 | char *p = buffer; | |
| 27 | ||
| 28 | assert(buflen >= CYLON_BUFFER_EXTRA + width + 1); | |
| 29 | assert(pos <= width+1); /* 0 or width+1 mean that the center light is behind the corner */ | |
| 30 | ||
| 31 | if (pos > 1) { | |
| 32 | if (pos > 2) | |
| 33 | p = mempset(p, ' ', pos-2); | |
| 34 | if (log_get_show_color()) | |
| 35 | p = stpcpy(p, ANSI_RED); | |
| 36 | *p++ = '*'; | |
| 37 | } | |
| 38 | ||
| 39 | if (pos > 0 && pos <= width) { | |
| 40 | if (log_get_show_color()) | |
| 41 | p = stpcpy(p, ANSI_HIGHLIGHT_RED); | |
| 42 | *p++ = '*'; | |
| 43 | } | |
| 44 | ||
| 45 | if (log_get_show_color()) | |
| 46 | p = stpcpy(p, ANSI_NORMAL); | |
| 47 | ||
| 48 | if (pos < width) { | |
| 49 | if (log_get_show_color()) | |
| 50 | p = stpcpy(p, ANSI_RED); | |
| 51 | *p++ = '*'; | |
| 52 | if (pos < width-1) | |
| 53 | p = mempset(p, ' ', width-1-pos); | |
| 54 | if (log_get_show_color()) | |
| 55 | p = stpcpy(p, ANSI_NORMAL); | |
| 56 | } | |
| 57 | ||
| 58 | *p = '\0'; | |
| 59 | } | |
| 60 | ||
| 422c8251 | 61 | bool urlify_enabled(void) { |
| e5d86ebe | 62 | #if ENABLE_URLIFY |
| 294bf0c3 ZJS |
63 | static int cached_urlify_enabled = -1; |
| 64 | ||
| 294bf0c3 ZJS |
65 | if (cached_urlify_enabled < 0) { |
| 66 | int val; | |
| 67 | ||
| 68 | val = getenv_bool("SYSTEMD_URLIFY"); | |
| 69 | if (val >= 0) | |
| 70 | cached_urlify_enabled = val; | |
| 71 | else | |
| ebef02dd | 72 | cached_urlify_enabled = colors_enabled(); |
| 294bf0c3 ZJS |
73 | } |
| 74 | ||
| 75 | return cached_urlify_enabled; | |
| e5d86ebe JH |
76 | #else |
| 77 | return 0; | |
| 78 | #endif | |
| 294bf0c3 ZJS |
79 | } |
| 80 | ||
| 0823d96a LP |
81 | static bool url_suitable_for_osc8(const char *url) { |
| 82 | assert(url); | |
| 83 | ||
| 84 | /* Not all URLs are safe for inclusion in OSC 8 due to charset and length restrictions. Let's detect | |
| 85 | * which ones those are */ | |
| 86 | ||
| 87 | /* If the URL is longer than 2K let's not try to do OSC 8. As per recommendation in | |
| 88 | * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda#length-limits */ | |
| 89 | if (strlen(url) > 2000) | |
| 90 | return false; | |
| 91 | ||
| 92 | /* OSC sequences may only contain chars from the 32..126 range, as per ECMA-48 */ | |
| 93 | for (const char *c = url; *c; c++) | |
| 94 | if (!osc_char_is_valid(*c)) | |
| 95 | return false; | |
| 96 | ||
| 97 | return true; | |
| 98 | } | |
| 99 | ||
| 294bf0c3 ZJS |
100 | int terminal_urlify(const char *url, const char *text, char **ret) { |
| 101 | char *n; | |
| 102 | ||
| 103 | assert(url); | |
| 104 | ||
| 5bc9ea07 | 105 | /* Takes a URL and a pretty string and formats it as clickable link for the terminal. See |
| 294bf0c3 ZJS |
106 | * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */ |
| 107 | ||
| 108 | if (isempty(text)) | |
| 109 | text = url; | |
| 110 | ||
| 0823d96a | 111 | if (urlify_enabled() && url_suitable_for_osc8(url)) |
| 03674247 LP |
112 | n = strjoin(ANSI_OSC "8;;", url, ANSI_ST, |
| 113 | text, | |
| 114 | ANSI_OSC "8;;" ANSI_ST); | |
| 294bf0c3 ZJS |
115 | else |
| 116 | n = strdup(text); | |
| 117 | if (!n) | |
| 118 | return -ENOMEM; | |
| 119 | ||
| 120 | *ret = n; | |
| 121 | return 0; | |
| 122 | } | |
| 123 | ||
| 62d6a1cc | 124 | int file_url_from_path(const char *path, char **ret) { |
| 294bf0c3 ZJS |
125 | _cleanup_free_ char *absolute = NULL; |
| 126 | struct utsname u; | |
| 62d6a1cc LP |
127 | char *url = NULL; |
| 128 | int r; | |
| 129 | ||
| 130 | if (uname(&u) < 0) | |
| 131 | return -errno; | |
| 132 | ||
| 133 | if (!path_is_absolute(path)) { | |
| 134 | r = path_make_absolute_cwd(path, &absolute); | |
| 135 | if (r < 0) | |
| 136 | return r; | |
| 137 | ||
| 138 | path = absolute; | |
| 139 | } | |
| 140 | ||
| 141 | /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local | |
| 142 | * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested | |
| 143 | * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly | |
| 144 | * careful with validating the strings either. */ | |
| 145 | ||
| 146 | url = strjoin("file://", u.nodename, path); | |
| 147 | if (!url) | |
| 148 | return -ENOMEM; | |
| 149 | ||
| 150 | *ret = url; | |
| 151 | return 0; | |
| 152 | } | |
| 153 | ||
| 154 | int terminal_urlify_path(const char *path, const char *text, char **ret) { | |
| 155 | _cleanup_free_ char *url = NULL; | |
| 294bf0c3 ZJS |
156 | int r; |
| 157 | ||
| 158 | assert(path); | |
| 159 | ||
| 160 | /* Much like terminal_urlify() above, but takes a file system path as input | |
| 161 | * and turns it into a proper file:// URL first. */ | |
| 162 | ||
| 163 | if (isempty(path)) | |
| 164 | return -EINVAL; | |
| 165 | ||
| 166 | if (isempty(text)) | |
| 167 | text = path; | |
| 168 | ||
| 454318d3 ZJS |
169 | if (!urlify_enabled()) |
| 170 | return strdup_to(ret, text); | |
| 294bf0c3 | 171 | |
| 62d6a1cc LP |
172 | r = file_url_from_path(path, &url); |
| 173 | if (r < 0) | |
| 174 | return r; | |
| 294bf0c3 ZJS |
175 | |
| 176 | return terminal_urlify(url, text, ret); | |
| 177 | } | |
| 178 | ||
| 179 | int terminal_urlify_man(const char *page, const char *section, char **ret) { | |
| 180 | const char *url, *text; | |
| 181 | ||
| 182 | url = strjoina("man:", page, "(", section, ")"); | |
| 183 | text = strjoina(page, "(", section, ") man page"); | |
| 184 | ||
| 185 | return terminal_urlify(url, text, ret); | |
| 186 | } | |
| 187 | ||
| 661b5bfd | 188 | static int cat_file(const ConfFile *c, bool *newline, CatFlags flags) { |
| 294bf0c3 | 189 | _cleanup_fclose_ FILE *f = NULL; |
| c04cec12 | 190 | _cleanup_free_ char *urlified = NULL, *section = NULL, *old_section = NULL; |
| 294bf0c3 ZJS |
191 | int r; |
| 192 | ||
| 661b5bfd YW |
193 | assert(c); |
| 194 | assert(c->original_path); | |
| 195 | assert(c->resolved_path); | |
| 196 | assert(c->fd >= 0); | |
| 42b71eb9 | 197 | |
| 86c4e423 YW |
198 | if (newline) { |
| 199 | if (*newline) | |
| 200 | putc('\n', stdout); | |
| 201 | *newline = true; | |
| 202 | } | |
| 294bf0c3 | 203 | |
| 661b5bfd YW |
204 | bool resolved = !path_equal(c->original_path, c->resolved_path); |
| 205 | ||
| 206 | r = terminal_urlify_path(c->resolved_path, NULL, &urlified); | |
| 294bf0c3 | 207 | if (r < 0) |
| 661b5bfd | 208 | return log_error_errno(r, "Failed to urlify path \"%s\": %m", c->resolved_path); |
| 294bf0c3 | 209 | |
| 661b5bfd | 210 | printf("%s# %s%s%s%s\n", |
| 294bf0c3 | 211 | ansi_highlight_blue(), |
| 661b5bfd YW |
212 | resolved ? c->original_path : "", |
| 213 | resolved ? " -> " : "", | |
| 294bf0c3 ZJS |
214 | urlified, |
| 215 | ansi_normal()); | |
| 86c4e423 | 216 | |
| 661b5bfd | 217 | f = fopen(FORMAT_PROC_FD_PATH(c->fd), "re"); |
| 86c4e423 | 218 | if (!f) |
| 661b5bfd | 219 | return log_error_errno(errno, "Failed to open \"%s\": %m", c->resolved_path); |
| 294bf0c3 | 220 | |
| e27fb39e | 221 | for (bool continued = false;;) { |
| 294bf0c3 ZJS |
222 | _cleanup_free_ char *line = NULL; |
| 223 | ||
| 224 | r = read_line(f, LONG_LINE_MAX, &line); | |
| 225 | if (r < 0) | |
| 661b5bfd | 226 | return log_error_errno(r, "Failed to read \"%s\": %m", c->resolved_path); |
| 294bf0c3 ZJS |
227 | if (r == 0) |
| 228 | break; | |
| 229 | ||
| 42b71eb9 YW |
230 | const char *l = skip_leading_chars(line, WHITESPACE); |
| 231 | ||
| 232 | /* comment */ | |
| 233 | if (*l != '\0' && strchr(COMMENTS, *l)) { | |
| 234 | if (!FLAGS_SET(flags, CAT_TLDR)) | |
| 235 | printf("%s%s%s\n", ansi_highlight_grey(), line, ansi_normal()); | |
| 236 | continue; | |
| 237 | } | |
| 238 | ||
| 239 | /* empty line */ | |
| e27fb39e | 240 | if (FLAGS_SET(flags, CAT_TLDR) && (isempty(l) || streq(l, "\\"))) |
| 42b71eb9 YW |
241 | continue; |
| 242 | ||
| 243 | /* section */ | |
| e27fb39e | 244 | if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && *l == '[' && !continued) { |
| 42b71eb9 YW |
245 | if (FLAGS_SET(flags, CAT_TLDR)) |
| 246 | /* On TLDR, let's not print it yet. */ | |
| 063c8382 | 247 | free_and_replace(section, line); |
| 42b71eb9 YW |
248 | else |
| 249 | printf("%s%s%s\n", ansi_highlight_cyan(), line, ansi_normal()); | |
| 250 | continue; | |
| 251 | } | |
| 063c8382 | 252 | |
| 42b71eb9 | 253 | /* normal line */ |
| 063c8382 | 254 | |
| 42b71eb9 YW |
255 | /* Before we print the line, print the last section header. */ |
| 256 | if (FLAGS_SET(flags, CAT_TLDR) && section) { | |
| 257 | /* Do not print redundant section headers */ | |
| 258 | if (!streq_ptr(section, old_section)) | |
| 259 | printf("%s%s%s\n", ansi_highlight_cyan(), section, ansi_normal()); | |
| c04cec12 | 260 | |
| 42b71eb9 | 261 | free_and_replace(old_section, section); |
| 063c8382 ZJS |
262 | } |
| 263 | ||
| e27fb39e YW |
264 | /* Check if the line ends with a backslash. */ |
| 265 | bool escaped = false; | |
| 5e244e72 YW |
266 | char *e; |
| 267 | for (e = line; *e != '\0'; e++) { | |
| e27fb39e YW |
268 | if (escaped) |
| 269 | escaped = false; | |
| 270 | else if (*e == '\\') | |
| 271 | escaped = true; | |
| 272 | } | |
| 273 | ||
| 5e244e72 YW |
274 | /* Highlight the trailing backslash. */ |
| 275 | if (escaped) { | |
| 276 | assert(e > line); | |
| 277 | *(e-1) = '\0'; | |
| 278 | ||
| 279 | if (!strextend(&line, ansi_highlight_red(), "\\", ansi_normal())) | |
| 280 | return log_oom(); | |
| 281 | } | |
| 282 | ||
| 22b0b7bf | 283 | /* Highlight the left side (directive) of a Foo=bar assignment */ |
| e27fb39e | 284 | if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && !continued) { |
| 22b0b7bf FS |
285 | const char *p = strchr(line, '='); |
| 286 | if (p) { | |
| 42b71eb9 | 287 | _cleanup_free_ char *directive = NULL; |
| 22b0b7bf FS |
288 | |
| 289 | directive = strndup(line, p - line); | |
| 290 | if (!directive) | |
| 291 | return log_oom(); | |
| 292 | ||
| 42b71eb9 | 293 | printf("%s%s=%s%s\n", ansi_highlight_green(), directive, ansi_normal(), p + 1); |
| e27fb39e | 294 | continued = escaped; |
| 42b71eb9 | 295 | continue; |
| 22b0b7bf FS |
296 | } |
| 297 | } | |
| 298 | ||
| 42b71eb9 YW |
299 | /* Otherwise, print the line as is. */ |
| 300 | printf("%s\n", line); | |
| e27fb39e | 301 | continued = escaped; |
| 294bf0c3 ZJS |
302 | } |
| 303 | ||
| 304 | return 0; | |
| 305 | } | |
| 306 | ||
| 661b5bfd | 307 | int cat_files_full(const ConfFile *file, ConfFile * const *dropins, size_t n_dropins, CatFlags flags) { |
| 86c4e423 YW |
308 | bool newline = false; |
| 309 | int ret = 0; | |
| 294bf0c3 | 310 | |
| 661b5bfd YW |
311 | assert(dropins || n_dropins == 0); |
| 312 | ||
| 86c4e423 YW |
313 | if (file) |
| 314 | ret = cat_file(file, &newline, flags); | |
| 294bf0c3 | 315 | |
| 661b5bfd YW |
316 | FOREACH_ARRAY(i, dropins, n_dropins) |
| 317 | RET_GATHER(ret, cat_file(*i, &newline, flags)); | |
| 318 | ||
| 319 | return ret; | |
| 320 | } | |
| 321 | ||
| 322 | static int cat_file_by_path(const char *p, bool *newline, CatFlags flags) { | |
| 323 | _cleanup_(conf_file_freep) ConfFile *c = NULL; | |
| 324 | int r; | |
| 325 | ||
| 326 | assert(p); | |
| 327 | ||
| 328 | r = conf_file_new(p, /* root = */ NULL, CHASE_MUST_BE_REGULAR, &c); | |
| 329 | if (r < 0) | |
| 330 | return log_error_errno(r, "Failed to chase '%s': %m", p); | |
| 331 | ||
| 332 | return cat_file(c, newline, flags); | |
| 333 | } | |
| 334 | ||
| 335 | int cat_files(const char *file, char **dropins, CatFlags flags) { | |
| 336 | bool newline = false; | |
| 337 | int ret = 0; | |
| 338 | ||
| 339 | if (file) | |
| 340 | ret = cat_file_by_path(file, &newline, flags); | |
| 341 | ||
| 86c4e423 | 342 | STRV_FOREACH(path, dropins) |
| 661b5bfd | 343 | RET_GATHER(ret, cat_file_by_path(*path, &newline, flags)); |
| 294bf0c3 | 344 | |
| 86c4e423 | 345 | return ret; |
| 294bf0c3 ZJS |
346 | } |
| 347 | ||
| 348 | void print_separator(void) { | |
| 349 | ||
| 350 | /* Outputs a separator line that resolves to whitespace when copied from the terminal. We do that by outputting | |
| 351 | * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */ | |
| 352 | ||
| 353 | if (underline_enabled()) { | |
| 2673d6fa | 354 | size_t c = columns(); |
| 294bf0c3 ZJS |
355 | |
| 356 | flockfile(stdout); | |
| 17e6e4d6 | 357 | fputs_unlocked(ansi_grey_underline(), stdout); |
| 294bf0c3 | 358 | |
| 2673d6fa | 359 | for (size_t i = 0; i < c; i++) |
| 294bf0c3 ZJS |
360 | fputc_unlocked(' ', stdout); |
| 361 | ||
| 17e6e4d6 YW |
362 | fputs_unlocked(ansi_normal(), stdout); |
| 363 | fputs_unlocked("\n\n", stdout); | |
| 294bf0c3 ZJS |
364 | funlockfile(stdout); |
| 365 | } else | |
| 366 | fputs("\n\n", stdout); | |
| 367 | } | |
| 368 | ||
| 6812498c | 369 | static int guess_type(const char **name, char ***ret_prefixes, bool *ret_is_collection, const char **ret_extension) { |
| f1d9d36a | 370 | /* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/, |
| 063c8382 ZJS |
371 | * i.e. a collection of directories without a main config file. |
| 372 | * Incidentally, all those formats don't use sections. So we return a single | |
| 373 | * is_collection boolean, which also means that the format doesn't use sections. | |
| 374 | */ | |
| f1d9d36a ZJS |
375 | |
| 376 | _cleanup_free_ char *n = NULL; | |
| 76d75d8b | 377 | bool run = false, coll = false; |
| f1d9d36a | 378 | const char *ext = ".conf"; |
| 81d791f1 ZJS |
379 | /* This is static so that the array doesn't get deallocated when we exit the function */ |
| 380 | static const char* const std_prefixes[] = { CONF_PATHS(""), NULL }; | |
| 81d791f1 | 381 | static const char* const run_prefixes[] = { "/run/", NULL }; |
| f1d9d36a ZJS |
382 | |
| 383 | if (path_equal(*name, "environment.d")) | |
| 384 | /* Special case: we need to include /etc/environment in the search path, even | |
| 385 | * though the whole concept is called environment.d. */ | |
| 386 | *name = "environment"; | |
| 387 | ||
| 388 | n = strdup(*name); | |
| 389 | if (!n) | |
| 390 | return log_oom(); | |
| 391 | ||
| 392 | delete_trailing_chars(n, "/"); | |
| 393 | ||
| 76d75d8b ZJS |
394 | /* We assume systemd-style config files support the /usr-/run-/etc split and dropins. */ |
| 395 | ||
| f1d9d36a ZJS |
396 | if (endswith(n, ".d")) |
| 397 | coll = true; | |
| 398 | ||
| f1d9d36a ZJS |
399 | if (path_equal(n, "udev/hwdb.d")) |
| 400 | ext = ".hwdb"; | |
| f3663c0e | 401 | else if (path_equal(n, "udev/rules.d")) |
| f1d9d36a | 402 | ext = ".rules"; |
| f3663c0e | 403 | else if (path_equal(n, "kernel/install.d")) |
| ea39de2f | 404 | ext = ".install"; |
| f3663c0e | 405 | else if (path_equal(n, "systemd/ntp-units.d")) { |
| afaae43b ZJS |
406 | coll = true; |
| 407 | ext = ".list"; | |
| f3663c0e | 408 | } else if (path_equal(n, "systemd/relabel-extra.d")) { |
| 81d791f1 ZJS |
409 | coll = run = true; |
| 410 | ext = ".relabel"; | |
| 4a8c3951 | 411 | } else if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset", "systemd/initrd-preset")) { |
| f1d9d36a ZJS |
412 | coll = true; |
| 413 | ext = ".preset"; | |
| 414 | } | |
| 415 | ||
| 6812498c ZJS |
416 | *ret_prefixes = (char**) (run ? run_prefixes : std_prefixes); |
| 417 | *ret_is_collection = coll; | |
| 418 | *ret_extension = ext; | |
| f1d9d36a ZJS |
419 | return 0; |
| 420 | } | |
| 421 | ||
| 063c8382 | 422 | int conf_files_cat(const char *root, const char *name, CatFlags flags) { |
| 661b5bfd | 423 | _cleanup_strv_free_ char **dirs = NULL; |
| de010b0b | 424 | char **prefixes = NULL; /* explicit initialization to appease gcc */ |
| 81d791f1 | 425 | bool is_collection; |
| f1d9d36a | 426 | const char *extension; |
| 294bf0c3 ZJS |
427 | int r; |
| 428 | ||
| 81d791f1 | 429 | r = guess_type(&name, &prefixes, &is_collection, &extension); |
| f1d9d36a ZJS |
430 | if (r < 0) |
| 431 | return r; | |
| 1c93632e ZJS |
432 | assert(prefixes); |
| 433 | assert(extension); | |
| f1d9d36a | 434 | |
| 81d791f1 ZJS |
435 | STRV_FOREACH(prefix, prefixes) { |
| 436 | assert(endswith(*prefix, "/")); | |
| 437 | r = strv_extendf(&dirs, "%s%s%s", *prefix, name, | |
| f1d9d36a | 438 | is_collection ? "" : ".d"); |
| 294bf0c3 ZJS |
439 | if (r < 0) |
| 440 | return log_error_errno(r, "Failed to build directory list: %m"); | |
| 441 | } | |
| 442 | ||
| 0895e873 ZJS |
443 | if (DEBUG_LOGGING) { |
| 444 | log_debug("Looking for configuration in:"); | |
| 445 | if (!is_collection) | |
| 446 | STRV_FOREACH(prefix, prefixes) | |
| 447 | log_debug(" %s%s%s", strempty(root), *prefix, name); | |
| 294bf0c3 | 448 | |
| 0895e873 ZJS |
449 | STRV_FOREACH(t, dirs) |
| 450 | log_debug(" %s%s/*%s", strempty(root), *t, extension); | |
| 451 | } | |
| 452 | ||
| 453 | /* First locate the main config file, if any */ | |
| 661b5bfd | 454 | _cleanup_(conf_file_freep) ConfFile *c = NULL; |
| f1d9d36a | 455 | if (!is_collection) { |
| 0895e873 | 456 | STRV_FOREACH(prefix, prefixes) { |
| 661b5bfd YW |
457 | _cleanup_free_ char *p = path_join(*prefix, name); |
| 458 | if (!p) | |
| 0895e873 | 459 | return log_oom(); |
| 661b5bfd YW |
460 | |
| 461 | if (conf_file_new(p, root, CHASE_MUST_BE_REGULAR, &c) >= 0) | |
| 2d0ec7f9 | 462 | break; |
| 0895e873 ZJS |
463 | } |
| 464 | ||
| 661b5bfd | 465 | if (!c) |
| 0895e873 ZJS |
466 | printf("%s# Main configuration file %s not found%s\n", |
| 467 | ansi_highlight_magenta(), | |
| 468 | name, | |
| 469 | ansi_normal()); | |
| f1d9d36a | 470 | } |
| 294bf0c3 | 471 | |
| 0895e873 | 472 | /* Then locate the drop-ins, if any */ |
| 661b5bfd YW |
473 | ConfFile **dropins = NULL; |
| 474 | size_t n_dropins = 0; | |
| 475 | CLEANUP_ARRAY(dropins, n_dropins, conf_file_free_many); | |
| 476 | r = conf_files_list_strv_full(extension, root, CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED, (const char* const*) dirs, &dropins, &n_dropins); | |
| 0895e873 ZJS |
477 | if (r < 0) |
| 478 | return log_error_errno(r, "Failed to query file list: %m"); | |
| 294bf0c3 | 479 | |
| 0895e873 | 480 | /* Show */ |
| 063c8382 ZJS |
481 | if (is_collection) |
| 482 | flags |= CAT_FORMAT_HAS_SECTIONS; | |
| 483 | ||
| 661b5bfd | 484 | return cat_files_full(c, dropins, n_dropins, flags); |
| 294bf0c3 | 485 | } |
| 3ef072ee LP |
486 | |
| 487 | int terminal_tint_color(double hue, char **ret) { | |
| 488 | double red, green, blue; | |
| 489 | int r; | |
| 490 | ||
| 491 | assert(ret); | |
| 492 | ||
| 493 | r = get_default_background_color(&red, &green, &blue); | |
| 494 | if (r < 0) | |
| 495 | return log_debug_errno(r, "Unable to get terminal background color: %m"); | |
| 496 | ||
| 497 | double s, v; | |
| d0ae0e4f | 498 | rgb_to_hsv(red, green, blue, /* ret_h= */ NULL, &s, &v); |
| 3ef072ee LP |
499 | |
| 500 | if (v > 50) /* If the background is bright, then pull down saturation */ | |
| 501 | s = 25; | |
| 502 | else /* otherwise pump it up */ | |
| 503 | s = 75; | |
| 504 | ||
| 2f7f0800 | 505 | v = MAX(20, v); /* Make sure we don't hide the color in black */ |
| 3ef072ee LP |
506 | |
| 507 | uint8_t r8, g8, b8; | |
| 508 | hsv_to_rgb(hue, s, v, &r8, &g8, &b8); | |
| 509 | ||
| 510 | if (asprintf(ret, "48;2;%u;%u;%u", r8, g8, b8) < 0) | |
| 511 | return -ENOMEM; | |
| 512 | ||
| 513 | return 0; | |
| 514 | } | |
| 71cb203a | 515 | |
| d4ffb37b LP |
516 | bool shall_tint_background(void) { |
| 517 | static int cache = -1; | |
| 518 | ||
| 519 | if (cache >= 0) | |
| 520 | return cache; | |
| 521 | ||
| 522 | cache = getenv_bool("SYSTEMD_TINT_BACKGROUND"); | |
| 523 | if (cache == -ENXIO) | |
| 524 | return (cache = true); | |
| 525 | if (cache < 0) | |
| 526 | log_debug_errno(cache, "Failed to parse $SYSTEMD_TINT_BACKGROUND, leaving background tinting enabled: %m"); | |
| 527 | ||
| 528 | return cache != 0; | |
| 529 | } | |
| 530 | ||
| 21abc0a9 | 531 | void draw_progress_bar_unbuffered(const char *prefix, double percentage) { |
| 37963914 YW |
532 | if (!on_tty()) |
| 533 | return; | |
| 534 | ||
| 6283f873 | 535 | fputc('\r', stderr); |
| 6e199216 | 536 | if (prefix) { |
| 71cb203a | 537 | fputs(prefix, stderr); |
| 6e199216 AV |
538 | fputc(' ', stderr); |
| 539 | } | |
| 71cb203a LP |
540 | |
| 541 | if (!terminal_is_dumb()) { | |
| 07b869b9 LP |
542 | /* Generate the Windows Terminal progress indication OSC sequence here. Most Linux terminals currently |
| 543 | * ignore this. But let's hope this changes one day. For details about this OSC sequence, see: | |
| 544 | * | |
| 545 | * https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC | |
| 546 | * https://github.com/microsoft/terminal/pull/8055 | |
| 547 | */ | |
| 03674247 | 548 | fprintf(stderr, ANSI_OSC "9;4;1;%u" ANSI_ST, (unsigned) ceil(percentage)); |
| 07b869b9 | 549 | |
| 71cb203a | 550 | size_t cols = columns(); |
| 6e199216 | 551 | size_t prefix_width = utf8_console_width(prefix) + 1 /* space */; |
| 1ad4e37d | 552 | size_t length = cols > prefix_width + 6 ? cols - prefix_width - 6 : 0; |
| 71cb203a | 553 | |
| 71cb203a LP |
554 | if (length > 5 && percentage >= 0.0 && percentage <= 100.0) { |
| 555 | size_t p = (size_t) (length * percentage / 100.0); | |
| 556 | bool separator_done = false; | |
| 557 | ||
| 6283f873 MY |
558 | fputs(ansi_highlight_green(), stderr); |
| 559 | ||
| 71cb203a LP |
560 | for (size_t i = 0; i < length; i++) { |
| 561 | ||
| 562 | if (i <= p) { | |
| 563 | if (get_color_mode() == COLOR_24BIT) { | |
| 564 | uint8_t r8, g8, b8; | |
| 565 | double z = i == 0 ? 0 : (((double) i / p) * 100); | |
| 566 | hsv_to_rgb(145 /* green */, z, 33 + z*2/3, &r8, &g8, &b8); | |
| 567 | fprintf(stderr, "\x1B[38;2;%u;%u;%um", r8, g8, b8); | |
| 568 | } | |
| 569 | ||
| 1ae9b0cf | 570 | fputs(glyph(GLYPH_HORIZONTAL_FAT), stderr); |
| 71cb203a LP |
571 | } else if (i+1 < length && !separator_done) { |
| 572 | fputs(ansi_normal(), stderr); | |
| 573 | fputc(' ', stderr); | |
| 574 | separator_done = true; | |
| 575 | fputs(ansi_grey(), stderr); | |
| 576 | } else | |
| 1ae9b0cf | 577 | fputs(glyph(GLYPH_HORIZONTAL_DOTTED), stderr); |
| 71cb203a LP |
578 | } |
| 579 | ||
| 580 | fputs(ansi_normal(), stderr); | |
| 581 | fputc(' ', stderr); | |
| 582 | } | |
| 583 | } | |
| 584 | ||
| 585 | fprintf(stderr, | |
| 586 | "%s%3.0f%%%s", | |
| 587 | ansi_highlight(), | |
| 588 | percentage, | |
| 589 | ansi_normal()); | |
| 590 | ||
| 591 | if (!terminal_is_dumb()) | |
| 592 | fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); | |
| 593 | ||
| 594 | fputc('\r', stderr); | |
| 71cb203a LP |
595 | } |
| 596 | ||
| 21abc0a9 | 597 | void clear_progress_bar_unbuffered(const char *prefix) { |
| 37963914 YW |
598 | if (!on_tty()) |
| 599 | return; | |
| 600 | ||
| 71cb203a LP |
601 | fputc('\r', stderr); |
| 602 | ||
| c66da2d9 | 603 | if (terminal_is_dumb()) |
| ad25ede4 | 604 | fputs(strrepa(" ", |
| 6e199216 AV |
605 | prefix ? utf8_console_width(prefix) + 5 : /* %3.0f%% (4 chars) + space */ |
| 606 | LESS_BY(columns(), 1U)), | |
| c66da2d9 MY |
607 | stderr); |
| 608 | else | |
| 07b869b9 | 609 | /* Undo Windows Terminal progress indication again. */ |
| 20785351 | 610 | fputs(ANSI_OSC "9;4;0;" ANSI_ST |
| 07b869b9 | 611 | ANSI_ERASE_TO_END_OF_LINE, stderr); |
| 71cb203a LP |
612 | |
| 613 | fputc('\r', stderr); | |
| 5f9dd9c6 AV |
614 | } |
| 615 | ||
| 616 | void draw_progress_bar(const char *prefix, double percentage) { | |
| 37963914 YW |
617 | if (!on_tty()) |
| 618 | return; | |
| 619 | ||
| 5f9dd9c6 AV |
620 | /* We are going output a bunch of small strings that shall appear as a single line to STDERR which is |
| 621 | * unbuffered by default. Let's temporarily turn on full buffering, so that this is passed to the tty | |
| 622 | * as a single buffer, to make things more efficient. */ | |
| 4edbea7d | 623 | WITH_BUFFERED_STDERR; |
| 21abc0a9 | 624 | draw_progress_bar_unbuffered(prefix, percentage); |
| 71cb203a | 625 | } |
| 5f9dd9c6 | 626 | |
| 91d64043 LP |
627 | int draw_progress_barf(double percentage, const char *prefixf, ...) { |
| 628 | _cleanup_free_ char *s = NULL; | |
| 629 | va_list ap; | |
| 630 | int r; | |
| 631 | ||
| 37963914 YW |
632 | if (!on_tty()) |
| 633 | return 0; | |
| 634 | ||
| 91d64043 LP |
635 | va_start(ap, prefixf); |
| 636 | r = vasprintf(&s, prefixf, ap); | |
| 637 | va_end(ap); | |
| 638 | ||
| 639 | if (r < 0) | |
| 640 | return -ENOMEM; | |
| 641 | ||
| 642 | draw_progress_bar(s, percentage); | |
| 643 | return 0; | |
| 644 | } | |
| 645 | ||
| 5f9dd9c6 | 646 | void clear_progress_bar(const char *prefix) { |
| 37963914 YW |
647 | if (!on_tty()) |
| 648 | return; | |
| 649 | ||
| 4edbea7d | 650 | WITH_BUFFERED_STDERR; |
| 21abc0a9 | 651 | clear_progress_bar_unbuffered(prefix); |
| 5f9dd9c6 | 652 | } |