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

systemd / systemd / 29060049768

09 Jul 2026 08:09PM UTC coverage: 72.702% (-0.1%) from 72.832%
29060049768

push

github

web-flow
Translations update from Fedora Weblate (#42956)

Translations update from [Fedora
Weblate](https://translate.fedoraproject.org) for
[systemd/main](https://translate.fedoraproject.org/projects/systemd/main/).



Current translation status:

![Weblate translation
status](https://translate.fedoraproject.org/widget/systemd/main/matrix-auto.svg)

342955 of 471728 relevant lines covered (72.7%)

1410039.18 hits per line

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

52.87
/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) {
336,193✔
42
        LIST_CLEAR(elements, e, free);
1,383,790✔
43

44
        return NULL;
336,193✔
45
}
46

47
DEFINE_TRIVIAL_CLEANUP_FUNC(PatternElement*, pattern_element_free_all);
694,674✔
48

49
static PatternElementType pattern_element_type_from_char(char c) {
373,645✔
50
        switch (c) {
373,645✔
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':
5,884✔
64
                return PATTERN_TRIES_DONE;
5,884✔
65
        case 'l':
9,280✔
66
                return PATTERN_TRIES_LEFT;
9,280✔
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) {
4,692,220✔
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)
4,692,220✔
86
                return false;
87

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

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

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

101
        assert(pattern);
358,481✔
102

103
        l = strlen(pattern);
358,481✔
104

105
        for (const char *e = pattern; *e != 0; e++) {
5,813,999✔
106
                if (*e == '@') {
5,455,518✔
107
                        if (!at) {
373,645✔
108
                                at = true;
373,645✔
109
                                continue;
373,645✔
110
                        }
111

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

115
                } else if (at) {
5,081,873✔
116
                        PatternElementType t;
373,645✔
117
                        uint64_t bit;
373,645✔
118

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

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

132
                        if (ret) {
373,645✔
133
                                PatternElement *z;
347,661✔
134

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

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

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

149
                if (*e == '/') {
4,708,228✔
150
                        if (ret) {
16,008✔
151
                                PatternElement *z;
13,544✔
152

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

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

162
                        last_literal = false;
16,008✔
163
                        last_slash = true;
16,008✔
164
                        continue ;
16,008✔
165
                }
166

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

176
                if (!ret)
4,692,220✔
177
                        continue;
305,502✔
178

179
                if (!last || last->type != PATTERN_LITERAL) {
4,386,718✔
180
                        PatternElement *z;
686,392✔
181

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

186
                        z->type = PATTERN_LITERAL;
686,392✔
187
                        k = 0;
686,392✔
188

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

193
                assert(last);
686,392✔
194
                assert(last->type == PATTERN_LITERAL);
4,386,718✔
195

196
                last->literal[k++] = *e;
4,386,718✔
197
        }
198

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

204
        if (ret)
358,481✔
205
                *ret = TAKE_PTR(first);
336,193✔
206

207
        return 0;
208
}
209

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

216
        assert(pattern);
334,220✔
217
        assert(s);
334,220✔
218

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

223
        p = s;
334,220✔
224
        LIST_FOREACH(elements, e, elements) {
580,890✔
225
                _cleanup_free_ char *t = NULL;
340,888✔
226
                const char *n;
514,368✔
227

228
                if (e->type == PATTERN_SLASH) {
514,368✔
229
                        if (*p == '/') {
8,816✔
230
                                ++p;
4,488✔
231
                                continue;
4,488✔
232
                        } else if (*p == '\0')
4,328✔
233
                                goto retry;
4,328✔
234
                        else
235
                                goto nope;
×
236
                }
237

238
                if (e->type == PATTERN_LITERAL) {
505,552✔
239
                        const char *k;
406,058✔
240

241
                        /* Skip literal fields */
242
                        k = startswith(p, e->literal);
406,058✔
243
                        if (!k)
406,058✔
244
                                goto nope;
241,394✔
245

246
                        p = k;
164,664✔
247
                        continue;
164,664✔
248
                }
249

250
                if (e->elements_next) {
99,494✔
251
                        if (e->elements_next->type == PATTERN_LITERAL) {
89,326✔
252
                                n = strstr(p, e->elements_next->literal);
89,326✔
253
                                if (!n)
89,326✔
254
                                        goto nope;
21,976✔
255
                        } else if (e->elements_next->type == PATTERN_SLASH)
×
256
                                n = strchrnul(p, '/');
×
257
                        else
258
                                assert_not_reached();
×
259

260
                } else
261
                        /* End of the string */
262
                        assert_se(n = strchr(p, 0));
10,168✔
263
                t = strndup(p, n - p);
77,518✔
264
                if (!t)
77,518✔
265
                        return -ENOMEM;
266

267
                switch (e->type) {
77,518✔
268

269
                case PATTERN_VERSION:
73,094✔
270
                        if (!version_is_valid(t)) {
73,094✔
271
                                log_debug("Version string is not valid, refusing: %s", t);
×
272
                                goto nope;
×
273
                        }
274

275
                        assert(!found.version);
73,094✔
276
                        found.version = TAKE_PTR(t);
73,094✔
277
                        break;
73,094✔
278

279
                case PATTERN_PARTITION_UUID: {
×
280
                        sd_id128_t id;
×
281

282
                        if (sd_id128_from_string(t, &id) < 0)
×
283
                                goto nope;
×
284

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

291
                case PATTERN_PARTITION_FLAGS: {
×
292
                        uint64_t f;
×
293

294
                        if (safe_atoux64(t, &f) < 0)
×
295
                                goto nope;
×
296

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

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

306
                case PATTERN_MTIME: {
×
307
                        uint64_t v;
×
308

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

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

321
                case PATTERN_MODE: {
×
322
                        mode_t m;
×
323

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

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

335
                case PATTERN_SIZE: {
×
336
                        uint64_t u;
×
337

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

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

349
                case PATTERN_TRIES_DONE: {
2,212✔
350
                        uint64_t u;
2,212✔
351

352
                        r = safe_atou64(t, &u);
2,212✔
353
                        if (r < 0)
2,212✔
354
                                goto nope;
×
355
                        if (u == UINT64_MAX)
2,212✔
356
                                goto nope;
×
357

358
                        assert(found.tries_done == UINT64_MAX);
2,212✔
359
                        found.tries_done = u;
2,212✔
360
                        break;
2,212✔
361
                }
362

363
                case PATTERN_TRIES_LEFT: {
2,212✔
364
                        uint64_t u;
2,212✔
365

366
                        r = safe_atou64(t, &u);
2,212✔
367
                        if (r < 0)
2,212✔
368
                                goto nope;
×
369
                        if (u == UINT64_MAX)
2,212✔
370
                                goto nope;
×
371

372
                        assert(found.tries_left == UINT64_MAX);
2,212✔
373
                        found.tries_left = u;
2,212✔
374
                        break;
2,212✔
375
                }
376

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

382
                        assert(found.no_auto < 0);
×
383
                        found.no_auto = r;
×
384
                        break;
×
385

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

391
                        assert(found.read_only < 0);
×
392
                        found.read_only = r;
×
393
                        break;
×
394

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

400
                        assert(found.growfs < 0);
×
401
                        found.growfs = r;
×
402
                        break;
×
403

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

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

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

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

424
                default:
×
425
                        assert_not_reached();
×
426
                }
427

428
                p = n;
77,518✔
429
        }
430

431
        /* We matched the whole pattern, but if the string continues over the end of the pattern, refuse */
432
        if (*p != '\0')
66,522✔
433
                goto nope;
10,844✔
434

435
        if (ret) {
55,678✔
436
                *ret = found;
55,657✔
437
                found = (InstanceMetadata) INSTANCE_METADATA_NULL;
55,657✔
438
        }
439

440
        return PATTERN_MATCH_YES;
441

442
nope:
274,214✔
443
        if (ret)
274,214✔
444
                *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
274,201✔
445

446
        return PATTERN_MATCH_NO;
447

448
retry:
4,328✔
449
        if (ret)
4,328✔
450
                *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
4,328✔
451

452
        return PATTERN_MATCH_RETRY;
453
}
454

455
int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret) {
329,858✔
456
        _cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
329,858✔
457
        int r;
329,858✔
458

459
        STRV_FOREACH(p, patterns) {
604,059✔
460
                r = pattern_match(*p, s, &found);
334,186✔
461
                if (r < 0)
334,186✔
462
                        return r;
463
                if (r != PATTERN_MATCH_NO) {
334,186✔
464
                        if (ret) {
59,985✔
465
                                *ret = found;
59,973✔
466
                                found = (InstanceMetadata) INSTANCE_METADATA_NULL;
59,973✔
467
                        }
468

469
                        return r;
470
                }
471
        }
472

473
        if (ret)
269,873✔
474
                *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
269,865✔
475

476
        return PATTERN_MATCH_NO;
477
}
478

479
int pattern_valid(const char *pattern) {
22,288✔
480
        int r;
22,288✔
481

482
        r = pattern_split(pattern, NULL);
22,288✔
483
        if (r == -EINVAL)
22,288✔
484
                return false;
485
        if (r < 0)
22,288✔
486
                return r;
×
487

488
        return true;
489
}
490

491
int pattern_format(
1,973✔
492
                const char *pattern,
493
                const InstanceMetadata *fields,
494
                char **ret) {
495

496
        _cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
×
497
        _cleanup_free_ char *j = NULL;
1,973✔
498
        int r;
1,973✔
499

500
        assert(pattern);
1,973✔
501
        assert(fields);
1,973✔
502
        assert(ret);
1,973✔
503

504
        r = pattern_split(pattern, &elements);
1,973✔
505
        if (r < 0)
1,973✔
506
                return r;
507

508
        LIST_FOREACH(elements, e, elements) {
8,773✔
509

510
                switch (e->type) {
6,800✔
511

512
                case PATTERN_SLASH:
304✔
513
                        if (!strextend(&j, "/"))
304✔
514
                                return -ENOMEM;
515

516
                        break;
517

518
                case PATTERN_LITERAL:
3,971✔
519
                        if (!strextend(&j, e->literal))
3,971✔
520
                                return -ENOMEM;
521

522
                        break;
523

524
                case PATTERN_VERSION:
1,973✔
525
                        if (!fields->version)
1,973✔
526
                                return -ENXIO;
527

528
                        if (!strextend(&j, fields->version))
1,973✔
529
                                return -ENOMEM;
530
                        break;
531

532
                case PATTERN_PARTITION_UUID: {
×
533
                        char formatted[SD_ID128_STRING_MAX];
×
534

535
                        if (!fields->partition_uuid_set)
×
536
                                return -ENXIO;
×
537

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

541
                        break;
×
542
                }
543

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

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

552
                        break;
553

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

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

562
                        break;
563

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

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

572
                        break;
573

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

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

583
                case PATTERN_TRIES_DONE:
276✔
584
                        if (fields->tries_done == UINT64_MAX)
276✔
585
                                return -ENXIO;
586

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

592
                case PATTERN_TRIES_LEFT:
276✔
593
                        if (fields->tries_left == UINT64_MAX)
276✔
594
                                return -ENXIO;
595

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

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

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

608
                        break;
609

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

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

617
                        break;
618

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

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

626
                        break;
627

628
                case PATTERN_SHA256SUM: {
×
629
                        _cleanup_free_ char *h = NULL;
×
630

631
                        if (!fields->sha256sum_set)
×
632
                                return -ENXIO;
633

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

638
                        if (!strextend(&j, h))
×
639
                                return -ENOMEM;
640

641
                        break;
×
642
                }
643

644
                default:
×
645
                        assert_not_reached();
×
646
                }
647
        }
648

649
        *ret = TAKE_PTR(j);
1,973✔
650
        return 0;
1,973✔
651
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc