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

systemd / systemd / 20401947236

20 Dec 2025 09:56PM UTC coverage: 72.701% (+0.1%) from 72.578%
20401947236

push

github

DaanDeMeyer
core/socket: modernize listen/accept_in_cgroup

4 of 9 new or added lines in 1 file covered. (44.44%)

7723 existing lines in 114 files now uncovered.

309972 of 426363 relevant lines covered (72.7%)

1133403.64 hits per line

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

68.85
/src/sysupdate/sysupdate-resource.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <fcntl.h>
4
#include <linux/magic.h>
5
#include <sys/stat.h>
6
#include <unistd.h>
7

8
#include "alloc-util.h"
9
#include "blockdev-util.h"
10
#include "build-path.h"
11
#include "chase.h"
12
#include "device-util.h"
13
#include "devnum-util.h"
14
#include "dirent-util.h"
15
#include "errno-util.h"
16
#include "fd-util.h"
17
#include "fdisk-util.h"
18
#include "fileio.h"
19
#include "find-esp.h"
20
#include "glyph-util.h"
21
#include "gpt.h"
22
#include "hexdecoct.h"
23
#include "import-util.h"
24
#include "pidref.h"
25
#include "process-util.h"
26
#include "sort-util.h"
27
#include "stat-util.h"
28
#include "string-table.h"
29
#include "strv.h"
30
#include "sysupdate-cache.h"
31
#include "sysupdate-instance.h"
32
#include "sysupdate-pattern.h"
33
#include "sysupdate-resource.h"
34
#include "time-util.h"
35
#include "utf8.h"
36

37
void resource_destroy(Resource *rr) {
1,112✔
38
        assert(rr);
1,112✔
39

40
        free(rr->path);
1,112✔
41
        strv_free(rr->patterns);
1,112✔
42

43
        for (size_t i = 0; i < rr->n_instances; i++)
4,124✔
44
                instance_free(rr->instances[i]);
3,012✔
45
        free(rr->instances);
1,112✔
46
}
1,112✔
47

48
static int resource_add_instance(
3,062✔
49
                Resource *rr,
50
                const char *path,
51
                const InstanceMetadata *f,
52
                Instance **ret) {
53

54
        Instance *i;
3,062✔
55
        int r;
3,062✔
56

57
        assert(rr);
3,062✔
58
        assert(path);
3,062✔
59
        assert(f);
3,062✔
60
        assert(f->version);
3,062✔
61

62
        if (!GREEDY_REALLOC(rr->instances, rr->n_instances + 1))
3,062✔
UNCOV
63
                return log_oom();
×
64

65
        r = instance_new(rr, path, f, &i);
3,062✔
66
        if (r < 0)
3,062✔
67
                return r;
68

69
        rr->instances[rr->n_instances++] = i;
3,062✔
70

71
        if (ret)
3,062✔
72
                *ret = i;
3,062✔
73

74
        return 0;
75
}
76

77
static int resource_load_from_directory_recursive(
1,050✔
78
                Resource *rr,
79
                DIR* d,
80
                const char* relpath,
81
                mode_t m) {
82
        int r;
16,356✔
83

84
        for (;;) {
16,356✔
UNCOV
85
                _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
×
86
                _cleanup_free_ char *joined = NULL, *rel_joined = NULL;
16,356✔
87
                Instance *instance;
16,356✔
88
                struct dirent *de;
16,356✔
89
                struct stat st;
16,356✔
90

91
                errno = 0;
16,356✔
92
                de = readdir_no_dot(d);
16,356✔
93
                if (!de) {
16,356✔
94
                        if (errno != 0)
1,050✔
UNCOV
95
                                return log_error_errno(errno, "Failed to read directory '%s': %m", rr->path);
×
96
                        break;
1,050✔
97
                }
98

99
                switch (de->d_type) {
15,306✔
100

101
                case DT_UNKNOWN:
102
                        break;
103

104
                case DT_DIR:
2,430✔
105
                        if (!IN_SET(m, S_IFDIR, S_IFREG))
2,430✔
UNCOV
106
                                continue;
×
107

108
                        break;
109

110
                case DT_REG:
12,788✔
111
                        if (m != S_IFREG)
12,788✔
112
                                continue;
1,950✔
113
                        break;
114

115
                default:
88✔
116
                        continue;
88✔
117
                }
118

119
                if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT) < 0) {
13,268✔
120
                        if (errno == ENOENT) /* Gone by now? */
×
UNCOV
121
                                continue;
×
122

UNCOV
123
                        return log_error_errno(errno, "Failed to stat %s/%s: %m", rr->path, de->d_name);
×
124
                }
