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

systemd / systemd / 25409762285

05 May 2026 08:45PM UTC coverage: 72.658% (-0.02%) from 72.674%
25409762285

push

github

web-flow
Couple of coverity fixes (#41951)

0 of 11 new or added lines in 2 files covered. (0.0%)

2705 existing lines in 63 files now uncovered.

326249 of 449021 relevant lines covered (72.66%)

1212712.0 hits per line

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

71.37
/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 "env-util.h"
16
#include "errno-util.h"
17
#include "fd-util.h"
18
#include "fdisk-util.h"
19
#include "fileio.h"
20
#include "find-esp.h"
21
#include "glyph-util.h"
22
#include "gpt.h"
23
#include "hexdecoct.h"
24
#include "import-util.h"
25
#include "iovec-util.h"
26
#include "pidref.h"
27
#include "process-util.h"
28
#include "sort-util.h"
29
#include "stat-util.h"
30
#include "string-table.h"
31
#include "strv.h"
32
#include "sysupdate-cache.h"
33
#include "sysupdate-instance.h"
34
#include "sysupdate-partition.h"
35
#include "sysupdate-pattern.h"
36
#include "sysupdate-resource.h"
37
#include "time-util.h"
38
#include "utf8.h"
39

40
void resource_destroy(Resource *rr) {
5,776✔
41
        assert(rr);
5,776✔
42

43
        free(rr->path);
5,776✔
44
        strv_free(rr->patterns);
5,776✔
45

46
        for (size_t i = 0; i < rr->n_instances; i++)
26,182✔
47
                instance_free(rr->instances[i]);
14,630✔
48
        free(rr->instances);
5,776✔
49
}
5,776✔
50

51
static int resource_add_instance(
14,830✔
52
                Resource *rr,
53
                const char *path,
54
                const InstanceMetadata *f,
55
                Instance **ret) {
56

57
        Instance *i;
14,830✔
58
        int r;
14,830✔
59

60
        assert(rr);
14,830✔
61
        assert(path);
14,830✔
62
        assert(f);
14,830✔
63
        assert(f->version);
14,830✔
64

65
        if (!GREEDY_REALLOC(rr->instances, rr->n_instances + 1))
14,830✔
66
                return log_oom();
×
67

68
        r = instance_new(rr, path, f, &i);
14,830✔
69
        if (r < 0)
14,830✔
70
                return r;
71

72
        rr->instances[rr->n_instances++] = i;
14,830✔
73

74
        if (ret)
14,830✔
75
                *ret = i;
14,830✔
76

77
        return 0;
78
}
79

80
static int resource_load_from_directory_recursive(
5,216✔
81
                Resource *rr,
82
                DIR* d,
83
                const char* relpath,
84
                const char* relpath_for_matching,
85
                mode_t m,
86
                bool ancestor_is_partial,
87
                bool ancestor_is_pending) {
88
        int r;
75,204✔
89

90
        for (;;) {
75,204✔
91
                _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
×
92
                _cleanup_free_ char *joined = NULL, *rel_joined = NULL;
×
93
                _cleanup_free_ char *rel_joined_for_matching = NULL;
75,204✔
94
                Instance *instance;
75,204✔
95
                struct dirent *de;
75,204✔
96
                const char *de_d_name_stripped;
75,204✔
97
                struct stat st;
75,204✔
98
                bool is_partial = ancestor_is_partial, is_pending = ancestor_is_pending;
75,204✔
99
                const char *stripped;
75,204✔
100

101
                errno = 0;
75,204✔
102
                de = readdir_no_dot(d);
75,204✔
103
                if (!de) {
75,204✔
104
                        if (errno != 0)
5,216✔
105
                                return log_error_errno(errno, "Failed to read directory '%s': %m", rr->path);
×
106
                        break;
5,216✔
107
                }
108

109
                switch (de->d_type) {
69,988✔
110

111
                case DT_UNKNOWN:
112
                        break;
113

114
                case DT_DIR:
11,482✔
115
                        if (!IN_SET(m, S_IFDIR, S_IFREG))
11,482✔
116
                                continue;
×
117

118
                        break;
119

120
                case DT_REG:
58,050✔
121
                        if (m != S_IFREG)
58,050✔
122
                                continue;
8,030✔
123
                        break;
124

125
                default:
456✔
126
                        continue;
456✔
127
                }
128

129
                if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT) < 0) {
61,502✔
130
                        if (errno == ENOENT) /* Gone by now? */
×
131
                                continue;
×
132

133
                        return log_error_errno(errno, "Failed to stat %s/%s: %m", rr->path, de->d_name);
×
134
                }
135

136
                if (!(S_ISDIR(st.st_mode) && S_ISREG(m)) && ((st.st_mode & S_IFMT) != m))
61,502✔
137
                        continue;
×
138

139
                if ((stripped = startswith(de->d_name, ".sysupdate.partial."))) {
61,502✔
140
                        de_d_name_stripped = stripped;
141
                        is_partial = true;
142
                } else if ((stripped = startswith(de->d_name, ".sysupdate.pending."))) {
61,502✔
143
                        de_d_name_stripped = stripped;
144
                        is_pending = true;
145
                } else
146
                        de_d_name_stripped = de->d_name;
61,226✔
147

148
                rel_joined = path_join(relpath, de->d_name);
61,502✔
149
                if (!rel_joined)
61,502✔
150
                        return log_oom();
×
151

152
                /* Match against the filename with any `.sysupdate.partial.` (etc.) prefix stripped, so the
153
                 * user’s patterns still apply. But don’t use the stripped version in any paths or recursion. */
154
                rel_joined_for_matching = path_join(relpath_for_matching, de_d_name_stripped);
61,502✔
155
                if (!rel_joined_for_matching)
61,502✔
156
                        return log_oom();
×
157

158
                r = pattern_match_many(rr->patterns, rel_joined_for_matching, &extracted_fields);
61,502✔
159
                if (r == PATTERN_MATCH_RETRY) {
61,502✔
160
                        _cleanup_closedir_ DIR *subdir = NULL;
58,130✔
161

162
                        subdir = xopendirat(dirfd(d), rel_joined, 0);
1,736✔
163
                        if (!subdir)
1,736✔
164
                                continue;
×
165

166
                        r = resource_load_from_directory_recursive(rr, subdir, rel_joined, rel_joined_for_matching, m, is_partial, is_pending);
1,736✔
167
                        if (r < 0)
1,736✔
168
                                return r;
×
169
                        if (r == 0)
1,736✔
170
                                continue;
1,736✔
171
                }
172
                else if (r < 0)
59,766✔
173
                        return log_error_errno(r, "Failed to match pattern: %m");
×
174
                else if (r == PATTERN_MATCH_NO)
59,766✔
175
                        continue;
47,908✔
176

177
                if (de->d_type == DT_DIR && m != S_IFDIR)
11,858✔
UNCOV
178
                        continue;
×
179

180
                joined = path_join(rr->path, rel_joined);
11,858✔
181
                if (!joined)
11,858✔
182
                        return log_oom();
×
183

184
                r = resource_add_instance(rr, joined, &extracted_fields, &instance);
11,858✔
185
                if (r < 0)
11,858✔
186
                        return r;
187

188
                /* Inherit these from the source, if not explicitly overwritten */
189
                if (instance->metadata.mtime == USEC_INFINITY)
11,858✔
190
                        instance->metadata.mtime = timespec_load(&st.st_mtim) ?: USEC_INFINITY;
11,858✔
191

192
                if (instance->metadata.mode == MODE_INVALID)
11,858✔
193
                        instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */
11,858✔
194

195
                instance->is_partial = is_partial;
11,858✔
196
                instance->is_pending = is_pending;
11,858✔
197
        }
