Coverage Report

Created: 2026-10-28 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/systemd/src/basic/conf-files.c
Line
Count
Source
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3
#include <stdio.h>
4
#include <stdlib.h>
5
6
#include "alloc-util.h"
7
#include "chase.h"
8
#include "conf-files.h"
9
#include "dirent-util.h"
10
#include "errno-util.h"
11
#include "fd-util.h"
12
#include "fileio.h"
13
#include "glyph-util.h"
14
#include "hashmap.h"
15
#include "log.h"
16
#include "nulstr-util.h"
17
#include "path-util.h"
18
#include "set.h"
19
#include "stat-util.h"
20
#include "string-util.h"
21
#include "strv.h"
22
23
7.56k
ConfFile* conf_file_free(ConfFile *c) {
24
7.56k
        if (!c)
25
0
                return NULL;
26
27
7.56k
        free(c->filename);
28
7.56k
        free(c->result);
29
7.56k
        free(c->original_path);
30
7.56k
        free(c->resolved_path);
31
7.56k
        safe_close(c->fd);
32
33
7.56k
        return mfree(c);
34
7.56k
}
35
36
0
DEFINE_POINTER_ARRAY_FREE_FUNC(ConfFile*, conf_file_free);
37
38
88.3k
static int conf_files_log_level(ConfFilesFlags flags) {
39
88.3k
        return FLAGS_SET(flags, CONF_FILES_WARN) ? LOG_WARNING : LOG_DEBUG;
40
88.3k
}
41
42
static int prepare_dirs(
43
                const char *root,
44
                ConfFilesFlags flags,
45
                char * const *dirs,
46
                char **ret_root,
47
                int *ret_rfd,
48
7.56k
                char ***ret_dirs) {
49
50
7.56k
        _cleanup_free_ char *root_abs = NULL;
51
7.56k
        _cleanup_strv_free_ char **dirs_abs = NULL;
52
7.56k
        int r;
53
54
7.56k
        assert(ret_root);
55
7.56k
        assert(ret_rfd);
56
7.56k
        assert(ret_dirs || strv_isempty(dirs));
57
58
7.56k
        int log_level = conf_files_log_level(flags);
59
60
7.56k
        r = empty_or_root_harder_to_null(&root);
61
7.56k
        if (r < 0)
62
7.56k
                return log_full_errno(log_level, r, "Failed to determine if '%s' points to the root directory: %m", strempty(root));
63
64
7.56k
        if (ret_dirs) {
65
0
                dirs_abs = strv_copy(dirs);
66
0
                if (!dirs_abs)
67
0
                        return log_oom_full(log_level);
68
0
        }
69
70
7.56k
        _cleanup_close_ int rfd = XAT_FDROOT;
71
7.56k
        if (root) {
72
                /* When a non-trivial root is specified, we will prefix the result later. Hence, it is not
73
                 * necessary to modify each config directories here. but needs to normalize the root directory. */
74
0
                r = path_make_absolute_cwd(root, &root_abs);
75
0
                if (r < 0)
76
0
                        return log_full_errno(log_level, r, "Failed to make '%s' absolute: %m", root);
77
78
0
                path_simplify(root_abs);
79
80
0
                rfd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH);
81
0
                if (rfd < 0)
82
0
                        return log_full_errno(log_level, errno, "Failed to open '%s': %m", root_abs);
83
84
7.56k
        } else if (ret_dirs) {
85
                /* When an empty root or "/" is specified, we will open "/" below, hence we need to make
86
                 * each config directory absolute if relative. */
87
0
                r = path_strv_make_absolute_cwd(dirs_abs);
88
0
                if (r < 0)
89
0
                        return log_full_errno(log_level, r, "Failed to make directories absolute: %m");
90
0
        }
91
92
7.56k
        *ret_root = TAKE_PTR(root_abs);
93
7.56k
        *ret_rfd = TAKE_FD(rfd);
94
7.56k
        if (ret_dirs)
95
0
                *ret_dirs = TAKE_PTR(dirs_abs);
96
7.56k
        return 0;
97
7.56k
}
98
99
7.56k
static int conf_file_prefix_root(ConfFile *c, const char *root, ConfFilesFlags flags) {
100
7.56k
        char *p;
101
7.56k
        int r;
102
103
7.56k
        assert(c);
104
105
7.56k
        if (FLAGS_SET(flags, CONF_FILES_DONT_PREFIX_ROOT))
106
0
                return 0;
107
108
7.56k
        int log_level = conf_files_log_level(flags);
109
110
7.56k
        r = chaseat_prefix_root(c->result, root, &p);
111
7.56k
        if (r < 0)
112
7.56k
                return log_full_errno(log_level, r, "Failed to prefix '%s' with root '%s': %m", c->result, root);
113
7.56k
        free_and_replace(c->result, p);
114
115
7.56k
        r = chaseat_prefix_root(c->resolved_path, root, &p);
116
7.56k
        if (r < 0)
117
7.56k
                return log_full_errno(log_level, r, "Failed to prefix '%s' with root '%s': %m", c->resolved_path, root);
118
7.56k
        free_and_replace(c->resolved_path, p);
119
120
        /* Do not use chaseat_prefix_root(), as it is for the result of chaseat(), but the path is not chased. */
121
7.56k
        p = path_join(empty_to_root(root), skip_leading_slash(c->original_path));
122
7.56k
        if (!p)
123
0
                return log_oom_full(log_level);
124
125
7.56k
        path_simplify(p);
126
7.56k
        return free_and_replace(c->original_path, p);
127
7.56k
}
128
129
15.1k
static bool conf_files_need_stat(ConfFilesFlags flags) {
130
15.1k
        return (flags & (CONF_FILES_FILTER_MASKED | CONF_FILES_REGULAR | CONF_FILES_DIRECTORY | CONF_FILES_EXECUTABLE)) != 0;
131
15.1k
}
132
133
247k
static ChaseFlags conf_files_extra_chase_flags(ConfFilesFlags flags) {
134
        /* Executables never fit under the 0664 file ceiling */
135
247k
        assert(!FLAGS_SET(flags, CONF_FILES_EXECUTABLE|CONF_FILES_CHASE_MAX_MODE));
136
137
247k
        return (FLAGS_SET(flags, CONF_FILES_CHASE_SAFE) ? CHASE_SAFE : 0) |
138
247k
                (FLAGS_SET(flags, CONF_FILES_CHASE_MAX_MODE) ? CHASE_MAX_MODE : 0);
139
247k
}
140
141
15.1k
static ChaseFlags conf_files_chase_flags(ConfFilesFlags flags) {
142
15.1k
        ChaseFlags chase_flags = conf_files_extra_chase_flags(flags);
143
144
15.1k
        if (!conf_files_need_stat(flags) || FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK))
