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

systemd / systemd / 26068670564

18 May 2026 09:43PM UTC coverage: 72.667% (-0.03%) from 72.698%
26068670564

push

github

poettering
update NEWS

331203 of 455781 relevant lines covered (72.67%)

1343187.38 hits per line

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

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

3
#include "alloc-util.h"
4
#include "hexdecoct.h"
5
#include "list.h"
6
#include "log.h"
7
#include "parse-util.h"
8
#include "string-util.h"
9
#include "strv.h"
10
#include "sysupdate-instance.h"
11
#include "sysupdate-pattern.h"
12
#include "time-util.h"
13

14
typedef enum PatternElementType {
15
        PATTERN_LITERAL,
16
        PATTERN_VERSION,
17
        PATTERN_PARTITION_UUID,
18
        PATTERN_PARTITION_FLAGS,
19
        PATTERN_MTIME,
20
        PATTERN_MODE,
21
        PATTERN_SIZE,
22
        PATTERN_TRIES_DONE,
23
        PATTERN_TRIES_LEFT,
24
        PATTERN_NO_AUTO,
25
        PATTERN_READ_ONLY,
26
        PATTERN_GROWFS,
27
        PATTERN_SHA256SUM,
28
        PATTERN_SLASH,
29
        _PATTERN_ELEMENT_TYPE_MAX,
30
        _PATTERN_ELEMENT_TYPE_INVALID = -EINVAL,
31
} PatternElementType;
32

33
typedef struct PatternElement PatternElement;
34

35
struct PatternElement {
36
        PatternElementType type;
37
        LIST_FIELDS(PatternElement, elements);
38
        char literal[];
39
};
40

41
static PatternElement *pattern_element_free_all(PatternElement *e) {
165,972✔
42
        LIST_CLEAR(elements, e, free);
681,644✔
43

44
        return NULL;
165,972✔
45
}
46

47
DEFINE_TRIVIAL_CLEANUP_FUNC(PatternElement*, pattern_element_free_all);
342,150✔
48

49
static PatternElementType pattern_element_type_from_char(char c) {
183,130✔
50
        switch (c) {
183,130✔
51
        case 'v':
52
                return PATTERN_VERSION;
53
        case 'u':
×
54
                return PATTERN_PARTITION_UUID;
×
55
        case 'f':
×
56
                return PATTERN_PARTITION_FLAGS;
×
57
        case 't':
×
58
                return PATTERN_MTIME;
×
59
        case 'm':
×
60
                return PATTERN_MODE;
×
61
        case 's':
×
62
                return PATTERN_SIZE;
×
63
        case 'd':
2,700✔
64
                return PATTERN_TRIES_DONE;
2,700✔
65
        case 'l':
4,252✔
66
                return PATTERN_TRIES_LEFT;
4,252✔
67
        case 'a':
×
68
                return PATTERN_NO_AUTO;
×
69
        case 'r':
×
70
                return PATTERN_READ_ONLY;
×
71
        case 'g':
×
72
                return PATTERN_GROWFS;
×
73
        case 'h':
×
74
                return PATTERN_SHA256SUM;
×
75
        default:
×
76
                return _PATTERN_ELEMENT_TYPE_INVALID;
×
77
        }
78
}
79

80
static bool valid_char(char x) {
2,297,142✔
81

82
        /* Let's refuse control characters here, and let's reserve some characters typically used in pattern
83
         * languages so that we can use them later, possibly. */
84

85
        if ((unsigned) x < ' ' || x >= 127)
2,297,142✔
86
                return false;
87

88
        return !IN_SET(x, '$', '*', '?', '[', ']', '!', '\\', '|');
2,297,142✔
89
}
90

