]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/conf-files.c
ci: use -p and -f when creating dirs/removing files in mkosi job btrfs setup
[thirdparty/systemd.git] / src / basic / conf-files.c
CommitLineData
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 23static 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 185static 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
223static 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 264static 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
335static 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
378int 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
405int 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 429int 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 433int 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 437int 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 449int 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
461int 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
506int 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 */
538int 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}