145
                /* Even if no verification is requested, let's unconditionally call chaseat(),
146
                 * to drop unsafe symlinks. */
147
0
                chase_flags |= CHASE_NONEXISTENT;
148
149
15.1k
        return chase_flags;
150
15.1k
}
151
152
static int conf_file_chase_and_verify(
153
                const char *root,          /* for logging, can be NULL */
154
                int rfd,
155
                const char *original_path, /* for logging */
156
                const char *path,
157
                const char *name,
158
                Set **masked,              /* optional */
159
                ConfFilesFlags flags,
160
                char **ret_path,
161
                int *ret_fd,
162
7.56k
                struct stat *ret_stat) {
163
164
7.56k
        _cleanup_free_ char *resolved_path = NULL;
165
7.56k
        _cleanup_close_ int fd = -EBADF;
166
7.56k
        struct stat st = {};
167
7.56k
        int r;
168
169
7.56k
        assert(wildcard_fd_is_valid(rfd));
170
7.56k
        assert(original_path);
171
7.56k
        assert(path);
172
7.56k
        assert(name);
173
174
7.56k
        int log_level = conf_files_log_level(flags);
175
176
7.56k
        root = empty_to_root(root);
177
178
7.56k
        r = chaseat(rfd, rfd, path, conf_files_chase_flags(flags), &resolved_path, &fd);
179
7.56k
        if (r < 0)
180
7.56k
                return log_full_errno(log_level, r, "Failed to chase '%s%s': %m",
181
7.56k
                                      root, skip_leading_slash(original_path));
182
7.56k
        if (r == 0) {
183
0
                if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK)) {
184
                        /* If the path points to /dev/null in a image or so, then the device node may not exist. */
185
0
                        if (path_equal(skip_leading_slash(resolved_path), "dev/null")) {
186
0
                                if (masked) {
187
                                        /* Mark this one as masked */
188
0
                                        r = set_put_strdup(masked, name);
189
0
                                        if (r < 0)
190
0
                                                return log_oom_full(log_level);
191
0
                                }
192
193
0
                                return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL),
194
0
                                                       "File '%s%s' is a mask (symlink to /dev/null).",
195
0
                                                       root, skip_leading_slash(original_path));
196
0
                        }
197
0
                }
198
199
0
                if (conf_files_need_stat(flags))
200
                        /* If we need to have stat, skip the entry. */
201
0
                        return log_full_errno(log_level, SYNTHETIC_ERRNO(ENOENT), "Failed to chase '%s%s': %m",
202
0
                                              root, skip_leading_slash(original_path));
203
0
        }
204
205
        /* Even if we do not need stat, let's take stat now. The caller may use the info later. */
206
7.56k
        if (fd >= 0 && fstat(fd, &st) < 0)
207
7.56k
                return log_full_errno(log_level, errno, "Failed to stat '%s%s': %m",
208
7.56k
                                      root, skip_leading_slash(original_path));
209
210
        /* Is this a masking entry? */
211
7.56k
        if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK) && stat_may_be_dev_null(&st)) {
212
0
                if (masked) {
213
                        /* Mark this one as masked */
214
0
                        r = set_put_strdup(masked, name);
215
0
                        if (r < 0)
216
0
                                return log_oom_full(log_level);
217
0
                }
218
219
0
                return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL),
220
0
                                       "File '%s%s' is a mask (symlink to /dev/null).",
221
0
                                       root, skip_leading_slash(original_path));
222
0
        }
223
224
7.56k
        if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_EMPTY) && stat_is_empty(&st)) {
225
0
                if (masked) {
226
                        /* Mark this one as masked */
227
0
                        r = set_put_strdup(masked, name);
228
0
                        if (r < 0)
229
0
                                return log_oom_full(log_level);
230
0
                }
231
232
0
                return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL),
233
0
                                       "File '%s%s' is a mask (an empty file).",
234
0
                                       root, skip_leading_slash(original_path));
235
0
        }
236
237
7.56k
        if (FLAGS_SET(flags, CONF_FILES_REGULAR|CONF_FILES_DIRECTORY)) {
238
0
                if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode))
239
0
                        return log_debug_errno(SYNTHETIC_ERRNO(EBADFD),
240
0
                                               "File '%s%s' is neither a regular file or directory.",
241
0
                                               root, skip_leading_slash(original_path));