125

126
                if (!(S_ISDIR(st.st_mode) && S_ISREG(m)) && ((st.st_mode & S_IFMT) != m))
13,268✔
UNCOV
127
                        continue;
×
128

129
                rel_joined = path_join(relpath, de->d_name);
13,268✔
130
                if (!rel_joined)
13,268✔
UNCOV
131
                        return log_oom();
×
132

133
                r = pattern_match_many(rr->patterns, rel_joined, &extracted_fields);
13,268✔
134
                if (r == PATTERN_MATCH_RETRY) {
13,268✔
135
                        _cleanup_closedir_ DIR *subdir = NULL;
12,772✔
136

137
                        subdir = xopendirat(dirfd(d), rel_joined, 0);
328✔
138
                        if (!subdir)
328✔
UNCOV
139
                                continue;
×
140

141
                        r = resource_load_from_directory_recursive(rr, subdir, rel_joined, m);
328✔
142
                        if (r < 0)
328✔
UNCOV
143
                                return r;
×
144
                        if (r == 0)
328✔
145
                                continue;
328✔
146
                }
147
                else if (r < 0)
12,940✔
UNCOV
148
                        return log_error_errno(r, "Failed to match pattern: %m");
×
149
                else if (r == PATTERN_MATCH_NO)
12,940✔
150
                        continue;
10,242✔
151

152
                if (de->d_type == DT_DIR && m != S_IFDIR)
2,698✔
153
                        continue;
164✔
154

155
                joined = path_join(rr->path, rel_joined);
2,534✔
156
                if (!joined)
2,534✔
UNCOV
157
                        return log_oom();
×
158

159
                r = resource_add_instance(rr, joined, &extracted_fields, &instance);
2,534✔
160
                if (r < 0)
2,534✔
161
                        return r;
162

163
                /* Inherit these from the source, if not explicitly overwritten */
164
                if (instance->metadata.mtime == USEC_INFINITY)
2,534✔
165
                        instance->metadata.mtime = timespec_load(&st.st_mtim) ?: USEC_INFINITY;
2,534✔
166

167
                if (instance->metadata.mode == MODE_INVALID)
2,534✔
168
                        instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */
2,534✔
169
        }
170

171
        return 0;
1,050✔
172
}
173

