• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 14895667988

07 May 2025 08:57PM UTC coverage: 72.225% (-0.007%) from 72.232%
14895667988

push

github

yuwata
network: log_link_message_debug_errno() automatically append %m if necessary

Follow-up for d28746ef5.
Fixes CID#1609753.

0 of 1 new or added line in 1 file covered. (0.0%)

20297 existing lines in 338 files now uncovered.

297407 of 411780 relevant lines covered (72.22%)

695716.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.93
/src/basic/conf-files.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <errno.h>
4
#include <stdarg.h>
5
#include <stdio.h>
6
#include <stdlib.h>
7

8
#include "alloc-util.h"
9
#include "chase.h"
10
#include "conf-files.h"
11
#include "constants.h"
12
#include "dirent-util.h"
13
#include "errno-util.h"
14
#include "fd-util.h"
15
#include "fileio.h"
16
#include "glyph-util.h"
17
#include "hashmap.h"
18
#include "log.h"
19
#include "macro.h"
20
#include "nulstr-util.h"
21
#include "path-util.h"
22
#include "set.h"
23
#include "stat-util.h"
24
#include "string-util.h"
25
#include "strv.h"
26
#include "terminal-util.h"
27

28
static int files_add(
38,960✔
29
                DIR *dir,
30
                const char *dirpath,
31
                Hashmap **files,
32
                Set **masked,
33
                const char *suffix,
34
                unsigned flags) {
35

36
        int r;
38,960✔
37

38
        assert(dir);
38,960✔
39
        assert(dirpath);
38,960✔
40
        assert(files);
38,960✔
41
        assert(masked);
38,960✔
42

43
        FOREACH_DIRENT(de, dir, return -errno) {
318,785✔
44
                _cleanup_free_ char *n = NULL, *p = NULL;
201,904✔
45
                struct stat st;
201,904✔
46

47
                /* Does this match the suffix? */
48
                if (suffix && !endswith(de->d_name, suffix))
201,904✔
49
                        continue;
100,211✔
50

51
                /* Has this file already been found in an earlier directory? */
52
                if (hashmap_contains(*files, de->d_name)) {
101,693✔
53
                        log_debug("Skipping overridden file '%s/%s'.", dirpath, de->d_name);
125✔
54
                        continue;
125✔
55
                }
56

57
                /* Has this been masked in an earlier directory? */
58
                if ((flags & CONF_FILES_FILTER_MASKED) && set_contains(*masked, de->d_name)) {
101,568✔
59
                        log_debug("File '%s/%s' is masked by previous entry.", dirpath, de->d_name);
15✔
60
                        continue;
15✔
61
                }
62

63
                /* Read file metadata if we shall validate the check for file masks, for node types or whether the node is marked executable. */
64
                if (flags & (CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR|CONF_FILES_DIRECTORY|CONF_FILES_EXECUTABLE))
101,553✔
65
                        if (fstatat(dirfd(dir), de->d_name, &st, 0) < 0) {
2,852✔
66
                                log_debug_errno(errno, "Failed to stat '%s/%s', ignoring: %m", dirpath, de->d_name);
×
UNCOV
67
                                continue;
×
68
                        }
69

70
                /* Is this a masking entry? */
71
                if ((flags & CONF_FILES_FILTER_MASKED))
2,852✔
72
                        if (null_or_empty(&st)) {
2,591✔
73
                                /* Mark this one as masked */
74
                                r = set_put_strdup(masked, de->d_name);
25✔
75
                                if (r < 0)
25✔
76
                                        return r;
77

78
                                log_debug("File '%s/%s' is a mask.", dirpath, de->d_name);
25✔
79
                                continue;
25✔
80
                        }
81

82
                /* Does this node have the right type? */
83
                if (flags & (CONF_FILES_REGULAR|CONF_FILES_DIRECTORY))
101,528✔
84
                        if (!((flags & CONF_FILES_DIRECTORY) && S_ISDIR(st.st_mode)) &&
2,752✔
85
                            !((flags & CONF_FILES_REGULAR) && S_ISREG(st.st_mode))) {
2,601✔
86
                                log_debug("Ignoring '%s/%s', as it does not have the right type.", dirpath, de->d_name);
×
UNCOV
87
                                continue;
×
88
                        }
89

90
                /* Does this node have the executable bit set? */
91
                if (flags & CONF_FILES_EXECUTABLE)
101,528✔
92
                        /* As requested: check if the file is marked executable. Note that we don't check access(X_OK)
93
                         * here, as we care about whether the file is marked executable at all, and not whether it is
94
                         * executable for us, because if so, such errors are stuff we should log about. */
95

96
                        if ((st.st_mode & 0111) == 0) { /* not executable */
896✔
97
                                log_debug("Ignoring '%s/%s', as it is not marked executable.", dirpath, de->d_name);
×
UNCOV
98
                                continue;
×
99
                        }
100

101
                n = strdup(de->d_name);
101,528✔
102
                if (!n)
101,528✔
103
                        return -ENOMEM;
104

105
                if ((flags & CONF_FILES_BASENAME))
101,528✔
106
                        r = hashmap_ensure_put(files, &string_hash_ops_free, n, n);
12✔
107
                else {
108
                        p = path_join(dirpath, de->d_name);
101,516✔
109
                        if (!p)
101,516✔
110
                                return -ENOMEM;
111

112
                        r = hashmap_ensure_put(files, &string_hash_ops_free_free, n, p);
101,516✔
113
                }
114
                if (r < 0)
101,528✔
115
                        return r;
116
                assert(r > 0);
101,528✔
117

118
                TAKE_PTR(n);
101,528✔
119
                TAKE_PTR(p);
101,528✔
120
        }
121

122
        return 0;
123
}
124