242
7.56k
        } else {
243
                /* Is this node a regular file? */
244
7.56k
                if (FLAGS_SET(flags, CONF_FILES_REGULAR)) {
245
7.56k
                        r = stat_verify_regular(&st);
246
7.56k
                        if (r < 0)
247
7.56k
                                return log_debug_errno(r, "File '%s%s' is not a regular file: %m",
248
7.56k
                                                       root, skip_leading_slash(original_path));
249
7.56k
                }
250
251
                /* Is this node a directory? */
252
7.56k
                if (FLAGS_SET(flags, CONF_FILES_DIRECTORY)) {
253
0
                        r = stat_verify_directory(&st);
254
0
                        if (r < 0)
255
0
                                return log_debug_errno(r, "File '%s%s' is not a directory: %m",
256
0
                                                       root, skip_leading_slash(original_path));
257
0
                }
258
7.56k
        }
259
260
        /* Does this node have the executable bit set?
261
         * As requested: check if the file is marked executable. Note that we don't check access(X_OK) here,
262
         * as we care about whether the file is marked executable at all, and not whether it is executable
263
         * for us, because if so, such errors are stuff we should log about. */
264
7.56k
        if (FLAGS_SET(flags, CONF_FILES_EXECUTABLE) && (st.st_mode & 0111) == 0)
265
7.56k
                return log_debug_errno(SYNTHETIC_ERRNO(ENOEXEC),
266
7.56k
                                       "File '%s%s' is not marked executable.",
267
7.56k
                                       root, skip_leading_slash(original_path));
268
269
7.56k
        if (ret_path)
270
7.56k
                *ret_path = TAKE_PTR(resolved_path);
271
7.56k
        if (ret_fd)
272
7.56k
                *ret_fd = TAKE_FD(fd);
273
7.56k
        if (ret_stat)
274
7.56k
                *ret_stat = st;
275
276
7.56k
        return 0;
277
7.56k
}
278
279
int conf_file_new_at(
280
                const char *path,
281
                const char *root,
282
                int rfd,
283
                ConfFilesFlags flags,
284
7.56k
                ConfFile **ret) {
285
7.56k
        int r;
286
287
7.56k
        assert(path);
288
7.56k
        assert(wildcard_fd_is_valid(rfd));
289
7.56k
        assert(ret);
290
291
7.56k
        int log_level = conf_files_log_level(flags);
292
293
7.56k
        _cleanup_free_ char *_root = NULL;
294
7.56k
        if (DEBUG_LOGGING && !root) {
295
0
                (void) fd_get_path(rfd, &_root);
296
0
                root = _root;
297
0
        }
298
299
7.56k
        _cleanup_(conf_file_freep) ConfFile *c = new(ConfFile, 1);
300
7.56k
        if (!c)
301
0
                return log_oom_full(log_level);
302
303
7.56k
        *c = (ConfFile) {
304
7.56k
                .original_path = strdup(path),
305
7.56k
                .fd = -EBADF,
306
7.56k
        };
307
308
7.56k
        if (!c->original_path)
309
0
                return log_oom_full(log_level);
310
311
7.56k
        r = path_extract_filename(path, &c->filename);
312
7.56k
        if (r < 0)
313
7.56k
                return log_full_errno(log_level, r, "Failed to extract filename from '%s': %m", path);
314
315
7.56k
        _cleanup_free_ char *dirpath = NULL, *resolved_dirpath = NULL;
316
7.56k
        r = path_extract_directory(path, &dirpath);
317
7.56k
        if (r < 0 && r != -EDESTADDRREQ)
318
7.56k
                return log_full_errno(log_level, r, "Failed to extract directory from '%s': %m", path);
319
7.56k
        if (r >= 0) {
320
7.56k
                r = chaseat(rfd, rfd, dirpath,
321
7.56k
                            CHASE_MUST_BE_DIRECTORY | conf_files_chase_flags(flags),
322
7.56k
                            &resolved_dirpath, /* ret_fd= */ NULL);
323
7.56k
                if (r < 0)
324
7.56k
                        return log_full_errno(log_level, r, "Failed to chase '%s%s': %m", empty_to_root(root), skip_leading_slash(dirpath));
325
7.56k
        }
326
327
7.56k
        c->result = path_join(resolved_dirpath, c->filename);
328
7.56k
        if (!c->result)
329
0
                return log_oom_full(log_level);
330
331
7.56k
        r = conf_file_chase_and_verify(
332
7.56k
                        root,
333
7.56k
                        rfd,
334
7.56k
                        c->original_path,
335
7.56k
                        c->result,
336
7.56k
                        c->filename,
337
7.56k
                        /* masked= */ NULL,
338
7.56k
                        flags,
339
7.56k
                        &c->resolved_path,
340
7.56k
                        &c->fd,
341
7.56k
                        &c->st);
342
7.56k
        if (r < 0)
343
0
                return r;
344
345
7.56k
        *ret = TAKE_PTR(c);
346
7.56k
        return 0;
347
7.56k
}
348
349
7.56k
int conf_file_new(const char *path, const char *root, ConfFilesFlags flags, ConfFile **ret) {
350
7.56k
        int r;
351
352
7.56k
        assert(path);
353
7.56k
        assert(ret);
354
355
7.56k
        _cleanup_free_ char *root_abs = NULL;
356
7.56k
        _cleanup_close_ int rfd = -EBADF;
357
7.56k
        r = prepare_dirs(root, flags, /* dirs= */ NULL, &root_abs, &rfd, /* ret_dirs= */ NULL);
358
7.56k
        if (r < 0)
359
0
                return r;
360
361
7.56k
        _cleanup_free_ char *path_abs = NULL;
362
7.56k
        if (!root_abs) {
363
7.56k
                r = path_make_absolute_cwd(path, &path_abs);
364
7.56k
                if (r < 0)
365
7.56k
                        return log_full_errno(conf_files_log_level(flags), r, "Failed to make '%s' absolute: %m", path);
366
367
7.56k
                path = path_abs;
368
7.56k
        }
369
370
7.56k
        _cleanup_(conf_file_freep) ConfFile *c = NULL;
371
7.56k
        r = conf_file_new_at(path, root_abs, rfd, flags, &c);
372
7.56k
        if (r < 0)
373
0
                return r;
374
375
7.56k
        r = conf_file_prefix_root(c, root_abs, flags);
376
7.56k
        if (r < 0)
377
0
                return r;
378
379
7.56k
        *ret = TAKE_PTR(c);
380
7.56k
        return 0;
381
7.56k
}
382
383
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
384
                conf_file_hash_ops,