174
static int resource_load_from_directory(
722✔
175
                Resource *rr,
176
                mode_t m) {
177
        _cleanup_closedir_ DIR *d = NULL;
722✔
178

179
        assert(rr);
722✔
180
        assert(IN_SET(rr->type, RESOURCE_TAR, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
722✔
181
        assert(IN_SET(m, S_IFREG, S_IFDIR));
722✔
182

183
        d = opendir(rr->path);
722✔
184
        if (!d) {
722✔
185
                if (errno == ENOENT) {
×
186
                        log_debug_errno(errno, "Directory %s does not exist, not loading any resources: %m", rr->path);
×
UNCOV
187
                        return 0;
×
188
                }
189

UNCOV
190
                return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path);
×
191
        }
192

193
        return resource_load_from_directory_recursive(rr, d, NULL, m);
722✔
194
}
195

196
static int resource_load_from_blockdev(Resource *rr) {
188✔
197
        _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
188✔
198
        _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
188✔
199
        size_t n_partitions;
188✔
200
        int r;
188✔
201

202
        assert(rr);
188✔
203

204
        r = fdisk_new_context_at(AT_FDCWD, rr->path, /* read_only= */ true, /* sector_size= */ UINT32_MAX, &c);
188✔
205
        if (r < 0)
188✔
UNCOV
206
                return log_error_errno(r, "Failed to create fdisk context from '%s': %m", rr->path);
×
207

208
        if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
188✔
UNCOV
209
                return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", rr->path);
×
210

211
        r = fdisk_get_partitions(c, &t);
188✔
212
        if (r < 0)
188✔
UNCOV
213
                return log_error_errno(r, "Failed to acquire partition table: %m");
×
214

215
        n_partitions = fdisk_table_get_nents(t);
188✔
216
        for (size_t i = 0; i < n_partitions; i++)  {
940✔
217
                _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
752✔
218
                _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
752✔
219
                Instance *instance;
752✔
220

221
                r = read_partition_info(c, t, i, &pinfo);
752✔
222
                if (r < 0)
752✔
223
                        return r;
224
                if (r == 0) /* not assigned */
752✔
UNCOV
225
                        continue;
×
226

227
                /* Check if partition type matches */
228
                if (rr->partition_type_set && !sd_id128_equal(pinfo.type, rr->partition_type.uuid))
1,128✔
229
                        continue;
376✔
230

231
                /* A label of "_empty" means "not used so far" for us */
232
                if (streq_ptr(pinfo.label, "_empty")) {
376✔
233
                        rr->n_empty++;
28✔
234
                        continue;
28✔
235
                }
236

237
                r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields);
348✔
238
                if (r < 0)
348✔
UNCOV
239
                        return log_error_errno(r, "Failed to match pattern: %m");
×
240
                if (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY))
348✔
UNCOV
241
                        continue;
×
242

243
                r = resource_add_instance(rr, pinfo.device, &extracted_fields, &instance);
348✔
244
                if (r < 0)
348✔
245
                        return r;
246

247
                instance->partition_info = pinfo;
348✔
248
                pinfo = (PartitionInfo) PARTITION_INFO_NULL;
348✔
249

250
                /* Inherit data from source if not configured explicitly */
251
                if (!instance->metadata.partition_uuid_set) {
348✔
252
                        instance->metadata.partition_uuid = instance->partition_info.uuid;
348✔
253
                        instance->metadata.partition_uuid_set = true;
348✔
254
                }
255

256
                if (!instance->metadata.partition_flags_set) {
348✔
257
                        instance->metadata.partition_flags = instance->partition_info.flags;
348✔
258
                        instance->metadata.partition_flags_set = true;
348✔
259
                }
260

261
                if (instance->metadata.read_only < 0)
348✔
262
                        instance->metadata.read_only = instance->partition_info.read_only;
348✔
263
        }
264

265
        return 0;
266
}
267

268
static int download_manifest(
12✔
269
                const char *url,
270
                bool verify_signature,
271
                char **ret_buffer,
272
                size_t *ret_size) {
273

274
        _cleanup_free_ char *buffer = NULL, *suffixed_url = NULL;
12✔
275
        _cleanup_close_pair_ int pfd[2] = EBADF_PAIR;
12✔
276
        _cleanup_fclose_ FILE *manifest = NULL;
12✔
277
        size_t size = 0;
12✔
278
        int r;
12✔
279

280
        assert(url);
12✔
281
        assert(ret_buffer);
12✔
282
        assert(ret_size);
12✔
283

284
        /* Download a SHA256SUMS file as manifest */
285

286
        r = import_url_append_component(url, "SHA256SUMS", &suffixed_url);
12✔
287
        if (r < 0)
12✔
288
                return log_error_errno(r, "Failed to append SHA256SUMS to URL: %m");
×
289

290
        if (pipe2(pfd, O_CLOEXEC) < 0)
12✔
291
                return log_error_errno(errno, "Failed to allocate pipe: %m");
×
292

293
        log_info("%s Acquiring manifest file %s%s", glyph(GLYPH_DOWNLOAD),
24✔
294
                 suffixed_url, glyph(GLYPH_ELLIPSIS));
295

296
        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
12✔
297
        r = pidref_safe_fork_full(
36✔
298
                        "(sd-pull)",
299
                        (int[]) { -EBADF, pfd[1], STDERR_FILENO },
12✔
300
                        NULL, 0,
301
                        FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG,
302
                        &pidref);
303
        if (r < 0)
24✔
304
                return r;
305
        if (r == 0) {
24✔
306
                /* Child */
307

308
                const char *cmdline[] = {
24✔
309
                        SYSTEMD_PULL_PATH,
310
                        "raw",
311
                        "--direct",                        /* just download the specified URL, don't download anything else */
312
                        "--verify", verify_signature ? "signature" : "no", /* verify the manifest file */
12✔
313
                        suffixed_url,
314
                        "-",                               /* write to stdout */
315
                        NULL
316
                };
317

318
                r = invoke_callout_binary(SYSTEMD_PULL_PATH, (char *const*) cmdline);
12✔
UNCOV
319
                log_error_errno(r, "Failed to execute %s tool: %m", SYSTEMD_PULL_PATH);
×
UNCOV
320
                _exit(EXIT_FAILURE);
×
321
        };
12✔
322

323
        pfd[1] = safe_close(pfd[1]);
12✔
324

325
        /* We'll first load the entire manifest into memory before parsing it. That's because the
326
         * systemd-pull tool can validate the download only after its completion, but still pass the data to
327
         * us as it runs. We thus need to check the return value of the process *before* parsing, to be
328
         * reasonably safe. */
329

330
        manifest = fdopen(pfd[0], "r");
12✔
331
        if (!manifest)
12✔
UNCOV
332
                return log_error_errno(errno, "Failed to allocate FILE object for manifest file: %m");
×
333

334
        TAKE_FD(pfd[0]);
12✔
335

336
        r = read_full_stream(manifest, &buffer, &size);
12✔
337
        if (r < 0)
12✔
UNCOV
338
                return log_error_errno(r, "Failed to read manifest file from child: %m");
×
339

340
        manifest = safe_fclose(manifest);
12✔
341

342
        r = pidref_wait_for_terminate_and_check("(sd-pull)", &pidref, WAIT_LOG);
12✔
343
        if (r < 0)
12✔
344
                return r;
345
        if (r != 0)
12✔
346
                return -EPROTO;
347

348
        *ret_buffer = TAKE_PTR(buffer);
12✔
349
        *ret_size = size;
12✔
350

351
        return 0;
12✔
352
}
353

