• 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

53.78
/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) {
72,504✔
42
        LIST_CLEAR(elements, e, free);
305,606✔
43

44
        return NULL;
72,504✔
45
}
46

47
DEFINE_TRIVIAL_CLEANUP_FUNC(PatternElement*, pattern_element_free_all);
151,744✔
48

49
static PatternElementType pattern_element_type_from_char(char c) {
85,300✔
50
        switch (c) {
85,300✔
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,356✔
64
                return PATTERN_TRIES_DONE;
2,356✔
65
        case 'l':
3,704✔
66
                return PATTERN_TRIES_LEFT;
3,704✔
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) {
988,468✔
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)
988,468✔
86
                return false;
87

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

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

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

101
        assert(pattern);
79,240✔
102

103
        l = strlen(pattern);
79,240✔
104

105
        for (const char *e = pattern; *e != 0; e++) {
1,244,730✔
106
                if (*e == '@') {
1,165,490✔
107
                        if (!at) {
85,300✔
108
                                at = true;
85,300✔
109
                                continue;
85,300✔
110
                        }
111

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

115
                } else if (at) {
1,080,190✔
116
                        PatternElementType t;
85,300✔
117
                        uint64_t bit;
85,300✔
118

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

123
                        bit = UINT64_C(1) << t;
85,300✔
124
                        if (mask_found & bit)
85,300✔
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)
85,300✔
130
                                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Found two pattern field markers without separating literal.");
×
131

132
                        if (ret) {
85,300✔
133
                                PatternElement *z;
77,124✔
134

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

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

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

149
                if (*e == '/') {
994,890✔
150
                        if (ret) {
6,422✔
151
                                PatternElement *z;
5,462✔
152

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

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

162
                        last_literal = false;
6,422✔
163
                        last_slash = true;
6,422✔
164
                        continue ;
6,422✔
165
                }
166

167
                if (!valid_char(*e))
988,468✔
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;
988,468✔
174
                last_slash = false;
988,468✔
175

176
                if (!ret)
988,468✔
177
                        continue;
89,600✔
178

179
                if (!last || last->type != PATTERN_LITERAL) {
898,868✔
180
                        PatternElement *z;
150,516✔
181

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

186
                        z->type = PATTERN_LITERAL;
150,516✔
187
                        k = 0;
150,516✔
188

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

193
                assert(last);
150,516✔
194
                assert(last->type == PATTERN_LITERAL);
898,868✔
195

196
                last->literal[k++] = *e;
898,868✔
197
        }
198

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

204
        if (ret)
79,240✔
205
                *ret = TAKE_PTR(first);
72,504✔
206

207
        return 0;
208
}
209

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

216
        assert(pattern);
71,910✔
217
        assert(s);
71,910✔
218

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

223
        p = s;
71,910✔
224
        LIST_FOREACH(elements, e, elements) {
134,260✔
225
                _cleanup_free_ char *t = NULL;
74,600✔
226
                const char *n;
118,562✔
227

228
                if (e->type == PATTERN_SLASH) {
118,562✔
229
                        if (*p == '/') {
3,548✔
230
                                ++p;
1,812✔
231
                                continue;
1,812✔
232
                        } else if (*p == '\0')
1,736✔
233
                                goto retry;
1,736✔
234
                        else
235
                                goto nope;
×
236
                }
237

238
                if (e->type == PATTERN_LITERAL) {
115,014✔
239
                        const char *k;
89,620✔
240

241
                        /* Skip literal fields */
242
                        k = startswith(p, e->literal);
89,620✔
243
                        if (!k)
89,620✔
244
                                goto nope;
49,206✔
245

246
                        p = k;
40,414✔
247
                        continue;
40,414✔
248
                }
249

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

254
                        n = strstr(p, e->elements_next->literal);
21,168✔
255
                        if (!n)
21,168✔
256
                                goto nope;
5,270✔
257

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

265
                switch (e->type) {
20,124✔
266

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

273
                        assert(!found.version);
18,340✔
274
                        found.version = TAKE_PTR(t);
18,340✔
275
                        break;
18,340✔
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: {
892✔
348
                        uint64_t u;
892✔
349

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

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

361
                case PATTERN_TRIES_LEFT: {
892✔
362
                        uint64_t u;
892✔
363

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

370
                        assert(found.tries_left == UINT64_MAX);
892✔
371
                        found.tries_left = u;
892✔
372
                        break;
892✔
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_se("unexpected pattern element");
20,124✔
424
                }
425

426
                p = n;
20,124✔
427
        }
428

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

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

438
        return PATTERN_MATCH_YES;
439

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

444
        return PATTERN_MATCH_NO;
445

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

450
        return PATTERN_MATCH_RETRY;
451
}
452

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

457
        STRV_FOREACH(p, patterns) {
125,518✔
458
                r = pattern_match(*p, s, &found);
71,910✔
459
                if (r < 0)
71,910✔
460
                        return r;
461
                if (r > 0) {
71,910✔
462
                        if (ret) {
16,566✔
463
                                *ret = found;
16,566✔
464
                                found = (InstanceMetadata) INSTANCE_METADATA_NULL;
16,566✔
465
                        }
466

467
                        return r;
468
                }
469
        }
470

471
        if (ret)
53,608✔
472
                *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
53,608✔
473

474
        return PATTERN_MATCH_NO;
475
}
476

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

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

486
        return true;
487
}
488

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

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

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

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

506
        LIST_FOREACH(elements, e, elements) {
2,752✔
507

508
                switch (e->type) {
2,158✔
509

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

514
                        break;
515

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

520
                        break;
521

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

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

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

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

UNCOV
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

UNCOV
546
                        r = strextendf(&j, "%" PRIx64, fields->partition_flags);
×
UNCOV
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

UNCOV
556
                        r = strextendf(&j, "%" PRIu64, fields->mtime);
×
UNCOV
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

UNCOV
566
                        r = strextendf(&j, "%03o", fields->mode);
×
UNCOV
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

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

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

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

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

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

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

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

606
                        break;
607

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

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

615
                        break;
616

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

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

624
                        break;
625

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

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

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

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

639
                        break;
×
640
                }
641

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

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