91
static int pattern_split(
176,178✔
92
                const char *pattern,
93
                PatternElement **ret) {
94

95
        _cleanup_(pattern_element_free_allp) PatternElement *first = NULL;
176,178✔
96
        bool at = false, last_literal = true, last_slash = false;
176,178✔
97
        PatternElement *last = NULL;
176,178✔
98
        uint64_t mask_found = 0;
176,178✔
99
        size_t l, k = 0;
176,178✔
100

101
        assert(pattern);
176,178✔
102

103
        l = strlen(pattern);
176,178✔
104

105
        for (const char *e = pattern; *e != 0; e++) {
2,846,904✔
106
                if (*e == '@') {
2,670,726✔
107
                        if (!at) {
183,130✔
108
                                at = true;
183,130✔
109
                                continue;
183,130✔
110
                        }
111

112
                        /* Two at signs in a sequence, write out one */
113
                        at = false;
114

115
                } else if (at) {
2,487,596✔
116
                        PatternElementType t;
183,130✔
117
                        uint64_t bit;
183,130✔
118

119
                        t = pattern_element_type_from_char(*e);
183,130✔
120
                        if (t < 0)
183,130✔
121
                                return log_debug_errno(t, "Unknown pattern field marker '@%c'.", *e);
×
122

123
                        bit = UINT64_C(1) << t;
183,130✔
124
                        if (mask_found & bit)
183,130✔
125
                                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Pattern field marker '@%c' appears twice in pattern.", *e);
×
126

127
                        /* We insist that two pattern field markers are separated by some literal string that
128
                         * we can use to separate the fields when parsing. */
129
                        if (!last_literal && !last_slash)
183,130✔
130
                                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Found two pattern field markers without separating literal.");
×
131

132
                        if (ret) {
183,130✔
133
                                PatternElement *z;
171,226✔
134

135
                                z = malloc(offsetof(PatternElement, literal));
171,226✔
136
                                if (!z)
171,226✔
137
                                        return -ENOMEM;
138

139
                                z->type = t;
171,226✔
140
                                LIST_INSERT_AFTER(elements, first, last, z);
171,226✔
141
                                last = z;
142
                        }
143

144
                        mask_found |= bit;
183,130✔
145
                        last_slash = last_literal = at = false;
183,130✔
146
                        continue;
183,130✔
147
                }
148

149
                if (*e == '/') {
2,304,466✔
150
                        if (ret) {
7,324✔
151
                                PatternElement *z;
6,192✔
152

153
                                z = malloc(offsetof(PatternElement, literal));
6,192✔
154
                                if (!z)
6,192✔
155
                                        return -ENOMEM;
156

157
                                z->type = PATTERN_SLASH;
6,192✔
158
                                LIST_INSERT_AFTER(elements, first, last, z);
6,192✔
159
                                last = z;
160
                        }
161

162
                        last_literal = false;
7,324✔
163
                        last_slash = true;
7,324✔
164
                        continue ;
7,324✔
165
                }
166

167
                if (!valid_char(*e))
2,297,142✔
168
                        return log_debug_errno(
×
169
                                        SYNTHETIC_ERRNO(EBADRQC),
170
                                        "Invalid character 0x%0x in pattern, refusing.",
171
                                        (unsigned) *e);
172

173
                last_literal = true;
2,297,142✔
174
                last_slash = false;
2,297,142✔
175

176
                if (!ret)
2,297,142✔
177
                        continue;
140,090✔
178

179
                if (!last || last->type != PATTERN_LITERAL) {
2,157,052✔
180
                        PatternElement *z;
338,254✔
181

182
                        z = malloc0(offsetof(PatternElement, literal) + l + 1); /* l is an upper bound to all literal elements */
338,254✔
183
                        if (!z)
338,254✔
184
                                return -ENOMEM;
185

186
                        z->type = PATTERN_LITERAL;
338,254✔
187
                        k = 0;
338,254✔
188

189
                        LIST_INSERT_AFTER(elements, first, last, z);
338,254✔
190
                        last = z;
191
                }
192

193
                assert(last);
338,254✔
194
                assert(last->type == PATTERN_LITERAL);
2,157,052✔
195

196
                last->literal[k++] = *e;
2,157,052✔
197
        }
198

199
        if (at)
176,178✔
200
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Trailing @ character found, refusing.");
×
201
        if (!(mask_found & (UINT64_C(1) << PATTERN_VERSION)))
176,178✔
202
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Version field marker '@v' not specified in pattern, refusing.");
×
203

204
        if (ret)
176,178✔
205
                *ret = TAKE_PTR(first);
165,972✔
206

207
        return 0;
208
}
209