125
static int copy_and_sort_files_from_hashmap(Hashmap *fh, char ***ret) {
105,219✔
126
        _cleanup_free_ char **sv = NULL;
105,219✔
127
        char **files;
105,219✔
128
        int r;
105,219✔
129

130
        assert(ret);
105,219✔
131

132
        r = hashmap_dump_sorted(fh, (void***) &sv, /* ret_n = */ NULL);
105,219✔
133
        if (r < 0)
105,219✔
134
                return r;
135

136
        /* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap. */
137
        files = strv_copy(sv);
105,219✔
138
        if (!files)
105,219✔
139
                return -ENOMEM;
140

141
        *ret = files;
105,219✔
142
        return 0;
105,219✔
143
}
144

145
int conf_files_list_strv(
105,211✔
146
                char ***ret,
147
                const char *suffix,
148
                const char *root,
149
                unsigned flags,
150
                const char * const *dirs) {
151

152
        _cleanup_hashmap_free_ Hashmap *fh = NULL;
105,211✔
153
        _cleanup_set_free_ Set *masked = NULL;
105,211✔
154
        int r;
105,211✔
155

156
        assert(ret);
105,211✔
157

158
        STRV_FOREACH(p, dirs) {
721,400✔
159
                _cleanup_closedir_ DIR *dir = NULL;
616,189✔
160
                _cleanup_free_ char *path = NULL;
616,189✔
161

162
                r = chase_and_opendir(*p, root, CHASE_PREFIX_ROOT, &path, &dir);
616,189✔
163
                if (r < 0) {
616,189✔
164
                        if (r != -ENOENT)
577,241✔
UNCOV
165
                                log_debug_errno(r, "Failed to chase and open directory '%s', ignoring: %m", *p);
×
166
                        continue;
577,241✔
167
                }
168

169
                r = files_add(dir, path, &fh, &masked, suffix, flags);
38,948✔
170
                if (r == -ENOMEM)
38,948✔
UNCOV
171
                        return r;
×
172
                if (r < 0)
38,948✔
173
                        log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path);
38,948✔
174
        }