198

199
        return 0;
5,216✔
200
}
201

202
static int resource_load_from_directory(
3,480✔
203
                Resource *rr,
204
                mode_t m) {
205
        _cleanup_closedir_ DIR *d = NULL;
3,480✔
206

207
        assert(rr);
3,480✔
208
        assert(IN_SET(rr->type, RESOURCE_TAR, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
3,480✔
209
        assert(IN_SET(m, S_IFREG, S_IFDIR));
3,480✔
210

211
        d = opendir(rr->path);
3,480✔
212
        if (!d) {
3,480✔
213
                if (errno == ENOENT) {
×
214
                        log_debug_errno(errno, "Directory %s does not exist, not loading any resources: %m", rr->path);
3,480✔
215
                        return 0;
216
                }
217

218
                return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path);
×
219
        }
220

221
        return resource_load_from_directory_recursive(rr, d, NULL, NULL, m, false, false);
3,480✔
222
}
223

224
static int resource_load_from_blockdev(Resource *rr) {
968✔
225
        _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
968✔
226
        _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
968✔
227
        size_t n_partitions;
968✔
228
        int r;
968✔
229

230
        assert(rr);
968✔
231

232
        r = dlopen_fdisk(LOG_DEBUG);
968✔
233
        if (r < 0)
968✔
234
                return r;
235

236
        r = fdisk_new_context_at(AT_FDCWD, rr->path, /* read_only= */ true, /* sector_size= */ UINT32_MAX, &c);
968✔
237
        if (r < 0)
968✔
238
                return log_error_errno(r, "Failed to create fdisk context from '%s': %m", rr->path);
×
239

240
        if (!sym_fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
968✔
241
                return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", rr->path);
×
242

243
        r = sym_fdisk_get_partitions(c, &t);
968✔
244
        if (r < 0)
968✔
245
                return log_error_errno(r, "Failed to acquire partition table: %m");
×
246

247
        n_partitions = sym_fdisk_table_get_nents(t);
968✔
248
        for (size_t i = 0; i < n_partitions; i++)  {
4,840✔
249
                _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
3,872✔
250
                _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
2,072✔
251
                Instance *instance;
3,872✔
252
                bool is_partial = false, is_pending = false;
3,872✔
253

254
                r = read_partition_info(c, t, i, &pinfo);
3,872✔
255
                if (r < 0)
3,872✔
256
                        return r;
257
                if (r == 0) /* not assigned */
3,872✔
258
                        continue;
×
259

260
                /* Check if partition type matches, either directly or via derived partial/pending type
261
                 * UUIDs. The derived UUIDs are computed from the configured partition type by hashing it
262
                 * with a fixed app-specific ID, so we can detect the state without relying on label
263
                 * prefixes. */
264
                if (rr->partition_type_set) {
3,872✔
265
                        sd_id128_t partial_type, pending_type;
3,872✔
266

267
                        r = gpt_partition_type_uuid_for_sysupdate_partial(rr->partition_type.uuid, &partial_type);
3,872✔
268
                        if (r < 0)
3,872✔
269
                                return log_error_errno(r, "Failed to derive partial partition type UUID: %m");
×
270

271
                        r = gpt_partition_type_uuid_for_sysupdate_pending(rr->partition_type.uuid, &pending_type);
3,872✔
272
                        if (r < 0)
3,872✔
273
                                return log_error_errno(r, "Failed to derive pending partition type UUID: %m");
×
274

275
                        if (sd_id128_equal(pinfo.type, partial_type))
3,872✔
276
                                is_partial = true;
277
                        else if (sd_id128_equal(pinfo.type, pending_type))
3,960✔
278
                                is_pending = true;
279
                        else if (!sd_id128_equal(pinfo.type, rr->partition_type.uuid))
5,632✔
280
                                continue;
1,936✔
281
                }
282

283
                /* A label of "_empty" means "not used so far" for us */
284
                if (streq_ptr(pinfo.label, "_empty")) {
1,936✔
285
                        rr->n_empty++;
136✔
286
                        continue;
136✔
287
                }
288

289
                r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields);
1,800✔
290
                if (r < 0)
1,800✔
291
                        return log_error_errno(r, "Failed to match pattern: %m");
×
292
                if (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY))
1,800✔
293
                        continue;
×
294

295
                r = resource_add_instance(rr, pinfo.device, &extracted_fields, &instance);
1,800✔
296
                if (r < 0)
1,800✔
297
                        return r;
298

299
                instance->partition_info = pinfo;
1,800✔
300
                pinfo = (PartitionInfo) PARTITION_INFO_NULL;
1,800✔
301

302
                /* Inherit data from source if not configured explicitly */
303
                if (!instance->metadata.partition_uuid_set) {
1,800✔
304
                        instance->metadata.partition_uuid = instance->partition_info.uuid;
1,800✔
305
                        instance->metadata.partition_uuid_set = true;
1,800✔
306
                }
307

308
                if (!instance->metadata.partition_flags_set) {
1,800✔
309
                        instance->metadata.partition_flags = instance->partition_info.flags;
1,800✔
310
                        instance->metadata.partition_flags_set = true;
1,800✔
311
                }
312

313
                if (instance->metadata.read_only < 0)
1,800✔
314
                        instance->metadata.read_only = instance->partition_info.read_only;
1,800✔
315

316
                instance->is_partial = is_partial;
1,800✔
317
                instance->is_pending = is_pending;
1,800✔
318
        }
319

320
        return 0;
321
}
322

