]>
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 | ||
21 | static bool urlify_enabled(void) { | |
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)) | |
177 | printf("%s# config file %s not found%s\n", | |
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 | ||
f1d9d36a ZJS |
216 | static int guess_type(const char **name, bool *is_usr, bool *is_collection, const char **extension) { |
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; | |
221 | bool usr = false, coll = false; | |
222 | const char *ext = ".conf"; | |
223 | ||
224 | if (path_equal(*name, "environment.d")) | |
225 | /* Special case: we need to include /etc/environment in the search path, even | |
226 | * though the whole concept is called environment.d. */ | |
227 | *name = "environment"; | |
228 | ||
229 | n = strdup(*name); | |
230 | if (!n) | |
231 | return log_oom(); | |
232 | ||
233 | delete_trailing_chars(n, "/"); | |
234 | ||
235 | if (endswith(n, ".d")) | |
236 | coll = true; | |
237 | ||
238 | if (path_equal(n, "environment")) | |
239 | usr = true; | |
240 | ||
241 | if (path_equal(n, "udev/hwdb.d")) | |
242 | ext = ".hwdb"; | |
243 | ||
244 | if (path_equal(n, "udev/rules.d")) | |
245 | ext = ".rules"; | |
246 | ||
ea39de2f ZJS |
247 | if (path_equal(n, "kernel/install.d")) |
248 | ext = ".install"; | |
249 | ||
f1d9d36a ZJS |
250 | if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) { |
251 | coll = true; | |
252 | ext = ".preset"; | |
253 | } | |
254 | ||
255 | if (path_equal(n, "systemd/user-preset")) | |
256 | usr = true; | |
257 | ||
258 | *is_usr = usr; | |
259 | *is_collection = coll; | |
260 | *extension = ext; | |
261 | return 0; | |
262 | } | |
263 | ||
294bf0c3 ZJS |
264 | int conf_files_cat(const char *root, const char *name) { |
265 | _cleanup_strv_free_ char **dirs = NULL, **files = NULL; | |
266 | _cleanup_free_ char *path = NULL; | |
f1d9d36a ZJS |
267 | char **dir; |
268 | bool is_usr, is_collection; | |
269 | const char *extension; | |
294bf0c3 ZJS |
270 | char **t; |
271 | int r; | |
272 | ||
f1d9d36a ZJS |
273 | r = guess_type(&name, &is_usr, &is_collection, &extension); |
274 | if (r < 0) | |
275 | return r; | |
276 | ||
277 | STRV_FOREACH(dir, is_usr ? CONF_PATHS_USR_STRV("") : CONF_PATHS_STRV("")) { | |
278 | assert(endswith(*dir, "/")); | |
279 | r = strv_extendf(&dirs, "%s%s%s", *dir, name, | |
280 | is_collection ? "" : ".d"); | |
294bf0c3 ZJS |
281 | if (r < 0) |
282 | return log_error_errno(r, "Failed to build directory list: %m"); | |
283 | } | |
284 | ||
f1d9d36a | 285 | r = conf_files_list_strv(&files, extension, root, 0, (const char* const*) dirs); |
294bf0c3 ZJS |
286 | if (r < 0) |
287 | return log_error_errno(r, "Failed to query file list: %m"); | |
288 | ||
f1d9d36a ZJS |
289 | if (!is_collection) { |
290 | path = path_join(root, "/etc", name); | |
291 | if (!path) | |
292 | return log_oom(); | |
293 | } | |
294bf0c3 ZJS |
294 | |
295 | if (DEBUG_LOGGING) { | |
296 | log_debug("Looking for configuration in:"); | |
f1d9d36a ZJS |
297 | if (path) |
298 | log_debug(" %s", path); | |
294bf0c3 | 299 | STRV_FOREACH(t, dirs) |
f1d9d36a | 300 | log_debug(" %s/*%s", *t, extension); |
294bf0c3 ZJS |
301 | } |
302 | ||
303 | /* show */ | |
304 | return cat_files(path, files, CAT_FLAGS_MAIN_FILE_OPTIONAL); | |
305 | } |