354
static int resource_load_from_web(
24✔
355
                Resource *rr,
356
                bool verify,
357
                Hashmap **web_cache) {
358

359
        size_t manifest_size = 0, left = 0;
24✔
360
        _cleanup_free_ char *buf = NULL;
24✔
361
        const char *manifest, *p;
24✔
362
        size_t line_nr = 1;
24✔
363
        WebCacheItem *ci;
24✔
364
        int r;
24✔
365

366
        assert(rr);
24✔
367

368
        ci = web_cache ? web_cache_get_item(*web_cache, rr->path, verify) : NULL;
24✔
369
        if (ci) {
24✔
370
                log_debug("Manifest web cache hit for %s.", rr->path);
12✔
371

372
                manifest = (char*) ci->data;
12✔
373
                manifest_size = ci->size;
12✔
374
        } else {
375
                log_debug("Manifest web cache miss for %s.", rr->path);
12✔
376

377
                r = download_manifest(rr->path, verify, &buf, &manifest_size);
12✔
378
                if (r < 0)
12✔
379
                        return r;
380

381
                manifest = buf;
12✔
382
        }
383

384
        if (memchr(manifest, 0, manifest_size))
24✔
385
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file has embedded NUL byte, refusing.");
×
386
        if (!utf8_is_valid_n(manifest, manifest_size))
24✔
UNCOV
387
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file is not valid UTF-8, refusing.");
×
388

389
        p = manifest;
390
        left = manifest_size;
391

392
        while (left > 0) {
1,080✔
UNCOV
393
                _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
×
UNCOV
394
                _cleanup_free_ char *fn = NULL;
×
395
                _cleanup_free_ void *h = NULL;
1,056✔
396
                Instance *instance;
1,056✔
397
                const char *e;
1,056✔
398
                size_t hlen;
1,056✔
399

400
                /* 64 character hash + separator + filename + newline */
401
                if (left < 67)
1,056✔
UNCOV
402
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Corrupt manifest at line %zu, refusing.", line_nr);
×
403

404
                if (p[0] == '\\')
1,056✔
UNCOV
405
                        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "File names with escapes not supported in manifest at line %zu, refusing.", line_nr);
×
406

407
                r = unhexmem_full(p, 64, /* secure= */ false, &h, &hlen);
1,056✔
408
                if (r < 0)
1,056✔
UNCOV
409
                        return log_error_errno(r, "Failed to parse digest at manifest line %zu, refusing.", line_nr);
×
410

411
                p += 64, left -= 64;
1,056✔
412

413
                if (*p != ' ')
1,056✔
UNCOV
414
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing space separator at manifest line %zu, refusing.", line_nr);
×
415
                p++, left--;
1,056✔
416

417
                if (!IN_SET(*p, '*', ' '))
1,056✔
UNCOV
418
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing binary/text input marker at manifest line %zu, refusing.", line_nr);
×
419
                p++, left--;
1,056✔
420

421
                e = memchr(p, '\n', left);
1,056✔
422
                if (!e)
1,056✔
423
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Truncated manifest file at line %zu, refusing.", line_nr);
×
424
                if (e == p)
1,056✔
UNCOV
425
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty filename specified at manifest line %zu, refusing.", line_nr);
×
426

427
                fn = strndup(p, e - p);
1,056✔
428
                if (!fn)
1,056✔
UNCOV
429
                        return log_oom();
×
430

431
                if (!filename_is_valid(fn))
1,056✔
432
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid filename specified at manifest line %zu, refusing.", line_nr);
×
433
                if (string_has_cc(fn, NULL))
1,056✔
UNCOV
434
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Filename contains control characters at manifest line %zu, refusing.", line_nr);
×
435

436
                r = pattern_match_many(rr->patterns, fn, &extracted_fields);
1,056✔
437
                if (r < 0)
1,056✔
UNCOV
438
                        return log_error_errno(r, "Failed to match pattern: %m");
×
439
                if (r == PATTERN_MATCH_YES) {
1,056✔
440
                        _cleanup_free_ char *path = NULL;
180✔
441

442
                        r = import_url_append_component(rr->path, fn, &path);
180✔
443
                        if (r < 0)
180✔
UNCOV
444
                                return log_error_errno(r, "Failed to build instance URL: %m");
×
445

446
                        r = resource_add_instance(rr, path, &extracted_fields, &instance);
180✔
447
                        if (r < 0)
180✔
448
                                return r;
449

450
                        assert(hlen == sizeof(instance->metadata.sha256sum));
180✔
451

452
                        if (instance->metadata.sha256sum_set) {
180✔
UNCOV
453
                                if (memcmp(instance->metadata.sha256sum, h, hlen) != 0)
×
UNCOV
454
                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SHA256 sum parsed from filename and manifest don't match at line %zu, refusing.", line_nr);
×
455
                        } else {
456
                                memcpy(instance->metadata.sha256sum, h, hlen);
180✔
457
                                instance->metadata.sha256sum_set = true;
180✔
458
                        }
459
                }
460

461
                left -= (e - p) + 1;
1,056✔
462
                p = e + 1;
1,056✔
463

464
                line_nr++;
1,056✔
465
        }
