]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <math.h> | |
4 | #include <stdio.h> | |
5 | #include <sys/utsname.h> | |
6 | #include <unistd.h> | |
7 | ||
8 | #include "alloc-util.h" | |
9 | #include "chase.h" | |
10 | #include "color-util.h" | |
11 | #include "conf-files.h" | |
12 | #include "constants.h" | |
13 | #include "env-util.h" | |
14 | #include "errno-util.h" | |
15 | #include "fd-util.h" | |
16 | #include "fileio.h" | |
17 | #include "fs-util.h" | |
18 | #include "log.h" | |
19 | #include "path-util.h" | |
20 | #include "pretty-print.h" | |
21 | #include "string-util.h" | |
22 | #include "strv.h" | |
23 | #include "terminal-util.h" | |
24 | #include "utf8.h" | |
25 | ||
26 | void 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 | ||
62 | bool urlify_enabled(void) { | |
63 | #if ENABLE_URLIFY | |
64 | static int cached_urlify_enabled = -1; | |
65 | ||
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 | |
73 | cached_urlify_enabled = colors_enabled(); | |
74 | } | |
75 | ||
76 | return cached_urlify_enabled; | |
77 | #else | |
78 | return 0; | |
79 | #endif | |
80 | } | |
81 | ||
82 | static 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 | ||
101 | int terminal_urlify(const char *url, const char *text, char **ret) { | |
102 | char *n; | |
103 | ||
104 | assert(url); | |
105 | ||
106 | /* Takes a URL and a pretty string and formats it as clickable link for the terminal. See | |
107 | * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */ | |
108 | ||
109 | if (isempty(text)) | |
110 | text = url; | |
111 | ||
112 | if (urlify_enabled() && url_suitable_for_osc8(url)) | |
113 | n = strjoin(ANSI_OSC "8;;", url, ANSI_ST, | |
114 | text, | |
115 | ANSI_OSC "8;;" ANSI_ST); | |
116 | else | |
117 | n = strdup(text); | |
118 | if (!n) | |
119 | return -ENOMEM; | |
120 | ||
121 | *ret = n; | |
122 | return 0; | |
123 | } | |
124 | ||
125 | int file_url_from_path(const char *path, char **ret) { | |
126 | _cleanup_free_ char *absolute = NULL; | |
127 | struct utsname u; | |
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 | ||
155 | int terminal_urlify_path(const char *path, const char *text, char **ret) { | |
156 | _cleanup_free_ char *url = NULL; | |
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 | ||
170 | if (!urlify_enabled()) | |
171 | return strdup_to(ret, text); | |
172 | ||
173 | r = file_url_from_path(path, &url); | |
174 | if (r < 0) | |
175 | return r; | |
176 | ||
177 | return terminal_urlify(url, text, ret); | |
178 | } | |
179 | ||
180 | int 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 | ||
189 | static int cat_file(const ConfFile *c, bool *newline, CatFlags flags) { | |
190 | _cleanup_fclose_ FILE *f = NULL; | |
191 | _cleanup_free_ char *urlified = NULL, *section = NULL, *old_section = NULL; | |
192 | int r; | |
193 | ||
194 | assert(c); | |
195 | assert(c->original_path); | |
196 | assert(c->resolved_path); | |
197 | assert(c->fd >= 0); | |
198 | ||
199 | if (newline) { | |
200 | if (*newline) | |
201 | putc('\n', stdout); | |
202 | *newline = true; | |
203 | } | |
204 | ||
205 | bool resolved = !path_equal(c->original_path, c->resolved_path); | |
206 | ||
207 | r = terminal_urlify_path(c->resolved_path, NULL, &urlified); | |
208 | if (r < 0) | |
209 | return log_error_errno(r, "Failed to urlify path \"%s\": %m", c->resolved_path); | |
210 | ||
211 | printf("%s# %s%s%s%s\n", | |
212 | ansi_highlight_blue(), | |
213 | resolved ? c->original_path : "", | |
214 | resolved ? " -> " : "", | |
215 | urlified, | |
216 | ansi_normal()); | |
217 | ||
218 | f = fopen(FORMAT_PROC_FD_PATH(c->fd), "re"); | |
219 | if (!f) | |
220 | return log_error_errno(errno, "Failed to open \"%s\": %m", c->resolved_path); | |
221 | ||
222 | for (bool continued = false;;) { | |
223 | _cleanup_free_ char *line = NULL; | |
224 | ||
225 | r = read_line(f, LONG_LINE_MAX, &line); | |
226 | if (r < 0) | |
227 | return log_error_errno(r, "Failed to read \"%s\": %m", c->resolved_path); | |
228 | if (r == 0) | |
229 | break; | |
230 | ||
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 */ | |
241 | if (FLAGS_SET(flags, CAT_TLDR) && (isempty(l) || streq(l, "\\"))) | |
242 | continue; | |
243 | ||
244 | /* section */ | |
245 | if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && *l == '[' && !continued) { | |
246 | if (FLAGS_SET(flags, CAT_TLDR)) | |
247 | /* On TLDR, let's not print it yet. */ | |
248 | free_and_replace(section, line); | |
249 | else | |
250 | printf("%s%s%s\n", ansi_highlight_cyan(), line, ansi_normal()); | |
251 | continue; | |
252 | } | |
253 | ||
254 | /* normal line */ | |
255 | ||
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()); | |
261 | ||
262 | free_and_replace(old_section, section); | |
263 | } | |
264 | ||
265 | /* Check if the line ends with a backslash. */ | |
266 | bool escaped = false; | |
267 | char *e; | |
268 | for (e = line; *e != '\0'; e++) { | |
269 | if (escaped) | |
270 | escaped = false; | |
271 | else if (*e == '\\') | |
272 | escaped = true; | |
273 | } | |
274 | ||
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 | ||
284 | /* Highlight the left side (directive) of a Foo=bar assignment */ | |
285 | if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && !continued) { | |
286 | const char *p = strchr(line, '='); | |
287 | if (p) { | |
288 | _cleanup_free_ char *directive = NULL; | |
289 | ||
290 | directive = strndup(line, p - line); | |
291 | if (!directive) | |
292 | return log_oom(); | |
293 | ||
294 | printf("%s%s=%s%s\n", ansi_highlight_green(), directive, ansi_normal(), p + 1); | |
295 | continued = escaped; | |
296 | continue; | |
297 | } | |
298 | } | |
299 | ||
300 | /* Otherwise, print the line as is. */ | |
301 | printf("%s\n", line); | |
302 | continued = escaped; | |
303 | } | |
304 | ||
305 | return 0; | |
306 | } | |
307 | ||
308 | int cat_files_full(const ConfFile *file, ConfFile * const *dropins, size_t n_dropins, CatFlags flags) { | |
309 | bool newline = false; | |
310 | int ret = 0; | |
311 | ||
312 | assert(dropins || n_dropins == 0); | |
313 | ||
314 | if (file) | |
315 | ret = cat_file(file, &newline, flags); | |
316 | ||
317 | FOREACH_ARRAY(i, dropins, n_dropins) | |
318 | RET_GATHER(ret, cat_file(*i, &newline, flags)); | |
319 | ||
320 | return ret; | |
321 | } | |
322 | ||
323 | static 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 | ||
336 | int 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 | ||
343 | STRV_FOREACH(path, dropins) | |
344 | RET_GATHER(ret, cat_file_by_path(*path, &newline, flags)); | |
345 | ||
346 | return ret; | |
347 | } | |
348 | ||
349 | void 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()) { | |
355 | size_t c = columns(); | |
356 | ||
357 | flockfile(stdout); | |
358 | fputs_unlocked(ansi_grey_underline(), stdout); | |
359 | ||
360 | for (size_t i = 0; i < c; i++) | |
361 | fputc_unlocked(' ', stdout); | |
362 | ||
363 | fputs_unlocked(ansi_normal(), stdout); | |
364 | fputs_unlocked("\n\n", stdout); | |
365 | funlockfile(stdout); | |
366 | } else | |
367 | fputs("\n\n", stdout); | |
368 | } | |
369 | ||
370 | static int guess_type(const char **name, char ***ret_prefixes, bool *ret_is_collection, const char **ret_extension) { | |
371 | /* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/, | |
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 | */ | |
376 | ||
377 | _cleanup_free_ char *n = NULL; | |
378 | bool run = false, coll = false; | |
379 | const char *ext = ".conf"; | |
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 }; | |
382 | static const char* const run_prefixes[] = { "/run/", NULL }; | |
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 | ||
395 | /* We assume systemd-style config files support the /usr-/run-/etc split and dropins. */ | |
396 | ||
397 | if (endswith(n, ".d")) | |
398 | coll = true; | |
399 | ||
400 | if (path_equal(n, "udev/hwdb.d")) | |
401 | ext = ".hwdb"; | |
402 | else if (path_equal(n, "udev/rules.d")) | |
403 | ext = ".rules"; | |
404 | else if (path_equal(n, "kernel/install.d")) | |
405 | ext = ".install"; | |
406 | else if (path_equal(n, "systemd/ntp-units.d")) { | |
407 | coll = true; | |
408 | ext = ".list"; | |
409 | } else if (path_equal(n, "systemd/relabel-extra.d")) { | |
410 | coll = run = true; | |
411 | ext = ".relabel"; | |
412 | } else if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset", "systemd/initrd-preset")) { | |
413 | coll = true; | |
414 | ext = ".preset"; | |
415 | } | |
416 | ||
417 | *ret_prefixes = (char**) (run ? run_prefixes : std_prefixes); | |
418 | *ret_is_collection = coll; | |
419 | *ret_extension = ext; | |
420 | return 0; | |
421 | } | |
422 | ||
423 | int conf_files_cat(const char *root, const char *name, CatFlags flags) { | |
424 | _cleanup_strv_free_ char **dirs = NULL; | |
425 | char **prefixes = NULL; /* explicit initialization to appease gcc */ | |
426 | bool is_collection; | |
427 | const char *extension; | |
428 | int r; | |
429 | ||
430 | r = guess_type(&name, &prefixes, &is_collection, &extension); | |
431 | if (r < 0) | |
432 | return r; | |
433 | assert(prefixes); | |
434 | assert(extension); | |
435 | ||
436 | STRV_FOREACH(prefix, prefixes) { | |
437 | assert(endswith(*prefix, "/")); | |
438 | r = strv_extendf(&dirs, "%s%s%s", *prefix, name, | |
439 | is_collection ? "" : ".d"); | |
440 | if (r < 0) | |
441 | return log_error_errno(r, "Failed to build directory list: %m"); | |
442 | } | |
443 | ||
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); | |
449 | ||
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 */ | |
455 | _cleanup_(conf_file_freep) ConfFile *c = NULL; | |
456 | if (!is_collection) { | |
457 | STRV_FOREACH(prefix, prefixes) { | |
458 | _cleanup_free_ char *p = path_join(*prefix, name); | |
459 | if (!p) | |
460 | return log_oom(); | |
461 | ||
462 | if (conf_file_new(p, root, CHASE_MUST_BE_REGULAR, &c) >= 0) | |
463 | break; | |
464 | } | |
465 | ||
466 | if (!c) | |
467 | printf("%s# Main configuration file %s not found%s\n", | |
468 | ansi_highlight_magenta(), | |
469 | name, | |
470 | ansi_normal()); | |
471 | } | |
472 | ||
473 | /* Then locate the drop-ins, if any */ | |
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); | |
478 | if (r < 0) | |
479 | return log_error_errno(r, "Failed to query file list: %m"); | |
480 | ||
481 | /* Show */ | |
482 | if (is_collection) | |
483 | flags |= CAT_FORMAT_HAS_SECTIONS; | |
484 | ||
485 | return cat_files_full(c, dropins, n_dropins, flags); | |
486 | } | |
487 | ||
488 | int 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; | |
499 | rgb_to_hsv(red, green, blue, /* ret_h= */ NULL, &s, &v); | |
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 | ||
506 | v = MAX(20, v); /* Make sure we don't hide the color in black */ | |
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 | } | |
516 | ||
517 | bool 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 | ||
532 | void draw_progress_bar_unbuffered(const char *prefix, double percentage) { | |
533 | fputc('\r', stderr); | |
534 | if (prefix) { | |
535 | fputs(prefix, stderr); | |
536 | fputc(' ', stderr); | |
537 | } | |
538 | ||
539 | if (!terminal_is_dumb()) { | |
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 | */ | |
546 | fprintf(stderr, ANSI_OSC "9;4;1;%u" ANSI_ST, (unsigned) ceil(percentage)); | |
547 | ||
548 | size_t cols = columns(); | |
549 | size_t prefix_width = utf8_console_width(prefix) + 1 /* space */; | |
550 | size_t length = cols > prefix_width + 6 ? cols - prefix_width - 6 : 0; | |
551 | ||
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 | ||
556 | fputs(ansi_highlight_green(), stderr); | |
557 | ||
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 | ||
568 | fputs(glyph(GLYPH_HORIZONTAL_FAT), stderr); | |
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 | |
575 | fputs(glyph(GLYPH_HORIZONTAL_DOTTED), stderr); | |
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); | |
593 | } | |
594 | ||
595 | void clear_progress_bar_unbuffered(const char *prefix) { | |
596 | fputc('\r', stderr); | |
597 | ||
598 | if (terminal_is_dumb()) | |
599 | fputs(strrepa(" ", | |
600 | prefix ? utf8_console_width(prefix) + 5 : /* %3.0f%% (4 chars) + space */ | |
601 | LESS_BY(columns(), 1U)), | |
602 | stderr); | |
603 | else | |
604 | /* Undo Windows Terminal progress indication again. */ | |
605 | fputs(ANSI_OSC "9;4;0;" ANSI_ST | |
606 | ANSI_ERASE_TO_END_OF_LINE, stderr); | |
607 | ||
608 | fputc('\r', stderr); | |
609 | } | |
610 | ||
611 | void draw_progress_bar(const char *prefix, double percentage) { | |
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. */ | |
615 | WITH_BUFFERED_STDERR; | |
616 | draw_progress_bar_unbuffered(prefix, percentage); | |
617 | } | |
618 | ||
619 | int 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 | ||
635 | void clear_progress_bar(const char *prefix) { | |
636 | WITH_BUFFERED_STDERR; | |
637 | clear_progress_bar_unbuffered(prefix); | |
638 | } |