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

systemd / systemd / 14554080340

19 Apr 2025 11:46AM UTC coverage: 72.101% (-0.03%) from 72.13%
14554080340

push

github

web-flow
Add two new paragraphs to coding style about header files (#37188)

296880 of 411754 relevant lines covered (72.1%)

687547.52 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 "alloc-util.h"
4
#include "chase.h"
5
#include "dirent-util.h"
6
#include "env-file.h"
7
#include "env-util.h"
8
#include "fd-util.h"
9
#include "fileio.h"
10
#include "fs-util.h"
11
#include "glyph-util.h"
12
#include "log.h"
13
#include "macro.h"
14
#include "os-util.h"
15
#include "parse-util.h"
16
#include "path-util.h"
17
#include "stat-util.h"
18
#include "string-table.h"
19
#include "string-util.h"
20
#include "strv.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,513✔
49
        if (!filename_is_valid(s))
24,513✔
50
                return false;
51

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

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

58
        /* Temporary files for atomically creating new files */
59
        if (startswith(s, ".#"))
19,328✔
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)
1✔
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✔
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", /* 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✔
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✔
135
                log_debug("%s/%s: 'user.extension-release.strict' attribute is true, ignoring file.",
×
136
                          extension_release_dir_path, filename);
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,116✔
148
        const char *e;
2,116✔
149
        int r;
2,116✔
150

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

153
        e = secure_getenv("SYSTEMD_OS_RELEASE");
2,116✔
154
        if (e)
2,116✔
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,592✔
158
                r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
2,392✔
159
                if (r != -ENOENT)
2,392✔
160
                        return r;
1,904✔
161
        }
162

163
        return -ENOENT;
200✔
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);
2,447✔
172
        if (rfd < 0)
1,240✔
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,541✔
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,541✔
200
        _cleanup_close_ int fd_found = -EBADF;
2,541✔
201
        _cleanup_closedir_ DIR *dir = NULL;
2,541✔
202
        bool found = false;
2,541✔
203
        const char *p;
2,541✔
204
        int r;
2,541✔
205

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

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

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

215
        if (!image_name_is_valid(extension))
1,665✔
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,325✔
219
        r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
1,665✔
220
        log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p);
1,665✔
221
        if (r != -ENOENT)
1,665✔
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;
816✔
230
        r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir);
816✔
231
        if (r < 0)
816✔
232
                return log_debug_errno(r, "Cannot open %s, ignoring: %m", p);
283✔
233

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

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

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

245
                if (!image_name_is_valid(image_name)) {
589✔
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✔
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✔
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✔
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,260✔
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,260✔
314
        _cleanup_free_ char *p = NULL;
2,260✔
315
        int r;
2,260✔
316

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

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

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

332
        if (ret_fd)
1,787✔
333
                *ret_fd = TAKE_FD(fd);
1,580✔
334

335
        return 0;
336
}
337

338
static int parse_extension_release_atv(
281✔
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;
281✔
346
        _cleanup_free_ char *p = NULL;
281✔
347
        int r;
281✔
348

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

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

355
        return parse_env_file_fdv(fd, p, ap);
274✔
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(
275✔
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;
275✔
384
        va_list ap;
275✔
385
        int r;
275✔
386

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

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

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

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

405
        STRV_FOREACH_PAIR(p, q, os_release_pairs) {
2,640✔
406
                char *line;
2,420✔
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,420✔
410
                        continue;
1,980✔
411

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

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

423
        return 0;
220✔
424
}
425

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

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

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

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

442
        if (!support_end) {
52✔
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,
49✔
447
                                     "SUPPORT_END", &_support_end_alloc);
448
                if (r < 0 && r != -ENOENT)
49✔
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;
49✔
453
        }
454

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

459
                return false;  /* no end date defined */
49✔
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✔
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✔
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