323
static int download_manifest(
84✔
324
                const char *url,
325
                bool verify_signature,
326
                char **ret_buffer,
327
                size_t *ret_size) {
328

329
        _cleanup_free_ char *buffer = NULL, *suffixed_url = NULL;
84✔
330
        _cleanup_close_pair_ int pfd[2] = EBADF_PAIR;
84✔
331
        _cleanup_fclose_ FILE *manifest = NULL;
84✔
332
        size_t size = 0;
84✔
333
        int r;
84✔
334

335
        assert(url);
84✔
336
        assert(ret_buffer);
84✔
337
        assert(ret_size);
84✔
338

339
        /* Download a SHA256SUMS file as manifest */
340

341
        r = import_url_append_component(url, "SHA256SUMS", &suffixed_url);
84✔
342
        if (r < 0)
84✔
343
                return log_error_errno(r, "Failed to append SHA256SUMS to URL: %m");
×
344

345
        if (pipe2(pfd, O_CLOEXEC) < 0)
84✔
346
                return log_error_errno(errno, "Failed to allocate pipe: %m");
×
347

348
        log_info("%s Acquiring manifest file %s%s", glyph(GLYPH_DOWNLOAD),
168✔
349
                 suffixed_url, glyph(GLYPH_ELLIPSIS));
350

351
        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
84✔
352
        r = pidref_safe_fork_full(
252✔
353
                        "(sd-pull)",
354
                        (int[]) { -EBADF, pfd[1], STDERR_FILENO },
84✔
355
                        NULL, 0,
356
                        FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG,
357
                        &pidref);
358
        if (r < 0)
168✔
359
                return r;
360
        if (r == 0) {
168✔
361
                /* Child */
362

363
                const char *cmdline[] = {
168✔
364
                        SYSTEMD_PULL_PATH,
365
                        "raw",
366
                        "--direct",                        /* just download the specified URL, don't download anything else */
367
                        "--verify", verify_signature ? "signature" : "no", /* verify the manifest file */
84✔
368
                        suffixed_url,
369
                        "-",                               /* write to stdout */
370
                        NULL
371
                };
372

373
                r = invoke_callout_binary(SYSTEMD_PULL_PATH, (char *const*) cmdline);
84✔
374
                log_error_errno(r, "Failed to execute %s tool: %m", SYSTEMD_PULL_PATH);
×
375
                _exit(EXIT_FAILURE);
×
376
        };
84✔
377

378
        pfd[1] = safe_close(pfd[1]);
84✔
379

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

385
        manifest = fdopen(pfd[0], "r");
84✔
386
        if (!manifest)
84✔
387
                return log_error_errno(errno, "Failed to allocate FILE object for manifest file: %m");
×
388

389
        TAKE_FD(pfd[0]);
84✔
390

391
        r = read_full_stream(manifest, &buffer, &size);
84✔
392
        if (r < 0)
84✔
393
                return log_error_errno(r, "Failed to read manifest file from child: %m");
×
394

395
        manifest = safe_fclose(manifest);
84✔
396

397
        r = pidref_wait_for_terminate_and_check("(sd-pull)", &pidref, WAIT_LOG);
84✔
398
        if (r < 0)
84✔
399
                return r;
400
        if (r != 0)
84✔
401
                return -EPROTO;
402

403
        *ret_buffer = TAKE_PTR(buffer);
84✔
404
        *ret_size = size;
84✔
405

406
        return 0;
84✔
407
}
408

