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

systemd / systemd / 15263807472

26 May 2025 08:53PM UTC coverage: 72.046% (-0.002%) from 72.048%
15263807472

push

github

yuwata
src/core/manager.c: log preset activity on first boot

This gives us a little more information about what units were enabled
or disabled on that first boot and will be useful for OS developers
tracking down the source of unit state.

An example with this enabled looks like:

```
NET: Registered PF_VSOCK protocol family
systemd[1]: Applying preset policy.
systemd[1]: Unit /etc/systemd/system/dnsmasq.service is masked, ignoring.
systemd[1]: Unit /etc/systemd/system/systemd-repart.service is masked, ignoring.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket'.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir.mount' → '/etc/systemd/system/var-mnt-workdir.mount'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir\x2dtmp.mount' → '/etc/systemd/system/var-mnt-workdir\x2dtmp.mount'.
systemd[1]: Created symlink '/etc/systemd/system/afterburn-sshkeys.target.requires/afterburn-sshkeys@core.service' → '/usr/lib/systemd/system/afterburn-sshkeys@.service'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket' → '/usr/lib/systemd/system/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket' → '/usr/lib/systemd/system/systemd-resolved-monitor.socket'.
systemd[1]: Populated /etc with preset unit settings.
```

Considering it only happens on first boot and not on every boot I think
the extra information is worth the extra verbosity in the logs just for
that boot.

5 of 6 new or added lines in 1 file covered. (83.33%)

5463 existing lines in 165 files now uncovered.

299151 of 415222 relevant lines covered (72.05%)

702386.45 hits per line

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

90.54
/src/basic/os-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <stdlib.h>
4

5
#include "alloc-util.h"
6
#include "chase.h"
7
#include "dirent-util.h"
8
#include "env-file.h"
9
#include "errno-util.h"
10
#include "fd-util.h"
11
#include "fs-util.h"
12
#include "glyph-util.h"
13
#include "log.h"
14
#include "os-util.h"
15
#include "path-util.h"
16
#include "stat-util.h"
17
#include "string-table.h"
18
#include "string-util.h"
19
#include "strv.h"
20
#include "time-util.h"
21
#include "utf8.h"
22
#include "xattr-util.h"
23

24
static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
25
        [IMAGE_MACHINE]  = "machine",
26
        [IMAGE_PORTABLE] = "portable",
27
        [IMAGE_SYSEXT]   = "sysext",
28
        [IMAGE_CONFEXT]  = "confext",
29
};
30

31
DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
212✔
32

33
/* Helper struct for naming simplicity and reusability */
34
static const struct {
35
        const char *release_file_directory;
36
        const char *release_file_path_prefix;
37
} image_class_release_info[_IMAGE_CLASS_MAX] = {
38
        [IMAGE_SYSEXT] = {
39
                .release_file_directory = "/usr/lib/extension-release.d/",
40
                .release_file_path_prefix = "/usr/lib/extension-release.d/extension-release.",
41
        },
42
        [IMAGE_CONFEXT] = {
43
                .release_file_directory = "/etc/extension-release.d/",
44
                .release_file_path_prefix = "/etc/extension-release.d/extension-release.",
45
        }
46
};
47

48
bool image_name_is_valid(const char *s) {
24,611✔
49
        if (!filename_is_valid(s))
24,611✔
50
                return false;
51

52
        if (string_has_cc(s, NULL))
19,426✔
53
                return false;
54

55
        if (!utf8_is_valid(s))
19,426✔
56
                return false;
57

58
        /* Temporary files for atomically creating new files */
59
        if (startswith(s, ".#"))
19,426✔
60
                return false;
1✔
61

62
        return true;
63
}
64

