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

systemd / systemd / 21192089104

20 Jan 2026 11:35PM UTC coverage: 72.524% (-0.3%) from 72.818%
21192089104

push

github

yuwata
mkdir: reset mtime *after* fchown()

Follow-up for 34c3d5747

Also, drop pointless shortcut.

1 of 2 new or added lines in 1 file covered. (50.0%)

2960 existing lines in 48 files now uncovered.

309808 of 427181 relevant lines covered (72.52%)

1236537.64 hits per line

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

70.04
/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-pattern.h"
35
#include "sysupdate-resource.h"
36
#include "time-util.h"
37
#include "utf8.h"
38

39
void resource_destroy(Resource *rr) {
1,208✔
40
        assert(rr);
1,208✔
41

42
        free(rr->path);
1,208✔
43
        strv_free(rr->patterns);
1,208✔
44

45
        for (size_t i = 0; i < rr->n_instances; i++)
4,576✔
46
                instance_free(rr->instances[i]);
3,368✔
47
        free(rr->instances);
1,208✔
48
}
1,208✔
49

50
static int resource_add_instance(
3,418✔
51
                Resource *rr,
52
                const char *path,
53
                const InstanceMetadata *f,
54
                Instance **ret) {
55

56
        Instance *i;
3,418✔
57
        int r;
3,418✔
58

59
        assert(rr);
3,418✔
60
        assert(path);
3,418✔
61
        assert(f);
3,418✔
62
        assert(f->version);
3,418✔
63

64
        if (!GREEDY_REALLOC(rr->instances, rr->n_instances + 1))
3,418✔
UNCOV
65
                return log_oom();
×
66

67
        r = instance_new(rr, path, f, &i);
3,418✔
68
        if (r < 0)
3,418✔
69
                return r;
70

71
        rr->instances[rr->n_instances++] = i;
3,418✔
72

73
        if (ret)
3,418✔
74
                *ret = i;
3,418✔
75

76
        return 0;
77
}
78

79
static int resource_load_from_directory_recursive(
1,134✔
80
                Resource *rr,
81
                DIR* d,
82
                const char* relpath,
83
                mode_t m) {
84
        int r;
17,734✔
85

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

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

101
                switch (de->d_type) {
16,600✔
102

103
                case DT_UNKNOWN:
104
                        break;
105

106
                case DT_DIR:
2,642✔
107
                        if (!IN_SET(m, S_IFDIR, S_IFREG))
2,642✔
UNCOV
108
                                continue;
×
109

110
                        break;
111

112
                case DT_REG:
13,862✔
113
                        if (m != S_IFREG)
13,862✔
114
                                continue;
1,950✔
115
                        break;
116

117
                default:
96✔
118
                        continue;
96✔
119
                }
120

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

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

128
                if (!(S_ISDIR(st.st_mode) && S_ISREG(m)) && ((st.st_mode & S_IFMT) != m))
14,554✔
UNCOV
129
                        continue;
×
130

131
                rel_joined = path_join(relpath, de->d_name);
14,554✔
132
                if (!rel_joined)
14,554✔
UNCOV
133
                        return log_oom();
×
134

135
                r = pattern_match_many(rr->patterns, rel_joined, &extracted_fields);
14,554✔
136
                if (r == PATTERN_MATCH_RETRY) {
14,554✔
137
                        _cleanup_closedir_ DIR *subdir = NULL;
13,840✔
138

139
                        subdir = xopendirat(dirfd(d), rel_joined, 0);
360✔
140
                        if (!subdir)
360✔
UNCOV
141
                                continue;
×
142

143
                        r = resource_load_from_directory_recursive(rr, subdir, rel_joined, m);
360✔
144
                        if (r < 0)
360✔
UNCOV
145
                                return r;
×
146
                        if (r == 0)
360✔
147
                                continue;
360✔
148
                }
149
                else if (r < 0)
14,194✔
UNCOV
150
                        return log_error_errno(r, "Failed to match pattern: %m");
×
151
                else if (r == PATTERN_MATCH_NO)
14,194✔
152
                        continue;
11,254✔
153

154
                if (de->d_type == DT_DIR && m != S_IFDIR)
2,940✔
155
                        continue;
180✔
156

157
                joined = path_join(rr->path, rel_joined);
2,760✔
158
                if (!joined)
2,760✔
UNCOV
159
                        return log_oom();
×
160

161
                r = resource_add_instance(rr, joined, &extracted_fields, &instance);
2,760✔
162
                if (r < 0)
2,760✔
163
                        return r;
164

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

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

173
        return 0;
1,134✔
174
}
175

