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

systemd / systemd / 14554080340

19 Apr 2025 11:46AM UTC coverage: 72.101% (-0.03%) from 72.13%
14554080340

push

github

web-flow
Add two new paragraphs to coding style about header files (#37188)

296880 of 411754 relevant lines covered (72.1%)

687547.52 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 "path-util.h"
9
#include "stdio-util.h"
10
#include "string-util.h"
11
#include "sysupdate-pattern.h"
12

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

32
typedef struct PatternElement PatternElement;
33

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

40
static PatternElement *pattern_element_free_all(PatternElement *e) {
15,074✔
41
        LIST_CLEAR(elements, e, free);
63,164✔
42

43
        return NULL;
15,074✔
44
}
45

46
DEFINE_TRIVIAL_CLEANUP_FUNC(PatternElement*, pattern_element_free_all);
31,444✔
47

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

79
static bool valid_char(char x) {
196,272✔
80

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

84
        if ((unsigned) x < ' ' || x >= 127)
196,272✔
85
                return false;
86

87
        return !IN_SET(x, '$', '*', '?', '[', ']', '!', '\\', '|');
196,272✔
88
}
89

90
static int pattern_split(
16,370✔
91
                const char *pattern,
92
                PatternElement **ret) {
93

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

100
        assert(pattern);
16,370✔
101

102
        l = strlen(pattern);
16,370✔
103

104
        for (const char *e = pattern; *e != 0; e++) {
248,868✔
105
                if (*e == '@') {
232,498✔
106
                        if (!at) {
17,506✔
107
                                at = true;
17,506✔
108
                                continue;
17,506✔
109
                        }
110

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

114
                } else if (at) {
214,992✔
115
                        PatternElementType t;
17,506✔
116
                        uint64_t bit;
17,506✔
117

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

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

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

131
                        if (ret) {
17,506✔
132
                                PatternElement *z;
15,934✔
133

134
                                z = malloc(offsetof(PatternElement, literal));
15,934✔
135
                                if (!z)
15,934✔
136
                                        return -ENOMEM;
137

138
                                z->type = t;
15,934✔
139
                                LIST_INSERT_AFTER(elements, first, last, z);
15,934✔
140
                                last = z;
141
                        }
142

143
                        mask_found |= bit;
17,506✔
144
                        last_slash = last_literal = at = false;
17,506✔
145
                        continue;
17,506✔
146
                }
147

148
                if (*e == '/') {
197,486✔
149
                        if (ret) {
1,214✔
150
                                PatternElement *z;
1,030✔
151

152
                                z = malloc(offsetof(PatternElement, literal));
1,030✔
153
                                if (!z)
1,030✔
154
                                        return -ENOMEM;
155

156
                                z->type = PATTERN_SLASH;
1,030✔
157
                                LIST_INSERT_AFTER(elements, first, last, z);
1,030✔
158
                                last = z;
159
                        }
160

161
                        last_literal = false;
1,214✔
162
                        last_slash = true;
1,214✔
163
                        continue ;
1,214✔
164
                }
165

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

172
                last_literal = true;
196,272✔
173
                last_slash = false;
196,272✔
174

175
                if (!ret)
196,272✔
176
                        continue;
15,236✔
177

178
                if (!last || last->type != PATTERN_LITERAL) {
181,036✔
179
                        PatternElement *z;
31,126✔
180

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

185
                        z->type = PATTERN_LITERAL;
31,126✔
186
                        k = 0;
31,126✔
187

188
                        LIST_INSERT_AFTER(elements, first, last, z);
31,126✔
189
                        last = z;
31,126✔
190
                }
191

192
                assert(last);
31,126✔
193
                assert(last->type == PATTERN_LITERAL);
181,036✔
194

195
                last->literal[k++] = *e;
181,036✔
196
        }
197

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

203
        if (ret)
16,370✔
204
                *ret = TAKE_PTR(first);
15,074✔
205

206
        return 0;
207
}
208

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

215
        assert(pattern);
15,000✔
216
        assert(s);
15,000✔
217

218
        r = pattern_split(pattern, &elements);
15,000✔
219
        if (r < 0)
15,000✔
220
                return r;
221

222
        p = s;
15,000✔
223
        LIST_FOREACH(elements, e, elements) {
27,538✔
224
                _cleanup_free_ char *t = NULL;
4,066✔
225
                const char *n;
24,312✔
226

227
                if (e->type == PATTERN_SLASH) {
24,312✔
228
                        if (*p == '/') {
672✔
229
                                ++p;
344✔
230
                                continue;
344✔
231
                        } else if (*p == '\0')
328✔
232
                                goto retry;
328✔
233
                        else
234
                                goto nope;
×
235
                }
236

237
                if (e->type == PATTERN_LITERAL) {
23,640✔
238
                        const char *k;
18,540✔
239

240
                        /* Skip literal fields */
241
                        k = startswith(p, e->literal);
18,540✔
242
                        if (!k)
18,540✔
243
                                goto nope;
10,412✔
244

245
                        p = k;
8,128✔
246
                        continue;
8,128✔
247
                }
248

249
                if (e->elements_next) {
5,100✔
250
                        /* The next element must be literal, as we use it to determine where to split */
251
                        assert(e->elements_next->type == PATTERN_LITERAL);
4,230✔
252

253
                        n = strstr(p, e->elements_next->literal);
4,230✔
254
                        if (!n)
4,230✔
255
                                goto nope;
1,034✔
256

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

264
                switch (e->type) {
4,066✔
265

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

272
                        assert(!found.version);
3,726✔
273
                        found.version = TAKE_PTR(t);
3,726✔
274
                        break;
3,726✔
275

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

346
                case PATTERN_TRIES_DONE: {
170✔
347
                        uint64_t u;
170✔
348

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

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

360
                case PATTERN_TRIES_LEFT: {
170✔
361
                        uint64_t u;
170✔
362

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

369
                        assert(found.tries_left == UINT64_MAX);
170✔
370
                        found.tries_left = u;
170✔
371
                        break;
170✔
372
                }
373

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

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

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

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

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

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

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

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

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

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

421
                default:
422
                        assert_se("unexpected pattern element");
4,066✔
423
                }
424

425
                p = n;
4,066✔
426
        }
427

428
        if (ret) {
3,226✔
429
                *ret = found;
3,226✔
430
                found = (InstanceMetadata) INSTANCE_METADATA_NULL;
3,226✔
431
        }
432

433
        return PATTERN_MATCH_YES;
434

435
nope:
11,446✔
436
        if (ret)
11,446✔
437
                *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
11,446✔
438

439
        return PATTERN_MATCH_NO;
440

441
retry:
328✔
442
        if (ret)
328✔
443
                *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
328✔
444

445
        return PATTERN_MATCH_RETRY;
446
}
447

448
int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret) {
14,672✔
449
        _cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
14,672✔
450
        int r;
14,672✔
451

452
        STRV_FOREACH(p, patterns) {
26,118✔
453
                r = pattern_match(*p, s, &found);
15,000✔
454
                if (r < 0)
15,000✔
455
                        return r;
456
                if (r > 0) {
15,000✔
457
                        if (ret) {
3,554✔
458
                                *ret = found;
3,554✔
459
                                found = (InstanceMetadata) INSTANCE_METADATA_NULL;
3,554✔
460
                        }
461

462
                        return r;
3,554✔
463
                }
464
        }
465

466
        if (ret)
11,118✔
467
                *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
11,118✔
468

469
        return PATTERN_MATCH_NO;
470
}
471

472
int pattern_valid(const char *pattern) {
1,296✔
473
        int r;
1,296✔
474

475
        r = pattern_split(pattern, NULL);
1,296✔
476
        if (r == -EINVAL)
1,296✔
477
                return false;
478
        if (r < 0)
1,296✔
479
                return r;
×
480

481
        return true;
482
}
483

484
int pattern_format(
74✔
485
                const char *pattern,
486
                const InstanceMetadata *fields,
487
                char **ret) {
488

489
        _cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
×
490
        _cleanup_free_ char *j = NULL;
74✔
491
        int r;
74✔
492

493
        assert(pattern);
74✔
494
        assert(fields);
74✔
495
        assert(ret);
74✔
496

497
        r = pattern_split(pattern, &elements);
74✔
498
        if (r < 0)
74✔
499
                return r;
500

501
        LIST_FOREACH(elements, e, elements) {
346✔
502

503
                switch (e->type) {
272✔
504

505
                case PATTERN_SLASH:
18✔
506
                        if (!strextend(&j, "/"))
18✔
507
                                return -ENOMEM;
508

509
                        break;
510

511
                case PATTERN_LITERAL:
152✔
512
                        if (!strextend(&j, e->literal))
152✔
513
                                return -ENOMEM;
514

515
                        break;
516

517
                case PATTERN_VERSION:
74✔
518
                        if (!fields->version)
74✔
519
                                return -ENXIO;
520

521
                        if (!strextend(&j, fields->version))
74✔
522
                                return -ENOMEM;
523
                        break;
524

525
                case PATTERN_PARTITION_UUID: {
×
526
                        char formatted[SD_ID128_STRING_MAX];
×
527

528
                        if (!fields->partition_uuid_set)
×
529
                                return -ENXIO;
×
530

531
                        if (!strextend(&j, sd_id128_to_string(fields->partition_uuid, formatted)))
×
532
                                return -ENOMEM;
533

534
                        break;
×
535
                }
536

537
                case PATTERN_PARTITION_FLAGS:
×
538
                        if (!fields->partition_flags_set)
×
539
                                return -ENXIO;
540

541
                        r = strextendf(&j, "%" PRIx64, fields->partition_flags);
×
542
                        if (r < 0)
×
543
                                return r;
544

545
                        break;
546

547
                case PATTERN_MTIME:
×
548
                        if (fields->mtime == USEC_INFINITY)
×
549
                                return -ENXIO;
550

551
                        r = strextendf(&j, "%" PRIu64, fields->mtime);
×
552
                        if (r < 0)
×
553
                                return r;
554

555
                        break;
556

557
                case PATTERN_MODE:
×
558
                        if (fields->mode == MODE_INVALID)
×
559
                                return -ENXIO;
560

561
                        r = strextendf(&j, "%03o", fields->mode);
×
562
                        if (r < 0)
×
563
                                return r;
564

565
                        break;
566

567
                case PATTERN_SIZE:
×
568
                        if (fields->size == UINT64_MAX)
×
569
                                return -ENXIO;
570

571
                        r = strextendf(&j, "%" PRIu64, fields->size);
×
572
                        if (r < 0)
×
573
                                return r;
574
                        break;
575

576
                case PATTERN_TRIES_DONE:
14✔
577
                        if (fields->tries_done == UINT64_MAX)
14✔
578
                                return -ENXIO;
579

580
                        r = strextendf(&j, "%" PRIu64, fields->tries_done);
14✔
581
                        if (r < 0)
14✔
582
                                return r;
583
                        break;
584

585
                case PATTERN_TRIES_LEFT:
14✔
586
                        if (fields->tries_left == UINT64_MAX)
14✔
587
                                return -ENXIO;
588

589
                        r = strextendf(&j, "%" PRIu64, fields->tries_left);
14✔
590
                        if (r < 0)
14✔
591
                                return r;
592
                        break;
593

594
                case PATTERN_NO_AUTO:
×
595
                        if (fields->no_auto < 0)
×
596
                                return -ENXIO;
597

598
                        if (!strextend(&j, one_zero(fields->no_auto)))
×
599
                                return -ENOMEM;
600

601
                        break;
602

603
                case PATTERN_READ_ONLY:
×
604
                        if (fields->read_only < 0)
×
605
                                return -ENXIO;
606

607
                        if (!strextend(&j, one_zero(fields->read_only)))
×
608
                                return -ENOMEM;
609

610
                        break;
611

612
                case PATTERN_GROWFS:
×
613
                        if (fields->growfs < 0)
×
614
                                return -ENXIO;
615

616
                        if (!strextend(&j, one_zero(fields->growfs)))
×
617
                                return -ENOMEM;
618

619
                        break;
620

621
                case PATTERN_SHA256SUM: {
×
622
                        _cleanup_free_ char *h = NULL;
×
623

624
                        if (!fields->sha256sum_set)
×
625
                                return -ENXIO;
626

627
                        h = hexmem(fields->sha256sum, sizeof(fields->sha256sum));
×
628
                        if (!h)
×
629
                                return -ENOMEM;
630

631
                        if (!strextend(&j, h))
×
632
                                return -ENOMEM;
633

634
                        break;
×
635
                }
636

637
                default:
×
638
                        assert_not_reached();
×
639
                }
640
        }
641

642
        *ret = TAKE_PTR(j);
74✔
643
        return 0;
74✔
644
}
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