210
int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) {
164,991✔
211
        _cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
164,991✔
212
        _cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
164,991✔
213
        const char *p;
164,991✔
214
        int r;
164,991✔
215

216
        assert(pattern);
164,991✔
217
        assert(s);
164,991✔
218

219
        r = pattern_split(pattern, &elements);
164,991✔
220
        if (r < 0)
164,991✔
221
                return r;
222

223
        p = s;
164,991✔
224
        LIST_FOREACH(elements, e, elements) {
282,868✔
225
                _cleanup_free_ char *t = NULL;
168,035✔
226
                const char *n;
250,841✔
227

228
                if (e->type == PATTERN_SLASH) {
250,841✔
229
                        if (*p == '/') {
4,020✔
230
                                ++p;
2,048✔
231
                                continue;
2,048✔
232
                        } else if (*p == '\0')
1,972✔
233
                                goto retry;
1,972✔
234
                        else
235
                                goto nope;
×
236
                }
237

238
                if (e->type == PATTERN_LITERAL) {
246,821✔
239
                        const char *k;
199,360✔
240

241
                        /* Skip literal fields */
242
                        k = startswith(p, e->literal);
199,360✔
243
                        if (!k)
199,360✔
244
                                goto nope;
120,574✔
245

246
                        p = k;
78,786✔
247
                        continue;
78,786✔
248
                }
249

250
                if (e->elements_next) {
47,461✔
251
                        /* The next element must be literal, as we use it to determine where to split */
252
                        assert(e->elements_next->type == PATTERN_LITERAL);
42,739✔
253

254
                        n = strstr(p, e->elements_next->literal);
42,739✔
255
                        if (!n)
42,739✔
256
                                goto nope;
10,418✔
257

258
                } else
259
                        /* End of the string */
260
                        assert_se(n = strchr(p, 0));
4,722✔
261
                t = strndup(p, n - p);
37,043✔
262
                if (!t)
37,043✔
263
                        return -ENOMEM;
264

265
                switch (e->type) {
37,043✔
266

267
                case PATTERN_VERSION:
35,023✔
268
                        if (!version_is_valid(t)) {
35,023✔
269
                                log_debug("Version string is not valid, refusing: %s", t);
×
270
                                goto nope;
×
271
                        }
272

273
                        assert(!found.version);
35,023✔
274
                        found.version = TAKE_PTR(t);
35,023✔
275
                        break;
35,023✔
276

277
                case PATTERN_PARTITION_UUID: {
×
278
                        sd_id128_t id;
×
279

280
                        if (sd_id128_from_string(t, &id) < 0)
×
281
                                goto nope;
×
282

283
                        assert(!found.partition_uuid_set);
×
284
                        found.partition_uuid = id;
×
285
                        found.partition_uuid_set = true;
×
286
                        break;
×
287
                }
288

289
                case PATTERN_PARTITION_FLAGS: {
×
290
                        uint64_t f;
×
291

292
                        if (safe_atoux64(t, &f) < 0)
×
293
                                goto nope;
×
294

295
                        if (found.partition_flags_set && found.partition_flags != f)
×
296
                                goto nope;
×
297

298
                        assert(!found.partition_flags_set);
×
299
                        found.partition_flags = f;
×
300
                        found.partition_flags_set = true;
×
301
                        break;
×
302
                }
303

304
                case PATTERN_MTIME: {
×
305
                        uint64_t v;
×
306

307
                        if (safe_atou64(t, &v) < 0)
×
308
                                goto nope;
×
309
                        if (v == USEC_INFINITY) /* Don't permit our internal special infinity value */
×
310
                                goto nope;
×
311
                        if (v / 1000000U > TIME_T_MAX) /* Make sure this fits in a timespec structure */
×
312
                                goto nope;
313

314
                        assert(found.mtime == USEC_INFINITY);
×
315
                        found.mtime = v;
×
316
                        break;
×
317
                }
318

319
                case PATTERN_MODE: {
×
320
                        mode_t m;
×
321

322
                        r = parse_mode(t, &m);
×
323
                        if (r < 0)
×
324
                                goto nope;
×
325
                        if (m & ~0775) /* Don't allow world-writable files or suid files to be generated this way */
×
326
                                goto nope;
×
327

328
                        assert(found.mode == MODE_INVALID);
×
329
                        found.mode = m;
×
330
                        break;
×
331
                }
332

333
                case PATTERN_SIZE: {
×
334
                        uint64_t u;
×
335

336
                        r = safe_atou64(t, &u);
×
337
                        if (r < 0)
×
338
                                goto nope;
×
339
                        if (u == UINT64_MAX)
×
340
                                goto nope;
×
341

342
                        assert(found.size == UINT64_MAX);
×
343
                        found.size = u;
×
344
                        break;
×
345
                }
346

347
                case PATTERN_TRIES_DONE: {
1,010✔
348
                        uint64_t u;
1,010✔
349

350
                        r = safe_atou64(t, &u);
1,010✔
351
                        if (r < 0)
1,010✔
352
                                goto nope;
×
353
                        if (u == UINT64_MAX)
1,010✔
354
                                goto nope;
×
355

356
                        assert(found.tries_done == UINT64_MAX);
1,010✔
357
                        found.tries_done = u;
1,010✔
358
                        break;
1,010✔
359
                }
360

361
                case PATTERN_TRIES_LEFT: {
1,010✔
362
                        uint64_t u;
1,010✔
363

364
                        r = safe_atou64(t, &u);
1,010✔
365
                        if (r < 0)
1,010✔
366
                                goto nope;
×
367
                        if (u == UINT64_MAX)
1,010✔
368
                                goto nope;
×
369

370
                        assert(found.tries_left == UINT64_MAX);
1,010✔
371
                        found.tries_left = u;
1,010✔
372
                        break;
1,010✔
373
                }
374

375
                case PATTERN_NO_AUTO:
×
376
                        r = parse_boolean(t);
×
377
                        if (r < 0)
×
378
                                goto nope;
×
379

380
                        assert(found.no_auto < 0);
×
381
                        found.no_auto = r;
×
382
                        break;
×
383

384
                case PATTERN_READ_ONLY:
×
385
                        r = parse_boolean(t);
×
386
                        if (r < 0)
×
387
                                goto nope;
×
388

389
                        assert(found.read_only < 0);
×
390
                        found.read_only = r;
×
391
                        break;
×
392

393
                case PATTERN_GROWFS:
×
394
                        r = parse_boolean(t);
×
395
                        if (r < 0)
×
396
                                goto nope;
×
397

398
                        assert(found.growfs < 0);
×
399
                        found.growfs = r;
×
400
                        break;
×
401

402
                case PATTERN_SHA256SUM: {
×
403
                        _cleanup_free_ void *d = NULL;
×
404
                        size_t l;
×
405

406
                        if (strlen(t) != sizeof(found.sha256sum) * 2)
×
407
                                goto nope;
×
408

409
                        r = unhexmem_full(t, sizeof(found.sha256sum) * 2, /* secure= */ false, &d, &l);
×
410
                        if (r == -ENOMEM)
×
411
                                return r;
×
412
                        if (r < 0)
×
413
                                goto nope;
×
414

415
                        assert(!found.sha256sum_set);
×
416
                        assert(l == sizeof(found.sha256sum));
×
417
                        memcpy(found.sha256sum, d, l);
×
418
                        found.sha256sum_set = true;
×
419
                        break;
×
420
                }
421

422
                default:
×
423
                        assert_not_reached();
×
424
                }
425

426
                p = n;
37,043✔
427
        }
428

429
        /* We matched the whole pattern, but if the string continues over the end of the pattern, refuse */
430
        if (*p != '\0')
32,027✔
431
                goto nope;
5,140✔
432

433
        if (ret) {
26,887✔
434
                *ret = found;
26,887✔
435
                found = (InstanceMetadata) INSTANCE_METADATA_NULL;
26,887✔
436
        }
437

438
        return PATTERN_MATCH_YES;
439

440
nope:
136,132✔
441
        if (ret)
136,132✔
442
                *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
136,132✔
443

444
        return PATTERN_MATCH_NO;
445

446
retry:
1,972✔
447
        if (ret)
1,972✔
448
                *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
1,972✔
449

450
        return PATTERN_MATCH_RETRY;
451
}
452