176
static int resource_load_from_directory(
774✔
177
                Resource *rr,
178
                mode_t m) {
179
        _cleanup_closedir_ DIR *d = NULL;
774✔
180

181
        assert(rr);
774✔
182
        assert(IN_SET(rr->type, RESOURCE_TAR, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
774✔
183
        assert(IN_SET(m, S_IFREG, S_IFDIR));
774✔
184

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

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

195
        return resource_load_from_directory_recursive(rr, d, NULL, m);
774✔
196
}
197

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

204
        assert(rr);
204✔
205

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

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

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

217
        n_partitions = fdisk_table_get_nents(t);
204✔
218
        for (size_t i = 0; i < n_partitions; i++)  {
1,020✔
219
                _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
816✔
220
                _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
816✔
221
                Instance *instance;
816✔
222

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

229
                /* Check if partition type matches */
230
                if (rr->partition_type_set && !sd_id128_equal(pinfo.type, rr->partition_type.uuid))
1,224✔
231
                        continue;
408✔
232

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

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

245
                r = resource_add_instance(rr, pinfo.device, &extracted_fields, &instance);
380✔
246
                if (r < 0)
380✔
247
                        return r;
248

249
                instance->partition_info = pinfo;
380✔
250
                pinfo = (PartitionInfo) PARTITION_INFO_NULL;
380✔
251

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

258
                if (!instance->metadata.partition_flags_set) {
380✔
259
                        instance->metadata.partition_flags = instance->partition_info.flags;
380✔
260
                        instance->metadata.partition_flags_set = true;
380✔
261
                }
262

263
                if (instance->metadata.read_only < 0)
380✔
264
                        instance->metadata.read_only = instance->partition_info.read_only;
380✔
265
        }
266

267
        return 0;
268
}
269

270
static int download_manifest(
20✔
271
                const char *url,
272
                bool verify_signature,
273
                char **ret_buffer,
274
                size_t *ret_size) {
275

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

282
        assert(url);
20✔
283
        assert(ret_buffer);
20✔
284
        assert(ret_size);
20✔
285

286
        /* Download a SHA256SUMS file as manifest */
287

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

292
        if (pipe2(pfd, O_CLOEXEC) < 0)
20✔
UNCOV
293
                return log_error_errno(errno, "Failed to allocate pipe: %m");
×
294

295
        log_info("%s Acquiring manifest file %s%s", glyph(GLYPH_DOWNLOAD),
40✔
296
                 suffixed_url, glyph(GLYPH_ELLIPSIS));
297

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

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

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

325
        pfd[1] = safe_close(pfd[1]);
20✔
326

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

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

336
        TAKE_FD(pfd[0]);
20✔
337

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

342
        manifest = safe_fclose(manifest);
20✔
343

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

350
        *ret_buffer = TAKE_PTR(buffer);
20✔
351
        *ret_size = size;
20✔
352

353
        return 0;
20✔
354
}
355

356
static int process_magic_file(
1,640✔
357
                const char *fn,
358
                const struct iovec *hash) {
359

360
        int r;
1,640✔
361

362
        assert(fn);
1,640✔
363
        assert(iovec_is_set(hash));
1,640✔
364

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

371
        const char *e = startswith(fn, "BEST-BEFORE-");
1,640✔
372
        if (!e)
1,640✔
373
                return 0;
1,640✔
374

375
        /* SHA256 hash of an empty file */
376
        static const uint8_t expected_hash[] = {
10✔
377
                0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
378
                0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
379
        };
380

381
        /* Even if we ignore if people have non-empty files for this file, let's nonetheless warn about it,
382
         * so that people fix it. After all we want to retain liberty to maybe one day place some useful data
383
         * inside it */
384
        if (iovec_memcmp(&IOVEC_MAKE(expected_hash, sizeof(expected_hash)), hash) != 0)
10✔
385
                log_warning("Hash of best before marker file '%s' has unexpected value, ignoring.", fn);
×
386

387
        struct tm parsed_tm = {};
10✔
388
        const char *n = strptime(e, "%Y-%m-%d", &parsed_tm);
10✔
389
        if (!n || *n != 0) {
10✔
390
                /* Doesn't parse? Then it's not a best-before date */
UNCOV
391
                log_warning("Found best before marker with an invalid date, ignoring: %s", fn);
×
UNCOV
392
                return 0;
×
393
        }
394

395
        struct tm copy_tm = parsed_tm;
10✔
396
        usec_t best_before;
10✔
397
        r = mktime_or_timegm_usec(&copy_tm, /* utc= */ true, &best_before);
10✔
398
        if (r < 0)
10✔
UNCOV
399
                return log_error_errno(r, "Failed to convert best before time: %m");
×
400
        if (copy_tm.tm_mday != parsed_tm.tm_mday ||
10✔
401
            copy_tm.tm_mon != parsed_tm.tm_mon ||
10✔
402
            copy_tm.tm_year != parsed_tm.tm_year) {
10✔
403
                /* date was not normalized? (e.g. "30th of feb") */
UNCOV
404
                log_warning("Found best before marker with a non-normalized data, ignoring: %s", fn);
×
405
                return 0;
×
406
        }
407

408
        usec_t nw = now(CLOCK_REALTIME);
10✔
409
        if (best_before < nw) {
10✔
410
                /* We are past the best before date! Yikes! */
411

412
                r = secure_getenv_bool("SYSTEMD_SYSUPDATE_VERIFY_FRESHNESS");
6✔
413
                if (r < 0 && r != -ENXIO)
6✔
414
                        log_debug_errno(r, "Failed to parse $SYSTEMD_SYSUPDATE_VERIFY_FRESHNESS, ignoring: %m");
×
415

416
                if (r == 0) {
6✔
417
                        log_warning("Best before marker indicates out-of-date file list, but told to ignore this, hence ignoring (%s < %s).",
4✔
418
                                    FORMAT_TIMESTAMP(best_before), FORMAT_TIMESTAMP(nw));
419
                        return 1; /* we processed this line, don't use for pattern matching */
4✔
420
                }
421

422
                return log_error_errno(
2✔
423
                                SYNTHETIC_ERRNO(ESTALE),
424
                                "Best before marker indicates out-of-date file list, refusing (%s < %s).",
425
                                FORMAT_TIMESTAMP(best_before), FORMAT_TIMESTAMP(nw));
426
        }
427

428
        log_info("Found best before marker, and it checks out, proceeding.");
4✔
429
        return 1; /* we processed this line, don't use for pattern matching */
430
}
431

432
static int resource_load_from_web(
38✔
433
                Resource *rr,
434
                bool verify,
435
                Hashmap **web_cache) {
436

437
        size_t manifest_size = 0, left = 0;
38✔
438
        _cleanup_free_ char *buf = NULL;
38✔
439
        const char *manifest, *p;
38✔
440
        size_t line_nr = 1;
38✔
441
        WebCacheItem *ci;
38✔
442
        int r;
38✔
443

444
        assert(rr);
38✔
445

446
        ci = web_cache ? web_cache_get_item(*web_cache, rr->path, verify) : NULL;
38✔
447
        if (ci) {
38✔
448
                log_debug("Manifest web cache hit for %s.", rr->path);
18✔
449

450
                manifest = (char*) ci->data;
18✔
451
                manifest_size = ci->size;
18✔
452
        } else {
453
                log_debug("Manifest web cache miss for %s.", rr->path);
20✔
454

455
                r = download_manifest(rr->path, verify, &buf, &manifest_size);
20✔
456
                if (r < 0)
20✔
457
                        return r;
458

459
                manifest = buf;
20✔
460
        }
461

462
        if (memchr(manifest, 0, manifest_size))
38✔
UNCOV
463
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file has embedded NUL byte, refusing.");
×
464
        if (!utf8_is_valid_n(manifest, manifest_size))
38✔
UNCOV
465
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file is not valid UTF-8, refusing.");
×
466

467
        p = manifest;
468
        left = manifest_size;
469

470
        while (left > 0) {
1,676✔
UNCOV
471
                _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
×
472
                _cleanup_(iovec_done) struct iovec h = {};
1,640✔
473
                _cleanup_free_ char *fn = NULL;
1,640✔
474
                Instance *instance;
1,640✔
475
                const char *e;
1,640✔
476

477
                /* 64 character hash + separator + filename + newline */
478
                if (left < 67)
1,640✔
UNCOV
479
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Corrupt manifest at line %zu, refusing.", line_nr);
×
480

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

484
                r = unhexmem_full(p, 64, /* secure= */ false, &h.iov_base, &h.iov_len);
1,640✔
485
                if (r < 0)
1,640✔
UNCOV
486
                        return log_error_errno(r, "Failed to parse digest at manifest line %zu, refusing.", line_nr);
×
487

488
                p += 64, left -= 64;
1,640✔
489

490
                if (*p != ' ')
1,640✔
UNCOV
491
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing space separator at manifest line %zu, refusing.", line_nr);
×
492
                p++, left--;
1,640✔
493

494
                if (!IN_SET(*p, '*', ' '))
1,640✔
UNCOV
495
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing binary/text input marker at manifest line %zu, refusing.", line_nr);
×
496
                p++, left--;
1,640✔
497

498
                e = memchr(p, '\n', left);
1,640✔
499
                if (!e)
1,640✔
UNCOV
500
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Truncated manifest file at line %zu, refusing.", line_nr);
×
501
                if (e == p)
1,640✔
UNCOV
502
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty filename specified at manifest line %zu, refusing.", line_nr);
×
503

504
                fn = strndup(p, e - p);
1,640✔
505
                if (!fn)
1,640✔
UNCOV
506
                        return log_oom();
×
507

508
                if (!filename_is_valid(fn))
1,640✔
UNCOV
509
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid filename specified at manifest line %zu, refusing.", line_nr);
×
510
                if (string_has_cc(fn, NULL))
1,640✔
UNCOV
511
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Filename contains control characters at manifest line %zu, refusing.", line_nr);
×
512

513
                r = process_magic_file(fn, &h);
1,640✔
514
                if (r < 0)
1,640✔
515
                        return r;
516
                if (r == 0) {
1,638✔
517
                        /* If this isn't a magic file, then do the pattern matching */
518

519
                        r = pattern_match_many(rr->patterns, fn, &extracted_fields);
1,630✔
520
                        if (r < 0)
1,630✔
UNCOV
521
                                return log_error_errno(r, "Failed to match pattern: %m");
×
522
                        if (r == PATTERN_MATCH_YES) {
1,630✔
523
                                _cleanup_free_ char *path = NULL;
278✔
524

525
                                r = import_url_append_component(rr->path, fn, &path);
278✔
526
                                if (r < 0)
278✔
UNCOV
527
                                        return log_error_errno(r, "Failed to build instance URL: %m");
×
528

529
                                r = resource_add_instance(rr, path, &extracted_fields, &instance);
278✔
530
                                if (r < 0)
278✔
531
                                        return r;
532

533
                                assert(h.iov_len == sizeof(instance->metadata.sha256sum));
278✔
534

535
                                if (instance->metadata.sha256sum_set) {
278✔
UNCOV
536
                                        if (memcmp(instance->metadata.sha256sum, h.iov_base, h.iov_len) != 0)
×
UNCOV
537
                                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SHA256 sum parsed from filename and manifest don't match at line %zu, refusing.", line_nr);
×
538
                                } else {
539
                                        memcpy(instance->metadata.sha256sum, h.iov_base, h.iov_len);
278✔
540
                                        instance->metadata.sha256sum_set = true;
278✔
541
                                }
542
                        }
543
                }
544

545
                left -= (e - p) + 1;
1,638✔
546
                p = e + 1;
1,638✔
547

548
                line_nr++;
1,638✔
549
        }
550

551
        if (!ci && web_cache) {
36✔
552
                r = web_cache_add_item(web_cache, rr->path, verify, manifest, manifest_size);
18✔
553
                if (r < 0)
18✔
554
                        log_debug_errno(r, "Failed to add manifest '%s' to cache, ignoring: %m", rr->path);
38✔
555
                else
556
                        log_debug("Added manifest '%s' to cache.", rr->path);
18✔
557
        }
558

559
        return 0;
560
}
561

562
static int instance_cmp(Instance *const*a, Instance *const*b) {
4,626✔
563
        int r;
4,626✔
564

565
        assert(a);
4,626✔
566
        assert(b);
4,626✔
567
        assert(*a);
4,626✔
568
        assert(*b);
4,626✔
569
        assert((*a)->metadata.version);
4,626✔
570
        assert((*b)->metadata.version);
4,626✔
571

572
        /* Newest version at the beginning */
573
        r = strverscmp_improved((*a)->metadata.version, (*b)->metadata.version);
4,626✔
574
        if (r != 0)
4,626✔
575
                return -r;
4,626✔
576

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

583
int resource_load_instances(Resource *rr, bool verify, Hashmap **web_cache) {
1,016✔
584
        int r;
1,016✔
585

586
        assert(rr);
1,016✔
587

588
        switch (rr->type) {
1,016✔
589

590
        case RESOURCE_TAR:
612✔
591
        case RESOURCE_REGULAR_FILE:
592
                r = resource_load_from_directory(rr, S_IFREG);
612✔
593
                break;
612✔
594

595
        case RESOURCE_DIRECTORY:
162✔
596
        case RESOURCE_SUBVOLUME:
597
                r = resource_load_from_directory(rr, S_IFDIR);
162✔
598
                break;
162✔
599

600
        case RESOURCE_PARTITION:
204✔
601
                r = resource_load_from_blockdev(rr);
204✔
602
                break;
204✔
603

604
        case RESOURCE_URL_FILE:
38✔
605
        case RESOURCE_URL_TAR:
606
                r = resource_load_from_web(rr, verify, web_cache);
38✔
607
                break;
38✔
608

UNCOV
609
        default:
×
UNCOV
610
                assert_not_reached();
×
611
        }
612
        if (r < 0)
1,016✔
613
                return r;
614

615
        typesafe_qsort(rr->instances, rr->n_instances, instance_cmp);
1,014✔
616
        return 0;
1,014✔
617
}
618

619
static int instance_version_match(Instance *const*a, Instance *const*b) {
6,862✔
620
        assert(a);
6,862✔
621
        assert(b);
6,862✔
622
        assert(*a);
6,862✔
623
        assert(*b);
6,862✔
624
        assert((*a)->metadata.version);
6,862✔
625
        assert((*b)->metadata.version);
6,862✔
626

627
        /* List is sorted newest-to-oldest */
628
        return -strverscmp_improved((*a)->metadata.version, (*b)->metadata.version);
6,862✔
629
}
630

631
Instance* resource_find_instance(Resource *rr, const char *version) {
3,510✔
632
        Instance key = {
3,510✔
633
                .metadata.version = (char*) version,
634
        }, *k = &key;
3,510✔
635

636
        Instance **found;
3,510✔
637
        found = typesafe_bsearch(&k, rr->instances, rr->n_instances, instance_version_match);
3,510✔
638
        if (!found)
3,510✔
639
                return NULL;
3,510✔
640

641
        return *found;
2,964✔
642
}
643

644
static int get_sysext_overlay_block(const char *p, dev_t *ret) {
×
645
        int r;
×
646

647
        assert(p);
×
UNCOV
648
        assert(ret);
×
649

650
        /* Tries to read the backing device information systemd-sysext puts in the virtual file
651
         * /usr/.systemd-sysext/backing */
652

653
        _cleanup_free_ char *j = path_join(p, ".systemd-sysext");
×
654
        if (!j)
×
655
                return log_oom_debug();
×
656

UNCOV
657
        _cleanup_close_ int fd = open(j, O_RDONLY|O_DIRECTORY);
×
658
        if (fd < 0)
×
UNCOV
659
                return log_debug_errno(errno, "Failed to open '%s': %m", j);
×
660

UNCOV
661
        r = fd_is_fs_type(fd, OVERLAYFS_SUPER_MAGIC);
×
UNCOV
662
        if (r < 0)
×
UNCOV
663
                return log_debug_errno(r, "Failed to determine backing file system of '%s': %m", j);
×
UNCOV
664
        if (r == 0)
×
UNCOV
665
                return log_debug_errno(SYNTHETIC_ERRNO(ENOTTY), "Backing file system of '%s' is not an overlayfs.", j);
×
666

667
        _cleanup_free_ char *buf = NULL;
×
UNCOV
668
        r = read_one_line_file_at(fd, "backing", &buf);
×
UNCOV
669
        if (r < 0)
×
670
                return log_debug_errno(r, "Failed to read contents of '%s/backing': %m", j);
×
671

UNCOV
672
        r = parse_devnum(buf, ret);
×
673
        if (r < 0)
×
UNCOV
674
                return log_debug_errno(r, "Failed to parse contents of '%s/backing': %m", j);
×
675

UNCOV
676
        if (major(*ret) == 0) { /* not a block device? */
×
UNCOV
677
                *ret = 0;
×
UNCOV
678
                return 0;
×
679
        }
680

681
        (void) block_get_originating(*ret, ret);
×
682
        return 1;
683
}
684

685
int resource_resolve_path(
1,208✔
686
                Resource *rr,
687
                const char *root,
688
                const char *relative_to_directory,
689
                const char *node) {
690

691
        _cleanup_free_ char *p = NULL;
1,208✔
692
        dev_t d;
1,208✔
693
        int r;
1,208✔
694

695
        assert(rr);
1,208✔
696

697
        if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_ESP, PATH_RELATIVE_TO_XBOOTLDR, PATH_RELATIVE_TO_BOOT) &&
1,208✔
698
            !IN_SET(rr->type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY))
300✔
UNCOV
699
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
700
                                       "Paths relative to %s are only allowed for regular-file or directory resources.",
701
                                       path_relative_to_to_string(rr->path_relative_to));