409
static int process_magic_file(
6,912✔
410
                const char *fn,
411
                const struct iovec *hash) {
412

413
        int r;
6,912✔
414

415
        assert(fn);
6,912✔
416
        assert(iovec_is_set(hash));
6,912✔
417

418
        /* Validates "BEST-BEFORE-*" magic files we find in SHA256SUMS manifests. For now we ignore the
419
         * contents of such files (which might change one day), and only look at the file name.
420
         *
421
         * Note that if multiple BEST-BEFORE-* files exist in the same listing we'll honour them all, and
422
         * fail whenever *any* of them indicate a date that's already in the past. */
423

424
        const char *e = startswith(fn, "BEST-BEFORE-");
6,912✔
425
        if (!e)
6,912✔
426
                return 0;
6,912✔
427

428
        /* SHA256 hash of an empty file */
429
        static const uint8_t expected_hash[] = {
40✔
430
                0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
431
                0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
432
        };
433

434
        /* Even if we ignore if people have non-empty files for this file, let's nonetheless warn about it,
435
         * so that people fix it. After all we want to retain liberty to maybe one day place some useful data
436
         * inside it */
437
        if (!iovec_equal(&IOVEC_MAKE(expected_hash, sizeof(expected_hash)), hash))
40✔
438
                log_warning("Hash of best before marker file '%s' has unexpected value, proceeding anyway.", fn);
×
439

440
        usec_t best_before;
40✔
441
        r = parse_calendar_date(e, &best_before);
40✔
442
        if (r < 0) {
40✔
443
                log_warning_errno(r, "Found best before marker with an invalid date, ignoring: %s", fn);
×
444
                return 0;
445
        }
446

447
        usec_t nw = now(CLOCK_REALTIME);
40✔
448
        if (best_before < nw) {
40✔
449
                /* We are past the best before date! Yikes! */
450

451
                r = secure_getenv_bool("SYSTEMD_SYSUPDATE_VERIFY_FRESHNESS");
24✔
452
                if (r < 0 && r != -ENXIO)
24✔
453
                        log_debug_errno(r, "Failed to parse $SYSTEMD_SYSUPDATE_VERIFY_FRESHNESS, ignoring: %m");
×
454

455
                if (r == 0) {
24✔
456
                        log_warning("Best before marker indicates out-of-date file list, but told to ignore this, hence ignoring (%s < %s).",
16✔
457
                                    FORMAT_TIMESTAMP(best_before), FORMAT_TIMESTAMP(nw));
458
                        return 1; /* we processed this line, don't use for pattern matching */
16✔
459
                }
460

461
                return log_error_errno(
8✔
462
                                SYNTHETIC_ERRNO(ESTALE),
463
                                "Best before marker indicates out-of-date file list, refusing (%s < %s).",
464
                                FORMAT_TIMESTAMP(best_before), FORMAT_TIMESTAMP(nw));
465
        }
466

467
        log_info("Found best before marker, and it checks out, proceeding.");
16✔
468
        return 1; /* we processed this line, don't use for pattern matching */
469
}
470

