1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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 
urlify_enabled(void)21 bool urlify_enabled(void) {
22 #if ENABLE_URLIFY
23         static int cached_urlify_enabled = -1;
24 
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
32                         cached_urlify_enabled = colors_enabled();
33         }
34 
35         return cached_urlify_enabled;
36 #else
37         return 0;
38 #endif
39 }
40 
terminal_urlify(const char * url,const char * text,char ** ret)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 
file_url_from_path(const char * path,char ** ret)63 int file_url_from_path(const char *path, char **ret) {
64         _cleanup_free_ char *absolute = NULL;
65         struct utsname u;
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 
terminal_urlify_path(const char * path,const char * text,char ** ret)93 int terminal_urlify_path(const char *path, const char *text, char **ret) {
94         _cleanup_free_ char *url = NULL;
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 
119         r = file_url_from_path(path, &url);
120         if (r < 0)
121                 return r;
122 
123         return terminal_urlify(url, text, ret);
124 }
125 
terminal_urlify_man(const char * page,const char * section,char ** ret)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 
cat_file(const char * filename,bool newline)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 
cat_files(const char * file,char ** dropins,CatFlags flags)170 int cat_files(const char *file, char **dropins, CatFlags flags) {
171         int r;
172 
173         if (file) {
174                 r = cat_file(file, false);
175                 if (r == -ENOENT && (flags & CAT_FLAGS_MAIN_FILE_OPTIONAL))
176                         printf("%s# Configuration file %s not found%s\n",
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 
print_separator(void)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 
guess_type(const char ** name,char *** prefixes,bool * is_collection,const char ** extension)215 static int guess_type(const char **name, char ***prefixes, bool *is_collection, const char **extension) {
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;
220         bool usr = false, run = false, coll = false;
221         const char *ext = ".conf";
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 };
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 
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 
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 
256         if (path_equal(n, "kernel/install.d"))
257                 ext = ".install";
258 
259         if (path_equal(n, "systemd/ntp-units.d")) {
260                 coll = true;
261                 ext = ".list";
262         }
263 
264         if (path_equal(n, "systemd/relabel-extra.d")) {
265                 coll = run = true;
266                 ext = ".relabel";
267         }
268 
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 
277         *prefixes = (char**) (usr ? usr_prefixes : run ? run_prefixes : std_prefixes);
278         *is_collection = coll;
279         *extension = ext;
280         return 0;
281 }
282 
conf_files_cat(const char * root,const char * name)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;
286         char **prefixes = NULL; /* explicit initialization to appease gcc */
287         bool is_collection;
288         const char *extension;
289         int r;
290 
291         r = guess_type(&name, &prefixes, &is_collection, &extension);
292         if (r < 0)
293                 return r;
294         assert(prefixes);
295         assert(extension);
296 
297         STRV_FOREACH(prefix, prefixes) {
298                 assert(endswith(*prefix, "/"));
299                 r = strv_extendf(&dirs, "%s%s%s", *prefix, name,
300                                  is_collection ? "" : ".d");
301                 if (r < 0)
302                         return log_error_errno(r, "Failed to build directory list: %m");
303         }
304 
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);
310 
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 */
316         if (!is_collection) {
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 
326                 if (!path)
327                         printf("%s# Main configuration file %s not found%s\n",
328                                ansi_highlight_magenta(),
329                                name,
330                                ansi_normal());
331         }
332 
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");
337 
338         /* Show */
339         return cat_files(path, files, 0);
340 }
341