65
int path_extract_image_name(const char *path, char **ret) {
6,778✔
66
        _cleanup_free_ char *fn = NULL;
6,778✔
67
        int r;
6,778✔
68

69
        assert(path);
6,778✔
70
        assert(ret);
6,778✔
71

72
        /* Extract last component from path, without any "/" suffixes. */
73
        r = path_extract_filename(path, &fn);
6,778✔
74
        if (r < 0)
6,778✔
75
                return r;
76
        if (r != O_DIRECTORY) {
6,778✔
77
                /* Chop off any image suffixes we recognize (unless we already know this must refer to some dir) */
78
                char *m = ENDSWITH_SET(fn, ".sysext.raw", ".confext.raw", ".raw");
6,778✔
79
                if (m)
6,778✔
80
                        *m = 0;
3,339✔
81
        }
82

83
        /* Truncate the version/counting suffixes */
84
        fn[strcspn(fn, "_+")] = 0;
6,778✔
85

86
        if (!image_name_is_valid(fn))
6,778✔
87
                return -EINVAL;
88

89
        *ret = TAKE_PTR(fn);
6,778✔
90
        return 0;
6,778✔
91
}
92

93
int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check) {
257✔
94
        int r;
257✔
95

96
        assert(path);
257✔
97

98
        /* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
99
         * always results in -ENOENT, and we can properly distinguish the case where the whole root doesn't exist from
100
         * the case where just the os-release file is missing. */
101
        r = access_nofollow(path, F_OK);
257✔
102
        if (r < 0)
257✔
103
                return r;
104

105
        /* We use /usr/lib/extension-release.d/extension-release[.NAME] as flag for something being a system extension,
106
         * /etc/extension-release.d/extension-release[.NAME] as flag for something being a system configuration, and finally,
107
         * and {/etc|/usr/lib}/os-release as a flag for something being an OS (when not an extension). */
108
        r = open_extension_release(path, image_class, extension, relax_extension_release_check, NULL, NULL);
256✔
109
        if (r == -ENOENT) /* We got nothing */
256✔
110
                return 0;
111
        if (r < 0)
207✔
UNCOV
112
                return r;
×
113

114
        return 1;
115
}
116

117
static int extension_release_strict_xattr_value(int extension_release_fd, const char *extension_release_dir_path, const char *filename) {
579✔
118
        int r;
579✔
119

120
        assert(extension_release_fd >= 0);
579✔
121
        assert(extension_release_dir_path);
579✔
122
        assert(filename);
579✔
123

124
        /* No xattr or cannot parse it? Then skip this. */
125
        r = getxattr_at_bool(extension_release_fd, /* path= */ NULL, "user.extension-release.strict", /* at_flags= */ 0);
579✔
126
        if (ERRNO_IS_NEG_XATTR_ABSENT(r))
579✔
127
                return log_debug_errno(r, "%s/%s does not have user.extension-release.strict xattr, ignoring.",
56✔
128
                                       extension_release_dir_path, filename);
129
        if (r < 0)
523✔
UNCOV
130
                return log_debug_errno(r, "%s/%s: Failed to read 'user.extension-release.strict' extended attribute from file, ignoring: %m",
×
131
                                       extension_release_dir_path, filename);
132

133
        /* Explicitly set to request strict matching? Skip it. */
134
        if (r > 0) {
523✔
UNCOV
135
                log_debug("%s/%s: 'user.extension-release.strict' attribute is true, ignoring file.",
×
136
                          extension_release_dir_path, filename);
UNCOV
137
                return true;
×
138
        }
139

140
        log_debug("%s/%s: 'user.extension-release.strict' attribute is false%s",
1,046✔
141
                  extension_release_dir_path, filename,
142
                  glyph(GLYPH_ELLIPSIS));
143

144
        return false;
145
}
146

147
int open_os_release_at(int rfd, char **ret_path, int *ret_fd) {
2,155✔
148
        const char *e;
2,155✔
149
        int r;
2,155✔
150

151
        assert(rfd >= 0 || rfd == AT_FDCWD);
2,155✔
152

153
        e = secure_getenv("SYSTEMD_OS_RELEASE");
2,155✔
154
        if (e)
2,155✔
155
                return chaseat(rfd, e, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
12✔
156

157
        FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
2,635✔
158
                r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
2,433✔
159
                if (r != -ENOENT)
2,433✔
160
                        return r;
1,941✔
161
        }
162

163
        return -ENOENT;
202✔
164
}
165