471
static int resource_load_from_web(
160✔
472
                Resource *rr,
473
                bool verify,
474
                Hashmap **web_cache) {
475

476
        size_t manifest_size = 0, left = 0;
160✔
477
        _cleanup_free_ char *buf = NULL;
160✔
478
        const char *manifest, *p;
160✔
479
        size_t line_nr = 1;
160✔
480
        WebCacheItem *ci;
160✔
481
        int r;
160✔
482

483
        assert(rr);
160✔
484
        POINTER_MAY_BE_NULL(web_cache);
160✔
485

486
        ci = web_cache ? web_cache_get_item(*web_cache, rr->path, verify) : NULL;
160✔
487
        if (ci) {
160✔
488
                log_debug("Manifest web cache hit for %s.", rr->path);
76✔
489

490
                manifest = (char*) ci->data;
76✔
491
                manifest_size = ci->size;
76✔
492
        } else {
493
                log_debug("Manifest web cache miss for %s.", rr->path);
84✔
494

495
                r = download_manifest(rr->path, verify, &buf, &manifest_size);
84✔
496
                if (r < 0)
84✔
497
                        return r;
498

499
                manifest = buf;
84✔
500
        }
501

502
        if (memchr(manifest, 0, manifest_size))
160✔
503
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file has embedded NUL byte, refusing.");
×
504
        if (!utf8_is_valid_n(manifest, manifest_size))
160✔
505
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file is not valid UTF-8, refusing.");
×
506

507
        p = manifest;
508
        left = manifest_size;
509

510
        while (left > 0) {
7,064✔
511
                _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
×
512
                _cleanup_(iovec_done) struct iovec h = {};
6,912✔
513
                _cleanup_free_ char *fn = NULL;
6,912✔
514
                Instance *instance;
6,912✔
515
                const char *e;
6,912✔
516

517
                /* 64 character hash + separator + filename + newline */
518
                if (left < 67)
6,912✔
519
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Corrupt manifest at line %zu, refusing.", line_nr);
×
520

521
                if (p[0] == '\\')
6,912✔
522
                        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "File names with escapes not supported in manifest at line %zu, refusing.", line_nr);
×
523

524
                r = unhexmem_full(p, 64, /* secure= */ false, &h.iov_base, &h.iov_len);
6,912✔
525
                if (r < 0)
6,912✔
526
                        return log_error_errno(r, "Failed to parse digest at manifest line %zu, refusing.", line_nr);
×
527

528
                p += 64, left -= 64;
6,912✔
529

530
                if (*p != ' ')
6,912✔
531
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing space separator at manifest line %zu, refusing.", line_nr);
×
532
                p++, left--;
6,912✔
533

534
                if (!IN_SET(*p, '*', ' '))
6,912✔
535
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing binary/text input marker at manifest line %zu, refusing.", line_nr);
×
536
                p++, left--;
6,912✔
537

538
                e = memchr(p, '\n', left);
6,912✔
539
                if (!e)
6,912✔
540
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Truncated manifest file at line %zu, refusing.", line_nr);
×
541
                if (e == p)
6,912✔
542
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty filename specified at manifest line %zu, refusing.", line_nr);
×
543

544
                fn = strndup(p, e - p);
6,912✔
545
                if (!fn)
6,912✔
546
                        return log_oom();
×
547

548
                if (!filename_is_valid(fn))
6,912✔
549
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid filename specified at manifest line %zu, refusing.", line_nr);
×
550
                if (string_has_cc(fn, NULL))
6,912✔
551
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Filename contains control characters at manifest line %zu, refusing.", line_nr);
×
552

553
                r = process_magic_file(fn, &h);
6,912✔
554
                if (r < 0)
6,912✔
555
                        return r;
556
                if (r == 0) {
6,904✔
557
                        /* If this isn't a magic file, then do the pattern matching */
558

559
                        r = pattern_match_many(rr->patterns, fn, &extracted_fields);
6,872✔
560
                        if (r < 0)
6,872✔
561
                                return log_error_errno(r, "Failed to match pattern: %m");
×
562
                        if (r == PATTERN_MATCH_YES) {
6,872✔
563
                                _cleanup_free_ char *path = NULL;
1,172✔
564

565
                                r = import_url_append_component(rr->path, fn, &path);
1,172✔
566
                                if (r < 0)
1,172✔
567
                                        return log_error_errno(r, "Failed to build instance URL: %m");
×
568

569
                                r = resource_add_instance(rr, path, &extracted_fields, &instance);
1,172✔
570
                                if (r < 0)
1,172✔
571
                                        return r;
572

573
                                assert(h.iov_len == sizeof(instance->metadata.sha256sum));
1,172✔
574

575
                                if (instance->metadata.sha256sum_set) {
1,172✔
576
                                        if (memcmp(instance->metadata.sha256sum, h.iov_base, h.iov_len) != 0)
×
577
                                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SHA256 sum parsed from filename and manifest don't match at line %zu, refusing.", line_nr);
×
578
                                } else {
579
                                        memcpy(instance->metadata.sha256sum, h.iov_base, h.iov_len);
1,172✔
580
                                        instance->metadata.sha256sum_set = true;
1,172✔
581
                                }
582

583
                                /* Web resources can only be a source, not a target, so
584
                                 * can never be partial or pending. */
585
                                instance->is_partial = false;
1,172✔
586
                                instance->is_pending = false;
1,172✔
587
                        }
588
                }
589

590
                left -= (e - p) + 1;
6,904✔
591
                p = e + 1;
6,904✔
592

593
                line_nr++;
6,904✔
594
        }