702

703
        if (rr->path_auto) {
1,208✔
704
                /* NB: If the root mount has been replaced by some form of volatile file system (overlayfs),
705
                 * the original root block device node is symlinked in /run/systemd/volatile-root. Let's
706
                 * follow that link here. If that doesn't exist, we check the backing device of "/usr". We
707
                 * don't actually check the backing device of the root fs "/", in order to support
708
                 * environments where the root fs is a tmpfs, and the OS itself placed exclusively in
709
                 * /usr/. */
710

UNCOV
711
                if (rr->type != RESOURCE_PARTITION)
×
UNCOV
712
                        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
×
713
                                               "Automatic root path discovery only supported for partition resources.");
714

UNCOV
715
                if (node) { /* If --image= is specified, directly use the loopback device */
×
UNCOV
716
                        r = free_and_strdup_warn(&rr->path, node);
×
UNCOV
717
                        if (r < 0)
×
718
                                return r;
719

UNCOV
720
                        return 0;
×
721
                }
722

UNCOV
723
                if (root)
×
UNCOV
724
                        return log_error_errno(SYNTHETIC_ERRNO(EPERM),
×
725
                                               "Block device is not allowed when using --root= mode.");
726

UNCOV
727
                struct stat orig_root_stats;
×
UNCOV
728
                r = RET_NERRNO(stat("/run/systemd/volatile-root", &orig_root_stats));