166
int open_os_release(const char *root, char **ret_path, int *ret_fd) {
1,240✔
167
        _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
1,240✔
168
        _cleanup_free_ char *p = NULL;
1,240✔
169
        int r;
1,240✔
170

171
        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
1,240✔
172
        if (rfd < 0)
1,240✔
UNCOV
173
                return -errno;
×
174

175
        r = open_os_release_at(rfd, ret_path ? &p : NULL, ret_fd ? &fd : NULL);
1,273✔
176
        if (r < 0)
1,240✔
177
                return r;
178

179
        if (ret_path) {
1,236✔
180
                r = chaseat_prefix_root(p, root, ret_path);
1,207✔
181
                if (r < 0)
1,207✔
182
                        return r;
183
        }
184

185
        if (ret_fd)
1,236✔
186
                *ret_fd = TAKE_FD(fd);
1,236✔
187

188
        return 0;
189
}
190

191
int open_extension_release_at(
2,636✔
192
                int rfd,
193
                ImageClass image_class,
194
                const char *extension,
195
                bool relax_extension_release_check,
196
                char **ret_path,
197
                int *ret_fd) {
198

199
        _cleanup_free_ char *dir_path = NULL, *path_found = NULL;
2,636✔
200
        _cleanup_close_ int fd_found = -EBADF;
2,636✔
201
        _cleanup_closedir_ DIR *dir = NULL;
2,636✔
202
        bool found = false;
2,636✔
203
        const char *p;
2,636✔
204
        int r;
2,636✔
205

206
        assert(rfd >= 0 || rfd == AT_FDCWD);
2,636✔
207
        assert(!extension || (image_class >= 0 && image_class < _IMAGE_CLASS_MAX));
2,636✔
208

209
        if (!extension)
2,636✔
210
                return open_os_release_at(rfd, ret_path, ret_fd);
915✔
211

212
        if (!IN_SET(image_class, IMAGE_SYSEXT, IMAGE_CONFEXT))
1,721✔
213
                return -EINVAL;
214

215
        if (!image_name_is_valid(extension))
1,721✔
UNCOV
216
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The extension name %s is invalid.", extension);
×
217

218
        p = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension);
8,605✔
219
        r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
1,721✔
220
        log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p);
1,721✔
221
        if (r != -ENOENT)
1,721✔
222
                return r;
223

224
        /* Cannot find the expected extension-release file? The image filename might have been mangled on
225
         * deployment, so fallback to checking for any file in the extension-release.d directory, and return
226
         * the first one with a user.extension-release xattr instead. The user.extension-release.strict
227
         * xattr is checked to ensure the author of the image considers it OK if names do not match. */
228

229
        p = image_class_release_info[image_class].release_file_directory;
857✔
230
        r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir);
857✔
231
        if (r < 0)
857✔
232
                return log_debug_errno(r, "Cannot open %s, ignoring: %m", p);
324✔
233

234
        FOREACH_DIRENT(de, dir, return -errno) {
2,188✔
235
                _cleanup_close_ int fd = -EBADF;
4,291✔
236
                const char *image_name;
589✔
237

238
                if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
589✔
UNCOV
239
                        continue;
×
240

241
                image_name = startswith(de->d_name, "extension-release.");
589✔
242
                if (!image_name)
589✔
UNCOV
243
                        continue;
×
244

245
                if (!image_name_is_valid(image_name)) {
589✔
UNCOV
246
                        log_debug("%s/%s is not a valid release file name, ignoring.", dir_path, de->d_name);
×
247
                        continue;
×
248
                }
249

250
                /* We already chased the directory, and checked that this is a real file, so we shouldn't
251
                 * fail to open it. */
252
                fd = openat(dirfd(dir), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
589✔
253
                if (fd < 0)
589✔
UNCOV
254
                        return log_debug_errno(errno, "Failed to open release file %s/%s: %m", dir_path, de->d_name);
×
255

256
                /* Really ensure it is a regular file after we open it. */
257
                r = fd_verify_regular(fd);
589✔
258
                if (r < 0) {
589✔
UNCOV
259
                        log_debug_errno(r, "%s/%s is not a regular file, ignoring: %m", dir_path, de->d_name);
×
260
                        continue;
×
261
                }
262

263
                if (!relax_extension_release_check) {
589✔
264
                        _cleanup_free_ char *base_extension = NULL;
583✔
265

266
                        r = path_extract_image_name(extension, &base_extension);
583✔
267
                        if (r < 0) {
583✔
UNCOV
268
                                log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", extension);
×
269
                                continue;
×
270
                        }
271

272
                        if (!streq(image_name, base_extension) &&
1,162✔
273
                            extension_release_strict_xattr_value(fd, dir_path, image_name) != 0)
579✔
274
                                continue;
56✔
275
                }
276

277
                /* We already found what we were looking for, but there's another candidate? We treat this as
278
                 * an error, as we want to enforce that there are no ambiguities in case we are in the
279
                 * fallback path. */
280
                if (found)
533✔
281
                        return -ENOTUNIQ;
282

283
                found = true;
533✔
284

285
                if (ret_fd)
533✔
286
                        fd_found = TAKE_FD(fd);
520✔
287

288
                if (ret_path) {
533✔
289
                        path_found = path_join(dir_path, de->d_name);
519✔
290
                        if (!path_found)
519✔
291
                                return -ENOMEM;
292
                }
293
        }
294
        if (!found)
533✔
295
                return -ENOENT;
296

297
        if (ret_fd)
533✔
298
                *ret_fd = TAKE_FD(fd_found);
520✔
299
        if (ret_path)
533✔
300
                *ret_path = TAKE_PTR(path_found);
519✔
301

302
        return 0;
303
}
304

305
int open_extension_release(
2,336✔
306
                const char *root,
307
                ImageClass image_class,
308
                const char *extension,
309
                bool relax_extension_release_check,
310
                char **ret_path,
311
                int *ret_fd) {
312

313
        _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
2,336✔
314
        _cleanup_free_ char *p = NULL;
2,336✔
315
        int r;
2,336✔
316

317
        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
2,336✔
318
        if (rfd < 0)
2,336✔
UNCOV
319
                return -errno;
×
320

321
        r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check,
2,936✔
322
                                      ret_path ? &p : NULL, ret_fd ? &fd : NULL);
323
        if (r < 0)
2,336✔
324
                return r;
325

326
        if (ret_path) {
1,820✔
327
                r = chaseat_prefix_root(p, root, ret_path);
1,594✔
328
                if (r < 0)
1,594✔
329
                        return r;
330
        }
331

332
        if (ret_fd)
1,820✔
333
                *ret_fd = TAKE_FD(fd);
1,613✔
334

335
        return 0;
336
}
337

338
static int parse_extension_release_atv(
300✔
339
                int rfd,
340
                ImageClass image_class,
341
                const char *extension,
342
                bool relax_extension_release_check,
343
                va_list ap) {
344

345
        _cleanup_close_ int fd = -EBADF;
300✔
346
        _cleanup_free_ char *p = NULL;
300✔
347
        int r;
300✔
348

349
        assert(rfd >= 0 || rfd == AT_FDCWD);
300✔
350

351
        r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check, &p, &fd);
300✔
352
        if (r < 0)
300✔
353
                return r;
354

355
        return parse_env_file_fdv(fd, p, ap);
293✔
356
}
357

358
int parse_extension_release_at_sentinel(
6✔
359
                int rfd,
360
                ImageClass image_class,
361
                bool relax_extension_release_check,
362
                const char *extension,
363
                ...) {
364

365
        va_list ap;
6✔
366
        int r;
6✔
367

368
        assert(rfd >= 0 || rfd == AT_FDCWD);
6✔
369

370
        va_start(ap, extension);
6✔
371
        r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
6✔
372
        va_end(ap);
6✔
373
        return r;
6✔
374
}
375

376
int parse_extension_release_sentinel(
294✔
377
                const char *root,
378
                ImageClass image_class,
379
                bool relax_extension_release_check,
380
                const char *extension,
381
                ...) {
382

383
        _cleanup_close_ int rfd = -EBADF;
294✔
384
        va_list ap;
294✔
385
        int r;
294✔
386

387
        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
294✔
388
        if (rfd < 0)
294✔
UNCOV
389
                return -errno;
×
390

391
        va_start(ap, extension);
294✔
392
        r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
294✔
393
        va_end(ap);
294✔
394
        return r;
294✔
395
}
396

397
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret) {
222✔
398
        _cleanup_strv_free_ char **os_release_pairs = NULL, **os_release_pairs_prefixed = NULL;
222✔
399
        int r;
222✔
400

401
        r = load_os_release_pairs(root, &os_release_pairs);
222✔
402
        if (r < 0)
222✔
403
                return r;
404

405
        STRV_FOREACH_PAIR(p, q, os_release_pairs) {
2,664✔
406
                char *line;
2,442✔
407

408
                /* We strictly return only the four main ID fields and ignore the rest */
409
                if (!STR_IN_SET(*p, "ID", "VERSION_ID", "BUILD_ID", "VARIANT_ID"))
2,442✔
410
                        continue;
1,998✔
411

412
                ascii_strlower(*p);
444✔
413
                line = strjoin(prefix, *p, "=", *q);
444✔
414
                if (!line)
444✔
415
                        return -ENOMEM;
416
                r = strv_consume(&os_release_pairs_prefixed, line);
444✔
417
                if (r < 0)
444✔
418
                        return r;
419
        }
420

421
        *ret = TAKE_PTR(os_release_pairs_prefixed);
222✔
422

423
        return 0;
222✔
424
}
425

426
int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret) {
1,013✔
427
        _cleanup_close_ int fd = -EBADF;
1,013✔
428
        _cleanup_free_ char *p = NULL;
1,013✔
429
        int r;
1,013✔
430

431
        r = open_extension_release(root, image_class, extension, relax_extension_release_check, &p, &fd);
1,013✔
432
        if (r < 0)
1,013✔
433
                return r;
434

435
        return load_env_file_pairs_fd(fd, p, ret);
618✔
436
}
437

438
int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol) {
53✔
439
        _cleanup_free_ char *_support_end_alloc = NULL;
53✔
440
        int r;
53✔
441

442
        if (!support_end) {
53✔
443
                /* If the caller has the variably handy, they can pass it in. If not, we'll read it
444
                 * ourselves. */
445

446
                r = parse_os_release(NULL,
50✔
447
                                     "SUPPORT_END", &_support_end_alloc);
448
                if (r < 0 && r != -ENOENT)
50✔
UNCOV
449
                        return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, r,
×
450
                                              "Failed to read os-release file, ignoring: %m");
451

452
                support_end = _support_end_alloc;
50✔
453
        }
454

455
        if (isempty(support_end)) { /* An empty string is a explicit way to say "no EOL exists" */
53✔
456
                if (ret_eol)
50✔
UNCOV
457
                        *ret_eol = USEC_INFINITY;
×
458

459
                return false;  /* no end date defined */
50✔
460
        }
461

462
        struct tm tm = {};
3✔
463
        const char *k = strptime(support_end, "%Y-%m-%d", &tm);
3✔
464
        if (!k || *k)
3✔
465
                return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, SYNTHETIC_ERRNO(EINVAL),
1✔
466
                                      "Failed to parse SUPPORT_END= from os-release file, ignoring: %s", support_end);
467

468
        usec_t eol;
2✔
469
        r = mktime_or_timegm_usec(&tm, /* utc= */ true, &eol);
2✔
470
        if (r < 0)
2✔
UNCOV
471
                return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, r,
×
472
                                      "Failed to convert SUPPORT_END= time from os-release file, ignoring: %m");
473

474
        if (ret_eol)
2✔
UNCOV
475
                *ret_eol = eol;
×
476

477
        return now(CLOCK_REALTIME) > eol;
2✔
478
}
479

480
const char* os_release_pretty_name(const char *pretty_name, const char *name) {
48✔
481
        /* Distills a "pretty" name to show from os-release data. First argument is supposed to be the
482
         * PRETTY_NAME= field, the second one the NAME= field. This function is trivial, of course, and
483
         * exists mostly to ensure we use the same logic wherever possible. */
484

485
        return empty_to_null(pretty_name) ?:
48✔
486
                empty_to_null(name) ?: "Linux";
6✔
487
}
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