595

596
        if (!ci && web_cache) {
152✔
597
                r = web_cache_add_item(web_cache, rr->path, verify, manifest, manifest_size);
76✔
598
                if (r < 0)
76✔
599
                        log_debug_errno(r, "Failed to add manifest '%s' to cache, ignoring: %m", rr->path);
160✔
600
                else
601
                        log_debug("Added manifest '%s' to cache.", rr->path);
76✔
602
        }
603

604
        return 0;
605
}
606

607
static int instance_cmp(Instance *const*a, Instance *const*b) {
20,186✔
608
        int r;
20,186✔
609

610
        assert(a);
20,186✔
611
        assert(b);
20,186✔
612
        assert(*a);
20,186✔
613
        assert(*b);
20,186✔
614
        assert((*a)->metadata.version);
20,186✔
615
        assert((*b)->metadata.version);
20,186✔
616

617
        /* Newest version at the beginning */
618
        r = strverscmp_improved((*a)->metadata.version, (*b)->metadata.version);
20,186✔
619
        if (r != 0)
20,186✔
620
                return -r;
20,186✔
621

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

628
int resource_load_instances(Resource *rr, bool verify, Hashmap **web_cache) {
4,608✔
629
        int r;
4,608✔
630

631
        assert(rr);
4,608✔
632

633
        switch (rr->type) {
4,608✔
634

635
        case RESOURCE_TAR:
2,742✔
636
        case RESOURCE_REGULAR_FILE:
637
                r = resource_load_from_directory(rr, S_IFREG);
2,742✔
638
                break;
2,742✔
639

640
        case RESOURCE_DIRECTORY:
738✔
641
        case RESOURCE_SUBVOLUME:
642
                r = resource_load_from_directory(rr, S_IFDIR);
738✔
643
                break;
738✔
644

645
        case RESOURCE_PARTITION:
968✔
646
                r = resource_load_from_blockdev(rr);
968✔
647
                break;
968✔
648

649
        case RESOURCE_URL_FILE:
160✔
650
        case RESOURCE_URL_TAR:
651
                r = resource_load_from_web(rr, verify, web_cache);
160✔
652
                break;
160✔
653

654
        default:
×
655
                assert_not_reached();
×
656
        }
657
        if (r < 0)
4,608✔
658
                return r;
659

660
        typesafe_qsort(rr->instances, rr->n_instances, instance_cmp);
4,600✔
661
        return 0;
4,600✔
662
}
663

664
static int instance_version_match(Instance *const*a, Instance *const*b) {
30,122✔
665
        assert(a);
30,122✔
666
        assert(b);
30,122✔
667
        assert(*a);
30,122✔
668
        assert(*b);
30,122✔
669
        assert((*a)->metadata.version);
30,122✔
670
        assert((*b)->metadata.version);
30,122✔
671

672
        /* List is sorted newest-to-oldest */
673
        return -strverscmp_improved((*a)->metadata.version, (*b)->metadata.version);
30,122✔
674
}
675

676
Instance* resource_find_instance(Resource *rr, const char *version) {
15,672✔
677
        Instance key = {
15,672✔
678
                .metadata.version = (char*) version,
679
        }, *k = &key;
15,672✔
680

681
        Instance **found;
15,672✔
682
        found = typesafe_bsearch(&k, rr->instances, rr->n_instances, instance_version_match);
15,672✔
683
        if (!found)
15,672✔
684
                return NULL;
15,672✔
685

686
        return *found;
13,214✔
687
}
688

689
static int get_sysext_overlay_block(const char *p, dev_t *ret) {
×
690
        int r;
×
691

692
        assert(p);
×
693
        assert(ret);
×
694

695
        /* Tries to read the backing device information systemd-sysext puts in the virtual file
696
         * /usr/.systemd-sysext/backing */
697

698
        _cleanup_free_ char *j = path_join(p, ".systemd-sysext");
×
699
        if (!j)
×
700
                return log_oom_debug();
×
701

702
        _cleanup_close_ int fd = open(j, O_RDONLY|O_DIRECTORY);
×
703
        if (fd < 0)
×
704
                return log_debug_errno(errno, "Failed to open '%s': %m", j);
×
705

706
        r = fd_is_fs_type(fd, OVERLAYFS_SUPER_MAGIC);
×
707
        if (r < 0)
×
708
                return log_debug_errno(r, "Failed to determine backing file system of '%s': %m", j);
×
709
        if (r == 0)
×
710
                return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Backing file system of '%s' is not an overlayfs.", j);
×
711

712
        _cleanup_free_ char *buf = NULL;
×
713
        r = read_one_line_file_at(fd, "backing", &buf);
×
714
        if (r < 0)
×
715
                return log_debug_errno(r, "Failed to read contents of '%s/backing': %m", j);
×
716

717
        r = parse_devnum(buf, ret);
×
718
        if (r < 0)
×
719
                return log_debug_errno(r, "Failed to parse contents of '%s/backing': %m", j);
×
720

721
        if (major(*ret) == 0) { /* not a block device? */
×
722
                *ret = 0;
×
723
                return 0;
×
724
        }
725

726
        (void) block_get_originating(*ret, ret, /* recursive= */ false);
×
727
        return 1;
728
}
729

730
int resource_resolve_path(
5,776✔
731
                Resource *rr,
732
                const char *root,
733
                const char *relative_to_directory,
734
                const char *node) {
735

736
        _cleanup_free_ char *p = NULL;
5,776✔
737
        dev_t d;
5,776✔
738
        int r;
5,776✔
739

740
        assert(rr);
5,776✔
741

742
        if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_ESP, PATH_RELATIVE_TO_XBOOTLDR, PATH_RELATIVE_TO_BOOT) &&
5,776✔
743
            !IN_SET(rr->type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY))
