]>
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" | |
8 | #include "conf-files.h" | |
9 | #include "def.h" | |
10 | #include "env-util.h" | |
11 | #include "fd-util.h" | |
12 | #include "fileio.h" | |
13 | #include "pager.h" | |
14 | #include "path-util.h" | |
15 | #include "pretty-print.h" | |
16 | #include "string-util.h" | |
17 | #include "strv.h" | |
18 | #include "terminal-util.h" | |
19 | #include "util.h" | |
20 | ||
422c8251 | 21 | bool urlify_enabled(void) { |
e5d86ebe | 22 | #if ENABLE_URLIFY |
294bf0c3 ZJS |
23 | static int cached_urlify_enabled = -1; |
24 | ||
294bf0c3 ZJS |
25 | if (cached_urlify_enabled < 0) { |
26 | int val; | |
27 | ||
28 | val = getenv_bool("SYSTEMD_URLIFY"); | |
29 | if (val >= 0) | |
30 | cached_urlify_enabled = val; | |
31 | else | |
ebef02dd | 32 | cached_urlify_enabled = colors_enabled(); |
294bf0c3 ZJS |
33 | } |
34 | ||
35 | return cached_urlify_enabled; | |
e5d86ebe JH |
36 | #else |
37 | return 0; | |
38 | #endif | |
294bf0c3 ZJS |
39 | } |
40 | ||
41 | int terminal_urlify(const char *url, const char *text, char **ret) { | |
42 | char *n; | |
43 | ||
44 | assert(url); | |
45 | ||
46 | /* Takes an URL and a pretty string and formats it as clickable link for the terminal. See | |
47 | * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */ | |
48 | ||
49 | if (isempty(text)) | |
50 | text = url; | |
51 | ||
52 | if (urlify_enabled()) | |
53 | n = strjoin("\x1B]8;;", url, "\a", text, "\x1B]8;;\a"); | |
54 | else | |
55 | n = strdup(text); | |
56 | if (!n) | |
57 | return -ENOMEM; | |
58 | ||
59 | *ret = n; | |
60 | return 0; | |
61 | } | |
62 | ||
62d6a1cc | 63 | int file_url_from_path(const char *path, char **ret) { |
294bf0c3 ZJS |
64 | _cleanup_free_ char *absolute = NULL; |
65 | struct utsname u; | |
62d6a1cc LP |
66 | char *url = NULL; |
67 | int r; | |
68 | ||
69 | if (uname(&u) < 0) | |
70 | return -errno; | |
71 | ||
72 | if (!path_is_absolute(path)) { | |
73 | r = path_make_absolute_cwd(path, &absolute); | |
74 | if (r < 0) | |
75 | return r; | |
76 | ||
77 | path = absolute; | |
78 | } | |
79 | ||
80 | /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local | |
81 | * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested | |
82 | * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly | |
83 | * careful with validating the strings either. */ | |
84 | ||
85 | url = strjoin("file://", u.nodename, path); | |
86 | if (!url) | |
87 | return -ENOMEM; | |
88 | ||
89 | *ret = url; | |
90 | return 0; | |
91 | } | |
92 | ||
93 | int terminal_urlify_path(const char *path, const char *text, char **ret) { | |
94 | _cleanup_free_ char *url = NULL; | |
294bf0c3 ZJS |
95 | int r; |
96 | ||
97 | assert(path); | |
98 | ||
99 | /* Much like terminal_urlify() above, but takes a file system path as input | |
100 | * and turns it into a proper file:// URL first. */ | |
101 | ||
102 | if (isempty(path)) | |
103 | return -EINVAL; | |
104 | ||
105 | if (isempty(text)) | |
106 | text = path; | |
107 | ||
108 | if (!urlify_enabled()) { | |
109 | char *n; | |
110 | ||
111 | n = strdup(text); | |
112 | if (!n) | |
113 | return -ENOMEM; | |
114 | ||
115 | *ret = n; | |
116 | return 0; | |
117 | } | |
118 | ||
62d6a1cc LP |
119 | r = file_url_from_path(path, &url); |
120 | if (r < 0) | |
121 | return r; | |
294bf0c3 ZJS |
122 | |
123 | return terminal_urlify(url, text, ret); | |
124 | } | |
125 | ||
126 | int terminal_urlify_man(const char *page, const char *section, char **ret) { | |
127 | const char *url, *text; | |
128 | ||
129 | url = strjoina("man:", page, "(", section, ")"); | |
130 | text = strjoina(page, "(", section, ") man page"); | |
131 | ||
132 | return terminal_urlify(url, text, ret); | |
133 | } | |
134 | ||
135 | static int cat_file(const char *filename, bool newline) { | |
136 | _cleanup_fclose_ FILE *f = NULL; | |
137 | _cleanup_free_ char *urlified = NULL; | |
138 | int r; | |
139 | ||
140 | f = fopen(filename, "re"); | |
141 | if (!f) | |
142 | return -errno; | |
143 | ||
144 | r = terminal_urlify_path(filename, NULL, &urlified); | |
145 | if (r < 0) | |
146 | return r; | |
147 | ||
148 | printf("%s%s# %s%s\n", | |
149 | newline ? "\n" : "", | |
150 | ansi_highlight_blue(), | |
151 | urlified, | |
152 | ansi_normal()); | |
153 | fflush(stdout); | |
154 | ||
155 | for (;;) { | |
156 | _cleanup_free_ char *line = NULL; | |
157 | ||
158 | r = read_line(f, LONG_LINE_MAX, &line); | |
159 | if (r < 0) | |
160 | return log_error_errno(r, "Failed to read \"%s\": %m", filename); | |
161 | if (r == 0) | |
162 | break; | |
163 | ||
164 | puts(line); | |
165 | } | |
166 | ||
167 | return 0; | |
168 | } | |
169 | ||
170 | int cat_files(const char *file, char **dropins, CatFlags flags) { | |
294bf0c3 ZJS |
171 | int r; |
172 | ||
173 | if (file) { | |
174 | r = cat_file(file, false); | |
175 | if (r == -ENOENT && (flags & CAT_FLAGS_MAIN_FILE_OPTIONAL)) | |
e2d839d3 | 176 | printf("%s# Configuration file %s not found%s\n", |
294bf0c3 ZJS |
177 | ansi_highlight_magenta(), |
178 | file, | |
179 | ansi_normal()); | |
180 | else if (r < 0) | |
181 | return log_warning_errno(r, "Failed to cat %s: %m", file); | |
182 | } | |
183 | ||
184 | STRV_FOREACH(path, dropins) { | |
185 | r = cat_file(*path, file || path != dropins); | |
186 | if (r < 0) | |
187 | return log_warning_errno(r, "Failed to cat %s: %m", *path); | |
188 | } | |
189 | ||
190 | return 0; | |
191 | } | |
192 | ||
193 | void print_separator(void) { | |
194 | ||
195 | /* Outputs a separator line that resolves to whitespace when copied from the terminal. We do that by outputting | |
196 | * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */ | |
197 | ||
198 | if (underline_enabled()) { | |
199 | size_t i, c; | |
200 | ||
201 | c = columns(); | |
202 | ||
203 | flockfile(stdout); | |
204 | fputs_unlocked(ANSI_UNDERLINE, stdout); | |
205 | ||
206 | for (i = 0; i < c; i++) | |
207 | fputc_unlocked(' ', stdout); | |
208 | ||
209 | fputs_unlocked(ANSI_NORMAL "\n\n", stdout); | |
210 | funlockfile(stdout); | |
211 | } else | |
212 | fputs("\n\n", stdout); | |
213 | } | |
214 | ||
81d791f1 | 215 | static int guess_type(const char **name, char ***prefixes, bool *is_collection, const char **extension) { |
f1d9d36a ZJS |
216 | /* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/, |
217 | * i.e. a collection of directories without a main config file. */ | |
218 | ||
219 | _cleanup_free_ char *n = NULL; | |
81d791f1 | 220 | bool usr = false, run = false, coll = false; |
f1d9d36a | 221 | const char *ext = ".conf"; |
81d791f1 ZJS |
222 | /* This is static so that the array doesn't get deallocated when we exit the function */ |
223 | static const char* const std_prefixes[] = { CONF_PATHS(""), NULL }; | |
224 | static const char* const usr_prefixes[] = { CONF_PATHS_USR(""), NULL }; | |
225 | static const char* const run_prefixes[] = { "/run/", NULL }; | |
f1d9d36a ZJS |
226 | |
227 | if (path_equal(*name, "environment.d")) | |
228 | /* Special case: we need to include /etc/environment in the search path, even | |
229 | * though the whole concept is called environment.d. */ | |
230 | *name = "environment"; | |
231 | ||
232 | n = strdup(*name); | |
233 | if (!n) | |
234 | return log_oom(); | |
235 | ||
28365e88 ZJS |
236 | /* All systemd-style config files should support the /usr-/etc-/run split and |
237 | * dropins. Let's add a blanket rule that allows us to support them without keeping | |
238 | * an explicit list. */ | |
239 | if (path_startswith(n, "systemd") && endswith(n, ".conf")) | |
240 | usr = true; | |
241 | ||
f1d9d36a ZJS |
242 | delete_trailing_chars(n, "/"); |
243 | ||
244 | if (endswith(n, ".d")) | |
245 | coll = true; | |
246 | ||
247 | if (path_equal(n, "environment")) | |
248 | usr = true; | |
249 | ||
250 | if (path_equal(n, "udev/hwdb.d")) | |
251 | ext = ".hwdb"; | |
252 | ||
253 | if (path_equal(n, "udev/rules.d")) | |
254 | ext = ".rules"; | |
255 | ||
ea39de2f ZJS |
256 | if (path_equal(n, "kernel/install.d")) |
257 | ext = ".install"; | |
258 | ||
afaae43b ZJS |
259 | if (path_equal(n, "systemd/ntp-units.d")) { |
260 | coll = true; | |
261 | ext = ".list"; | |
262 | } | |
263 | ||
81d791f1 ZJS |
264 | if (path_equal(n, "systemd/relabel-extra.d")) { |
265 | coll = run = true; | |
266 | ext = ".relabel"; | |
267 | } | |
268 | ||
f1d9d36a ZJS |
269 | if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) { |
270 | coll = true; | |
271 | ext = ".preset"; | |
272 | } | |
273 | ||
274 | if (path_equal(n, "systemd/user-preset")) | |
275 | usr = true; | |
276 | ||
81d791f1 | 277 | *prefixes = (char**) (usr ? usr_prefixes : run ? run_prefixes : std_prefixes); |
f1d9d36a ZJS |
278 | *is_collection = coll; |
279 | *extension = ext; | |
280 | return 0; | |
281 | } | |
282 | ||
294bf0c3 ZJS |
283 | int conf_files_cat(const char *root, const char *name) { |
284 | _cleanup_strv_free_ char **dirs = NULL, **files = NULL; | |
285 | _cleanup_free_ char *path = NULL; | |
de010b0b | 286 | char **prefixes = NULL; /* explicit initialization to appease gcc */ |
81d791f1 | 287 | bool is_collection; |
f1d9d36a | 288 | const char *extension; |
294bf0c3 ZJS |
289 | int r; |
290 | ||
81d791f1 | 291 | r = guess_type(&name, &prefixes, &is_collection, &extension); |
f1d9d36a ZJS |
292 | if (r < 0) |
293 | return r; | |
1c93632e ZJS |
294 | assert(prefixes); |
295 | assert(extension); | |
f1d9d36a | 296 | |
81d791f1 ZJS |
297 | STRV_FOREACH(prefix, prefixes) { |
298 | assert(endswith(*prefix, "/")); | |
299 | r = strv_extendf(&dirs, "%s%s%s", *prefix, name, | |
f1d9d36a | 300 | is_collection ? "" : ".d"); |
294bf0c3 ZJS |
301 | if (r < 0) |
302 | return log_error_errno(r, "Failed to build directory list: %m"); | |
303 | } | |
304 | ||
0895e873 ZJS |
305 | if (DEBUG_LOGGING) { |
306 | log_debug("Looking for configuration in:"); | |
307 | if (!is_collection) | |
308 | STRV_FOREACH(prefix, prefixes) | |
309 | log_debug(" %s%s%s", strempty(root), *prefix, name); | |
294bf0c3 | 310 | |
0895e873 ZJS |
311 | STRV_FOREACH(t, dirs) |
312 | log_debug(" %s%s/*%s", strempty(root), *t, extension); | |
313 | } | |
314 | ||
315 | /* First locate the main config file, if any */ | |
f1d9d36a | 316 | if (!is_collection) { |
0895e873 ZJS |
317 | STRV_FOREACH(prefix, prefixes) { |
318 | path = path_join(root, *prefix, name); | |
319 | if (!path) | |
320 | return log_oom(); | |
321 | if (access(path, F_OK) == 0) | |
322 | break; | |
323 | path = mfree(path); | |
324 | } | |
325 | ||
f1d9d36a | 326 | if (!path) |
0895e873 ZJS |
327 | printf("%s# Main configuration file %s not found%s\n", |
328 | ansi_highlight_magenta(), | |
329 | name, | |
330 | ansi_normal()); | |
f1d9d36a | 331 | } |
294bf0c3 | 332 | |
0895e873 ZJS |
333 | /* Then locate the drop-ins, if any */ |
334 | r = conf_files_list_strv(&files, extension, root, 0, (const char* const*) dirs); | |
335 | if (r < 0) | |
336 | return log_error_errno(r, "Failed to query file list: %m"); | |
294bf0c3 | 337 | |
0895e873 ZJS |
338 | /* Show */ |
339 | return cat_files(path, files, 0); | |
294bf0c3 | 340 | } |