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