×
UNCOV
729
                if (r < 0) {
×
730
                        if (r != -ENOENT)
×
UNCOV
731
                                return log_error_errno(r, "Failed to stat /run/systemd/volatile-root: %m");
×
732

733
                        /* volatile-root not found */
UNCOV
734
                        r = get_block_device_harder("/usr/", &d);
×
UNCOV
735
                        if (r == 0) /* Not backed by a block device? Let's see if this is a sysext overlayfs instance */
×
UNCOV
736
                                r = get_sysext_overlay_block("/usr/", &d);
×
737
                        if (r < 0)
×
738
                                return log_error_errno(r, "Failed to determine block device of file system: %m");
×
739
                } else if (!S_ISBLK(orig_root_stats.st_mode)) /* symlink was present but not block device */
×
740
                        return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "/run/systemd/volatile-root is not linked to a block device.");
×
741
                else /* symlink was present and a block device */
UNCOV
742
                        d = orig_root_stats.st_rdev;
×
743

744
        } else if (rr->type == RESOURCE_PARTITION) {
1,208✔
745
                _cleanup_close_ int fd = -EBADF, real_fd = -EBADF;
408✔
746
                _cleanup_free_ char *resolved = NULL;
204✔
747
                struct stat st;
204✔
748

749
                r = chase(rr->path, root, CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS, &resolved, &fd);
204✔
750
                if (r < 0)
204✔
751
                        return log_error_errno(r, "Failed to resolve '%s': %m", rr->path);
×
752

753
                if (fstat(fd, &st) < 0)
204✔
754
                        return log_error_errno(errno, "Failed to stat '%s': %m", resolved);
×
755

756
                if (S_ISBLK(st.st_mode) && root)
204✔
UNCOV
757
                        return log_error_errno(SYNTHETIC_ERRNO(EPERM), "When using --root= or --image= access to device nodes is prohibited.");
×
758

759
                if (S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) {
204✔
760
                        /* Not a directory, hence no need to find backing block device for the path */
761
                        free_and_replace(rr->path, resolved);
204✔
762
                        return 0;
204✔
763
                }
764

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

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

UNCOV
773
                        return 0;
×
774
                }
