• 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

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 "errno-util.h"
9
#include "fd-util.h"
10
#include "fileio.h"
11
#include "fs-util.h"
12
#include "glyph-util.h"
13
#include "log.h"
14
#include "macro.h"
15
#include "os-util.h"
16
#include "parse-util.h"
17
#include "path-util.h"
18
#include "stat-util.h"
19
#include "string-table.h"
20
#include "string-util.h"
21
#include "strv.h"
22
#include "utf8.h"
23
#include "xattr-util.h"
24

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

32
DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
212✔
33

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

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

53
        if (string_has_cc(s, NULL))
19,354✔
54
                return false;
55

56
        if (!utf8_is_valid(s))
19,354✔
57
                return false;
58

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

63
        return true;
64
}
65

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

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

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

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

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

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

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

97
        assert(path);
255✔
98

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

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

115
        return 1;
116
}
117

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

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

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

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

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

145
        return false;
146
}
147

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

152
        assert(rfd >= 0 || rfd == AT_FDCWD);
2,124✔
153

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

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

164
        return -ENOENT;
201✔
165
}
166

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

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

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

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

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

189
        return 0;
190
}
191

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

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

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

210
        if (!extension)
2,564✔
211
                return open_os_release_at(rfd, ret_path, ret_fd);
884✔
212

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

284
                found = true;
533✔
285

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

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

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

303
        return 0;
304
}
305

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

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

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

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

327
        if (ret_path) {
1,792✔
328
                r = chaseat_prefix_root(p, root, ret_path);
1,568✔
329
                if (r < 0)
1,568✔
330
                        return r;
331
        }
332

333
        if (ret_fd)
1,792✔
334
                *ret_fd = TAKE_FD(fd);
1,586✔
335

336
        return 0;
337
}
338

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

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

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

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

356
        return parse_env_file_fdv(fd, p, ap);
274✔
357
}
358

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

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

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

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

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

384
        _cleanup_close_ int rfd = -EBADF;
275✔
385
        va_list ap;
275✔
386
        int r;
275✔
387

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

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

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

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

406
        STRV_FOREACH_PAIR(p, q, os_release_pairs) {
2,640✔
407
                char *line;
2,420✔
408

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

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

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

424
        return 0;
220✔
425
}
426

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

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

436
        return load_env_file_pairs_fd(fd, p, ret);
592✔
437
}
438

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

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

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

453
                support_end = _support_end_alloc;
49✔
454
        }
455

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

460
                return false;  /* no end date defined */
49✔
461
        }
462

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

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

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

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

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

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