175

176
        return copy_and_sort_files_from_hashmap(fh, ret);
105,211✔
177
}
178

179
int conf_files_list_strv_at(
8✔
180
                char ***ret,
181
                const char *suffix,
182
                int rfd,
183
                unsigned flags,
184
                const char * const *dirs) {
185

186
        _cleanup_hashmap_free_ Hashmap *fh = NULL;
8✔
187
        _cleanup_set_free_ Set *masked = NULL;
8✔
188
        int r;
8✔
189

190
        assert(rfd >= 0 || rfd == AT_FDCWD);
8✔
191
        assert(ret);
8✔
192

193
        STRV_FOREACH(p, dirs) {
20✔
194
                _cleanup_closedir_ DIR *dir = NULL;
12✔
195
                _cleanup_free_ char *path = NULL;
12✔
196

197
                r = chase_and_opendirat(rfd, *p, CHASE_AT_RESOLVE_IN_ROOT, &path, &dir);
12✔
198
                if (r < 0) {
12✔
199
                        if (r != -ENOENT)
×
200
                                log_debug_errno(r, "Failed to chase and open directory '%s', ignoring: %m", *p);
×
UNCOV
201
                        continue;
×
202
                }
203

204
                r = files_add(dir, path, &fh, &masked, suffix, flags);
12✔
205
                if (r == -ENOMEM)
12✔
UNCOV
206
                        return r;
×
207
                if (r < 0)
12✔
208
                        log_debug_errno(r, "Failed to search for files in '%s', ignoring: %m", path);
12✔
209
        }
210

211
        return copy_and_sort_files_from_hashmap(fh, ret);
8✔
212
}
213

214
int conf_files_insert(char ***strv, const char *root, char **dirs, const char *path) {
74✔
215
        /* Insert a path into strv, at the place honouring the usual sorting rules:
216
         * - we first compare by the basename
217
         * - and then we compare by dirname, allowing just one file with the given
218
         *   basename.
219
         * This means that we will
220
         * - add a new entry if basename(path) was not on the list,
221
         * - do nothing if an entry with higher priority was already present,
222
         * - do nothing if our new entry matches the existing entry,
223
         * - replace the existing entry if our new entry has higher priority.
224
         */
225
        size_t i, n;
74✔
226
        char *t;
74✔
227
        int r;
74✔
228

229
        n = strv_length(*strv);
74✔
230
        for (i = 0; i < n; i++) {
107✔
231
                int c;
101✔
232

233
                c = path_compare_filename((*strv)[i], path);
101✔
234
                if (c == 0)
101✔
235
                        /* Oh, there already is an entry with a matching name (the last component). */
236
                        STRV_FOREACH(dir, dirs) {
92✔
237
                                _cleanup_free_ char *rdir = NULL;
92✔
238
                                char *p1, *p2;
92✔
239

240
                                rdir = path_join(root, *dir);
92✔
241
                                if (!rdir)
92✔
242
                                        return -ENOMEM;
243

244
                                p1 = path_startswith((*strv)[i], rdir);
92✔
245
                                if (p1)
92✔
246
                                        /* Existing entry with higher priority
247
                                         * or same priority, no need to do anything. */
248
                                        return 0;
249

250
                                p2 = path_startswith(path, *dir);
33✔
251
                                if (p2) {
33✔
252
                                        /* Our new entry has higher priority */
253

254
                                        t = path_join(root, path);
3✔
255
                                        if (!t)
3✔
UNCOV
256
                                                return log_oom();
×
257

258
                                        return free_and_replace((*strv)[i], t);
3✔
259
                                }
260
                        }
261

262
                else if (c > 0)
39✔
263
                        /* Following files have lower priority, let's go insert our
264
                         * new entry. */
265
                        break;
266

267
                /* … we are not there yet, let's continue */
268
        }
269

270
        /* The new file has lower priority than all the existing entries */
271
        t = path_join(root, path);
12✔
272
        if (!t)
12✔
273
                return -ENOMEM;
274

275
        r = strv_insert(strv, i, t);
12✔
276
        if (r < 0)
12✔
UNCOV
277
                free(t);
×
278

279
        return r;
280
}
281