1,440✔
744
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
745
                                       "Paths relative to %s are only allowed for regular-file or directory resources.",
746
                                       path_relative_to_to_string(rr->path_relative_to));
747

748
        if (rr->path_auto) {
5,776✔
749
                /* NB: If the root mount has been replaced by some form of volatile file system (overlayfs),
750
                 * the original root block device node is symlinked in /run/systemd/volatile-root. Let's
751
                 * follow that link here. If that doesn't exist, we check the backing device of "/usr". We
752
                 * don't actually check the backing device of the root fs "/", in order to support
753
                 * environments where the root fs is a tmpfs, and the OS itself placed exclusively in
754
                 * /usr/. */
755

756
                if (rr->type != RESOURCE_PARTITION)
×
757
                        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
×
758
                                               "Automatic root path discovery only supported for partition resources.");
759

760
                if (node) { /* If --image= is specified, directly use the loopback device */
×
761
                        r = free_and_strdup_warn(&rr->path, node);
×
762
                        if (r < 0)
×
763
                                return r;
764

765
                        return 0;
×
766
                }
767

768
                if (root)
×
769
                        return log_error_errno(SYNTHETIC_ERRNO(EPERM),
×
770
                                               "Block device is not allowed when using --root= mode.");
771

772
                struct stat orig_root_stats;
×
773
                r = RET_NERRNO(stat("/run/systemd/volatile-root", &orig_root_stats));
×
774
                if (r < 0) {
×
775
                        if (r != -ENOENT)
×
776
                                return log_error_errno(r, "Failed to stat /run/systemd/volatile-root: %m");
×
777

778
                        /* volatile-root not found */
779
                        r = get_block_device_harder("/usr/", &d);
×
780
                        if (r == 0) /* Not backed by a block device? Let's see if this is a sysext overlayfs instance */
×
781
                                r = get_sysext_overlay_block("/usr/", &d);
×
782
                        if (r < 0)
×
783
                                return log_error_errno(r, "Failed to determine block device of file system: %m");
×
784
                } else if (!S_ISBLK(orig_root_stats.st_mode)) /* symlink was present but not block device */
×
785
                        return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "/run/systemd/volatile-root is not linked to a block device.");
×
786
                else /* symlink was present and a block device */
787
                        d = orig_root_stats.st_rdev;
×
788

