]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/pretty-print.c
pull: add pretty progress bar to systemd-pull too
[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"
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
25void 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 61bool 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
81static 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
100int 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 124int 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
154int 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
179int 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 188static 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 307int 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
322static 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
335int 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
348void 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 369static 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 422int 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
487int 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
516bool 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 531void 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 597void 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
616void 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
627int 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 646void 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}