282
int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir) {
4✔
283
        return conf_files_list_strv(ret, suffix, root, flags, STRV_MAKE_CONST(dir));
4✔
284
}
285

286
int conf_files_list_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dir) {
4✔
287
        return conf_files_list_strv_at(ret, suffix, rfd, flags, STRV_MAKE_CONST(dir));
4✔
288
}
289

290
int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs) {
14,495✔
291
        _cleanup_strv_free_ char **d = NULL;
14,495✔
292

293
        assert(ret);
14,495✔
294

295
        d = strv_split_nulstr(dirs);
14,495✔
296
        if (!d)
14,495✔
297
                return -ENOMEM;
298

299
        return conf_files_list_strv(ret, suffix, root, flags, (const char**) d);
14,495✔
300
}
301

302
int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, unsigned flags, const char *dirs) {
×
UNCOV
303
        _cleanup_strv_free_ char **d = NULL;
×
304

UNCOV
305
        assert(ret);
×
306

307
        d = strv_split_nulstr(dirs);
×
UNCOV
308
        if (!d)
×
309
                return -ENOMEM;
310

UNCOV
311
        return conf_files_list_strv_at(ret, suffix, rfd, flags, (const char**) d);
×
312
}
313

314
int conf_files_list_with_replacement(
631✔
315
                const char *root,
316
                char **config_dirs,
317
                const char *replacement,
318
                char ***ret_files,
319
                char **ret_replace_file) {
320

UNCOV
321
        _cleanup_strv_free_ char **f = NULL;
×
322
        _cleanup_free_ char *p = NULL;
631✔
323
        int r;
631✔
324

325
        assert(config_dirs);
631✔
326
        assert(ret_files);
631✔
327
        assert(ret_replace_file || !replacement);
631✔
328

329
        r = conf_files_list_strv(&f, ".conf", root, 0, (const char* const*) config_dirs);
631✔
330
        if (r < 0)
631✔
UNCOV
331
                return log_error_errno(r, "Failed to enumerate config files: %m");
×
332

333
        if (replacement) {
631✔
334
                r = conf_files_insert(&f, root, config_dirs, replacement);
35✔
335
                if (r < 0)
35✔
UNCOV
336
                        return log_error_errno(r, "Failed to extend config file list: %m");
×
337

338
                p = path_join(root, replacement);
35✔
339
                if (!p)
35✔
UNCOV
340
                        return log_oom();
×
341
        }
342

343
        *ret_files = TAKE_PTR(f);
631✔
344
        if (ret_replace_file)
631✔
345
                *ret_replace_file = TAKE_PTR(p);
631✔
346

347
        return 0;
348
}
349

350
int conf_files_list_dropins(
47,259✔
351
                char ***ret,
352
                const char *dropin_dirname,
353
                const char *root,
354
                const char * const *dirs) {
355

356
        _cleanup_strv_free_ char **dropin_dirs = NULL;
47,259✔
357
        const char *suffix;
47,259✔
358
        int r;
47,259✔
359

360
        assert(ret);
47,259✔
361
        assert(dropin_dirname);
47,259✔
362
        assert(dirs);
47,259✔
363

364
        suffix = strjoina("/", dropin_dirname);
236,295✔
365
        r = strv_extend_strv_concat(&dropin_dirs, dirs, suffix);
47,259✔
366
        if (r < 0)
47,259✔
367
                return r;
368

369
        return conf_files_list_strv(ret, ".conf", root, 0, (const char* const*) dropin_dirs);
47,259✔
370
}
371

