]>
Commit | Line | Data |
---|---|---|
294bf0c3 ZJS |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
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) { |
294bf0c3 ZJS |
22 | static int cached_urlify_enabled = -1; |
23 | ||
24 | /* Unfortunately 'less' doesn't support links like this yet ðŸ˜, hence let's disable this as long as there's a | |
25 | * pager in effect. Let's drop this check as soon as less got fixed a and enough time passed so that it's safe | |
26 | * to assume that a link-enabled 'less' version has hit most installations. */ | |
27 | ||
28 | if (cached_urlify_enabled < 0) { | |
29 | int val; | |
30 | ||
31 | val = getenv_bool("SYSTEMD_URLIFY"); | |
32 | if (val >= 0) | |
33 | cached_urlify_enabled = val; | |
34 | else | |
35 | cached_urlify_enabled = colors_enabled() && !pager_have(); | |
36 | } | |
37 | ||
38 | return cached_urlify_enabled; | |
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) { | |
171 | char **path; | |
172 | int r; | |
173 | ||
174 | if (file) { | |
175 | r = cat_file(file, false); | |
176 | if (r == -ENOENT && (flags & CAT_FLAGS_MAIN_FILE_OPTIONAL)) | |
e2d839d3 | 177 | printf("%s# Configuration file %s not found%s\n", |
294bf0c3 ZJS |
178 | ansi_highlight_magenta(), |
179 | file, | |
180 | ansi_normal()); | |
181 | else if (r < 0) | |
182 | return log_warning_errno(r, "Failed to cat %s: %m", file); | |
183 | } | |
184 | ||
185 | STRV_FOREACH(path, dropins) { | |
186 | r = cat_file(*path, file || path != dropins); | |
187 | if (r < 0) | |
188 | return log_warning_errno(r, "Failed to cat %s: %m", *path); | |
189 | } | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
194 | void print_separator(void) { | |
195 | ||
196 | /* Outputs a separator line that resolves to whitespace when copied from the terminal. We do that by outputting | |
197 | * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */ | |
198 | ||
199 | if (underline_enabled()) { | |
200 | size_t i, c; | |
201 | ||
202 | c = columns(); | |
203 | ||
204 | flockfile(stdout); | |
205 | fputs_unlocked(ANSI_UNDERLINE, stdout); | |
206 | ||
207 | for (i = 0; i < c; i++) | |
208 | fputc_unlocked(' ', stdout); | |
209 | ||
210 | fputs_unlocked(ANSI_NORMAL "\n\n", stdout); | |
211 | funlockfile(stdout); | |
212 | } else | |
213 | fputs("\n\n", stdout); | |
214 | } | |
215 | ||
81d791f1 | 216 | static int guess_type(const char **name, char ***prefixes, bool *is_collection, const char **extension) { |
f1d9d36a ZJS |
217 | /* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/, |
218 | * i.e. a collection of directories without a main config file. */ | |
219 | ||
220 | _cleanup_free_ char *n = NULL; | |
81d791f1 | 221 | bool usr = false, run = false, coll = false; |
f1d9d36a | 222 | const char *ext = ".conf"; |
81d791f1 ZJS |
223 | /* This is static so that the array doesn't get deallocated when we exit the function */ |
224 | static const char* const std_prefixes[] = { CONF_PATHS(""), NULL }; | |
225 | static const char* const usr_prefixes[] = { CONF_PATHS_USR(""), NULL }; | |
226 | static const char* const run_prefixes[] = { "/run/", NULL }; | |
f1d9d36a ZJS |
227 | |
228 | if (path_equal(*name, "environment.d")) | |
229 | /* Special case: we need to include /etc/environment in the search path, even | |
230 | * though the whole concept is called environment.d. */ | |
231 | *name = "environment"; | |
232 | ||
233 | n = strdup(*name); | |
234 | if (!n) | |
235 | return log_oom(); | |
236 | ||
28365e88 ZJS |
237 | /* All systemd-style config files should support the /usr-/etc-/run split and |
238 | * dropins. Let's add a blanket rule that allows us to support them without keeping | |
239 | * an explicit list. */ | |
240 | if (path_startswith(n, "systemd") && endswith(n, ".conf")) | |
241 | usr = true; | |
242 | ||
f1d9d36a ZJS |
243 | delete_trailing_chars(n, "/"); |
244 | ||
245 | if (endswith(n, ".d")) | |
246 | coll = true; | |
247 | ||
248 | if (path_equal(n, "environment")) | |
249 | usr = true; | |
250 | ||
251 | if (path_equal(n, "udev/hwdb.d")) | |
252 | ext = ".hwdb"; | |
253 | ||
254 | if (path_equal(n, "udev/rules.d")) | |
255 | ext = ".rules"; | |
256 | ||
ea39de2f ZJS |
257 | if (path_equal(n, "kernel/install.d")) |
258 | ext = ".install"; | |
259 | ||
afaae43b ZJS |
260 | if (path_equal(n, "systemd/ntp-units.d")) { |
261 | coll = true; | |
262 | ext = ".list"; | |
263 | } | |
264 | ||
81d791f1 ZJS |
265 | if (path_equal(n, "systemd/relabel-extra.d")) { |
266 | coll = run = true; | |
267 | ext = ".relabel"; | |
268 | } | |
269 | ||
f1d9d36a ZJS |
270 | if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) { |
271 | coll = true; | |
272 | ext = ".preset"; | |
273 | } | |
274 | ||
275 | if (path_equal(n, "systemd/user-preset")) | |
276 | usr = true; | |
277 | ||
81d791f1 | 278 | *prefixes = (char**) (usr ? usr_prefixes : run ? run_prefixes : std_prefixes); |
f1d9d36a ZJS |
279 | *is_collection = coll; |
280 | *extension = ext; | |
281 | return 0; | |
282 | } | |
283 | ||
294bf0c3 ZJS |
284 | int conf_files_cat(const char *root, const char *name) { |
285 | _cleanup_strv_free_ char **dirs = NULL, **files = NULL; | |
286 | _cleanup_free_ char *path = NULL; | |
81d791f1 ZJS |
287 | char **prefixes, **prefix; |
288 | bool is_collection; | |
f1d9d36a | 289 | const char *extension; |
294bf0c3 ZJS |
290 | char **t; |
291 | int r; | |
292 | ||
81d791f1 | 293 | r = guess_type(&name, &prefixes, &is_collection, &extension); |
f1d9d36a ZJS |
294 | if (r < 0) |
295 | return r; | |
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 | ||
f1d9d36a | 305 | r = conf_files_list_strv(&files, extension, root, 0, (const char* const*) dirs); |
294bf0c3 ZJS |
306 | if (r < 0) |
307 | return log_error_errno(r, "Failed to query file list: %m"); | |
308 | ||
f1d9d36a ZJS |
309 | if (!is_collection) { |
310 | path = path_join(root, "/etc", name); | |
311 | if (!path) | |
312 | return log_oom(); | |
313 | } | |
294bf0c3 ZJS |
314 | |
315 | if (DEBUG_LOGGING) { | |
316 | log_debug("Looking for configuration in:"); | |
f1d9d36a ZJS |
317 | if (path) |
318 | log_debug(" %s", path); | |
294bf0c3 | 319 | STRV_FOREACH(t, dirs) |
f1d9d36a | 320 | log_debug(" %s/*%s", *t, extension); |
294bf0c3 ZJS |
321 | } |
322 | ||
323 | /* show */ | |
324 | return cat_files(path, files, CAT_FLAGS_MAIN_FILE_OPTIONAL); | |
325 | } |