775

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

UNCOV
780
                r = get_block_device_harder_fd(real_fd, &d);
×
UNCOV
781
                if (r < 0)
×
UNCOV
782
                        return log_error_errno(r, "Failed to determine block device of file system: %m");
×
783

784
        } else if (RESOURCE_IS_FILESYSTEM(rr->type)) {
2,172✔
785
                _cleanup_free_ char *resolved = NULL, *relative_to = NULL;
964✔
786
                ChaseFlags chase_flags = CHASE_NONEXISTENT | CHASE_PREFIX_ROOT | CHASE_TRIGGER_AUTOFS;
964✔
787

788
                if (rr->path_relative_to == PATH_RELATIVE_TO_EXPLICIT) {
964✔
UNCOV
789
                        assert(relative_to_directory);
×
790

UNCOV
791
                        relative_to = strdup(relative_to_directory);
×
UNCOV
792
                        if (!relative_to)
×
UNCOV
793
                                return log_oom();
×
794
                } else if (rr->path_relative_to == PATH_RELATIVE_TO_ROOT) {
964✔
795
                        relative_to = strdup(empty_to_root(root));
664✔
796
                        if (!relative_to)
664✔
UNCOV
797
                                return log_oom();
×
798
                } else { /* boot, esp, or xbootldr */
799
                        r = 0;
300✔
800
                        if (IN_SET(rr->path_relative_to, PATH_RELATIVE_TO_BOOT, PATH_RELATIVE_TO_XBOOTLDR))
300✔
801
                                r = find_xbootldr_and_warn(root, NULL, /* unprivileged_mode= */ -1, &relative_to, NULL, NULL);
300✔
802
                        if (r == -ENOKEY || rr->path_relative_to == PATH_RELATIVE_TO_ESP)
300✔
UNCOV
803
                                r = find_esp_and_warn(root, NULL, -1, &relative_to, NULL, NULL, NULL, NULL, NULL);
×
804
                        if (r < 0)
300✔
UNCOV
805
                                return log_error_errno(r, "Failed to resolve $BOOT: %m");
×
806
                        log_debug("Resolved $BOOT to '%s'", relative_to);
300✔
807

808
                        /* Since this partition is read from EFI, there should be no symlinks */
809
                        chase_flags |= CHASE_PROHIBIT_SYMLINKS;
810
                }
811

812
                r = chase(rr->path, relative_to, chase_flags, &resolved, NULL);
964✔
813
                if (r < 0)
964✔
UNCOV
814
                        return log_error_errno(r, "Failed to resolve '%s' (relative to '%s'): %m", rr->path, relative_to);
×
815

816
                free_and_replace(rr->path, resolved);
964✔
817
                return 0;
964✔
818
        } else
