]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2c21044f | 2 | |
2c21044f | 3 | #include <stdio.h> |
07630cea | 4 | #include <stdlib.h> |
2c21044f | 5 | |
6553db60 | 6 | #include "alloc-util.h" |
f461a28d | 7 | #include "chase.h" |
3ffd4af2 | 8 | #include "conf-files.h" |
a0956174 | 9 | #include "dirent-util.h" |
f6a1346e | 10 | #include "errno-util.h" |
3ffd4af2 | 11 | #include "fd-util.h" |
f6a1346e ZJS |
12 | #include "fileio.h" |
13 | #include "glyph-util.h" | |
07630cea LP |
14 | #include "hashmap.h" |
15 | #include "log.h" | |
08af3cc5 | 16 | #include "nulstr-util.h" |
9eb977db | 17 | #include "path-util.h" |
3e36211b | 18 | #include "set.h" |
b5084605 | 19 | #include "stat-util.h" |
07630cea LP |
20 | #include "string-util.h" |
21 | #include "strv.h" | |
2c21044f | 22 | |
3e36211b | 23 | static int files_add( |
a5af5f80 YW |
24 | DIR *dir, |
25 | const char *dirpath, | |
50c81130 YW |
26 | int rfd, |
27 | const char *root, /* for logging, can be NULL */ | |
a5af5f80 | 28 | Hashmap **files, |
9d0d39ee | 29 | Set **masked, |
3e36211b | 30 | const char *suffix, |
1de31f23 | 31 | ConfFilesFlags flags) { |
3e36211b | 32 | |
1d13f648 | 33 | int r; |
e02caf30 | 34 | |
a5af5f80 YW |
35 | assert(dir); |
36 | assert(dirpath); | |
50c81130 | 37 | assert(rfd >= 0 || rfd == AT_FDCWD); |
a5af5f80 | 38 | assert(files); |
9d0d39ee | 39 | assert(masked); |
2c21044f | 40 | |
50c81130 YW |
41 | root = strempty(root); |
42 | ||
43 | FOREACH_DIRENT(de, dir, return log_debug_errno(errno, "Failed to read directory '%s/%s': %m", | |
44 | root, skip_leading_slash(dirpath))) { | |
3e36211b LP |
45 | |
46 | /* Does this match the suffix? */ | |
47 | if (suffix && !endswith(de->d_name, suffix)) | |
48 | continue; | |
2c21044f | 49 | |
3e36211b | 50 | /* Has this file already been found in an earlier directory? */ |
a5af5f80 | 51 | if (hashmap_contains(*files, de->d_name)) { |
50c81130 YW |
52 | log_debug("Skipping overridden file '%s/%s/%s'.", |
53 | root, skip_leading_slash(dirpath), de->d_name); | |
2c21044f | 54 | continue; |
e34aa8ed | 55 | } |
2c21044f | 56 | |
3e36211b | 57 | /* Has this been masked in an earlier directory? */ |
50c81130 YW |
58 | if ((flags & CONF_FILES_FILTER_MASKED) != 0 && set_contains(*masked, de->d_name)) { |
59 | log_debug("File '%s/%s/%s' is masked by previous entry.", | |
60 | root, skip_leading_slash(dirpath), de->d_name); | |
3e36211b LP |
61 | continue; |
62 | } | |
63 | ||
50c81130 YW |
64 | _cleanup_free_ char *p = path_join(dirpath, de->d_name); |
65 | if (!p) | |
66 | return log_oom_debug(); | |
67 | ||
68 | _cleanup_free_ char *resolved_path = NULL; | |
69 | bool need_stat = (flags & (CONF_FILES_FILTER_MASKED | CONF_FILES_REGULAR | CONF_FILES_DIRECTORY | CONF_FILES_EXECUTABLE)) != 0; | |
70 | struct stat st; | |
71 | ||
72 | if (!need_stat || FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK)) { | |
73 | ||
74 | /* Even if no verification is requested, let's unconditionally call chaseat(), | |
75 | * to drop unsafe symlinks. */ | |
76 | ||
77 | r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &resolved_path, /* ret_fd = */ NULL); | |
78 | if (r < 0) { | |
79 | log_debug_errno(r, "Failed to chase '%s/%s', ignoring: %m", | |
80 | root, skip_leading_slash(p)); | |
81 | continue; | |
82 | } | |
83 | if (r == 0 && FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK)) { | |
84 | ||
85 | /* If the path points to /dev/null in a image or so, then the device node may not exist. */ | |
86 | if (path_equal(skip_leading_slash(resolved_path), "dev/null")) { | |
87 | /* Mark this one as masked */ | |
88 | r = set_put_strdup(masked, de->d_name); | |
89 | if (r < 0) | |
90 | return log_oom_debug(); | |
91 | ||
92 | log_debug("File '%s/%s' is a mask (symlink to /dev/null).", | |
93 | root, skip_leading_slash(p)); | |
94 | continue; | |
95 | } | |
96 | ||
97 | /* If the flag is set, we need to have stat, hence, skip the entry. */ | |
98 | log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to chase '%s/%s', ignoring: %m", | |
99 | root, skip_leading_slash(p)); | |
3e36211b LP |
100 | continue; |
101 | } | |
102 | ||
cdd3aba3 YW |
103 | if (need_stat && fstatat(rfd, resolved_path, &st, AT_SYMLINK_NOFOLLOW) < 0) { |
104 | log_debug_errno(errno, "Failed to stat '%s/%s', ignoring: %m", | |
105 | root, skip_leading_slash(p)); | |
106 | continue; | |
50c81130 YW |
107 | } |
108 | ||
109 | } else { | |
110 | r = chase_and_statat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &resolved_path, &st); | |
111 | if (r < 0) { | |
112 | log_debug_errno(r, "Failed to chase and stat '%s/%s', ignoring: %m", | |
113 | root, skip_leading_slash(p)); | |
114 | continue; | |
115 | } | |
116 | } | |
117 | ||
3e36211b | 118 | /* Is this a masking entry? */ |
e93d8c84 YW |
119 | if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK) && stat_may_be_dev_null(&st)) { |
120 | /* Mark this one as masked */ | |
121 | r = set_put_strdup(masked, de->d_name); | |
122 | if (r < 0) | |
50c81130 | 123 | return log_oom_debug(); |
3e36211b | 124 | |
50c81130 | 125 | log_debug("File '%s/%s' is a mask (symlink to /dev/null).", root, skip_leading_slash(p)); |
e93d8c84 YW |
126 | continue; |
127 | } | |
128 | ||
129 | if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_EMPTY) && stat_is_empty(&st)) { | |
130 | /* Mark this one as masked */ | |
131 | r = set_put_strdup(masked, de->d_name); | |
132 | if (r < 0) | |
50c81130 | 133 | return log_oom_debug(); |
e93d8c84 | 134 | |
50c81130 | 135 | log_debug("File '%s/%s' is a mask (an empty file).", root, skip_leading_slash(p)); |
e93d8c84 YW |
136 | continue; |
137 | } | |
3e36211b | 138 | |
77bf8f89 LP |
139 | if (FLAGS_SET(flags, CONF_FILES_REGULAR|CONF_FILES_DIRECTORY)) { |
140 | if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) { | |
141 | log_debug("Ignoring '%s/%s', as it is neither a regular file or directory.", root, skip_leading_slash(p)); | |
142 | continue; | |
143 | } | |
144 | } else { | |
145 | /* Is this node a regular file? */ | |
146 | if (FLAGS_SET(flags, CONF_FILES_REGULAR) && !S_ISREG(st.st_mode)) { | |
147 | log_debug("Ignoring '%s/%s', as it is not a regular file.", root, skip_leading_slash(p)); | |
148 | continue; | |
149 | } | |
b5084605 | 150 | |
77bf8f89 LP |
151 | /* Is this node a directory? */ |
152 | if (FLAGS_SET(flags, CONF_FILES_DIRECTORY) && !S_ISDIR(st.st_mode)) { | |
153 | log_debug("Ignoring '%s/%s', as it is not a directory.", root, skip_leading_slash(p)); | |
154 | continue; | |
155 | } | |
50c81130 | 156 | } |
b5084605 | 157 | |
50c81130 YW |
158 | /* Does this node have the executable bit set? |
159 | * As requested: check if the file is marked executable. Note that we don't check access(X_OK) | |
160 | * here, as we care about whether the file is marked executable at all, and not whether it is | |
161 | * executable for us, because if so, such errors are stuff we should log about. */ | |
162 | if (FLAGS_SET(flags, CONF_FILES_EXECUTABLE) && (st.st_mode & 0111) == 0) { | |
163 | log_debug("Ignoring '%s/%s', as it is not marked executable.", root, skip_leading_slash(p)); | |
164 | continue; | |
165 | } | |
b5084605 | 166 | |
50c81130 | 167 | _cleanup_free_ char *n = strdup(de->d_name); |
9d0d39ee | 168 | if (!n) |
50c81130 | 169 | return log_oom_debug(); |
b5084605 | 170 | |
50c81130 YW |
171 | r = hashmap_ensure_put(files, &string_hash_ops_free_free, n, p); |
172 | if (r < 0) { | |
173 | assert(r == -ENOMEM); | |
174 | return log_oom_debug(); | |
3e36211b | 175 | } |
3e36211b | 176 | assert(r > 0); |
9d0d39ee YW |
177 | |
178 | TAKE_PTR(n); | |
179 | TAKE_PTR(p); | |
2c21044f KS |
180 | } |
181 | ||
e02caf30 | 182 | return 0; |
2c21044f KS |
183 | } |
184 | ||
50c81130 | 185 | static int copy_and_sort_files_from_hashmap(Hashmap *fh, const char *root, ConfFilesFlags flags, char ***ret) { |
1a39bddf | 186 | _cleanup_free_ char **sv = NULL; |
50c81130 YW |
187 | _cleanup_strv_free_ char **files = NULL; |
188 | size_t n = 0; | |
667fc1d9 | 189 | int r; |
1a39bddf YW |
190 | |
191 | assert(ret); | |
192 | ||
667fc1d9 YW |
193 | r = hashmap_dump_sorted(fh, (void***) &sv, /* ret_n = */ NULL); |
194 | if (r < 0) | |
50c81130 | 195 | return log_oom_debug(); |
1a39bddf | 196 | |
667fc1d9 | 197 | /* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap. */ |
50c81130 YW |
198 | STRV_FOREACH(s, sv) { |
199 | _cleanup_free_ char *p = NULL; | |
200 | ||
201 | if (FLAGS_SET(flags, CONF_FILES_BASENAME)) { | |
202 | r = path_extract_filename(*s, &p); | |
203 | if (r < 0) | |
204 | return log_debug_errno(r, "Failed to extract filename from '%s': %m", *s); | |
205 | } else if (root) { | |
9000db5f YW |
206 | r = chaseat_prefix_root(*s, root, &p); |
207 | if (r < 0) | |
208 | return log_debug_errno(r, "Failed to prefix '%s' with root '%s': %m", *s, root); | |
50c81130 | 209 | } |
1a39bddf | 210 | |
50c81130 YW |
211 | if (p) |
212 | r = strv_consume_with_size(&files, &n, TAKE_PTR(p)); | |
213 | else | |
214 | r = strv_extend_with_size(&files, &n, *s); | |
215 | if (r < 0) | |
216 | return log_oom_debug(); | |
217 | } | |
218 | ||
219 | *ret = TAKE_PTR(files); | |
1a39bddf YW |
220 | return 0; |
221 | } | |
222 | ||
41fb5859 YW |
223 | static int insert_replacement(Hashmap **fh, char *filename_replacement, char *resolved_replacement, char **ret) { |
224 | _cleanup_free_ char *fname = ASSERT_PTR(filename_replacement), *path = ASSERT_PTR(resolved_replacement); | |
225 | int r; | |
226 | ||
227 | assert(fh); | |
228 | ||
229 | /* This consumes the input filename and path. */ | |
230 | ||
231 | const char *existing = hashmap_get(*fh, fname); | |
232 | if (existing) { | |
233 | log_debug("An entry with higher priority already exists ('%s' -> '%s'), ignoring the replacement: %s", | |
234 | fname, existing, path); | |
235 | if (ret) | |
236 | *ret = NULL; | |
237 | return 0; | |
238 | } | |
239 | ||
240 | _cleanup_free_ char *copy = NULL; | |
241 | if (ret) { | |
242 | copy = strdup(path); | |
243 | if (!copy) | |
244 | return log_oom_debug(); | |
245 | } | |
246 | ||
247 | r = hashmap_ensure_put(fh, &string_hash_ops_free_free, fname, path); | |
248 | if (r < 0) { | |
249 | assert(r == -ENOMEM); | |
250 | return log_oom_debug(); | |
251 | } | |
252 | assert(r > 0); | |
253 | ||
254 | log_debug("Inserted replacement: '%s' -> '%s'", fname, path); | |
255 | ||
256 | TAKE_PTR(fname); | |
257 | TAKE_PTR(path); | |
258 | ||
259 | if (ret) | |
260 | *ret = TAKE_PTR(copy); | |
261 | return 0; | |
262 | } | |
263 | ||
50c81130 | 264 | static int conf_files_list_impl( |
94916255 | 265 | const char *suffix, |
50c81130 YW |
266 | int rfd, |
267 | const char *root, /* for logging, can be NULL */ | |
1de31f23 | 268 | ConfFilesFlags flags, |
50c81130 | 269 | const char * const *dirs, |
41fb5859 YW |
270 | const char *replacement, |
271 | Hashmap **ret, | |
272 | char **ret_inserted) { | |
94916255 | 273 | |
e1d75803 | 274 | _cleanup_hashmap_free_ Hashmap *fh = NULL; |
9d0d39ee | 275 | _cleanup_set_free_ Set *masked = NULL; |
578ac060 | 276 | int r; |
2c21044f | 277 | |
50c81130 | 278 | assert(rfd >= 0 || rfd == AT_FDCWD); |
94916255 | 279 | assert(ret); |
fabe5c0e | 280 | |
41fb5859 YW |
281 | _cleanup_free_ char *filename_replacement = NULL, *resolved_dirpath_replacement = NULL, *resolved_replacement = NULL, *inserted = NULL; |
282 | if (replacement) { | |
283 | r = path_extract_filename(replacement, &filename_replacement); | |
284 | if (r < 0) | |
285 | return r; | |
286 | ||
287 | _cleanup_free_ char *d = NULL; | |
288 | r = path_extract_directory(replacement, &d); | |
289 | if (r < 0) | |
290 | return r; | |
291 | ||
292 | r = chaseat(rfd, d, CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &resolved_dirpath_replacement, /* ret_fd = */ NULL); | |
293 | if (r < 0) | |
294 | return r; | |
295 | ||
296 | resolved_replacement = path_join(resolved_dirpath_replacement, filename_replacement); | |
297 | if (!resolved_replacement) | |
298 | return log_oom_debug(); | |
41fb5859 YW |
299 | } |
300 | ||
2c21044f | 301 | STRV_FOREACH(p, dirs) { |
a5af5f80 YW |
302 | _cleanup_closedir_ DIR *dir = NULL; |
303 | _cleanup_free_ char *path = NULL; | |
304 | ||
50c81130 | 305 | r = chase_and_opendirat(rfd, *p, CHASE_AT_RESOLVE_IN_ROOT, &path, &dir); |
a5af5f80 YW |
306 | if (r < 0) { |
307 | if (r != -ENOENT) | |
50c81130 | 308 | log_debug_errno(r, "Failed to chase and open directory '%s%s', ignoring: %m", strempty(root), *p); |
a5af5f80 YW |
309 | continue; |
310 | } | |
311 | ||
41fb5859 YW |
312 | if (resolved_replacement && path_equal(resolved_dirpath_replacement, path)) { |
313 | r = insert_replacement(&fh, TAKE_PTR(filename_replacement), TAKE_PTR(resolved_replacement), ret_inserted ? &inserted : NULL); | |
314 | if (r < 0) | |
315 | return r; | |
316 | } | |
317 | ||
50c81130 | 318 | r = files_add(dir, path, rfd, root, &fh, &masked, suffix, flags); |
31d5192d | 319 | if (r == -ENOMEM) |
fabe5c0e | 320 | return r; |
2c21044f KS |
321 | } |
322 | ||
41fb5859 YW |
323 | if (resolved_replacement) { |
324 | r = insert_replacement(&fh, TAKE_PTR(filename_replacement), TAKE_PTR(resolved_replacement), ret_inserted ? &inserted : NULL); | |
325 | if (r < 0) | |
326 | return r; | |
327 | } | |
328 | ||
50c81130 | 329 | *ret = TAKE_PTR(fh); |
41fb5859 YW |
330 | if (ret_inserted) |
331 | *ret_inserted = TAKE_PTR(inserted); | |
50c81130 YW |
332 | return 0; |
333 | } | |
334 | ||
9000db5f YW |
335 | static int prepare_dirs(const char *root, char * const *dirs, int *ret_rfd, char **ret_root, char ***ret_dirs) { |
336 | _cleanup_free_ char *root_abs = NULL; | |
337 | _cleanup_strv_free_ char **dirs_abs = NULL; | |
338 | int r; | |
339 | ||
340 | assert(ret_rfd); | |
341 | assert(ret_root); | |
342 | assert(ret_dirs); | |
343 | ||
344 | r = empty_or_root_harder_to_null(&root); | |
345 | if (r < 0) | |
346 | return log_debug_errno(r, "Failed to determine if '%s' points to the root directory: %m", strempty(root)); | |
347 | ||
348 | dirs_abs = strv_copy(dirs); | |
349 | if (!dirs_abs) | |
350 | return log_oom(); | |
351 | ||
352 | if (root) { | |
353 | /* WHen a non-trivial root is specified, we will prefix the result later. Hence, it is not | |
354 | * necessary to modify each config directories here. but needs to normalize the root directory. */ | |
355 | r = path_make_absolute_cwd(root, &root_abs); | |
356 | if (r < 0) | |
357 | return log_debug_errno(r, "Failed to make '%s' absolute: %m", root); | |
358 | ||
359 | path_simplify(root_abs); | |
360 | } else { | |
361 | /* When an empty root or "/" is specified, we will open "/" below, hence we need to make | |
362 | * each config directory absolute if relative. */ | |
363 | r = path_strv_make_absolute_cwd(dirs_abs); | |
364 | if (r < 0) | |
365 | return log_debug_errno(r, "Failed to make directories absolute: %m"); | |
366 | } | |
367 | ||
368 | _cleanup_close_ int rfd = open(empty_to_root(root_abs), O_CLOEXEC|O_DIRECTORY|O_PATH); | |
369 | if (rfd < 0) | |
370 | return log_debug_errno(errno, "Failed to open '%s': %m", empty_to_root(root_abs)); | |
371 | ||
372 | *ret_rfd = TAKE_FD(rfd); | |
373 | *ret_root = TAKE_PTR(root_abs); | |
374 | *ret_dirs = TAKE_PTR(dirs_abs); | |
375 | return 0; | |
376 | } | |
377 | ||
50c81130 YW |
378 | int conf_files_list_strv( |
379 | char ***ret, | |
380 | const char *suffix, | |
381 | const char *root, | |
382 | ConfFilesFlags flags, | |
383 | const char * const *dirs) { | |
384 | ||
385 | _cleanup_hashmap_free_ Hashmap *fh = NULL; | |
9000db5f YW |
386 | _cleanup_close_ int rfd = -EBADF; |
387 | _cleanup_free_ char *root_abs = NULL; | |
388 | _cleanup_strv_free_ char **dirs_abs = NULL; | |
50c81130 YW |
389 | int r; |
390 | ||
391 | assert(ret); | |
392 | ||
9000db5f YW |
393 | r = prepare_dirs(root, (char**) dirs, &rfd, &root_abs, &dirs_abs); |
394 | if (r < 0) | |
395 | return r; | |
50c81130 | 396 | |
9000db5f YW |
397 | r = conf_files_list_impl(suffix, rfd, root_abs, flags, (const char * const *) dirs_abs, |
398 | /* replacement = */ NULL, &fh, /* ret_inserted = */ NULL); | |
50c81130 YW |
399 | if (r < 0) |
400 | return r; | |
401 | ||
9000db5f | 402 | return copy_and_sort_files_from_hashmap(fh, empty_to_root(root_abs), flags, ret); |
fabe5c0e LP |
403 | } |
404 | ||
b1229544 YW |
405 | int conf_files_list_strv_at( |
406 | char ***ret, | |
407 | const char *suffix, | |
408 | int rfd, | |
1de31f23 | 409 | ConfFilesFlags flags, |
b1229544 YW |
410 | const char * const *dirs) { |
411 | ||
412 | _cleanup_hashmap_free_ Hashmap *fh = NULL; | |
50c81130 | 413 | _cleanup_free_ char *root = NULL; |
b1229544 YW |
414 | int r; |
415 | ||
416 | assert(rfd >= 0 || rfd == AT_FDCWD); | |
417 | assert(ret); | |
418 | ||
50c81130 YW |
419 | if (rfd >= 0 && DEBUG_LOGGING) |
420 | (void) fd_get_path(rfd, &root); /* for logging */ | |
b1229544 | 421 | |
41fb5859 | 422 | r = conf_files_list_impl(suffix, rfd, root, flags, dirs, /* replacement = */ NULL, &fh, /* ret_inserted = */ NULL); |
50c81130 YW |
423 | if (r < 0) |
424 | return r; | |
b1229544 | 425 | |
50c81130 | 426 | return copy_and_sort_files_from_hashmap(fh, /* root = */ NULL, flags, ret); |
b1229544 YW |
427 | } |
428 | ||
1de31f23 | 429 | int conf_files_list(char ***ret, const char *suffix, const char *root, ConfFilesFlags flags, const char *dir) { |
a5af5f80 | 430 | return conf_files_list_strv(ret, suffix, root, flags, STRV_MAKE_CONST(dir)); |
fabe5c0e | 431 | } |
2c21044f | 432 | |
1de31f23 | 433 | int conf_files_list_at(char ***ret, const char *suffix, int rfd, ConfFilesFlags flags, const char *dir) { |
b1229544 YW |
434 | return conf_files_list_strv_at(ret, suffix, rfd, flags, STRV_MAKE_CONST(dir)); |
435 | } | |
436 | ||
1de31f23 | 437 | int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, ConfFilesFlags flags, const char *dirs) { |
a6d8474f | 438 | _cleanup_strv_free_ char **d = NULL; |
fabe5c0e | 439 | |
94916255 | 440 | assert(ret); |
fabe5c0e | 441 | |
a6d8474f ZJS |
442 | d = strv_split_nulstr(dirs); |
443 | if (!d) | |
fabe5c0e | 444 | return -ENOMEM; |
2c21044f | 445 | |
a5af5f80 | 446 | return conf_files_list_strv(ret, suffix, root, flags, (const char**) d); |
2c21044f | 447 | } |
854a42fb | 448 | |
1de31f23 | 449 | int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, ConfFilesFlags flags, const char *dirs) { |
b1229544 YW |
450 | _cleanup_strv_free_ char **d = NULL; |
451 | ||
452 | assert(ret); | |
453 | ||
454 | d = strv_split_nulstr(dirs); | |
455 | if (!d) | |
456 | return -ENOMEM; | |
457 | ||
458 | return conf_files_list_strv_at(ret, suffix, rfd, flags, (const char**) d); | |
459 | } | |
460 | ||
ceaaeb9b ZJS |
461 | int conf_files_list_with_replacement( |
462 | const char *root, | |
463 | char **config_dirs, | |
464 | const char *replacement, | |
94916255 | 465 | char ***ret_files, |
41fb5859 | 466 | char **ret_inserted) { |
ceaaeb9b | 467 | |
41fb5859 YW |
468 | _cleanup_hashmap_free_ Hashmap *fh = NULL; |
469 | _cleanup_free_ char *inserted = NULL; | |
c43223ff | 470 | ConfFilesFlags flags = CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED_BY_SYMLINK; |
9000db5f YW |
471 | _cleanup_close_ int rfd = -EBADF; |
472 | _cleanup_free_ char *root_abs = NULL; | |
473 | _cleanup_strv_free_ char **dirs_abs = NULL; | |
ceaaeb9b ZJS |
474 | int r; |
475 | ||
94916255 | 476 | assert(ret_files); |
ceaaeb9b | 477 | |
9000db5f YW |
478 | r = prepare_dirs(root, config_dirs, &rfd, &root_abs, &dirs_abs); |
479 | if (r < 0) | |
480 | return r; | |
ceaaeb9b | 481 | |
9000db5f | 482 | r = conf_files_list_impl(".conf", rfd, root_abs, flags, (const char * const *) dirs_abs, |
41fb5859 YW |
483 | replacement, &fh, ret_inserted ? &inserted : NULL); |
484 | if (r < 0) | |
485 | return r; | |
ceaaeb9b | 486 | |
41fb5859 | 487 | if (inserted) { |
9000db5f YW |
488 | char *p; |
489 | ||
490 | r = chaseat_prefix_root(inserted, root_abs, &p); | |
491 | if (r < 0) | |
492 | return log_debug_errno(r, "Failed to prefix '%s' with root '%s': %m", inserted, empty_to_root(root_abs)); | |
41fb5859 YW |
493 | |
494 | free_and_replace(inserted, p); | |
ceaaeb9b ZJS |
495 | } |
496 | ||
9000db5f | 497 | r = copy_and_sort_files_from_hashmap(fh, empty_to_root(root_abs), flags, ret_files); |
41fb5859 YW |
498 | if (r < 0) |
499 | return r; | |
94916255 | 500 | |
41fb5859 YW |
501 | if (ret_inserted) |
502 | *ret_inserted = TAKE_PTR(inserted); | |
ceaaeb9b ZJS |
503 | return 0; |
504 | } | |
35c0e344 MY |
505 | |
506 | int conf_files_list_dropins( | |
507 | char ***ret, | |
508 | const char *dropin_dirname, | |
509 | const char *root, | |
510 | const char * const *dirs) { | |
511 | ||
512 | _cleanup_strv_free_ char **dropin_dirs = NULL; | |
513 | const char *suffix; | |
514 | int r; | |
515 | ||
516 | assert(ret); | |
517 | assert(dropin_dirname); | |
518 | assert(dirs); | |
519 | ||
520 | suffix = strjoina("/", dropin_dirname); | |
9bc74930 | 521 | r = strv_extend_strv_concat(&dropin_dirs, dirs, suffix); |
35c0e344 MY |
522 | if (r < 0) |
523 | return r; | |
524 | ||
525 | return conf_files_list_strv(ret, ".conf", root, 0, (const char* const*) dropin_dirs); | |
526 | } | |
f6a1346e ZJS |
527 | |
528 | /** | |
529 | * Open and read a config file. | |
530 | * | |
531 | * The <fn> argument may be: | |
532 | * - '-', meaning stdin. | |
ec3917d2 ZJS |
533 | * - a file name without a path. In this case <config_dirs> are searched. |
534 | * - a path, either relative or absolute. In this case <fn> is opened directly. | |
535 | * | |
536 | * This method is only suitable for configuration files which have a flat layout without dropins. | |
f6a1346e ZJS |
537 | */ |
538 | int conf_file_read( | |
539 | const char *root, | |
540 | const char **config_dirs, | |
541 | const char *fn, | |
542 | parse_line_t parse_line, | |
543 | void *userdata, | |
544 | bool ignore_enoent, | |
545 | bool *invalid_config) { | |
546 | ||
547 | _cleanup_fclose_ FILE *_f = NULL; | |
548 | _cleanup_free_ char *_fn = NULL; | |
549 | unsigned v = 0; | |
550 | FILE *f; | |
ec3917d2 | 551 | int r = 0; |
f6a1346e ZJS |
552 | |
553 | assert(fn); | |
554 | ||
555 | if (streq(fn, "-")) { | |
556 | f = stdin; | |
557 | fn = "<stdin>"; | |
558 | ||
1ae9b0cf | 559 | log_debug("Reading config from stdin%s", glyph(GLYPH_ELLIPSIS)); |
f6a1346e | 560 | |
ec3917d2 ZJS |
561 | } else if (is_path(fn)) { |
562 | r = path_make_absolute_cwd(fn, &_fn); | |
563 | if (r < 0) | |
564 | return log_error_errno(r, "Failed to make path absolute: %m"); | |
565 | fn = _fn; | |
566 | ||
567 | f = _f = fopen(fn, "re"); | |
568 | if (!_f) | |
569 | r = -errno; | |
570 | else | |
1ae9b0cf | 571 | log_debug("Reading config file \"%s\"%s", fn, glyph(GLYPH_ELLIPSIS)); |
ec3917d2 | 572 | |
f6a1346e ZJS |
573 | } else { |
574 | r = search_and_fopen(fn, "re", root, config_dirs, &_f, &_fn); | |
ec3917d2 ZJS |
575 | if (r >= 0) { |
576 | f = _f; | |
577 | fn = _fn; | |
1ae9b0cf | 578 | log_debug("Reading config file \"%s\"%s", fn, glyph(GLYPH_ELLIPSIS)); |
f6a1346e | 579 | } |
ec3917d2 | 580 | } |
f6a1346e | 581 | |
ec3917d2 ZJS |
582 | if (r == -ENOENT && ignore_enoent) { |
583 | log_debug_errno(r, "Failed to open \"%s\", ignoring: %m", fn); | |
584 | return 0; /* No error, but nothing happened. */ | |
f6a1346e | 585 | } |
ec3917d2 ZJS |
586 | if (r < 0) |
587 | return log_error_errno(r, "Failed to read '%s': %m", fn); | |
f6a1346e ZJS |
588 | |
589 | r = 1; /* We entered the part where we may modify state. */ | |
590 | ||
591 | for (;;) { | |
592 | _cleanup_free_ char *line = NULL; | |
593 | bool invalid_line = false; | |
594 | int k; | |
595 | ||
596 | k = read_stripped_line(f, LONG_LINE_MAX, &line); | |
597 | if (k < 0) | |
598 | return log_error_errno(k, "Failed to read '%s': %m", fn); | |
599 | if (k == 0) | |
600 | break; | |
601 | ||
602 | v++; | |
603 | ||
604 | if (IN_SET(line[0], 0, '#')) | |
605 | continue; | |
606 | ||
15d660fb ZJS |
607 | k = parse_line(fn, v, line, invalid_config ? &invalid_line : NULL, userdata); |
608 | if (k < 0 && invalid_line) | |
f6a1346e | 609 | /* Allow reporting with a special code if the caller requested this. */ |
15d660fb ZJS |
610 | *invalid_config = true; |
611 | else | |
f6a1346e ZJS |
612 | /* The first error, if any, becomes our return value. */ |
613 | RET_GATHER(r, k); | |
614 | } | |
615 | ||
616 | if (ferror(f)) | |
617 | RET_GATHER(r, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read from file %s.", fn)); | |
618 | ||
619 | return r; | |
620 | } |