466

467
        if (!ci && web_cache) {
24✔
468
                r = web_cache_add_item(web_cache, rr->path, verify, manifest, manifest_size);
12✔
469
                if (r < 0)
12✔
470
                        log_debug_errno(r, "Failed to add manifest '%s' to cache, ignoring: %m", rr->path);
24✔
471
                else
472
                        log_debug("Added manifest '%s' to cache.", rr->path);
12✔
473
        }
474

475
        return 0;
476
}
477

478
static int instance_cmp(Instance *const*a, Instance *const*b) {
4,086✔
479
        int r;
4,086✔
480

481
        assert(a);
4,086✔
482
        assert(b);
4,086✔
483
        assert(*a);
4,086✔
484
        assert(*b);
4,086✔
485
        assert((*a)->metadata.version);
4,086✔
486
        assert((*b)->metadata.version);
4,086✔
487

488
        /* Newest version at the beginning */
489
        r = strverscmp_improved((*a)->metadata.version, (*b)->metadata.version);
4,086✔
490
        if (r != 0)
4,086✔
491
                return -r;
4,086✔
492

493
        /* Instances don't have to be uniquely named (uniqueness on partition tables is not enforced at all,
494
         * and since we allow multiple matching patterns not even in directories they are unique). Hence
495
         * let's order by path as secondary ordering key. */
UNCOV
496
        return path_compare((*a)->path, (*b)->path);
×
497
}
498

499
int resource_load_instances(Resource *rr, bool verify, Hashmap **web_cache) {
934✔
500
        int r;
934✔
501

502
        assert(rr);
934✔
503

504
        switch (rr->type) {
934✔
505

506
        case RESOURCE_TAR:
568✔
507
        case RESOURCE_REGULAR_FILE:
508
                r = resource_load_from_directory(rr, S_IFREG);
568✔
509
                break;
568✔
510

511
        case RESOURCE_DIRECTORY:
154✔
512
        case RESOURCE_SUBVOLUME:
513
                r = resource_load_from_directory(rr, S_IFDIR);
154✔
514
                break;
154✔
515

516
        case RESOURCE_PARTITION:
188✔
517
                r = resource_load_from_blockdev(rr);
188✔
518
                break;
188✔
519

520
        case RESOURCE_URL_FILE:
24✔
521
        case RESOURCE_URL_TAR:
522
                r = resource_load_from_web(rr, verify, web_cache);
24✔
523
                break;
24✔
524

UNCOV
525
        default:
×
UNCOV
526
                assert_not_reached();
×
527
        }
528
        if (r < 0)
934✔
529
                return r;
530

531
        typesafe_qsort(rr->instances, rr->n_instances, instance_cmp);
934✔
532
        return 0;
934✔
533
}
534