385
                char, string_hash_func, string_compare_func,
386
                ConfFile, conf_file_free);
387
388
static int files_add(
389
                DIR *dir,
390
                const char *original_dirpath,
391
                const char *resolved_dirpath,
392
                const char *root, /* for logging, can be NULL */
393
                int rfd,
394
                Hashmap **files,
395
                Set **masked,
396
                const char *suffix,
397
0
                ConfFilesFlags flags) {
398
399
0
        int r;
400
401
0
        assert(dir);
402
0
        assert(original_dirpath);
403
0
        assert(resolved_dirpath);
404
0
        assert(wildcard_fd_is_valid(rfd));
405
0
        assert(files);
406
0
        assert(masked);
407
408
0
        int log_level = conf_files_log_level(flags);
409
410
0
        root = empty_to_root(root);
411
412
0
        FOREACH_DIRENT(de, dir, return log_full_errno(log_level, errno, "Failed to read directory '%s%s': %m",
413
0
                                                      root, skip_leading_slash(original_dirpath))) {
414
415
0
                _cleanup_free_ char *original_path = path_join(original_dirpath, de->d_name);
416
0
                if (!original_path)
417
0
                        return log_oom_full(log_level);
418
419
                /* Does this match the suffix? */
420
0
                if (suffix && !endswith(de->d_name, suffix)) {
421
0
                        log_debug("Skipping file '%s%s', suffix is not '%s'.", root, skip_leading_slash(original_path), suffix);
422
0
                        continue;
423
0
                }
424
425
                /* Has this file already been found in an earlier directory? */
426
0
                if (hashmap_contains(*files, de->d_name)) {
427
0
                        log_debug("Skipping overridden file '%s%s'.", root, skip_leading_slash(original_path));
428
0
                        continue;
429
0
                }
430
431
                /* Has this been masked in an earlier directory? */
432
0
                if ((flags & CONF_FILES_FILTER_MASKED) != 0 && set_contains(*masked, de->d_name)) {
433
0
                        log_debug("File '%s%s' is masked by previous entry.", root, skip_leading_slash(original_path));
434
0
                        continue;
435
0
                }
436
437
0
                _cleanup_free_ char *p = path_join(resolved_dirpath, de->d_name);
438
0
                if (!p)
439
0
                        return log_oom_full(log_level);
440
441
0
                _cleanup_free_ char *resolved_path = NULL;
442
0
                _cleanup_close_ int fd = -EBADF;
443
0
                struct stat st;
444
0
                r = conf_file_chase_and_verify(
445
0
                                root,
446
0
                                rfd,
447
0
                                original_path,
448
0
                                p,
449
0
                                de->d_name,
450
0
                                masked,
451
0
                                flags,
452
0
                                &resolved_path,
453
0
                                &fd,
454
0
                                &st);
455
0
                if (r == -ENOMEM)
456
0
                        return r;
457
0
                if (r < 0)
458
0
                        continue;
459
460
0
                _cleanup_(conf_file_freep) ConfFile *c = new(ConfFile, 1);
461
0
                if (!c)
462
0
                        return log_oom_full(log_level);
463
464
0
                *c = (ConfFile) {
465
0
                        .filename = strdup(de->d_name),
466
0
                        .result = TAKE_PTR(p),
467
0
                        .original_path = TAKE_PTR(original_path),
468
0
                        .resolved_path = TAKE_PTR(resolved_path),
469
0
                        .fd = TAKE_FD(fd),
470
0
                        .st = st,
471
0
                };
472
473
0
                if (!c->filename)
474
0
                        return log_oom_full(log_level);
475
476
0
                r = hashmap_ensure_put(files, &conf_file_hash_ops, c->filename, c);
477
0
                if (r < 0) {
478
0
                        assert(r == -ENOMEM);
479
0
                        return log_oom_full(log_level);
480
0
                }
481
0
                assert(r > 0);
482
483
0
                TAKE_PTR(c);
484
0
        }
485
486
0
        return 0;
487
0
}
488
489
0
static int dump_files(Hashmap *fh, const char *root, ConfFilesFlags flags, ConfFile ***ret_files, size_t *ret_n_files) {
490
0
        ConfFile **files = NULL;
491
0
        size_t n_files = 0;
492
0
        int r;
493
494
0
        CLEANUP_ARRAY(files, n_files, conf_file_free_array);
495
496
0
        assert(ret_files);
497
0
        assert(ret_n_files);
498
499
        /* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap. */
500
0
        r = hashmap_dump_sorted(fh, (void***) &files, &n_files);
501
0
        if (r < 0)
502
0
                return log_oom_full(conf_files_log_level(flags));
503
504
        /* Hence, we need to remove them from the hashmap. */
505
0
        FOREACH_ARRAY(i, files, n_files)
506
0
                assert_se(hashmap_remove(fh, (*i)->filename) == *i);
507
508
0
        if (root)
509
0
                FOREACH_ARRAY(i, files, n_files) {
510
0
                        r = conf_file_prefix_root(*i, root, flags);
511
0
                        if (r < 0)
512
0
                                return r;
513
0
                }
514
515
0
        *ret_files = TAKE_PTR(files);
516
0
        *ret_n_files = n_files;
517
0
        return 0;
518
0
}
519
520
static int copy_and_sort_files_from_hashmap(
521
                Hashmap *fh,
522
                const char *suffix,
523
                const char *root,
524
                ConfFilesFlags flags,
525
58.0k
                char ***ret) {
526
527
58.0k
        _cleanup_strv_free_ char **results = NULL;
528
58.0k
        _cleanup_free_ ConfFile **files = NULL;
529
58.0k
        size_t n_files = 0, n_results = 0;
530
58.0k
        int r;
531
532
58.0k
        assert(ret);
533
534
58.0k
        int log_level = conf_files_log_level(flags);
535
536
        /* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap.
537
         * Hence, do not use conf_file_free_array() for 'entries' */
538
58.0k
        r = hashmap_dump_sorted(fh, (void***) &files, &n_files);
539
58.0k
        if (r < 0)
540
0
                return log_oom_full(log_level);
541
542
58.0k
        FOREACH_ARRAY(i, files, n_files) {
543
0
                ConfFile *c = *i;
544
0
                const char *add = NULL;
545
546
0
                if (FLAGS_SET(flags, CONF_FILES_BASENAME))
547
0
                        add = c->filename;
548
0
                else if (root && !FLAGS_SET(flags, CONF_FILES_DONT_PREFIX_ROOT)) {
549
0
                        _cleanup_free_ char *p = NULL;
550
551
0
                        r = chaseat_prefix_root(c->result, root, &p);
552
0
                        if (r < 0)
553
0
                                return log_full_errno(log_level, r, "Failed to prefix '%s' with root '%s': %m", c->result, root);
554
555
0
                        if (FLAGS_SET(flags, CONF_FILES_TRUNCATE_SUFFIX) && suffix) {
556
0
                                char *e = endswith(p, suffix);
557
0
                                if (!e)
558
0
                                        continue;
559
560
0
                                *e = 0;
561
0
                        }
562
563
0
                        if (strv_consume_with_size(&results, &n_results, TAKE_PTR(p)) < 0)
564
0
                                return log_oom_full(log_level);
565
566
0
                        continue;
567
0
                } else
568
0
                        add = c->result;
569
570
0
                if (FLAGS_SET(flags, CONF_FILES_TRUNCATE_SUFFIX)) {
571
0
                        const char *e = endswith(add, suffix);
572
0
                        if (!e)
573
0
                                continue;
574
575
0
                        _cleanup_free_ char *n = strndup(add, e - add);
576
0
                        if (!n)
577
0
                                return log_oom_full(log_level);
578
579
0
                        r = strv_consume_with_size(&results, &n_results, TAKE_PTR(n));
580
0
                } else
581
0
                        r = strv_extend_with_size(&results, &n_results, add);
582
0
                if (r < 0)
583
0
                        return log_oom_full(log_level);
584
0
        }
585
586
58.0k
        *ret = TAKE_PTR(results);
587
58.0k
        return 0;
588
58.0k
}
589
590
0
static int insert_replacement(Hashmap **fh, ConfFile *replacement, ConfFilesFlags flags, const ConfFile **ret) {
591
0
        _cleanup_(conf_file_freep) ConfFile *c = ASSERT_PTR(replacement);
592
0
        int r;
593
594
0
        assert(fh);
595
0
        assert(ret);
596
597
        /* This consumes the input ConfFile. */
598
599
0
        ConfFile *existing = hashmap_get(*fh, c->filename);
600
0
        if (existing) {
601
0
                log_debug("An entry with higher priority '%s' -> '%s' already exists, ignoring the replacement: %s",
602
0
                          existing->filename, existing->result, c->original_path);
603
0
                *ret = NULL;
604
0
                return 0;
605
0
        }
606
607
0
        r = hashmap_ensure_put(fh, &conf_file_hash_ops, c->filename, c);
608
0
        if (r < 0) {
609
0
                assert(r == -ENOMEM);
610
0
                return log_oom_full(conf_files_log_level(flags));
611
0
        }
612
0
        assert(r > 0);
613
614
0
        log_debug("Inserted replacement: '%s' -> '%s'", c->filename, c->result);
615
616
0
        *ret = TAKE_PTR(c);
617
0
        return 0;
618
0
}
619
620
static int conf_files_list_impl(
621
                const char *suffix,
622
                const char *root, /* for logging, can be NULL */
623
                int rfd,
624
                ConfFilesFlags flags,
625
                const char * const *dirs,
626
                const char *replacement,
627
                Hashmap **ret,
628
58.0k
                const ConfFile **ret_inserted) {
629
630
58.0k
        _cleanup_hashmap_free_ Hashmap *fh = NULL;
631
58.0k
        _cleanup_set_free_ Set *masked = NULL;
632
58.0k
        _cleanup_(conf_file_freep) ConfFile *c = NULL;
633
58.0k
        const ConfFile *inserted = NULL;
634
58.0k
        int r;
635
636
58.0k
        assert(wildcard_fd_is_valid(rfd));
637
58.0k
        assert(ret);
638
639
58.0k
        root = empty_to_root(root);
640
641
58.0k
        if (replacement) {
642
0
                r = conf_file_new_at(
643
0
                                replacement, root, rfd,
644
0
                                flags & (CONF_FILES_WARN|CONF_FILES_CHASE_SAFE|CONF_FILES_CHASE_MAX_MODE),
645
0
                                &c);
646
0
                if (r < 0)
647
0
                        return r;
648
0
        }
649
650
232k
        STRV_FOREACH(p, dirs) {
651
232k
                _cleanup_closedir_ DIR *dir = NULL;
652
232k
                _cleanup_free_ char *path = NULL;
653
654
232k
                r = chase_and_opendirat(rfd, rfd, *p, conf_files_extra_chase_flags(flags), &path, &dir);
655
232k
                if (r < 0) {
656
232k
                        if (r != -ENOENT)
657
0
                                log_full_errno(conf_files_log_level(flags), r,
658
232k
                                               "Failed to chase and open directory '%s%s', ignoring: %m",
659
232k
                                               root, skip_leading_slash(*p));
660
232k
                        continue;
661
232k
                }
662
663
0
                if (c && streq_ptr(path_startswith(c->result, path), c->filename)) {
664
0
                        r = insert_replacement(&fh, TAKE_PTR(c), flags, &inserted);
665
0
                        if (r < 0)
666
0
                                return r;
667
0
                }
668
669
0
                r = files_add(dir, *p, path, root, rfd, &fh, &masked, suffix, flags);
670
0
                if (r == -ENOMEM)
671
0
                        return r;
672
0
        }
673
674
58.0k
        if (c) {
675
0
                r = insert_replacement(&fh, TAKE_PTR(c), flags, &inserted);
676
0
                if (r < 0)
677
0
                        return r;
678
0
        }
679
680
58.0k
        *ret = TAKE_PTR(fh);
681
58.0k
        if (ret_inserted)
682
0
                *ret_inserted = inserted;
683
58.0k
        return 0;
684
58.0k
}
685
686
int conf_files_list_strv(
687
                char ***ret,
688
                const char *suffix,
689
                const char *root,
690
                ConfFilesFlags flags,
691
0
                const char * const *dirs) {
692
693
0
        _cleanup_hashmap_free_ Hashmap *fh = NULL;
694
0
        _cleanup_close_ int rfd = -EBADF;
695
0
        _cleanup_free_ char *root_abs = NULL;
696
0
        _cleanup_strv_free_ char **dirs_abs = NULL;
697
0
        int r;
698
699
0
        assert(ret);
700
701
0
        r = prepare_dirs(root, flags, (char**) dirs, &root_abs, &rfd, &dirs_abs);
702
0
        if (r < 0)
703
0
                return r;
704
705
0
        r = conf_files_list_impl(suffix, root_abs, rfd, flags, (const char * const *) dirs_abs,
706
0
                                 /* replacement= */ NULL, &fh, /* ret_inserted= */ NULL);
707
0
        if (r < 0)
708
0
                return r;
709
710
0
        return copy_and_sort_files_from_hashmap(fh, suffix, empty_to_root(root_abs), flags, ret);
711
0
}
712
713
int conf_files_list_strv_full(
714
                const char *suffix,
715
                const char *root,
716
                ConfFilesFlags flags,
717
                const char * const *dirs,
718
                ConfFile ***ret_files,
719
0
                size_t *ret_n_files) {
720
721
0
        _cleanup_hashmap_free_ Hashmap *fh = NULL;
722
0
        _cleanup_close_ int rfd = -EBADF;
723
0
        _cleanup_free_ char *root_abs = NULL;
724
0
        _cleanup_strv_free_ char **dirs_abs = NULL;
725
0
        int r;
726
727
0
        assert(ret_files);
728
0
        assert(ret_n_files);
729
730
0
        r = prepare_dirs(root, flags, (char**) dirs, &root_abs, &rfd, &dirs_abs);
731
0
        if (r < 0)
732
0
                return r;
733
734
0
        r = conf_files_list_impl(suffix, root_abs, rfd, flags, (const char * const *) dirs_abs,
735
0
                                 /* replacement= */ NULL, &fh, /* ret_inserted= */ NULL);
736
0
        if (r < 0)
737
0
                return r;
738
739
0
        return dump_files(fh, empty_to_root(root_abs), flags, ret_files, ret_n_files);
740
0
}
741
742
int conf_files_list_strv_at(
743
                char ***ret,
744
                const char *suffix,
745
                int rfd,
746
                ConfFilesFlags flags,
747
58.0k
                const char * const *dirs) {
748
749
58.0k
        _cleanup_hashmap_free_ Hashmap *fh = NULL;
750
58.0k
        _cleanup_free_ char *root = NULL;
751
58.0k
        int r;
752
753
58.0k
        assert(wildcard_fd_is_valid(rfd));
754
58.0k
        assert(ret);
755
756
58.0k
        if (DEBUG_LOGGING)
757
0
                (void) fd_get_path(rfd, &root); /* for logging */
758
759
58.0k
        r = conf_files_list_impl(suffix, root, rfd, flags, dirs, /* replacement= */ NULL, &fh, /* ret_inserted= */ NULL);
760
58.0k
        if (r < 0)
761
0
                return r;
762
763
58.0k
        return copy_and_sort_files_from_hashmap(fh, suffix, /* root= */ NULL, flags, ret);
764
58.0k
}
765
766
int conf_files_list_strv_at_full(
767
                const char *suffix,
768
                int rfd,
769
                ConfFilesFlags flags,
770
                const char * const *dirs,
771
                ConfFile ***ret_files,
772
0
                size_t *ret_n_files) {
773
774
0
        _cleanup_hashmap_free_ Hashmap *fh = NULL;
775
0
        _cleanup_free_ char *root = NULL;
776
0
        int r;
777
778
0
        assert(wildcard_fd_is_valid(rfd));
779
0
        assert(ret_files);
780
0
        assert(ret_n_files);
781
782
0
        if (DEBUG_LOGGING)
783
0
                (void) fd_get_path(rfd, &root); /* for logging */
784
785
0
        r = conf_files_list_impl(suffix, root, rfd, flags, dirs, /* replacement= */ NULL, &fh, /* ret_inserted= */ NULL);
786
0
        if (r < 0)
787
0
                return r;
788
789
0
        return dump_files(fh, /* root= */ NULL, flags, ret_files, ret_n_files);
790
0
}
791
792
0
int conf_files_list(char ***ret, const char *suffix, const char *root, ConfFilesFlags flags, const char *dir) {
793
0
        return conf_files_list_strv(ret, suffix, root, flags, STRV_MAKE_CONST(dir));
794
0
}
795
796
0
int conf_files_list_full(const char *suffix, const char *root, ConfFilesFlags flags, const char *dir, ConfFile ***ret_files, size_t *ret_n_files) {
797
0
        return conf_files_list_strv_full(suffix, root, flags, STRV_MAKE_CONST(dir), ret_files, ret_n_files);
798
0
}
799
800
0
int conf_files_list_at(char ***ret, const char *suffix, int rfd, ConfFilesFlags flags, const char *dir) {
801
0
        return conf_files_list_strv_at(ret, suffix, rfd, flags, STRV_MAKE_CONST(dir));
802
0
}
803
804
0
int conf_files_list_at_full(const char *suffix, int rfd, ConfFilesFlags flags, const char *dir, ConfFile ***ret_files, size_t *ret_n_files) {
805
0
        return conf_files_list_strv_at_full(suffix, rfd, flags, STRV_MAKE_CONST(dir), ret_files, ret_n_files);
806
0
}
807
808
0
int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, ConfFilesFlags flags, const char *dirs) {
809
0
        _cleanup_strv_free_ char **d = NULL;
810
811
0
        assert(ret);
812
813
0
        d = strv_split_nulstr(dirs);
814
0
        if (!d)
815
0
                return log_oom_full(conf_files_log_level(flags));
816
817
0
        return conf_files_list_strv(ret, suffix, root, flags, (const char**) d);
818
0
}
819
820
0
int conf_files_list_nulstr_full(const char *suffix, const char *root, ConfFilesFlags flags, const char *dirs, ConfFile ***ret_files, size_t *ret_n_files) {
821
0
        _cleanup_strv_free_ char **d = NULL;
822
823
0
        assert(ret_files);
824
0
        assert(ret_n_files);
825
826
0
        d = strv_split_nulstr(dirs);
827
0
        if (!d)
828
0
                return log_oom_full(conf_files_log_level(flags));
829
830
0
        return conf_files_list_strv_full(suffix, root, flags, (const char**) d, ret_files, ret_n_files);
831
0
}
832
833
0
int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, ConfFilesFlags flags, const char *dirs) {
834
0
        _cleanup_strv_free_ char **d = NULL;
835
836
0
        assert(ret);
837
838
0
        d = strv_split_nulstr(dirs);
839
0
        if (!d)
840
0
                return log_oom_full(conf_files_log_level(flags));
841
842
0
        return conf_files_list_strv_at(ret, suffix, rfd, flags, (const char**) d);
843
0
}
844
845
0
int conf_files_list_nulstr_at_full(const char *suffix, int rfd, ConfFilesFlags flags, const char *dirs, ConfFile ***ret_files, size_t *ret_n_files) {
846
0
        _cleanup_strv_free_ char **d = NULL;
847
848
0
        assert(ret_files);
849
0
        assert(ret_n_files);
850
851
0
        d = strv_split_nulstr(dirs);
852
0
        if (!d)
853
0
                return log_oom_full(conf_files_log_level(flags));
854
855
0
        return conf_files_list_strv_at_full(suffix, rfd, flags, (const char**) d, ret_files, ret_n_files);
856
0
}
857
858
int conf_files_list_with_replacement(
859
                const char *root,
860
                char **config_dirs,
861
                const char *replacement,
862
                char ***ret_files,
863
0
                char **ret_inserted) {
864
865
0
        _cleanup_hashmap_free_ Hashmap *fh = NULL;
866
0
        _cleanup_free_ char *inserted = NULL;
867
0
        ConfFilesFlags flags = CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED_BY_SYMLINK | CONF_FILES_WARN;
868
0
        _cleanup_close_ int rfd = -EBADF;
869
0
        _cleanup_free_ char *root_abs = NULL;
870
0
        _cleanup_strv_free_ char **dirs_abs = NULL;
871
0
        const ConfFile *c = NULL;
872
0
        int r;
873
874
0
        assert(ret_files);
875
876
0
        r = prepare_dirs(root, flags, config_dirs, &root_abs, &rfd, &dirs_abs);
877
0
        if (r < 0)
878
0
                return r;
879
880
0
        r = conf_files_list_impl(".conf", root_abs, rfd, flags, (const char * const *) dirs_abs,
881
0
                                 replacement, &fh, ret_inserted ? &c : NULL);
882
0
        if (r < 0)
883
0
                return r;
884
885
0
        if (c) {
886
0
                r = chaseat_prefix_root(c->result, root_abs, &inserted);
887
0
                if (r < 0)
888
0
                        return log_full_errno(conf_files_log_level(flags), r,
889
0
                                              "Failed to prefix '%s' with root '%s': %m",
890
0
                                              c->result, empty_to_root(root_abs));
891
0
        }
892
893
0
        r = copy_and_sort_files_from_hashmap(fh, ".conf", empty_to_root(root_abs), flags, ret_files);
894
0
        if (r < 0)
895
0
                return r;
896
897
0
        if (ret_inserted)
898
0
                *ret_inserted = TAKE_PTR(inserted);
899
0
        return 0;
900
0
}
901
902
int conf_files_list_dropins(
903
                char ***ret,
904
                const char *dropin_dirname,
905
                const char *root,
906
                int root_fd,
907
                ConfFilesFlags flags,
908
58.0k
                const char * const *dirs) {
909
910
58.0k
        _cleanup_strv_free_ char **dropin_dirs = NULL;
911
58.0k
        const char *suffix;
912
58.0k
        int r;
913
914
58.0k
        assert(ret);
915
58.0k
        assert(dropin_dirname);
916
58.0k
        assert(dirs);
917
918
58.0k
        suffix = strjoina("/", dropin_dirname);
919
58.0k
        r = strv_extend_strv_concat(&dropin_dirs, dirs, suffix);
920
58.0k
        if (r < 0)
921
0
                return log_oom_full(conf_files_log_level(flags));
922
923
58.0k
        return conf_files_list_strv_at(ret, ".conf", root_fd, flags, (const char* const*) dropin_dirs);
924
58.0k
}
925
926
/**
927
 * Open and read a config file.
928
 *
929
 * The <fn> argument may be:
930
 * - '-', meaning stdin.
931
 * - a file name without a path. In this case <config_dirs> are searched.
932
 * - a path, either relative or absolute. In this case <fn> is opened directly.
933
 *
934
 * This method is only suitable for configuration files which have a flat layout without dropins.
935
 */
