]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
294bf0c3 ZJS |
2 | |
3 | #include <sys/utsname.h> | |
4 | #include <errno.h> | |
5 | #include <stdio.h> | |
6 | ||
7 | #include "alloc-util.h" | |
3ef072ee | 8 | #include "color-util.h" |
294bf0c3 | 9 | #include "conf-files.h" |
28db6fbf | 10 | #include "constants.h" |
294bf0c3 ZJS |
11 | #include "env-util.h" |
12 | #include "fd-util.h" | |
13 | #include "fileio.h" | |
14 | #include "pager.h" | |
15 | #include "path-util.h" | |
16 | #include "pretty-print.h" | |
17 | #include "string-util.h" | |
18 | #include "strv.h" | |
19 | #include "terminal-util.h" | |
1ad4e37d | 20 | #include "utf8.h" |
294bf0c3 | 21 | |
d61a4dbb YW |
22 | void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) { |
23 | char *p = buffer; | |
24 | ||
25 | assert(buflen >= CYLON_BUFFER_EXTRA + width + 1); | |
26 | assert(pos <= width+1); /* 0 or width+1 mean that the center light is behind the corner */ | |
27 | ||
28 | if (pos > 1) { | |
29 | if (pos > 2) | |
30 | p = mempset(p, ' ', pos-2); | |
31 | if (log_get_show_color()) | |
32 | p = stpcpy(p, ANSI_RED); | |
33 | *p++ = '*'; | |
34 | } | |
35 | ||
36 | if (pos > 0 && pos <= width) { | |
37 | if (log_get_show_color()) | |
38 | p = stpcpy(p, ANSI_HIGHLIGHT_RED); | |
39 | *p++ = '*'; | |
40 | } | |
41 | ||
42 | if (log_get_show_color()) | |
43 | p = stpcpy(p, ANSI_NORMAL); | |
44 | ||
45 | if (pos < width) { | |
46 | if (log_get_show_color()) | |
47 | p = stpcpy(p, ANSI_RED); | |
48 | *p++ = '*'; | |
49 | if (pos < width-1) | |
50 | p = mempset(p, ' ', width-1-pos); | |
51 | if (log_get_show_color()) | |
52 | p = stpcpy(p, ANSI_NORMAL); | |
53 | } | |
54 | ||
55 | *p = '\0'; | |
56 | } | |
57 | ||
422c8251 | 58 | bool urlify_enabled(void) { |
e5d86ebe | 59 | #if ENABLE_URLIFY |
294bf0c3 ZJS |
60 | static int cached_urlify_enabled = -1; |
61 | ||
294bf0c3 ZJS |
62 | if (cached_urlify_enabled < 0) { |
63 | int val; | |
64 | ||
65 | val = getenv_bool("SYSTEMD_URLIFY"); | |
66 | if (val >= 0) | |
67 | cached_urlify_enabled = val; | |
68 | else | |
ebef02dd | 69 | cached_urlify_enabled = colors_enabled(); |
294bf0c3 ZJS |
70 | } |
71 | ||
72 | return cached_urlify_enabled; | |
e5d86ebe JH |
73 | #else |
74 | return 0; | |
75 | #endif | |
294bf0c3 ZJS |
76 | } |
77 | ||
78 | int terminal_urlify(const char *url, const char *text, char **ret) { | |
79 | char *n; | |
80 | ||
81 | assert(url); | |
82 | ||
5bc9ea07 | 83 | /* Takes a URL and a pretty string and formats it as clickable link for the terminal. See |
294bf0c3 ZJS |
84 | * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */ |
85 | ||
86 | if (isempty(text)) | |
87 | text = url; | |
88 | ||
89 | if (urlify_enabled()) | |
90 | n = strjoin("\x1B]8;;", url, "\a", text, "\x1B]8;;\a"); | |
91 | else | |
92 | n = strdup(text); | |
93 | if (!n) | |
94 | return -ENOMEM; | |
95 | ||
96 | *ret = n; | |
97 | return 0; | |
98 | } | |
99 | ||
62d6a1cc | 100 | int file_url_from_path(const char *path, char **ret) { |
294bf0c3 ZJS |
101 | _cleanup_free_ char *absolute = NULL; |
102 | struct utsname u; | |
62d6a1cc LP |
103 | char *url = NULL; |
104 | int r; | |
105 | ||
106 | if (uname(&u) < 0) | |
107 | return -errno; | |
108 | ||
109 | if (!path_is_absolute(path)) { | |
110 | r = path_make_absolute_cwd(path, &absolute); | |
111 | if (r < 0) | |
112 | return r; | |
113 | ||
114 | path = absolute; | |
115 | } | |
116 | ||
117 | /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local | |
118 | * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested | |
119 | * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly | |
120 | * careful with validating the strings either. */ | |
121 | ||
122 | url = strjoin("file://", u.nodename, path); | |
123 | if (!url) | |
124 | return -ENOMEM; | |
125 | ||
126 | *ret = url; | |
127 | return 0; | |
128 | } | |
129 | ||
130 | int terminal_urlify_path(const char *path, const char *text, char **ret) { | |
131 | _cleanup_free_ char *url = NULL; | |
294bf0c3 ZJS |
132 | int r; |
133 | ||
134 | assert(path); | |
135 | ||
136 | /* Much like terminal_urlify() above, but takes a file system path as input | |
137 | * and turns it into a proper file:// URL first. */ | |
138 | ||
139 | if (isempty(path)) | |
140 | return -EINVAL; | |
141 | ||
142 | if (isempty(text)) | |
143 | text = path; | |
144 | ||
454318d3 ZJS |
145 | if (!urlify_enabled()) |
146 | return strdup_to(ret, text); | |
294bf0c3 | 147 | |
62d6a1cc LP |
148 | r = file_url_from_path(path, &url); |
149 | if (r < 0) | |
150 | return r; | |
294bf0c3 ZJS |
151 | |
152 | return terminal_urlify(url, text, ret); | |
153 | } | |
154 | ||
155 | int terminal_urlify_man(const char *page, const char *section, char **ret) { | |
156 | const char *url, *text; | |
157 | ||
158 | url = strjoina("man:", page, "(", section, ")"); | |
159 | text = strjoina(page, "(", section, ") man page"); | |
160 | ||
161 | return terminal_urlify(url, text, ret); | |
162 | } | |
163 | ||
a9e68035 ZJS |
164 | typedef enum { |
165 | LINE_SECTION, | |
166 | LINE_COMMENT, | |
167 | LINE_NORMAL, | |
168 | } LineType; | |
169 | ||
170 | static LineType classify_line_type(const char *line, CatFlags flags) { | |
171 | const char *t = skip_leading_chars(line, WHITESPACE); | |
172 | ||
173 | if ((flags & CAT_FORMAT_HAS_SECTIONS) && *t == '[') | |
174 | return LINE_SECTION; | |
175 | if (IN_SET(*t, '#', ';', '\0')) | |
176 | return LINE_COMMENT; | |
177 | return LINE_NORMAL; | |
178 | } | |
179 | ||
063c8382 | 180 | static int cat_file(const char *filename, bool newline, CatFlags flags) { |
294bf0c3 | 181 | _cleanup_fclose_ FILE *f = NULL; |
c04cec12 | 182 | _cleanup_free_ char *urlified = NULL, *section = NULL, *old_section = NULL; |
294bf0c3 ZJS |
183 | int r; |
184 | ||
185 | f = fopen(filename, "re"); | |
186 | if (!f) | |
187 | return -errno; | |
188 | ||
189 | r = terminal_urlify_path(filename, NULL, &urlified); | |
190 | if (r < 0) | |
191 | return r; | |
192 | ||
193 | printf("%s%s# %s%s\n", | |
194 | newline ? "\n" : "", | |
195 | ansi_highlight_blue(), | |
196 | urlified, | |
197 | ansi_normal()); | |
198 | fflush(stdout); | |
199 | ||
200 | for (;;) { | |
201 | _cleanup_free_ char *line = NULL; | |
202 | ||
203 | r = read_line(f, LONG_LINE_MAX, &line); | |
204 | if (r < 0) | |
205 | return log_error_errno(r, "Failed to read \"%s\": %m", filename); | |
206 | if (r == 0) | |
207 | break; | |
208 | ||
a9e68035 | 209 | LineType line_type = classify_line_type(line, flags); |
22b0b7bf | 210 | if (FLAGS_SET(flags, CAT_TLDR)) { |
a9e68035 | 211 | if (line_type == LINE_SECTION) { |
063c8382 ZJS |
212 | /* The start of a section, let's not print it yet. */ |
213 | free_and_replace(section, line); | |
214 | continue; | |
215 | } | |
216 | ||
a9e68035 | 217 | if (line_type == LINE_COMMENT) |
063c8382 ZJS |
218 | continue; |
219 | ||
220 | /* Before we print the actual line, print the last section header */ | |
221 | if (section) { | |
c04cec12 ZJS |
222 | /* Do not print redundant section headers */ |
223 | if (!streq_ptr(section, old_section)) | |
224 | printf("%s%s%s\n", | |
225 | ansi_highlight_cyan(), | |
226 | section, | |
227 | ansi_normal()); | |
228 | ||
229 | free_and_replace(old_section, section); | |
063c8382 ZJS |
230 | } |
231 | } | |
232 | ||
22b0b7bf FS |
233 | /* Highlight the left side (directive) of a Foo=bar assignment */ |
234 | if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && line_type == LINE_NORMAL) { | |
235 | const char *p = strchr(line, '='); | |
236 | if (p) { | |
237 | _cleanup_free_ char *highlighted = NULL, *directive = NULL; | |
238 | ||
239 | directive = strndup(line, p - line); | |
240 | if (!directive) | |
241 | return log_oom(); | |
242 | ||
243 | highlighted = strjoin(ansi_highlight_green(), | |
244 | directive, | |
245 | "=", | |
246 | ansi_normal(), | |
247 | p + 1); | |
248 | if (!highlighted) | |
249 | return log_oom(); | |
250 | ||
251 | free_and_replace(line, highlighted); | |
252 | } | |
253 | } | |
254 | ||
a9e68035 ZJS |
255 | printf("%s%s%s\n", |
256 | line_type == LINE_SECTION ? ansi_highlight_cyan() : | |
257 | line_type == LINE_COMMENT ? ansi_highlight_grey() : | |
258 | "", | |
259 | line, | |
260 | line_type != LINE_NORMAL ? ansi_normal() : ""); | |
294bf0c3 ZJS |
261 | } |
262 | ||
263 | return 0; | |
264 | } | |
265 | ||
266 | int cat_files(const char *file, char **dropins, CatFlags flags) { | |
294bf0c3 ZJS |
267 | int r; |
268 | ||
269 | if (file) { | |
063c8382 | 270 | r = cat_file(file, /* newline= */ false, flags); |
80788a0b | 271 | if (r < 0) |
294bf0c3 ZJS |
272 | return log_warning_errno(r, "Failed to cat %s: %m", file); |
273 | } | |
274 | ||
275 | STRV_FOREACH(path, dropins) { | |
063c8382 | 276 | r = cat_file(*path, /* newline= */ file || path != dropins, flags); |
294bf0c3 ZJS |
277 | if (r < 0) |
278 | return log_warning_errno(r, "Failed to cat %s: %m", *path); | |
279 | } | |
280 | ||
281 | return 0; | |
282 | } | |
283 | ||
284 | void print_separator(void) { | |
285 | ||
286 | /* Outputs a separator line that resolves to whitespace when copied from the terminal. We do that by outputting | |
287 | * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */ | |
288 | ||
289 | if (underline_enabled()) { | |
2673d6fa | 290 | size_t c = columns(); |
294bf0c3 ZJS |
291 | |
292 | flockfile(stdout); | |
ef4bfa55 | 293 | fputs_unlocked(ANSI_GREY_UNDERLINE, stdout); |
294bf0c3 | 294 | |
2673d6fa | 295 | for (size_t i = 0; i < c; i++) |
294bf0c3 ZJS |
296 | fputc_unlocked(' ', stdout); |
297 | ||
298 | fputs_unlocked(ANSI_NORMAL "\n\n", stdout); | |
299 | funlockfile(stdout); | |
300 | } else | |
301 | fputs("\n\n", stdout); | |
302 | } | |
303 | ||
6812498c | 304 | static int guess_type(const char **name, char ***ret_prefixes, bool *ret_is_collection, const char **ret_extension) { |
f1d9d36a | 305 | /* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/, |
063c8382 ZJS |
306 | * i.e. a collection of directories without a main config file. |
307 | * Incidentally, all those formats don't use sections. So we return a single | |
308 | * is_collection boolean, which also means that the format doesn't use sections. | |
309 | */ | |
f1d9d36a ZJS |
310 | |
311 | _cleanup_free_ char *n = NULL; | |
76d75d8b | 312 | bool run = false, coll = false; |
f1d9d36a | 313 | const char *ext = ".conf"; |
81d791f1 ZJS |
314 | /* This is static so that the array doesn't get deallocated when we exit the function */ |
315 | static const char* const std_prefixes[] = { CONF_PATHS(""), NULL }; | |
81d791f1 | 316 | static const char* const run_prefixes[] = { "/run/", NULL }; |
f1d9d36a ZJS |
317 | |
318 | if (path_equal(*name, "environment.d")) | |
319 | /* Special case: we need to include /etc/environment in the search path, even | |
320 | * though the whole concept is called environment.d. */ | |
321 | *name = "environment"; | |
322 | ||
323 | n = strdup(*name); | |
324 | if (!n) | |
325 | return log_oom(); | |
326 | ||
327 | delete_trailing_chars(n, "/"); | |
328 | ||
76d75d8b ZJS |
329 | /* We assume systemd-style config files support the /usr-/run-/etc split and dropins. */ |
330 | ||
f1d9d36a ZJS |
331 | if (endswith(n, ".d")) |
332 | coll = true; | |
333 | ||
f1d9d36a ZJS |
334 | if (path_equal(n, "udev/hwdb.d")) |
335 | ext = ".hwdb"; | |
f3663c0e | 336 | else if (path_equal(n, "udev/rules.d")) |
f1d9d36a | 337 | ext = ".rules"; |
f3663c0e | 338 | else if (path_equal(n, "kernel/install.d")) |
ea39de2f | 339 | ext = ".install"; |
f3663c0e | 340 | else if (path_equal(n, "systemd/ntp-units.d")) { |
afaae43b ZJS |
341 | coll = true; |
342 | ext = ".list"; | |
f3663c0e | 343 | } else if (path_equal(n, "systemd/relabel-extra.d")) { |
81d791f1 ZJS |
344 | coll = run = true; |
345 | ext = ".relabel"; | |
f3663c0e | 346 | } else if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) { |
f1d9d36a ZJS |
347 | coll = true; |
348 | ext = ".preset"; | |
349 | } | |
350 | ||
6812498c ZJS |
351 | *ret_prefixes = (char**) (run ? run_prefixes : std_prefixes); |
352 | *ret_is_collection = coll; | |
353 | *ret_extension = ext; | |
f1d9d36a ZJS |
354 | return 0; |
355 | } | |
356 | ||
063c8382 | 357 | int conf_files_cat(const char *root, const char *name, CatFlags flags) { |
294bf0c3 ZJS |
358 | _cleanup_strv_free_ char **dirs = NULL, **files = NULL; |
359 | _cleanup_free_ char *path = NULL; | |
de010b0b | 360 | char **prefixes = NULL; /* explicit initialization to appease gcc */ |
81d791f1 | 361 | bool is_collection; |
f1d9d36a | 362 | const char *extension; |
294bf0c3 ZJS |
363 | int r; |
364 | ||
81d791f1 | 365 | r = guess_type(&name, &prefixes, &is_collection, &extension); |
f1d9d36a ZJS |
366 | if (r < 0) |
367 | return r; | |
1c93632e ZJS |
368 | assert(prefixes); |
369 | assert(extension); | |
f1d9d36a | 370 | |
81d791f1 ZJS |
371 | STRV_FOREACH(prefix, prefixes) { |
372 | assert(endswith(*prefix, "/")); | |
373 | r = strv_extendf(&dirs, "%s%s%s", *prefix, name, | |
f1d9d36a | 374 | is_collection ? "" : ".d"); |
294bf0c3 ZJS |
375 | if (r < 0) |
376 | return log_error_errno(r, "Failed to build directory list: %m"); | |
377 | } | |
378 | ||
0895e873 ZJS |
379 | if (DEBUG_LOGGING) { |
380 | log_debug("Looking for configuration in:"); | |
381 | if (!is_collection) | |
382 | STRV_FOREACH(prefix, prefixes) | |
383 | log_debug(" %s%s%s", strempty(root), *prefix, name); | |
294bf0c3 | 384 | |
0895e873 ZJS |
385 | STRV_FOREACH(t, dirs) |
386 | log_debug(" %s%s/*%s", strempty(root), *t, extension); | |
387 | } | |
388 | ||
389 | /* First locate the main config file, if any */ | |
f1d9d36a | 390 | if (!is_collection) { |
0895e873 ZJS |
391 | STRV_FOREACH(prefix, prefixes) { |
392 | path = path_join(root, *prefix, name); | |
393 | if (!path) | |
394 | return log_oom(); | |
395 | if (access(path, F_OK) == 0) | |
396 | break; | |
397 | path = mfree(path); | |
398 | } | |
399 | ||
f1d9d36a | 400 | if (!path) |
0895e873 ZJS |
401 | printf("%s# Main configuration file %s not found%s\n", |
402 | ansi_highlight_magenta(), | |
403 | name, | |
404 | ansi_normal()); | |
f1d9d36a | 405 | } |
294bf0c3 | 406 | |
0895e873 ZJS |
407 | /* Then locate the drop-ins, if any */ |
408 | r = conf_files_list_strv(&files, extension, root, 0, (const char* const*) dirs); | |
409 | if (r < 0) | |
410 | return log_error_errno(r, "Failed to query file list: %m"); | |
294bf0c3 | 411 | |
0895e873 | 412 | /* Show */ |
063c8382 ZJS |
413 | if (is_collection) |
414 | flags |= CAT_FORMAT_HAS_SECTIONS; | |
415 | ||
416 | return cat_files(path, files, flags); | |
294bf0c3 | 417 | } |
3ef072ee LP |
418 | |
419 | int terminal_tint_color(double hue, char **ret) { | |
420 | double red, green, blue; | |
421 | int r; | |
422 | ||
423 | assert(ret); | |
424 | ||
425 | r = get_default_background_color(&red, &green, &blue); | |
426 | if (r < 0) | |
427 | return log_debug_errno(r, "Unable to get terminal background color: %m"); | |
428 | ||
429 | double s, v; | |
430 | rgb_to_hsv(red, green, blue, /* h= */ NULL, &s, &v); | |
431 | ||
432 | if (v > 50) /* If the background is bright, then pull down saturation */ | |
433 | s = 25; | |
434 | else /* otherwise pump it up */ | |
435 | s = 75; | |
436 | ||
2f7f0800 | 437 | v = MAX(20, v); /* Make sure we don't hide the color in black */ |
3ef072ee LP |
438 | |
439 | uint8_t r8, g8, b8; | |
440 | hsv_to_rgb(hue, s, v, &r8, &g8, &b8); | |
441 | ||
442 | if (asprintf(ret, "48;2;%u;%u;%u", r8, g8, b8) < 0) | |
443 | return -ENOMEM; | |
444 | ||
445 | return 0; | |
446 | } | |
71cb203a | 447 | |
d4ffb37b LP |
448 | bool shall_tint_background(void) { |
449 | static int cache = -1; | |
450 | ||
451 | if (cache >= 0) | |
452 | return cache; | |
453 | ||
454 | cache = getenv_bool("SYSTEMD_TINT_BACKGROUND"); | |
455 | if (cache == -ENXIO) | |
456 | return (cache = true); | |
457 | if (cache < 0) | |
458 | log_debug_errno(cache, "Failed to parse $SYSTEMD_TINT_BACKGROUND, leaving background tinting enabled: %m"); | |
459 | ||
460 | return cache != 0; | |
461 | } | |
462 | ||
71cb203a LP |
463 | void draw_progress_bar(const char *prefix, double percentage) { |
464 | ||
aab74e1d LP |
465 | /* We are going output a bunch of small strings that shall appear as a single line to STDERR which is |
466 | * unbuffered by default. Let's temporarily turn on full buffering, so that this is passed to the tty | |
467 | * as a single buffer, to make things more efficient. */ | |
468 | char buffer[LONG_LINE_MAX]; | |
469 | setvbuf(stderr, buffer, _IOFBF, sizeof(buffer)); | |
470 | ||
6283f873 | 471 | fputc('\r', stderr); |
71cb203a LP |
472 | if (prefix) |
473 | fputs(prefix, stderr); | |
474 | ||
475 | if (!terminal_is_dumb()) { | |
476 | size_t cols = columns(); | |
1ad4e37d LP |
477 | size_t prefix_width = utf8_console_width(prefix); |
478 | size_t length = cols > prefix_width + 6 ? cols - prefix_width - 6 : 0; | |
71cb203a | 479 | |
71cb203a LP |
480 | if (length > 5 && percentage >= 0.0 && percentage <= 100.0) { |
481 | size_t p = (size_t) (length * percentage / 100.0); | |
482 | bool separator_done = false; | |
483 | ||
6283f873 MY |
484 | fputs(ansi_highlight_green(), stderr); |
485 | ||
71cb203a LP |
486 | for (size_t i = 0; i < length; i++) { |
487 | ||
488 | if (i <= p) { | |
489 | if (get_color_mode() == COLOR_24BIT) { | |
490 | uint8_t r8, g8, b8; | |
491 | double z = i == 0 ? 0 : (((double) i / p) * 100); | |
492 | hsv_to_rgb(145 /* green */, z, 33 + z*2/3, &r8, &g8, &b8); | |
493 | fprintf(stderr, "\x1B[38;2;%u;%u;%um", r8, g8, b8); | |
494 | } | |
495 | ||
496 | fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_FAT), stderr); | |
497 | } else if (i+1 < length && !separator_done) { | |
498 | fputs(ansi_normal(), stderr); | |
499 | fputc(' ', stderr); | |
500 | separator_done = true; | |
501 | fputs(ansi_grey(), stderr); | |
502 | } else | |
503 | fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_DOTTED), stderr); | |
504 | } | |
505 | ||
506 | fputs(ansi_normal(), stderr); | |
507 | fputc(' ', stderr); | |
508 | } | |
509 | } | |
510 | ||
511 | fprintf(stderr, | |
512 | "%s%3.0f%%%s", | |
513 | ansi_highlight(), | |
514 | percentage, | |
515 | ansi_normal()); | |
516 | ||
517 | if (!terminal_is_dumb()) | |
518 | fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); | |
519 | ||
520 | fputc('\r', stderr); | |
521 | fflush(stderr); | |
aab74e1d LP |
522 | |
523 | /* Disable buffering again */ | |
524 | setvbuf(stderr, NULL, _IONBF, 0); | |
71cb203a LP |
525 | } |
526 | ||
527 | void clear_progress_bar(const char *prefix) { | |
528 | ||
aab74e1d LP |
529 | char buffer[LONG_LINE_MAX]; |
530 | setvbuf(stderr, buffer, _IOFBF, sizeof(buffer)); | |
531 | ||
71cb203a LP |
532 | fputc('\r', stderr); |
533 | ||
c66da2d9 | 534 | if (terminal_is_dumb()) |
1ad4e37d | 535 | fputs(strrepa(" ", utf8_console_width(prefix) + 4), /* 4: %3.0f%% */ |
c66da2d9 MY |
536 | stderr); |
537 | else | |
71cb203a LP |
538 | fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); |
539 | ||
540 | fputc('\r', stderr); | |
541 | fflush(stderr); | |
aab74e1d LP |
542 | |
543 | /* Disable buffering again */ | |
544 | setvbuf(stderr, NULL, _IONBF, 0); | |
71cb203a | 545 | } |