535
static int instance_version_match(Instance *const*a, Instance *const*b) {
6,202✔
536
        assert(a);
6,202✔
537
        assert(b);
6,202✔
538
        assert(*a);
6,202✔
539
        assert(*b);
6,202✔
540
        assert((*a)->metadata.version);
6,202✔
541
        assert((*b)->metadata.version);
6,202✔
542

543
        /* List is sorted newest-to-oldest */
544
        return -strverscmp_improved((*a)->metadata.version, (*b)->metadata.version);
6,202✔
545
}
546

547
Instance* resource_find_instance(Resource *rr, const char *version) {
3,210✔
548
        Instance key = {
3,210✔
549
                .metadata.version = (char*) version,
550
        }, *k = &key;
3,210✔
551

552
        Instance **found;
3,210✔
553
        found = typesafe_bsearch(&k, rr->instances, rr->n_instances, instance_version_match);
3,210✔
554
        if (!found)
3,210✔
555
                return NULL;
3,210✔
556

557
        return *found;
2,694✔
558
}
559

UNCOV
560
static int get_sysext_overlay_block(const char *p, dev_t *ret) {
×
561
        int r;
×
562

UNCOV
563
        assert(p);
×
UNCOV
564
        assert(ret);
×
565

566
        /* Tries to read the backing device information systemd-sysext puts in the virtual file
567
         * /usr/.systemd-sysext/backing */
568

569
        _cleanup_free_ char *j = path_join(p, ".systemd-sysext");
×
UNCOV
570
        if (!j)
×
571
                return log_oom_debug();
×
572

573
        _cleanup_close_ int fd = open(j, O_RDONLY|O_DIRECTORY);
×
UNCOV
574
        if (fd < 0)
×
575
                return log_debug_errno(errno, "Failed to open '%s': %m", j);
×
576

577
        r = fd_is_fs_type(fd, OVERLAYFS_SUPER_MAGIC);
×
578
        if (r < 0)
×
579
                return log_debug_errno(r, "Failed to determine backing file system of '%s': %m", j);
×
UNCOV
580
        if (r == 0)
×
581
                return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Backing file system of '%s' is not an overlayfs.", j);
×
582

583
        _cleanup_free_ char *buf = NULL;
×
584
        r = read_one_line_file_at(fd, "backing", &buf);
×
UNCOV
585
        if (r < 0)
×
586
                return log_debug_errno(r, "Failed to read contents of '%s/backing': %m", j);
×
587

588
        r = parse_devnum(buf, ret);
×
UNCOV
589
        if (r < 0)
×
590
                return log_debug_errno(r, "Failed to parse contents of '%s/backing': %m", j);
×
591

592
        if (major(*ret) == 0) { /* not a block device? */
×
UNCOV
593
                *ret = 0;
×
UNCOV
594
                return 0;
×
595
        }
596

UNCOV
597
        (void) block_get_originating(*ret, ret);
×
598
        return 1;
599
}
600

601
int resource_resolve_path(
1,112✔
602
                Resource *rr,
603
                const char *root,
604
                const char *relative_to_directory,
605
                const char *node) {
606

607
        _cleanup_free_ char *p = NULL;
1,112✔
608
        dev_t d;
1,112✔
609
        int r;
1,112✔
610

611
        assert(rr);
1,112✔
612

613
        if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_ESP, PATH_RELATIVE_TO_XBOOTLDR, PATH_RELATIVE_TO_BOOT) &&
1,112✔
614
            !IN_SET(rr->type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY))
276✔
UNCOV
615
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
616
                                       "Paths relative to %s are only allowed for regular-file or directory resources.",
617
                                       path_relative_to_to_string(rr->path_relative_to));
618