936
int conf_file_read(
937
                const char *root,
938
                const char **config_dirs,
939
                const char *fn,
940
                parse_line_t parse_line,
941
                void *userdata,
942
                bool ignore_enoent,
943
0
                bool *invalid_config) {
944
945
0
        _cleanup_fclose_ FILE *_f = NULL;
946
0
        _cleanup_free_ char *_fn = NULL;
947
0
        unsigned v = 0;
948
0
        FILE *f;
949
0
        int r = 0;
950
951
0
        assert(fn);
952
953
0
        if (streq(fn, "-")) {
954
0
                f = stdin;
955
0
                fn = "<stdin>";
956
957
0
                log_debug("Reading config from stdin%s", glyph(GLYPH_ELLIPSIS));
958
959
0
        } else if (is_path(fn)) {
960
0
                r = path_make_absolute_cwd(fn, &_fn);
961
0
                if (r < 0)
962
0
                        return log_error_errno(r, "Failed to make path absolute: %m");
963
0
                fn = _fn;
964
965
0
                f = _f = fopen(fn, "re");
966
0
                if (!_f)
967
0
                        r = -errno;
968
0
                else
969
0
                        log_debug("Reading config file \"%s\"%s", fn, glyph(GLYPH_ELLIPSIS));
970
971
0
        } else {
972
0
                r = search_and_fopen(fn, "re", root, config_dirs, &_f, &_fn);
973
0
                if (r >= 0) {
974
0
                        f = _f;
975
0
                        fn = _fn;
976
0
                        log_debug("Reading config file \"%s\"%s", fn, glyph(GLYPH_ELLIPSIS));
977
0
                }
978
0
        }
979
980
0
        if (r == -ENOENT && ignore_enoent) {
981
0
                log_debug_errno(r, "Failed to open \"%s\", ignoring: %m", fn);
982
0
                return 0; /* No error, but nothing happened. */
983
0
        }
984
0
        if (r < 0)
985
0
                return log_error_errno(r, "Failed to read '%s': %m", fn);
986
987
0
        r = 1;  /* We entered the part where we may modify state. */
988
989
0
        for (;;) {
990
0
                _cleanup_free_ char *line = NULL;
991
0
                bool invalid_line = false;
992
0
                int k;
993
994
0
                k = read_stripped_line(f, LONG_LINE_MAX, &line);
995
0
                if (k < 0)
996
0
                        return log_error_errno(k, "Failed to read '%s': %m", fn);
997
0
                if (k == 0)
998
0
                        break;
999
1000
0
                v++;
1001
1002
0
                if (IN_SET(line[0], 0, '#'))
1003
0
                        continue;
1004
1005
0
                k = parse_line(fn, v, line, invalid_config ? &invalid_line : NULL, userdata);
1006
0
                if (k < 0 && invalid_line)
1007
                        /* Allow reporting with a special code if the caller requested this. */
1008
0
                        *invalid_config = true;
1009
0
                else
1010
                        /* The first error, if any, becomes our return value. */
1011
0
                        RET_GATHER(r, k);
1012
0
        }
1013
1014
0
        if (ferror(f))
1015
0
                RET_GATHER(r, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read from file %s.", fn));
1016
1017
0
        return r;
1018
0
}