819
                return 0; /* Otherwise assume there's nothing to resolve */
820

UNCOV
821
        r = block_get_whole_disk(d, &d);
×
UNCOV
822
        if (r < 0)
×
UNCOV
823
                return log_error_errno(r, "Failed to find whole disk device for partition backing file system: %m");
×
UNCOV
824
        if (r == 0)
×
UNCOV
825
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
826
                                       "File system is not placed on a partition block device, cannot determine whole block device backing root file system.");
827

UNCOV
828
        r = devname_from_devnum(S_IFBLK, d, &p);
×
UNCOV
829
        if (r < 0)
×
830
                return r;
831

UNCOV
832
        if (rr->path)
×
UNCOV
833
                log_info("Automatically discovered block device '%s' from '%s'.", p, rr->path);
×
834
        else
UNCOV
835
                log_info("Automatically discovered root block device '%s'.", p);
×
836

UNCOV
837
        free_and_replace(rr->path, p);
×
UNCOV
838
        return 1;
×
839
}
840

841
static const char *resource_type_table[_RESOURCE_TYPE_MAX] = {
842
        [RESOURCE_URL_FILE]     = "url-file",
843
        [RESOURCE_URL_TAR]      = "url-tar",
844
        [RESOURCE_TAR]          = "tar",
845
        [RESOURCE_PARTITION]    = "partition",
846
        [RESOURCE_REGULAR_FILE] = "regular-file",
847
        [RESOURCE_DIRECTORY]    = "directory",
848
        [RESOURCE_SUBVOLUME]    = "subvolume",
849
};
850

851
DEFINE_STRING_TABLE_LOOKUP(resource_type, ResourceType);
1,516✔
852

853
static const char *path_relative_to_table[_PATH_RELATIVE_TO_MAX] = {
854
        [PATH_RELATIVE_TO_ROOT]     = "root",
855
        [PATH_RELATIVE_TO_ESP]      = "esp",
856
        [PATH_RELATIVE_TO_XBOOTLDR] = "xbootldr",
857
        [PATH_RELATIVE_TO_BOOT]     = "boot",
858
        [PATH_RELATIVE_TO_EXPLICIT] = "explicit",
859
};
860

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