789
        } else if (rr->type == RESOURCE_PARTITION) {
5,776✔
790
                _cleanup_close_ int fd = -EBADF, real_fd = -EBADF;
1,936✔
791
                _cleanup_free_ char *resolved = NULL;
968✔
792
                struct stat st;
968✔
793

794
                r = chase(rr->path, root, CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS, &resolved, &fd);
968✔
795
                if (r < 0)
968✔
796
                        return log_error_errno(r, "Failed to resolve '%s': %m", rr->path);
×
797

798
                if (fstat(fd, &st) < 0)
968✔
799
                        return log_error_errno(errno, "Failed to stat '%s': %m", resolved);
×
800

801
                if (S_ISBLK(st.st_mode) && root)
968✔
802
                        return log_error_errno(SYNTHETIC_ERRNO(EPERM), "When using --root= or --image= access to device nodes is prohibited.");
×
803

804
                if (S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) {
968✔
805
                        /* Not a directory, hence no need to find backing block device for the path */
806
                        free_and_replace(rr->path, resolved);
968✔
807
                        return 0;
968✔
808
                }
809

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

813
                if (node) { /* If --image= is specified all file systems are backed by the same loopback device, hence shortcut things. */
×
814
                        r = free_and_strdup_warn(&rr->path, node);
×
815
                        if (r < 0)
×
816
                                return r;
817

818
                        return 0;
×
819
                }
820

821
                real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
×
822
                if (real_fd < 0)
×
823
                        return log_error_errno(real_fd, "Failed to convert O_PATH file descriptor for %s to regular file descriptor: %m", rr->path);
×
824

825
                r = get_block_device_harder_fd(real_fd, &d);
×
826
                if (r < 0)
×
827
                        return log_error_errno(r, "Failed to determine block device of file system: %m");
×
828

829
        } else if (RESOURCE_IS_FILESYSTEM(rr->type)) {
10,392✔
830
                _cleanup_free_ char *resolved = NULL, *relative_to = NULL;
4,616✔
831
                ChaseFlags chase_flags = CHASE_NONEXISTENT | CHASE_PREFIX_ROOT | CHASE_TRIGGER_AUTOFS;
4,616✔
832

833
                if (rr->path_relative_to == PATH_RELATIVE_TO_EXPLICIT) {
4,616✔
834
                        assert(relative_to_directory);
×
835

836
                        relative_to = strdup(relative_to_directory);
×
837
                        if (!relative_to)
×
838
                                return log_oom();
×
839
                } else if (rr->path_relative_to == PATH_RELATIVE_TO_ROOT) {
4,616✔
840
                        relative_to = strdup(empty_to_root(root));
3,176✔
841
                        if (!relative_to)
3,176✔
842
                                return log_oom();
×
843
                } else { /* boot, esp, or xbootldr */
844
                        r = 0;
1,440✔
845
                        if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_BOOT, PATH_RELATIVE_TO_XBOOTLDR))
1,440✔
846
                                r = find_xbootldr_and_warn(root, /* path= */ NULL, /* unprivileged_mode= */ -1, &relative_to, /* ret_fd= */ NULL);
1,440✔
847
                        if (r == -ENOKEY || rr->path_relative_to == PATH_RELATIVE_TO_ESP)
1,440✔
848
                                r = find_esp_and_warn(root, /* path= */ NULL, /* unprivileged_mode= */ -1, &relative_to, /* ret_fd= */ NULL);
×
849
                        if (r < 0)
1,440✔
850
                                return log_error_errno(r, "Failed to resolve $BOOT: %m");
×
851
                        log_debug("Resolved $BOOT to '%s'", relative_to);
1,440✔
852

853
                        /* Since this partition is read from EFI, there should be no symlinks */
854
                        chase_flags |= CHASE_PROHIBIT_SYMLINKS;
855
                }
856

857
                r = chase(rr->path, relative_to, chase_flags, &resolved, NULL);
4,616✔
858
                if (r < 0)
4,616✔
859
                        return log_error_errno(r, "Failed to resolve '%s' (relative to '%s'): %m", rr->path, relative_to);
×
860

861
                free_and_replace(rr->path, resolved);
4,616✔
862
                return 0;
4,616✔
863
        } else
864
                return 0; /* Otherwise assume there's nothing to resolve */
865

866
        r = block_get_whole_disk(d, &d);
×
867
        if (r < 0)
×
868
                return log_error_errno(r, "Failed to find whole disk device for partition backing file system: %m");
×
869
        if (r == 0)
×
870
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
871
                                       "File system is not placed on a partition block device, cannot determine whole block device backing root file system.");
872

873
        r = devname_from_devnum(S_IFBLK, d, &p);
×
874
        if (r < 0)
×
875
                return r;
876

877
        if (rr->path)
×
878
                log_info("Automatically discovered block device '%s' from '%s'.", p, rr->path);
×
879
        else
880
                log_info("Automatically discovered root block device '%s'.", p);
×
881

882
        free_and_replace(rr->path, p);
×
883
        return 1;
×
884
}
885

886
static const char *resource_type_table[_RESOURCE_TYPE_MAX] = {
887
        [RESOURCE_URL_FILE]     = "url-file",
888
        [RESOURCE_URL_TAR]      = "url-tar",
889
        [RESOURCE_TAR]          = "tar",
890
        [RESOURCE_PARTITION]    = "partition",
891
        [RESOURCE_REGULAR_FILE] = "regular-file",
892
        [RESOURCE_DIRECTORY]    = "directory",
893
        [RESOURCE_SUBVOLUME]    = "subvolume",
894
};
895

896
DEFINE_STRING_TABLE_LOOKUP(resource_type, ResourceType);
7,008✔
897

898
static const char *path_relative_to_table[_PATH_RELATIVE_TO_MAX] = {
899
        [PATH_RELATIVE_TO_ROOT]     = "root",
900
        [PATH_RELATIVE_TO_ESP]      = "esp",
901
        [PATH_RELATIVE_TO_XBOOTLDR] = "xbootldr",
902
        [PATH_RELATIVE_TO_BOOT]     = "boot",
903
        [PATH_RELATIVE_TO_EXPLICIT] = "explicit",
904
};
905

906
DEFINE_STRING_TABLE_LOOKUP(path_relative_to, PathRelativeTo);
1,440✔
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