chiark / gitweb /
tmpfiles: add --cat-config
[elogind.git] / src / basic / conf-files.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   This file is part of systemd.
4
5   Copyright 2010 Lennart Poettering
6 ***/
7
8 #include <dirent.h>
9 #include <errno.h>
10 #include <stdarg.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14
15 #include "conf-files.h"
16 //#include "def.h"
17 #include "dirent-util.h"
18 #include "fd-util.h"
19 #include "hashmap.h"
20 #include "log.h"
21 #include "macro.h"
22 #include "missing.h"
23 #include "path-util.h"
24 #include "stat-util.h"
25 #include "string-util.h"
26 #include "strv.h"
27 //#include "terminal-util.h"
28 #include "util.h"
29
30 static int files_add(Hashmap *h, const char *suffix, const char *root, unsigned flags, const char *path) {
31         _cleanup_closedir_ DIR *dir = NULL;
32         const char *dirpath;
33         struct dirent *de;
34         int r;
35
36         assert(path);
37
38         dirpath = prefix_roota(root, path);
39
40         dir = opendir(dirpath);
41         if (!dir) {
42                 if (errno == ENOENT)
43                         return 0;
44                 return -errno;
45         }
46
47         FOREACH_DIRENT(de, dir, return -errno) {
48                 char *p;
49
50                 if (!dirent_is_file_with_suffix(de, suffix)) {
51                         log_debug("Ignoring %s/%s, because it's not a regular file with suffix %s.", dirpath, de->d_name, strna(suffix));
52                         continue;
53                 }
54
55                 if (flags & CONF_FILES_EXECUTABLE) {
56                         struct stat st;
57
58                         /* As requested: check if the file is marked exectuable. Note that we don't check access(X_OK)
59                          * here, as we care about whether the file is marked executable at all, and not whether it is
60                          * executable for us, because if such errors are stuff we should log about. */
61
62                         if (fstatat(dirfd(dir), de->d_name, &st, 0) < 0) {
63                                 log_debug_errno(errno, "Failed to stat %s/%s, ignoring: %m", dirpath, de->d_name);
64                                 continue;
65                         }
66
67                         if (!null_or_empty(&st)) {
68                                 /* A mask is a symlink to /dev/null or an empty file. It does not even
69                                  * have to be executable. Other entries must be regular executable files
70                                  * or symlinks to them. */
71                                 if (S_ISREG(st.st_mode)) {
72                                         if ((st.st_mode & 0111) == 0) { /* not executable */
73                                                 log_debug("Ignoring %s/%s, as it is not marked executable.",
74                                                           dirpath, de->d_name);
75                                                 continue;
76                                         }
77                                 } else {
78                                         log_debug("Ignoring %s/%s, as it is neither a regular file nor a mask.",
79                                                   dirpath, de->d_name);
80                                         continue;
81                                 }
82                         }
83                 }
84
85                 p = strjoin(dirpath, "/", de->d_name);
86                 if (!p)
87                         return -ENOMEM;
88
89                 r = hashmap_put(h, basename(p), p);
90                 if (r == -EEXIST) {
91                         log_debug("Skipping overridden file: %s.", p);
92                         free(p);
93                 } else if (r < 0) {
94                         free(p);
95                         return r;
96                 } else if (r == 0) {
97                         log_debug("Duplicate file %s", p);
98                         free(p);
99                 }
100         }
101
102         return 0;
103 }
104
105 static int base_cmp(const void *a, const void *b) {
106         const char *s1, *s2;
107
108         s1 = *(char * const *)a;
109         s2 = *(char * const *)b;
110         return strcmp(basename(s1), basename(s2));
111 }
112
113 static int conf_files_list_strv_internal(char ***strv, const char *suffix, const char *root, unsigned flags, char **dirs) {
114         _cleanup_hashmap_free_ Hashmap *fh = NULL;
115         char **files, **p;
116         int r;
117
118         assert(strv);
119
120         /* This alters the dirs string array */
121         if (!path_strv_resolve_uniq(dirs, root))
122                 return -ENOMEM;
123
124         fh = hashmap_new(&string_hash_ops);
125         if (!fh)
126                 return -ENOMEM;
127
128         STRV_FOREACH(p, dirs) {
129                 r = files_add(fh, suffix, root, flags, *p);
130                 if (r == -ENOMEM)
131                         return r;
132                 if (r < 0)
133                         log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p);
134         }
135
136         files = hashmap_get_strv(fh);
137         if (!files)
138                 return -ENOMEM;
139
140         qsort_safe(files, hashmap_size(fh), sizeof(char *), base_cmp);
141         *strv = files;
142
143         return 0;
144 }
145
146 int conf_files_insert(char ***strv, const char *root, char **dirs, const char *path) {
147         /* Insert a path into strv, at the place honouring the usual sorting rules:
148          * - we first compare by the basename
149          * - and then we compare by dirname, allowing just one file with the given
150          *   basename.
151          * This means that we will
152          * - add a new entry if basename(path) was not on the list,
153          * - do nothing if an entry with higher priority was already present,
154          * - do nothing if our new entry matches the existing entry,
155          * - replace the existing entry if our new entry has higher priority.
156          */
157         char *t;
158         unsigned i;
159         int r;
160
161         for (i = 0; i < strv_length(*strv); i++) {
162                 int c;
163
164                 c = base_cmp(*strv + i, &path);
165                 if (c == 0) {
166                         char **dir;
167
168                         /* Oh, we found our spot and it already contains something. */
169                         STRV_FOREACH(dir, dirs) {
170                                 char *p1, *p2;
171
172                                 p1 = path_startswith((*strv)[i], root);
173                                 if (p1)
174                                         /* Skip "/" in *dir, because p1 is without "/" too */
175                                         p1 = path_startswith(p1, *dir + 1);
176                                 if (p1)
177                                         /* Existing entry with higher priority
178                                          * or same priority, no need to do anything. */
179                                         return 0;
180
181                                 p2 = path_startswith(path, *dir);
182                                 if (p2) {
183                                         /* Our new entry has higher priority */
184                                         t = path_join(root, path, NULL);
185                                         if (!t)
186                                                 return log_oom();
187
188                                         return free_and_replace((*strv)[i], t);
189                                 }
190                         }
191
192                 } else if (c > 0)
193                         /* Following files have lower priority, let's go insert our
194                          * new entry. */
195                         break;
196
197                 /* … we are not there yet, let's continue */
198         }
199
200         t = path_join(root, path, NULL);
201         if (!t)
202                 return log_oom();
203
204         r = strv_insert(strv, i, t);
205         if (r < 0)
206                 free(t);
207         return r;
208 }
209
210 int conf_files_insert_nulstr(char ***strv, const char *root, const char *dirs, const char *path) {
211         _cleanup_strv_free_ char **d = NULL;
212
213         assert(strv);
214
215         d = strv_split_nulstr(dirs);
216         if (!d)
217                 return -ENOMEM;
218
219         return conf_files_insert(strv, root, d, path);
220 }
221
222 int conf_files_list_strv(char ***strv, const char *suffix, const char *root, unsigned flags, const char* const* dirs) {
223         _cleanup_strv_free_ char **copy = NULL;
224
225         assert(strv);
226
227         copy = strv_copy((char**) dirs);
228         if (!copy)
229                 return -ENOMEM;
230
231         return conf_files_list_strv_internal(strv, suffix, root, flags, copy);
232 }
233
234 int conf_files_list(char ***strv, const char *suffix, const char *root, unsigned flags, const char *dir, ...) {
235         _cleanup_strv_free_ char **dirs = NULL;
236         va_list ap;
237
238         assert(strv);
239
240         va_start(ap, dir);
241         dirs = strv_new_ap(dir, ap);
242         va_end(ap);
243
244         if (!dirs)
245                 return -ENOMEM;
246
247         return conf_files_list_strv_internal(strv, suffix, root, flags, dirs);
248 }
249
250 int conf_files_list_nulstr(char ***strv, const char *suffix, const char *root, unsigned flags, const char *dirs) {
251         _cleanup_strv_free_ char **d = NULL;
252
253         assert(strv);
254
255         d = strv_split_nulstr(dirs);
256         if (!d)
257                 return -ENOMEM;
258
259         return conf_files_list_strv_internal(strv, suffix, root, flags, d);
260 }
261
262 int conf_files_list_with_replacement(
263                 const char *root,
264                 char **config_dirs,
265                 const char *replacement,
266                 char ***files,
267                 char **replace_file) {
268
269         _cleanup_strv_free_ char **f = NULL;
270         _cleanup_free_ char *p = NULL;
271         int r;
272
273         assert(config_dirs);
274         assert(files);
275         assert(replace_file || !replacement);
276
277         r = conf_files_list_strv(&f, ".conf", root, 0, (const char* const*) config_dirs);
278         if (r < 0)
279                 return log_error_errno(r, "Failed to enumerate config files: %m");
280
281         if (replacement) {
282                 r = conf_files_insert(&f, root, config_dirs, replacement);
283                 if (r < 0)
284                         return log_error_errno(r, "Failed to extend config file list: %m");
285
286                 p = path_join(root, replacement, NULL);
287                 if (!p)
288                         return log_oom();
289         }
290
291         *files = TAKE_PTR(f);
292         if (replace_file)
293                 *replace_file = TAKE_PTR(p);
294         return 0;
295 }
296
297 int conf_files_cat(const char *name) {
298         _cleanup_strv_free_ char **dirs = NULL, **files = NULL;
299         const char *dir;
300         char **t;
301         int r;
302
303         NULSTR_FOREACH(dir, CONF_PATHS_NULSTR("")) {
304                 assert(endswith(dir, "/"));
305                 r = strv_extendf(&dirs, "%s%s.d", dir, name);
306                 if (r < 0)
307                         return log_error("Failed to build directory list: %m");
308         }
309
310         r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char* const*) dirs);
311         if (r < 0)
312                 return log_error_errno(r, "Failed to query file list: %m");
313
314         name = strjoina("/etc/", name);
315
316         if (DEBUG_LOGGING) {
317                 log_debug("Looking for configuration in:");
318                 log_debug("   %s", name);
319                 STRV_FOREACH(t, dirs)
320                         log_debug("   %s/*.conf", *t);
321         }
322
323         /* show */
324         return cat_files(name, files, CAT_FLAGS_MAIN_FILE_OPTIONAL);
325 }