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