619
        if (rr->path_auto) {
1,112✔
620
                /* NB: If the root mount has been replaced by some form of volatile file system (overlayfs),
621
                 * the original root block device node is symlinked in /run/systemd/volatile-root. Let's
622
                 * follow that link here. If that doesn't exist, we check the backing device of "/usr". We
623
                 * don't actually check the backing device of the root fs "/", in order to support
624
                 * environments where the root fs is a tmpfs, and the OS itself placed exclusively in
625
                 * /usr/. */
626

UNCOV
627
                if (rr->type != RESOURCE_PARTITION)
×
UNCOV
628
                        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
×
629
                                               "Automatic root path discovery only supported for partition resources.");
630

631
                if (node) { /* If --image= is specified, directly use the loopback device */
×
UNCOV
632
                        r = free_and_strdup_warn(&rr->path, node);
×
UNCOV
633
                        if (r < 0)
×
634
                                return r;
635

UNCOV
636
                        return 0;
×
637
                }
638

UNCOV
639
                if (root)
×
UNCOV
640
                        return log_error_errno(SYNTHETIC_ERRNO(EPERM),
×
641
                                               "Block device is not allowed when using --root= mode.");
642

643
                struct stat orig_root_stats;
×
644
                r = RET_NERRNO(stat("/run/systemd/volatile-root", &orig_root_stats));
×
645
                if (r < 0) {
×
UNCOV
646
                        if (r != -ENOENT)
×
UNCOV
647
                                return log_error_errno(r, "Failed to stat /run/systemd/volatile-root: %m");
×
648

649
                        /* volatile-root not found */
650
                        r = get_block_device_harder("/usr/", &d);
×
651
                        if (r == 0) /* Not backed by a block device? Let's see if this is a sysext overlayfs instance */
×
652
                                r = get_sysext_overlay_block("/usr/", &d);
×
653
                        if (r < 0)
×
654
                                return log_error_errno(r, "Failed to determine block device of file system: %m");
×
UNCOV
655
                } else if (!S_ISBLK(orig_root_stats.st_mode)) /* symlink was present but not block device */
×
656
                        return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "/run/systemd/volatile-root is not linked to a block device.");
×
657
                else /* symlink was present and a block device */
UNCOV
658
                        d = orig_root_stats.st_rdev;
×
659

660
        } else if (rr->type == RESOURCE_PARTITION) {
1,112✔
661
                _cleanup_close_ int fd = -EBADF, real_fd = -EBADF;
376✔
662
                _cleanup_free_ char *resolved = NULL;
188✔
663
                struct stat st;
188✔
664

665
                r = chase(rr->path, root, CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS, &resolved, &fd);
188✔
666
                if (r < 0)
188✔
UNCOV
667
                        return log_error_errno(r, "Failed to resolve '%s': %m", rr->path);
×
668

669
                if (fstat(fd, &st) < 0)
188✔
UNCOV
670
                        return log_error_errno(errno, "Failed to stat '%s': %m", resolved);
×
671

672
                if (S_ISBLK(st.st_mode) && root)
188✔
UNCOV
673
                        return log_error_errno(SYNTHETIC_ERRNO(EPERM), "When using --root= or --image= access to device nodes is prohibited.");
×
674

675
                if (S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) {
188✔
676
                        /* Not a directory, hence no need to find backing block device for the path */
677
                        free_and_replace(rr->path, resolved);
188✔
678
                        return 0;
188✔
679
                }
680

UNCOV
681
                if (!S_ISDIR(st.st_mode))
×
682
                        return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Target path '%s' does not refer to regular file, directory or block device, refusing.",  rr->path);
×
683

684
                if (node) { /* If --image= is specified all file systems are backed by the same loopback device, hence shortcut things. */
×
UNCOV
685
                        r = free_and_strdup_warn(&rr->path, node);
×
UNCOV
686
                        if (r < 0)
×
687
                                return r;
688

UNCOV
689
                        return 0;
×
690
                }
691

692
                real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
×
UNCOV
693
                if (real_fd < 0)
×
694
                        return log_error_errno(real_fd, "Failed to convert O_PATH file descriptor for %s to regular file descriptor: %m", rr->path);
×
695

696
                r = get_block_device_harder_fd(real_fd, &d);
×
UNCOV
697
                if (r < 0)
×
UNCOV
698
                        return log_error_errno(r, "Failed to determine block device of file system: %m");
×
699

700
        } else if (RESOURCE_IS_FILESYSTEM(rr->type)) {
2,012✔
701
                _cleanup_free_ char *resolved = NULL, *relative_to = NULL;
900✔
702
                ChaseFlags chase_flags = CHASE_NONEXISTENT | CHASE_PREFIX_ROOT | CHASE_TRIGGER_AUTOFS;
900✔
703

704
                if (rr->path_relative_to == PATH_RELATIVE_TO_EXPLICIT) {
900✔
705
                        assert(relative_to_directory);
×
706

707
                        relative_to = strdup(relative_to_directory);
×
UNCOV
708
                        if (!relative_to)
×
UNCOV
709
                                return log_oom();
×
710
                } else if (rr->path_relative_to == PATH_RELATIVE_TO_ROOT) {
900✔
711
                        relative_to = strdup(empty_to_root(root));
624✔
712
                        if (!relative_to)
624✔
UNCOV
713
                                return log_oom();
×
714
                } else { /* boot, esp, or xbootldr */
715
                        r = 0;
276✔
716
                        if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_BOOT, PATH_RELATIVE_TO_XBOOTLDR))