372
/**
373
 * Open and read a config file.
374
 *
375
 * The <fn> argument may be:
376
 * - '-', meaning stdin.
377
 * - a file name without a path. In this case <config_dirs> are searched.
378
 * - a path, either relative or absolute. In this case <fn> is opened directly.
379
 *
380
 * This method is only suitable for configuration files which have a flat layout without dropins.
381
 */
382
int conf_file_read(
15,690✔
383
                const char *root,
384
                const char **config_dirs,
385
                const char *fn,
386
                parse_line_t parse_line,
387
                void *userdata,
388
                bool ignore_enoent,
389
                bool *invalid_config) {
390

391
        _cleanup_fclose_ FILE *_f = NULL;
15,690✔
392
        _cleanup_free_ char *_fn = NULL;
15,690✔
393
        unsigned v = 0;
15,690✔
394
        FILE *f;
15,690✔
395
        int r = 0;
15,690✔
396

397
        assert(fn);
15,690✔
398

399
        if (streq(fn, "-")) {
15,690✔
400
                f = stdin;
175✔
401
                fn = "<stdin>";
175✔
402

403
                log_debug("Reading config from stdin%s", glyph(GLYPH_ELLIPSIS));
207✔
404

405
        } else if (is_path(fn)) {
15,515✔
406
                r = path_make_absolute_cwd(fn, &_fn);
15,515✔
407
                if (r < 0)
15,515✔
UNCOV
408
                        return log_error_errno(r, "Failed to make path absolute: %m");
×
409
                fn = _fn;
15,515✔
410

411
                f = _f = fopen(fn, "re");
15,515✔
412
                if (!_f)
15,515✔
413
                        r = -errno;
358✔
414
                else
415
                        log_debug("Reading config file \"%s\"%s", fn, glyph(GLYPH_ELLIPSIS));
30,021✔
416

417
        } else {
418
                r = search_and_fopen(fn, "re", root, config_dirs, &_f, &_fn);
×
419
                if (r >= 0) {
×
420
                        f = _f;
×
421
                        fn = _fn;
×
UNCOV
422
                        log_debug("Reading config file \"%s\"%s", fn, glyph(GLYPH_ELLIPSIS));
×
423
                }
424
        }
425

426
        if (r == -ENOENT && ignore_enoent) {
15,690✔
427
                log_debug_errno(r, "Failed to open \"%s\", ignoring: %m", fn);
358✔
428
                return 0; /* No error, but nothing happened. */
358✔
429
        }
430
        if (r < 0)
15,332✔
UNCOV
431
                return log_error_errno(r, "Failed to read '%s': %m", fn);
×
432

433
        r = 1;  /* We entered the part where we may modify state. */
434

435
        for (;;) {
199,347✔
436
                _cleanup_free_ char *line = NULL;
184,015✔
437
                bool invalid_line = false;
199,347✔
438
                int k;
199,347✔
439

440
                k = read_stripped_line(f, LONG_LINE_MAX, &line);
199,347✔
441
                if (k < 0)
199,347✔
UNCOV
442
                        return log_error_errno(k, "Failed to read '%s': %m", fn);
×
443
                if (k == 0)
199,347✔
444
                        break;
445

446
                v++;
184,015✔
447

448
                if (IN_SET(line[0], 0, '#'))
184,015✔
449
                        continue;
118,704✔
450

451
                k = parse_line(fn, v, line, invalid_config ? &invalid_line : NULL, userdata);
65,986✔
452
                if (k < 0 && invalid_line)
65,311✔
453
                        /* Allow reporting with a special code if the caller requested this. */
454
                        *invalid_config = true;
10✔
455
                else
456
                        /* The first error, if any, becomes our return value. */
457
                        RET_GATHER(r, k);
65,301✔
458
        }
459

460
        if (ferror(f))
15,332✔
UNCOV
461
                RET_GATHER(r, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read from file %s.", fn));
×
462

463
        return r;
464
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc