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

systemd / systemd / 21419361949

27 Jan 2026 02:53PM UTC coverage: 72.793% (-0.03%) from 72.821%
21419361949

push

github

keszybz
kernel-install: handle removal unsuccessful UKIs and loader entries separately

When a tries file exists, 90-uki-copy.install removes a previous UKI of the
same kernel version and all it's unbooted variants. This removal is guarded
behind a check for the existence of the already booted UKI, i.e. if uki.efi
already exists, uki.efi and uki+*.efi will be removed.

This leaves the edge case that if uki.efi does not exist, but only an unbooted,
e.g. uki+3.efi, it will not be removed. This is not a problem, if the number of
tries is constant between both builds, since a new uki+3.efi would overwrite
the existing one, but if the number of tries is changed to, e.g. uki+5.efi, we
are left with both uki+3.efi and uki+5.efi.

The same is done for loader entries.

311334 of 427698 relevant lines covered (72.79%)

1157141.18 hits per line

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

88.79
/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);
317✔
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) {
25,616✔
49
        if (!filename_is_valid(s))
25,616✔
50
                return false;
51

52
        if (string_has_cc(s, NULL))
20,264✔
53
                return false;
54

55
        if (!utf8_is_valid(s))
20,264✔
56
                return false;
57

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

62
        return true;
63
}
64

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

69
        assert(path);
7,018✔
70
        assert(ret);
7,018✔
71

72
        /* Extract last component from path, without any "/" suffixes. */
73
        r = path_extract_filename(path, &fn);
7,018✔
74
        if (r < 0)
7,018✔
75
                return r;
76
        if (r != O_DIRECTORY) {
7,018✔
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");
7,018✔
79
                if (m)
7,018✔
80
                        *m = 0;
3,555✔
81
        }
82

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

86
        if (!image_name_is_valid(fn))
7,018✔
87
                return -EINVAL;
88

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

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

96
        assert(path);
324✔
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);
324✔
102
        if (r < 0)
324✔
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);
323✔
109
        if (r == -ENOENT) /* We got nothing */
323✔
110
                return 0;
111
        if (r < 0)
286✔
112
                return r;
×
113

114
        return 1;
115
}
116

117
int fd_is_os_tree(int fd) {
31✔
118
        int r;
31✔
119

120
        assert(fd >= 0);
31✔
121

122
        r = open_extension_release_at(
31✔
123
                        fd,
124
                        IMAGE_MACHINE,
125
                        /* extension= */ NULL,
126
                        /* relax_extension_release_check= */ false,
127
                        /* ret_path= */ NULL,
128
                        /* ret_fd= */ NULL);
129
        if (r == -ENOENT)
31✔
130
                return false;
131
        if (r < 0)
6✔
132
                return r;
×
133

134
        return true;
135
}
136

137
static int extension_release_strict_xattr_value(int extension_release_fd, const char *extension_release_dir_path, const char *filename) {
599✔
138
        int r;
599✔
139

140
        assert(extension_release_fd >= 0);
599✔
141
        assert(extension_release_dir_path);
599✔
142
        assert(filename);
599✔
143

144
        /* No xattr or cannot parse it? Then skip this. */
145
        r = getxattr_at_bool(extension_release_fd, /* path= */ NULL, "user.extension-release.strict", /* at_flags= */ 0);
599✔
146
        if (ERRNO_IS_NEG_XATTR_ABSENT(r))
599✔
147
                return log_debug_errno(r, "%s/%s does not have user.extension-release.strict xattr, ignoring.",
56✔
148
                                       extension_release_dir_path, filename);
149
        if (r < 0)
543✔
150
                return log_debug_errno(r, "%s/%s: Failed to read 'user.extension-release.strict' extended attribute from file, ignoring: %m",
×
151
                                       extension_release_dir_path, filename);
152

153
        /* Explicitly set to request strict matching? Skip it. */
154
        if (r > 0) {
543✔
155
                log_debug("%s/%s: 'user.extension-release.strict' attribute is true, ignoring file.",
×
156
                          extension_release_dir_path, filename);
157
                return true;
×
158
        }
159

160
        log_debug("%s/%s: 'user.extension-release.strict' attribute is false%s",
1,086✔
161
                  extension_release_dir_path, filename,
162
                  glyph(GLYPH_ELLIPSIS));
163

164
        return false;
165
}
166

167
int open_os_release_at(int rfd, char **ret_path, int *ret_fd) {
2,337✔
168
        const char *e;
2,337✔
169
        int r;
2,337✔
170

171
        assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT));
2,337✔
172

173
        e = secure_getenv("SYSTEMD_OS_RELEASE");
2,337✔
174
        if (e)
2,337✔
175
                return chaseat(rfd, e, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
12✔
176

177
        FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
2,891✔
178
                r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
2,658✔
179
                if (r != -ENOENT)
2,658✔
180
                        return r;
2,092✔
181
        }
182

183
        return -ENOENT;
233✔
184
}
185

186
int open_os_release(const char *root, char **ret_path, int *ret_fd) {
33✔
187
        _cleanup_close_ int rfd = XAT_FDROOT, fd = -EBADF;
33✔
188
        _cleanup_free_ char *p = NULL;
33✔
189
        int r;
33✔
190

191
        if (!empty_or_root(root)) {
33✔
192
                rfd = open(root, O_CLOEXEC | O_DIRECTORY | O_PATH);
×
193
                if (rfd < 0)
×
194
                        return -errno;
×
195
        }
196

197
        r = open_os_release_at(rfd, ret_path ? &p : NULL, ret_fd ? &fd : NULL);
66✔
198
        if (r < 0)
33✔
199
                return r;
200

201
        if (ret_path) {
29✔
202
                r = chaseat_prefix_root(p, root, ret_path);
×
203
                if (r < 0)
×
204
                        return r;
205
        }
206

207
        if (ret_fd)
29✔
208
                *ret_fd = TAKE_FD(fd);
29✔
209

210
        return 0;
211
}
212

213
int open_extension_release_at(
2,948✔
214
                int rfd,
215
                ImageClass image_class,
216
                const char *extension,
217
                bool relax_extension_release_check,
218
                char **ret_path,
219
                int *ret_fd) {
220

221
        _cleanup_free_ char *dir_path = NULL, *path_found = NULL;
2,948✔
222
        _cleanup_close_ int fd_found = -EBADF;
2,948✔
223
        _cleanup_closedir_ DIR *dir = NULL;
2,948✔
224
        bool found = false;
2,948✔
225
        const char *p;
2,948✔
226
        int r;
2,948✔
227

228
        assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT));
2,948✔
229
        assert(!extension || (image_class >= 0 && image_class < _IMAGE_CLASS_MAX));
2,948✔
230

231
        if (!extension)
2,948✔
232
                return open_os_release_at(rfd, ret_path, ret_fd);
1,065✔
233

234
        if (!IN_SET(image_class, IMAGE_SYSEXT, IMAGE_CONFEXT))
1,883✔
235
                return -EINVAL;
236

237
        if (!image_name_is_valid(extension))
1,883✔
238
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The extension name %s is invalid.", extension);
×
239

240
        p = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension);
9,415✔
241
        r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
1,883✔
242
        log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p);
1,883✔
243
        if (r != -ENOENT)
1,883✔
244
                return r;
245

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

251
        p = image_class_release_info[image_class].release_file_directory;
915✔
252
        r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir);
915✔
253
        if (r < 0)
915✔
254
                return log_debug_errno(r, "Cannot open %s, ignoring: %m", p);
352✔
255

256
        FOREACH_DIRENT(de, dir, return -errno) {
2,308✔
257
                _cleanup_close_ int fd = -EBADF;
4,693✔
258
                const char *image_name;
619✔
259

260
                if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
619✔
261
                        continue;
×
262

263
                image_name = startswith(de->d_name, "extension-release.");
619✔
264
                if (!image_name)
619✔
265
                        continue;
×
266

267
                if (!image_name_is_valid(image_name)) {
619✔
268
                        log_debug("%s/%s is not a valid release file name, ignoring.", dir_path, de->d_name);
×
269
                        continue;
×
270
                }
271

272
                /* We already chased the directory, and checked that this is a real file, so we shouldn't
273
                 * fail to open it. */
274
                fd = openat(dirfd(dir), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
619✔
275
                if (fd < 0)
619✔
276
                        return log_debug_errno(errno, "Failed to open release file %s/%s: %m", dir_path, de->d_name);
×
277

278
                /* Really ensure it is a regular file after we open it. */
279
                r = fd_verify_regular(fd);
619✔
280
                if (r < 0) {
619✔
281
                        log_debug_errno(r, "%s/%s is not a regular file, ignoring: %m", dir_path, de->d_name);
×
282
                        continue;
×
283
                }
284

285
                if (!relax_extension_release_check) {
619✔
286
                        _cleanup_free_ char *base_extension = NULL;
607✔
287

288
                        r = path_extract_image_name(extension, &base_extension);
607✔
289
                        if (r < 0) {
607✔
290
                                log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", extension);
×
291
                                continue;
×
292
                        }
293

294
                        if (!streq(image_name, base_extension) &&
1,206✔
295
                            extension_release_strict_xattr_value(fd, dir_path, image_name) != 0)
599✔
296
                                continue;
56✔
297
                }
298

299
                /* We already found what we were looking for, but there's another candidate? We treat this as
300
                 * an error, as we want to enforce that there are no ambiguities in case we are in the
301
                 * fallback path. */
302
                if (found)
563✔
303
                        return -ENOTUNIQ;
304

305
                found = true;
563✔
306

307
                if (ret_fd)
563✔
308
                        fd_found = TAKE_FD(fd);
535✔
309

310
                if (ret_path) {
563✔
311
                        path_found = path_join(dir_path, de->d_name);
534✔
312
                        if (!path_found)
534✔
313
                                return -ENOMEM;
314
                }
315
        }
316
        if (!found)
563✔
317
                return -ENOENT;
318

319
        if (ret_fd)
563✔
320
                *ret_fd = TAKE_FD(fd_found);
535✔
321
        if (ret_path)
563✔
322
                *ret_path = TAKE_PTR(path_found);
534✔
323

324
        return 0;
325
}
326

327
int open_extension_release(
1,561✔
328
                const char *root,
329
                ImageClass image_class,
330
                const char *extension,
331
                bool relax_extension_release_check,
332
                char **ret_path,
333
                int *ret_fd) {
334

335
        _cleanup_close_ int rfd = XAT_FDROOT, fd = -EBADF;
1,561✔
336
        _cleanup_free_ char *p = NULL;
1,561✔
337
        int r;
1,561✔
338

339
        if (!empty_or_root(root)) {
1,561✔
340
                rfd = open(root, O_CLOEXEC | O_DIRECTORY | O_PATH);
1,060✔
341
                if (rfd < 0)
1,060✔
342
                        return -errno;
×
343
        }
344

345
        r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check,
2,311✔
346
                                      ret_path ? &p : NULL, ret_fd ? &fd : NULL);
347
        if (r < 0)
1,561✔
348
                return r;
349

350
        if (ret_path) {
1,017✔
351
                r = chaseat_prefix_root(p, root, ret_path);
704✔
352
                if (r < 0)
704✔
353
                        return r;
354
        }
355

356
        if (ret_fd)
1,017✔
357
                *ret_fd = TAKE_FD(fd);
731✔
358

359
        return 0;
360
}
361

362
static int parse_extension_release_atv(
349✔
363
                int rfd,
364
                ImageClass image_class,
365
                const char *extension,
366
                bool relax_extension_release_check,
367
                va_list ap) {
368

369
        _cleanup_close_ int fd = -EBADF;
349✔
370
        _cleanup_free_ char *p = NULL;
349✔
371
        int r;
349✔
372

373
        assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT));
349✔
374

375
        r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check, &p, &fd);
349✔
376
        if (r < 0)
349✔
377
                return r;
378

379
        return parse_env_file_fdv(fd, p, ap);
342✔
380
}
381

382
int parse_extension_release_at_sentinel(
6✔
383
                int rfd,
384
                ImageClass image_class,
385
                bool relax_extension_release_check,
386
                const char *extension,
387
                ...) {
388

389
        va_list ap;
6✔
390
        int r;
6✔
391

392
        assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT));
6✔
393

394
        va_start(ap, extension);
6✔
395
        r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
6✔
396
        va_end(ap);
6✔
397
        return r;
6✔
398
}
399

400
int parse_extension_release_sentinel(
343✔
401
                const char *root,
402
                ImageClass image_class,
403
                bool relax_extension_release_check,
404
                const char *extension,
405
                ...) {
406

407
        _cleanup_close_ int rfd = XAT_FDROOT;
343✔
408
        va_list ap;
343✔
409
        int r;
343✔
410

411
        if (!empty_or_root(root)) {
343✔
412
                rfd = open(root, O_CLOEXEC | O_DIRECTORY | O_PATH);
66✔
413
                if (rfd < 0)
66✔
414
                        return -errno;
×
415
        }
416

417
        va_start(ap, extension);
343✔
418
        r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
343✔
419
        va_end(ap);
343✔
420

421
        return r;
343✔
422
}
423

424
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret) {
250✔
425
        _cleanup_strv_free_ char **os_release_pairs = NULL, **os_release_pairs_prefixed = NULL;
250✔
426
        int r;
250✔
427

428
        r = load_os_release_pairs(root, &os_release_pairs);
250✔
429
        if (r < 0)
250✔
430
                return r;
431

432
        STRV_FOREACH_PAIR(p, q, os_release_pairs) {
3,000✔
433
                char *line;
2,750✔
434

435
                /* We strictly return only the four main ID fields and ignore the rest */
436
                if (!STR_IN_SET(*p, "ID", "VERSION_ID", "BUILD_ID", "VARIANT_ID"))
2,750✔
437
                        continue;
2,250✔
438

439
                ascii_strlower(*p);
500✔
440
                line = strjoin(prefix, *p, "=", *q);
500✔
441
                if (!line)
500✔
442
                        return -ENOMEM;
443
                r = strv_consume(&os_release_pairs_prefixed, line);
500✔
444
                if (r < 0)
500✔
445
                        return r;
446
        }
447

448
        *ret = TAKE_PTR(os_release_pairs_prefixed);
250✔
449

450
        return 0;
250✔
451
}
452

453
int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret) {
1,134✔
454
        _cleanup_close_ int fd = -EBADF;
1,134✔
455
        _cleanup_free_ char *p = NULL;
1,134✔
456
        int r;
1,134✔
457

458
        r = open_extension_release(root, image_class, extension, relax_extension_release_check, &p, &fd);
1,134✔
459
        if (r < 0)
1,134✔
460
                return r;
461

462
        return load_env_file_pairs_fd(fd, p, ret);
704✔
463
}
464

465
int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol) {
62✔
466
        _cleanup_free_ char *_support_end_alloc = NULL;
62✔
467
        int r;
62✔
468

469
        if (!support_end) {
62✔
470
                /* If the caller has the variably handy, they can pass it in. If not, we'll read it
471
                 * ourselves. */
472

473
                r = parse_os_release(NULL,
59✔
474
                                     "SUPPORT_END", &_support_end_alloc);
475
                if (r < 0 && r != -ENOENT)
59✔
476
                        return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, r,
×
477
                                              "Failed to read os-release file, ignoring: %m");
478

479
                support_end = _support_end_alloc;
59✔
480
        }
481

482
        if (isempty(support_end)) { /* An empty string is a explicit way to say "no EOL exists" */
62✔
483
                if (ret_eol)
59✔
484
                        *ret_eol = USEC_INFINITY;
×
485

486
                return false;  /* no end date defined */
59✔
487
        }
488

489
        struct tm tm = {};
3✔
490
        const char *k = strptime(support_end, "%Y-%m-%d", &tm);
3✔
491
        if (!k || *k)
3✔
492
                return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, SYNTHETIC_ERRNO(EINVAL),
×
493
                                      "Failed to parse SUPPORT_END= from os-release file, ignoring: %s", support_end);
494

495
        usec_t eol;
3✔
496
        r = mktime_or_timegm_usec(&tm, /* utc= */ true, &eol);
3✔
497
        if (r < 0)
3✔
498
                return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, r,
1✔
499
                                      "Failed to convert SUPPORT_END= time from os-release file, ignoring: %m");
500

501
        if (ret_eol)
2✔
502
                *ret_eol = eol;
×
503

504
        return now(CLOCK_REALTIME) > eol;
2✔
505
}
506

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

512
        return empty_to_null(pretty_name) ?:
55✔
513
                empty_to_null(name) ?: "Linux";
6✔
514
}
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