453
int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret) {
163,019✔
454
        _cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
163,019✔
455
        int r;
163,019✔
456

457
        STRV_FOREACH(p, patterns) {
299,151✔
458
                r = pattern_match(*p, s, &found);
164,991✔
459
                if (r < 0)
164,991✔
460
                        return r;
461
                if (r > 0) {
164,991✔
462
                        if (ret) {
28,859✔
463
                                *ret = found;
28,859✔
464
                                found = (InstanceMetadata) INSTANCE_METADATA_NULL;
28,859✔
465
                        }
466

467
                        return r;
468
                }
469
        }
470

471
        if (ret)
134,160✔
472
                *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
134,160✔
473

474
        return PATTERN_MATCH_NO;
475
}
476

477
int pattern_valid(const char *pattern) {
10,206✔
478
        int r;
10,206✔
479

480
        r = pattern_split(pattern, NULL);
10,206✔
481
        if (r == -EINVAL)
10,206✔
482
                return false;
483
        if (r < 0)
10,206✔
484
                return r;
×
485

486
        return true;
487
}
488

489
int pattern_format(
981✔
490
                const char *pattern,
491
                const InstanceMetadata *fields,
492
                char **ret) {
493

494
        _cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
×
495
        _cleanup_free_ char *j = NULL;
981✔
496
        int r;
981✔
497

498
        assert(pattern);
981✔
499
        assert(fields);
981✔
500
        assert(ret);
981✔
501

502
        r = pattern_split(pattern, &elements);
981✔
503
        if (r < 0)
981✔
504
                return r;
505

506
        LIST_FOREACH(elements, e, elements) {
4,366✔
507

508
                switch (e->type) {
3,385✔
509

510
                case PATTERN_SLASH:
152✔
511
                        if (!strextend(&j, "/"))
152✔
512
                                return -ENOMEM;
513

514
                        break;
515

516
                case PATTERN_LITERAL:
1,976✔
517
                        if (!strextend(&j, e->literal))
1,976✔
518
                                return -ENOMEM;
519

520
                        break;
521

522
                case PATTERN_VERSION:
981✔
523
                        if (!fields->version)
981✔
524
                                return -ENXIO;
525

526
                        if (!strextend(&j, fields->version))
981✔
527
                                return -ENOMEM;
528
                        break;
529

530
                case PATTERN_PARTITION_UUID: {
×
531
                        char formatted[SD_ID128_STRING_MAX];
×
532

533
                        if (!fields->partition_uuid_set)
×
534
                                return -ENXIO;
×
535

536
                        if (!strextend(&j, sd_id128_to_string(fields->partition_uuid, formatted)))
×
537
                                return -ENOMEM;
538

539
                        break;
×
540
                }
541

542
                case PATTERN_PARTITION_FLAGS:
×
543
                        if (!fields->partition_flags_set)
×
544
                                return -ENXIO;
545

546
                        r = strextendf(&j, "%" PRIx64, fields->partition_flags);
×
547
                        if (r < 0)
×
548
                                return r;
549

550
                        break;
551

552
                case PATTERN_MTIME:
×
553
                        if (fields->mtime == USEC_INFINITY)
×
554
                                return -ENXIO;
555

556
                        r = strextendf(&j, "%" PRIu64, fields->mtime);
×
557
                        if (r < 0)
×
558
                                return r;
559

560
                        break;
561

562
                case PATTERN_MODE:
×
563
                        if (fields->mode == MODE_INVALID)
×
564
                                return -ENXIO;
565

566
                        r = strextendf(&j, "%03o", fields->mode);
×
567
                        if (r < 0)
×
568
                                return r;
569

570
                        break;
571

572
                case PATTERN_SIZE:
×
573
                        if (fields->size == UINT64_MAX)
×
574
                                return -ENXIO;
575

576
                        r = strextendf(&j, "%" PRIu64, fields->size);
×
577
                        if (r < 0)
×
578
                                return r;
579
                        break;
580

581
                case PATTERN_TRIES_DONE:
138✔
582
                        if (fields->tries_done == UINT64_MAX)
138✔
583
                                return -ENXIO;
584

585
                        r = strextendf(&j, "%" PRIu64, fields->tries_done);
138✔
586
                        if (r < 0)
138✔
587
                                return r;
588
                        break;
589

590
                case PATTERN_TRIES_LEFT:
138✔
591
                        if (fields->tries_left == UINT64_MAX)
138✔
592
                                return -ENXIO;
593

594
                        r = strextendf(&j, "%" PRIu64, fields->tries_left);
138✔
595
                        if (r < 0)
138✔
596
                                return r;
597
                        break;
598

599
                case PATTERN_NO_AUTO:
×
600
                        if (fields->no_auto < 0)
×
601
                                return -ENXIO;
602

603
                        if (!strextend(&j, one_zero(fields->no_auto)))
×
604
                                return -ENOMEM;
605

606
                        break;
607

608
                case PATTERN_READ_ONLY:
×
609
                        if (fields->read_only < 0)
×
610
                                return -ENXIO;
611

612
                        if (!strextend(&j, one_zero(fields->read_only)))
×
613
                                return -ENOMEM;
614

615
                        break;
616

617
                case PATTERN_GROWFS:
×
618
                        if (fields->growfs < 0)
×
619
                                return -ENXIO;
620

621
                        if (!strextend(&j, one_zero(fields->growfs)))
×
622
                                return -ENOMEM;
623

624
                        break;
625

626
                case PATTERN_SHA256SUM: {
×
627
                        _cleanup_free_ char *h = NULL;
×
628

629
                        if (!fields->sha256sum_set)
×
630
                                return -ENXIO;
631

632
                        h = hexmem(fields->sha256sum, sizeof(fields->sha256sum));
×
633
                        if (!h)
×
634
                                return -ENOMEM;
635

636
                        if (!strextend(&j, h))
×
637
                                return -ENOMEM;
638

639
                        break;
×
640
                }
641

642
                default:
×
643
                        assert_not_reached();
×
644
                }
645
        }
646

647
        *ret = TAKE_PTR(j);
981✔
648
        return 0;
981✔
649
}
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