276✔
717
                                r = find_xbootldr_and_warn(root, NULL, /* unprivileged_mode= */ -1, &relative_to, NULL, NULL);
276✔
718
                        if (r == -ENOKEY || rr->path_relative_to == PATH_RELATIVE_TO_ESP)
276✔
719
                                r = find_esp_and_warn(root, NULL, -1, &relative_to, NULL, NULL, NULL, NULL, NULL);
×
720
                        if (r < 0)
276✔
UNCOV
721
                                return log_error_errno(r, "Failed to resolve $BOOT: %m");
×
722
                        log_debug("Resolved $BOOT to '%s'", relative_to);
276✔
723

724
                        /* Since this partition is read from EFI, there should be no symlinks */
725
                        chase_flags |= CHASE_PROHIBIT_SYMLINKS;
726
                }
727

728
                r = chase(rr->path, relative_to, chase_flags, &resolved, NULL);
900✔
729
                if (r < 0)
900✔
UNCOV
730
                        return log_error_errno(r, "Failed to resolve '%s' (relative to '%s'): %m", rr->path, relative_to);
×
731

732
                free_and_replace(rr->path, resolved);
900✔
733
                return 0;
900✔
734
        } else
735
                return 0; /* Otherwise assume there's nothing to resolve */
736

737
        r = block_get_whole_disk(d, &d);
×
738
        if (r < 0)
×
739
                return log_error_errno(r, "Failed to find whole disk device for partition backing file system: %m");
×
UNCOV
740
        if (r == 0)
×
UNCOV
741
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
742
                                       "File system is not placed on a partition block device, cannot determine whole block device backing root file system.");
743

UNCOV
744
        r = devname_from_devnum(S_IFBLK, d, &p);
×
UNCOV
745
        if (r < 0)
×
746
                return r;
747

UNCOV
748
        if (rr->path)
×
749
                log_info("Automatically discovered block device '%s' from '%s'.", p, rr->path);
×
750
        else
751
                log_info("Automatically discovered root block device '%s'.", p);
×
752

UNCOV
753
        free_and_replace(rr->path, p);
×
UNCOV
754
        return 1;
×
755
}
756

757
static const char *resource_type_table[_RESOURCE_TYPE_MAX] = {
758
        [RESOURCE_URL_FILE]     = "url-file",
759
        [RESOURCE_URL_TAR]      = "url-tar",
760
        [RESOURCE_TAR]          = "tar",
761
        [RESOURCE_PARTITION]    = "partition",
762
        [RESOURCE_REGULAR_FILE] = "regular-file",
763
        [RESOURCE_DIRECTORY]    = "directory",
764
        [RESOURCE_SUBVOLUME]    = "subvolume",
765
};
766

767
DEFINE_STRING_TABLE_LOOKUP(resource_type, ResourceType);
1,420✔
768

769
static const char *path_relative_to_table[_PATH_RELATIVE_TO_MAX] = {
770
        [PATH_RELATIVE_TO_ROOT]     = "root",
771
        [PATH_RELATIVE_TO_ESP]      = "esp",
772
        [PATH_RELATIVE_TO_XBOOTLDR] = "xbootldr",
773
        [PATH_RELATIVE_TO_BOOT]     = "boot",
774
        [PATH_RELATIVE_TO_EXPLICIT] = "explicit",
775
};
776

777
DEFINE_STRING_TABLE_LOOKUP(path_relative_to, PathRelativeTo);
276✔
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