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

systemd / systemd / 14458263136

14 Apr 2025 06:41PM UTC coverage: 72.031% (+0.001%) from 72.03%
14458263136

push

github

yuwata
test: drop error conditions for old kernels (<3.2)

Now our baseline on the kernel is 5.4.

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

1428 existing lines in 44 files now uncovered.

297292 of 412726 relevant lines covered (72.03%)

683119.61 hits per line

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

85.52
/src/shared/condition.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <ctype.h>
4
#include <errno.h>
5
#include <fcntl.h>
6
#include <fnmatch.h>
7
#include <gnu/libc-version.h>
8
#include <limits.h>
9
#include <stdlib.h>
10
#include <sys/stat.h>
11
#include <sys/types.h>
12
#include <sys/utsname.h>
13
#include <time.h>
14
#include <unistd.h>
15

16
#include "sd-id128.h"
17

18
#include "alloc-util.h"
19
#include "apparmor-util.h"
20
#include "architecture.h"
21
#include "audit-util.h"
22
#include "battery-util.h"
23
#include "bitfield.h"
24
#include "blockdev-util.h"
25
#include "cap-list.h"
26
#include "capability-util.h"
27
#include "cgroup-util.h"
28
#include "compare-operator.h"
29
#include "condition.h"
30
#include "confidential-virt.h"
31
#include "cpu-set-util.h"
32
#include "creds-util.h"
33
#include "efi-api.h"
34
#include "efi-loader.h"
35
#include "env-file.h"
36
#include "env-util.h"
37
#include "extract-word.h"
38
#include "fd-util.h"
39
#include "fileio.h"
40
#include "fs-util.h"
41
#include "glob-util.h"
42
#include "hostname-setup.h"
43
#include "hostname-util.h"
44
#include "ima-util.h"
45
#include "id128-util.h"
46
#include "initrd-util.h"
47
#include "limits-util.h"
48
#include "list.h"
49
#include "macro.h"
50
#include "mountpoint-util.h"
51
#include "nulstr-util.h"
52
#include "os-util.h"
53
#include "parse-util.h"
54
#include "path-util.h"
55
#include "percent-util.h"
56
#include "proc-cmdline.h"
57
#include "process-util.h"
58
#include "psi-util.h"
59
#include "selinux-util.h"
60
#include "smack-util.h"
61
#include "special.h"
62
#include "stat-util.h"
63
#include "string-table.h"
64
#include "string-util.h"
65
#include "tomoyo-util.h"
66
#include "tpm2-util.h"
67
#include "uid-classification.h"
68
#include "user-util.h"
69
#include "virt.h"
70

71
Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) {
21,779✔
72
        Condition *c;
21,779✔
73

74
        assert(type >= 0);
21,779✔
75
        assert(type < _CONDITION_TYPE_MAX);
21,779✔
76
        assert(parameter);
21,779✔
77

78
        c = new(Condition, 1);
21,779✔
79
        if (!c)
21,779✔
80
                return NULL;
81

82
        *c = (Condition) {
21,779✔
83
                .type = type,
84
                .trigger = trigger,
85
                .negate = negate,
86
        };
87

88
        if (parameter) {
21,779✔
89
                c->parameter = strdup(parameter);
21,779✔
90
                if (!c->parameter)
21,779✔
91
                        return mfree(c);
×
92
        }
93

94
        return c;
95
}
96

97
Condition* condition_free(Condition *c) {
21,779✔
98
        assert(c);
21,779✔
99

100
        free(c->parameter);
21,779✔
101
        return mfree(c);
21,779✔
102
}
103

104
Condition* condition_free_list_type(Condition *head, ConditionType type) {
165,440✔
105
        LIST_FOREACH(conditions, c, head)
186,933✔
106
                if (type < 0 || c->type == type) {
21,493✔
107
                        LIST_REMOVE(conditions, head, c);
21,493✔
108
                        condition_free(c);
21,493✔
109
                }
110

111
        assert(type >= 0 || !head);
165,440✔
112
        return head;
165,440✔
113
}
114

115
static int condition_test_kernel_command_line(Condition *c, char **env) {
152✔
116
        _cleanup_strv_free_ char **args = NULL;
152✔
117
        int r;
152✔
118

119
        assert(c);
152✔
120
        assert(c->parameter);
152✔
121
        assert(c->type == CONDITION_KERNEL_COMMAND_LINE);
152✔
122

123
        r = proc_cmdline_strv(&args);
152✔
124
        if (r < 0)
152✔
125
                return r;
126

127
        bool equal = strchr(c->parameter, '=');
152✔
128

129
        STRV_FOREACH(word, args) {
5,368✔
130
                bool found;
5,216✔
131

132
                if (equal)
5,216✔
133
                        found = streq(*word, c->parameter);
822✔
134
                else {
135
                        const char *f;
4,394✔
136

137
                        f = startswith(*word, c->parameter);
4,394✔
138
                        found = f && IN_SET(*f, 0, '=');
4,394✔
139
                }
140

141
                if (found)
822✔
142
                        return true;
143
        }
144

145
        return false;
146
}
147

148
static int condition_test_credential(Condition *c, char **env) {
38✔
149
        int r;
38✔
150

151
        assert(c);
38✔
152
        assert(c->parameter);
38✔
153
        assert(c->type == CONDITION_CREDENTIAL);
38✔
154

155
        /* For now we'll do a very simple existence check and are happy with either a regular or an encrypted
156
         * credential. Given that we check the syntax of the argument we have the option to later maybe allow
157
         * contents checks too without breaking compatibility, but for now let's be minimalistic. */
158

159
        if (!credential_name_valid(c->parameter)) /* credentials with invalid names do not exist */
38✔
160
                return false;
161

162
        int (*gd)(const char **ret);
37✔
163
        FOREACH_ARGUMENT(gd, get_credentials_dir, get_encrypted_credentials_dir) {
108✔
164
                _cleanup_free_ char *j = NULL;
38✔
165
                const char *cd;
73✔
166

167
                r = gd(&cd);
73✔
168
                if (r == -ENXIO) /* no env var set */
73✔
169
                        continue;
35✔
170
                if (r < 0)
38✔
171
                        return r;
172

173
                j = path_join(cd, c->parameter);
38✔
174
                if (!j)
38✔
175
                        return -ENOMEM;
176

177
                r = access_nofollow(j, F_OK);
38✔
178
                if (r >= 0)
36✔
179
                        return true; /* yay! */
2✔
180
                if (r != -ENOENT)
36✔
181
                        return r;
182

183
                /* not found in this dir */
184
        }
185

186
        return false;
35✔
187
}
188

189
static int condition_test_version_cmp(const char *condition, const char *ver) {
55✔
190
        CompareOperator operator;
55✔
191
        bool first = true;
55✔
192

193
        assert(condition);
55✔
194
        assert(ver);
55✔
195

196
        for (const char *p = condition;;) {
55✔
197
                _cleanup_free_ char *word = NULL;
59✔
198
                const char *s;
86✔
199
                int r;
86✔
200

201
                r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE);
86✔
202
                if (r < 0)
86✔
203
                        return log_debug_errno(r, "Failed to parse condition string \"%s\": %m", p);
×
204
                if (r == 0)
86✔
205
                        break;
206

207
                s = strstrip(word);
59✔
208
                operator = parse_compare_operator(&s, COMPARE_ALLOW_FNMATCH|COMPARE_EQUAL_BY_STRING);
59✔
209
                if (operator < 0) /* No prefix? Then treat as glob string */
59✔
210
                        operator = COMPARE_FNMATCH_EQUAL;
6✔
211

212
                s += strspn(s, WHITESPACE);
59✔
213
                if (isempty(s)) {
59✔
214
                        if (first) {
38✔
215
                                /* For backwards compatibility, allow whitespace between the operator and
216
                                 * value, without quoting, but only in the first expression. */
217
                                word = mfree(word);
36✔
218
                                r = extract_first_word(&p, &word, NULL, 0);
36✔
219
                                if (r < 0)
36✔
220
                                        return log_debug_errno(r, "Failed to parse condition string \"%s\": %m", p);
×
221
                                if (r == 0)
36✔
222
                                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected end of expression: %s", p);
2✔
223
                                s = word;
34✔
224
                        } else
225
                                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected end of expression: %s", p);
2✔
226
                }
227

228
                r = version_or_fnmatch_compare(operator, ver, s);
55✔
229
                if (r < 0)
55✔
230
                        return r;
231
                if (!r)
55✔
232
                        return false;
233

234
                first = false;
31✔
235
        }
236

237
        return true;
27✔
238
}
239

240
static int condition_test_version(Condition *c, char **env) {
57✔
241
        int r;
57✔
242

243
        assert(c);
57✔
244
        assert(c->type == CONDITION_VERSION);
57✔
245

246
        /* An empty condition is considered true. */
247
        if (isempty(c->parameter))
57✔
248
                return true;
57✔
249

250
        const char *p = c->parameter;
55✔
251
        _cleanup_free_ char *word = NULL;
55✔
252
        r = extract_first_word(&p, &word, COMPARE_OPERATOR_WITH_FNMATCH_CHARS WHITESPACE,
55✔
253
                               EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_SEPARATORS);
254
        if (r < 0)
55✔
255
                return log_debug_errno(r, "Failed to parse compare predicate \"%s\": %m", p);
×
256
        if (r == 0)
55✔
257
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing right operand in condition: %s", c->parameter);
×
258

259
        if (streq(word, "systemd"))
55✔
260
                return condition_test_version_cmp(p, STRINGIFY(PROJECT_VERSION));
21✔
261

262
        if (streq(word, "glibc"))
34✔
263
                return condition_test_version_cmp(p, gnu_get_libc_version());
6✔
264

265
        /* if no predicate has been set, default to "kernel" and use the whole parameter as condition */
266
        if (!streq(word, "kernel"))
28✔
267
                p = c->parameter;
28✔
268

269
        struct utsname u;
28✔
270
        assert_se(uname(&u) >= 0);
28✔
271
        return condition_test_version_cmp(p, u.release);
28✔
272
}
273

274
static int condition_test_osrelease(Condition *c, char **env) {
17✔
275
        int r;
17✔
276

277
        assert(c);
17✔
278
        assert(c->type == CONDITION_OS_RELEASE);
17✔
279

280
        for (const char *parameter = ASSERT_PTR(c->parameter);;) {
17✔
281
                _cleanup_free_ char *key = NULL, *condition = NULL, *actual_value = NULL;
16✔
282
                CompareOperator operator;
21✔
283
                const char *word;
21✔
284

285
                r = extract_first_word(&parameter, &condition, NULL, EXTRACT_UNQUOTE);
21✔
286
                if (r < 0)
21✔
287
                        return log_debug_errno(r, "Failed to parse parameter: %m");
×
288
                if (r == 0)
21✔
289
                        break;
290

291
                /* parse_compare_operator() needs the string to start with the comparators */
292
                word = condition;
16✔
293
                r = extract_first_word(&word, &key, COMPARE_OPERATOR_WITH_FNMATCH_CHARS, EXTRACT_RETAIN_SEPARATORS);
16✔
294
                if (r < 0)
16✔
295
                        return log_debug_errno(r, "Failed to parse parameter: %m");
×
296
                /* The os-release spec mandates env-var-like key names */
297
                if (r == 0 || isempty(word) || !env_name_is_valid(key))
27✔
298
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
5✔
299
                                        "Failed to parse parameter, key/value format expected.");
300

301
                /* Do not allow whitespace after the separator, as that's not a valid os-release format */
302
                operator = parse_compare_operator(&word, COMPARE_ALLOW_FNMATCH|COMPARE_EQUAL_BY_STRING);
11✔
303
                if (operator < 0 || isempty(word) || strchr(WHITESPACE, *word) != NULL)
20✔
304
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
2✔
305
                                        "Failed to parse parameter, key/value format expected.");
306

307
                r = parse_os_release(NULL, key, &actual_value);
9✔
308
                if (r < 0)
9✔
309
                        return log_debug_errno(r, "Failed to parse os-release: %m");
×
310

311
                /* If not found, use "". This means that missing and empty assignments
312
                 * in the file have the same result. */
313
                r = version_or_fnmatch_compare(operator, strempty(actual_value), word);
14✔
314
                if (r < 0)
9✔
315
                        return r;
316
                if (!r)
9✔
317
                        return false;
318
        }
319

320
        return true;
5✔
321
}
322

323
static int condition_test_memory(Condition *c, char **env) {
36✔
324
        CompareOperator operator;
36✔
325
        uint64_t m, k;
36✔
326
        const char *p;
36✔
327
        int r;
36✔
328

329
        assert(c);
36✔
330
        assert(c->parameter);
36✔
331
        assert(c->type == CONDITION_MEMORY);
36✔
332

333
        m = physical_memory();
36✔
334

335
        p = c->parameter;
36✔
336
        operator = parse_compare_operator(&p, 0);
36✔
337
        if (operator < 0)
36✔
338
                operator = COMPARE_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */
×
339

340
        r = parse_size(p, 1024, &k);
36✔
341
        if (r < 0)
36✔
342
                return log_debug_errno(r, "Failed to parse size '%s': %m", p);
×
343

344
        return test_order(CMP(m, k), operator);
36✔
345
}
346

347
static int condition_test_cpus(Condition *c, char **env) {
18✔
348
        CompareOperator operator;
18✔
349
        const char *p;
18✔
350
        unsigned k;
18✔
351
        int r, n;
18✔
352

353
        assert(c);
18✔
354
        assert(c->parameter);
18✔
355
        assert(c->type == CONDITION_CPUS);
18✔
356

357
        n = cpus_in_affinity_mask();
18✔
358
        if (n < 0)
18✔
359
                return log_debug_errno(n, "Failed to determine CPUs in affinity mask: %m");
×
360

361
        p = c->parameter;
18✔
362
        operator = parse_compare_operator(&p, 0);
18✔
363
        if (operator < 0)
18✔
364
                operator = COMPARE_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */
×
365

366
        r = safe_atou(p, &k);
18✔
367
        if (r < 0)
18✔
368
                return log_debug_errno(r, "Failed to parse number of CPUs: %m");
×
369

370
        return test_order(CMP((unsigned) n, k), operator);
18✔
371
}
372

373
static int condition_test_user(Condition *c, char **env) {
7✔
374
        uid_t id;
7✔
375
        int r;
7✔
376

377
        assert(c);
7✔
378
        assert(c->parameter);
7✔
379
        assert(c->type == CONDITION_USER);
7✔
380

381
        /* Do the quick&easy comparisons first, and only parse the UID later. */
382
        if (streq(c->parameter, "root"))
7✔
383
                return getuid() == 0 || geteuid() == 0;
7✔
384
        if (streq(c->parameter, NOBODY_USER_NAME))
6✔
385
                return getuid() == UID_NOBODY || geteuid() == UID_NOBODY;
1✔
386
        if (streq(c->parameter, "@system"))
5✔
387
                return uid_is_system(getuid()) || uid_is_system(geteuid());
1✔
388

389
        r = parse_uid(c->parameter, &id);
4✔
390
        if (r >= 0)
4✔
391
                return id == getuid() || id == geteuid();
2✔
392

393
        if (getpid_cached() == 1)  /* We already checked for "root" above, and we know that
2✔
394
                                    * PID 1 is running as root, hence we know it cannot match. */
395
                return false;
396

397
        /* getusername_malloc() may do an nss lookup, which is not allowed in PID 1. */
398
        _cleanup_free_ char *username = getusername_malloc();
4✔
399
        if (!username)
2✔
400
                return -ENOMEM;
401

402
        if (streq(username, c->parameter))
2✔
403
                return 1;
404

405
        const char *u = c->parameter;
2✔
406
        r = get_user_creds(&u, &id, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING);
2✔
407
        if (r < 0)
2✔
408
                return 0;
409

410
        return id == getuid() || id == geteuid();
×
411
}
412

413
static int condition_test_control_group_controller(Condition *c, char **env) {
32✔
414
        CGroupMask system_mask, wanted_mask;
32✔
415
        int r;
32✔
416

417
        assert(c);
32✔
418
        assert(c->parameter);
32✔
419
        assert(c->type == CONDITION_CONTROL_GROUP_CONTROLLER);
32✔
420

421
        if (streq(c->parameter, "v2"))
32✔
422
                return true;
32✔
423
        if (streq(c->parameter, "v1"))
31✔
424
                return false;
425

426
        r = cg_mask_supported(&system_mask);
30✔
427
        if (r < 0)
30✔
UNCOV
428
                return log_debug_errno(r, "Failed to determine supported controllers: %m");
×
429

430
        r = cg_mask_from_string(c->parameter, &wanted_mask);
30✔
431
        if (r < 0 || wanted_mask <= 0) {
30✔
432
                /* This won't catch the case that we have an unknown controller
433
                 * mixed in with valid ones -- these are only assessed on the
434
                 * validity of the valid controllers found. */
435
                log_debug("Failed to parse cgroup string: %s", c->parameter);
2✔
436
                return true;
2✔
437
        }
438

439
        return FLAGS_SET(system_mask, wanted_mask);
28✔
440
}
441

442
static int condition_test_group(Condition *c, char **env) {
4✔
443
        gid_t id;
4✔
444
        int r;
4✔
445

446
        assert(c);
4✔
447
        assert(c->parameter);
4✔
448
        assert(c->type == CONDITION_GROUP);
4✔
449

450
        r = parse_gid(c->parameter, &id);
4✔
451
        if (r >= 0)
4✔
452
                return in_gid(id);
2✔
453

454
        /* Avoid any NSS lookups if we are PID1 */
455
        if (getpid_cached() == 1)
2✔
UNCOV
456
                return streq(c->parameter, "root");
×
457

458
        return in_group(c->parameter) > 0;
2✔
459
}
460

461
static int condition_test_virtualization(Condition *c, char **env) {
1,354✔
462
        Virtualization v;
1,354✔
463
        int b;
1,354✔
464

465
        assert(c);
1,354✔
466
        assert(c->parameter);
1,354✔
467
        assert(c->type == CONDITION_VIRTUALIZATION);
1,354✔
468

469
        if (streq(c->parameter, "private-users"))
1,354✔
470
                return running_in_userns();
61✔
471

472
        v = detect_virtualization();
1,293✔
473
        if (v < 0)
1,293✔
474
                return v;
475

476
        /* First, compare with yes/no */
477
        b = parse_boolean(c->parameter);
1,293✔
478
        if (b >= 0)
1,293✔
479
                return b == (v != VIRTUALIZATION_NONE);
9✔
480

481
        /* Then, compare categorization */
482
        if (streq(c->parameter, "vm"))
1,284✔
483
                return VIRTUALIZATION_IS_VM(v);
1✔
484

485
        if (streq(c->parameter, "container"))
1,283✔
486
                return VIRTUALIZATION_IS_CONTAINER(v);
1,257✔
487

488
        /* Finally compare id */
489
        return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v));
26✔
490
}
491

492
static int condition_test_architecture(Condition *c, char **env) {
4✔
493
        Architecture a, b;
4✔
494

495
        assert(c);
4✔
496
        assert(c->parameter);
4✔
497
        assert(c->type == CONDITION_ARCHITECTURE);
4✔
498

499
        a = uname_architecture();
4✔
500
        if (a < 0)
4✔
501
                return a;
502

503
        if (streq(c->parameter, "native"))
4✔
504
                b = native_architecture();
505
        else {
506
                b = architecture_from_string(c->parameter);
4✔
507
                if (b < 0) /* unknown architecture? Then it's definitely not ours */
4✔
508
                        return false;
509
        }
510

511
        return a == b;
3✔
512
}
513

514
#define DTCOMPAT_FILE "/proc/device-tree/compatible"
515
static int condition_test_firmware_devicetree_compatible(const char *dtcarg) {
1✔
516
        int r;
1✔
517
        _cleanup_free_ char *dtcompat = NULL;
1✔
518
        _cleanup_strv_free_ char **dtcompatlist = NULL;
1✔
519
        size_t size;
1✔
520

521
        r = read_full_virtual_file(DTCOMPAT_FILE, &dtcompat, &size);
1✔
522
        if (r < 0) {
1✔
523
                /* if the path doesn't exist it is incompatible */
524
                if (r != -ENOENT)
1✔
UNCOV
525
                        log_debug_errno(r, "Failed to open() '%s', assuming machine is incompatible: %m", DTCOMPAT_FILE);
×
526
                return false;
1✔
527
        }
528

529
        /* Not sure this can happen, but play safe. */
UNCOV
530
        if (size == 0) {
×
UNCOV
531
                log_debug("%s has zero length, assuming machine is incompatible", DTCOMPAT_FILE);
×
UNCOV
532
                return false;
×
533
        }
534

535
         /* /proc/device-tree/compatible consists of one or more strings, each ending in '\0'.
536
          * So the last character in dtcompat must be a '\0'. */
UNCOV
537
        if (dtcompat[size - 1] != '\0') {
×
UNCOV
538
                log_debug("%s is in an unknown format, assuming machine is incompatible", DTCOMPAT_FILE);
×
UNCOV
539
                return false;
×
540
        }
541

542
        dtcompatlist = strv_parse_nulstr(dtcompat, size);
×
543
        if (!dtcompatlist)
×
544
                return -ENOMEM;
545

546
        return strv_contains(dtcompatlist, dtcarg);
×
547
}
548

549
static int condition_test_firmware_smbios_field(const char *expression) {
17✔
550
        _cleanup_free_ char *field = NULL, *expected_value = NULL, *actual_value = NULL;
17✔
551
        CompareOperator operator;
17✔
552
        int r;
17✔
553

554
        assert(expression);
17✔
555

556
        /* Parse SMBIOS field */
557
        r = extract_first_word(&expression, &field, COMPARE_OPERATOR_WITH_FNMATCH_CHARS, EXTRACT_RETAIN_SEPARATORS);
17✔
558
        if (r < 0)
17✔
559
                return r;
560
        if (r == 0 || isempty(expression))
17✔
561
                return -EINVAL;
562

563
        /* Remove trailing spaces from SMBIOS field */
564
        delete_trailing_chars(field, WHITESPACE);
15✔
565

566
        /* Parse operator */
567
        operator = parse_compare_operator(&expression, COMPARE_ALLOW_FNMATCH|COMPARE_EQUAL_BY_STRING);
15✔
568
        if (operator < 0)
15✔
569
                return operator;
570

571
        /* Parse expected value */
572
        r = extract_first_word(&expression, &expected_value, NULL, EXTRACT_UNQUOTE);
15✔
573
        if (r < 0)
15✔
574
                return r;
575
        if (r == 0 || !isempty(expression))
30✔
576
                return -EINVAL;
577

578
        /* Read actual value from sysfs */
579
        if (!filename_is_valid(field))
12✔
UNCOV
580
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid SMBIOS field name.");
×
581

582
        const char *p = strjoina("/sys/class/dmi/id/", field);
60✔
583
        r = read_virtual_file(p, SIZE_MAX, &actual_value, NULL);
12✔
584
        if (r < 0) {
12✔
585
                log_debug_errno(r, "Failed to read %s: %m", p);
1✔
586
                if (r == -ENOENT)
1✔
587
                        return false;
UNCOV
588
                return r;
×
589
        }
590

591
        /* Remove trailing newline */
592
        delete_trailing_chars(actual_value, WHITESPACE);
11✔
593

594
        /* Finally compare actual and expected value */
595
        return version_or_fnmatch_compare(operator, actual_value, expected_value);
11✔
596
}
597

598
static int condition_test_firmware(Condition *c, char **env) {
22✔
599
        sd_char *arg;
22✔
600
        int r;
22✔
601

602
        assert(c);
22✔
603
        assert(c->parameter);
22✔
604
        assert(c->type == CONDITION_FIRMWARE);
22✔
605

606
        if (streq(c->parameter, "device-tree")) {
22✔
607
                if (access("/sys/firmware/devicetree/", F_OK) < 0) {
1✔
608
                        if (errno != ENOENT)
1✔
UNCOV
609
                                log_debug_errno(errno, "Unexpected error when checking for /sys/firmware/devicetree/: %m");
×
610
                        return false;
1✔
611
                } else
612
                        return true;
613
        } else if ((arg = startswith(c->parameter, "device-tree-compatible("))) {
21✔
614
                _cleanup_free_ char *dtc_arg = NULL;
1✔
615
                char *end;
1✔
616

617
                end = strrchr(arg, ')');
1✔
618
                if (!end || *(end + 1) != '\0') {
1✔
UNCOV
619
                        log_debug("Malformed ConditionFirmware=%s", c->parameter);
×
UNCOV
620
                        return false;
×
621
                }
622

623
                dtc_arg = strndup(arg, end - arg);
1✔
624
                if (!dtc_arg)
1✔
625
                        return -ENOMEM;
626

627
                return condition_test_firmware_devicetree_compatible(dtc_arg);
1✔
628
        } else if (streq(c->parameter, "uefi"))
20✔
629
                return is_efi_boot();
1✔
630
        else if ((arg = startswith(c->parameter, "smbios-field("))) {
19✔
631
                _cleanup_free_ char *smbios_arg = NULL;
18✔
632
                char *end;
18✔
633

634
                end = strrchr(arg, ')');
18✔
635
                if (!end || *(end + 1) != '\0')
18✔
636
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed ConditionFirmware=%s.", c->parameter);
1✔
637

638
                smbios_arg = strndup(arg, end - arg);
17✔
639
                if (!smbios_arg)
17✔
UNCOV
640
                        return log_oom_debug();
×
641

642
                r = condition_test_firmware_smbios_field(smbios_arg);
17✔
643
                if (r < 0)
17✔
644
                        return log_debug_errno(r, "Malformed ConditionFirmware=%s: %m", c->parameter);
5✔
645
                return r;
646
        } else {
647
                log_debug("Unsupported Firmware condition \"%s\"", c->parameter);
1✔
648
                return false;
1✔
649
        }
650
}
651

652
static int condition_test_host(Condition *c, char **env) {
4✔
653
        _cleanup_free_ char *h = NULL;
4✔
654
        int r;
4✔
655

656
        assert(c);
4✔
657
        assert(c->parameter);
4✔
658
        assert(c->type == CONDITION_HOST);
4✔
659

660
        sd_id128_t x;
4✔
661
        if (sd_id128_from_string(c->parameter, &x) >= 0) {
4✔
662
                static const struct {
663
                        const char *name;
664
                        int (*get_id)(sd_id128_t *ret);
665
                } table[] = {
666
                        { "machine ID",   sd_id128_get_machine },
667
                        { "boot ID",      sd_id128_get_boot    },
668
                        { "product UUID", id128_get_product    },
669
                };
670

671
                /* If this is a UUID, check if this matches the machine ID, boot ID or product UUID */
672
                FOREACH_ELEMENT(i, table) {
2✔
673
                        sd_id128_t y;
2✔
674

675
                        r = i->get_id(&y);
2✔
676
                        if (r < 0)
2✔
UNCOV
677
                                log_debug_errno(r, "Failed to get %s, ignoring: %m", i->name);
×
678
                        else if (sd_id128_equal(x, y))
2✔
679
                                return true;
2✔
680
                }
681

682
                /* Fall through, also allow setups where people set hostnames to UUIDs. Kinda weird, but no
683
                 * reason not to allow that */
684
        }
685

686
        h = gethostname_malloc();
2✔
687
        if (!h)
2✔
688
                return -ENOMEM;
689

690
        r = fnmatch(c->parameter, h, FNM_CASEFOLD);
2✔
691
        if (r == FNM_NOMATCH)
2✔
692
                return false;
693
        if (r != 0)
1✔
UNCOV
694
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "fnmatch() failed.");
×
695

696
        return true;
697
}
698

699
static int condition_test_ac_power(Condition *c, char **env) {
4✔
700
        int r;
4✔
701

702
        assert(c);
4✔
703
        assert(c->parameter);
4✔
704
        assert(c->type == CONDITION_AC_POWER);
4✔
705

706
        r = parse_boolean(c->parameter);
4✔
707
        if (r < 0)
4✔
708
                return r;
709

710
        return (on_ac_power() != 0) == !!r;
4✔
711
}
712

UNCOV
713
static int has_tpm2(void) {
×
714
        /* Checks whether the kernel has the TPM subsystem enabled and the firmware reports support. Note
715
         * we don't check for actual TPM devices, since we might not have loaded the driver for it yet, i.e.
716
         * during early boot where we very likely want to use this condition check).
717
         *
718
         * Note that we don't check if we ourselves are built with TPM2 support here! */
719

UNCOV
720
        return FLAGS_SET(tpm2_support_full(TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_FIRMWARE), TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_FIRMWARE);
×
721
}
722

723
static int condition_test_security(Condition *c, char **env) {
263✔
724
        assert(c);
263✔
725
        assert(c->parameter);
263✔
726
        assert(c->type == CONDITION_SECURITY);
263✔
727

728
        if (streq(c->parameter, "selinux"))
263✔
729
                return mac_selinux_use();
1✔
730
        if (streq(c->parameter, "smack"))
262✔
731
                return mac_smack_use();
1✔
732
        if (streq(c->parameter, "apparmor"))
261✔
733
                return mac_apparmor_use();
1✔
734
        if (streq(c->parameter, "audit"))
260✔
735
                return use_audit();
13✔
736
        if (streq(c->parameter, "ima"))
247✔
737
                return use_ima();
1✔
738
        if (streq(c->parameter, "tomoyo"))
246✔
739
                return mac_tomoyo_use();
1✔
740
        if (streq(c->parameter, "uefi-secureboot"))
245✔
741
                return is_efi_secure_boot();
1✔
742
        if (streq(c->parameter, "tpm2"))
244✔
UNCOV
743
                return has_tpm2();
×
744
        if (streq(c->parameter, "cvm"))
244✔
745
                return detect_confidential_virtualization() > 0;
1✔
746
        if (streq(c->parameter, "measured-uki"))
243✔
747
                return efi_measured_uki(LOG_DEBUG);
242✔
748

749
        return false;
750
}
751

752
static int condition_test_capability(Condition *c, char **env) {
568✔
753
        int r;
568✔
754

755
        assert(c);
568✔
756
        assert(c->parameter);
568✔
757
        assert(c->type == CONDITION_CAPABILITY);
568✔
758

759
        /* If it's an invalid capability, we don't have it */
760
        int value = capability_from_name(c->parameter);
568✔
761
        if (value < 0)
568✔
762
                return -EINVAL;
568✔
763

764
        CapabilityQuintet q;
568✔
765
        r = pidref_get_capability(&PIDREF_MAKE_FROM_PID(getpid_cached()), &q);
568✔
766
        if (r < 0)
568✔
767
                return r;
768

769
        return BIT_SET(q.bounding, value);
568✔
770
}
771

772
static int condition_test_needs_update(Condition *c, char **env) {
198✔
773
        struct stat usr, other;
198✔
774
        const char *p;
198✔
775
        bool b;
198✔
776
        int r;
198✔
777

778
        assert(c);
198✔
779
        assert(c->parameter);
198✔
780
        assert(c->type == CONDITION_NEEDS_UPDATE);
198✔
781

782
        r = proc_cmdline_get_bool("systemd.condition_needs_update", /* flags = */ 0, &b);
198✔
783
        if (r < 0)
198✔
UNCOV
784
                log_debug_errno(r, "Failed to parse systemd.condition_needs_update= kernel command line argument, ignoring: %m");
×
785
        if (r > 0)
198✔
786
                return b;
198✔
787

788
        if (in_initrd()) {
198✔
789
                log_debug("We are in an initrd, not doing any updates.");
54✔
790
                return false;
54✔
791
        }
792

793
        if (!path_is_absolute(c->parameter)) {
144✔
UNCOV
794
                log_debug("Specified condition parameter '%s' is not absolute, assuming an update is needed.", c->parameter);
×
UNCOV
795
                return true;
×
796
        }
797

798
        /* If the file system is read-only we shouldn't suggest an update */
799
        r = path_is_read_only_fs(c->parameter);
144✔
800
        if (r < 0)
144✔
UNCOV
801
                log_debug_errno(r, "Failed to determine if '%s' is read-only, ignoring: %m", c->parameter);
×
802
        if (r > 0)
144✔
803
                return false;
804

805
        /* Any other failure means we should allow the condition to be true, so that we rather invoke too
806
         * many update tools than too few. */
807

808
        p = strjoina(c->parameter, "/.updated");
720✔
809
        if (lstat(p, &other) < 0) {
144✔
UNCOV
810
                if (errno != ENOENT)
×
UNCOV
811
                        log_debug_errno(errno, "Failed to stat() '%s', assuming an update is needed: %m", p);
×
UNCOV
812
                return true;
×
813
        }
814

815
        if (lstat("/usr/", &usr) < 0) {
144✔
816
                log_debug_errno(errno, "Failed to stat() /usr/, assuming an update is needed: %m");
×
UNCOV
817
                return true;
×
818
        }
819

820
        /*
821
         * First, compare seconds as they are always accurate...
822
         */
823
        if (usr.st_mtim.tv_sec != other.st_mtim.tv_sec)
144✔
UNCOV
824
                return usr.st_mtim.tv_sec > other.st_mtim.tv_sec;
×
825

826
        /*
827
         * ...then compare nanoseconds.
828
         *
829
         * A false positive is only possible when /usr's nanoseconds > 0
830
         * (otherwise /usr cannot be strictly newer than the target file)
831
         * AND the target file's nanoseconds == 0
832
         * (otherwise the filesystem supports nsec timestamps, see stat(2)).
833
         */
834
        if (usr.st_mtim.tv_nsec == 0 || other.st_mtim.tv_nsec > 0)
144✔
835
                return usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec;
144✔
836

UNCOV
837
        _cleanup_free_ char *timestamp_str = NULL;
×
UNCOV
838
        r = parse_env_file(NULL, p, "TIMESTAMP_NSEC", &timestamp_str);
×
UNCOV
839
        if (r < 0) {
×
UNCOV
840
                log_debug_errno(r, "Failed to parse timestamp file '%s', using mtime: %m", p);
×
841
                return true;
×
842
        }
843
        if (isempty(timestamp_str)) {
×
844
                log_debug("No data in timestamp file '%s', using mtime.", p);
×
845
                return true;
×
846
        }
847

848
        uint64_t timestamp;
×
849
        r = safe_atou64(timestamp_str, &timestamp);
×
UNCOV
850
        if (r < 0) {
×
UNCOV
851
                log_debug_errno(r, "Failed to parse timestamp value '%s' in file '%s', using mtime: %m", timestamp_str, p);
×
852
                return true;
×
853
        }
854

855
        return timespec_load_nsec(&usr.st_mtim) > timestamp;
×
856
}
857

858
static bool in_first_boot(void) {
66✔
859
        static int first_boot = -1;
66✔
860
        int r;
66✔
861

862
        if (first_boot >= 0)
66✔
UNCOV
863
                return first_boot;
×
864

865
        const char *e = secure_getenv("SYSTEMD_FIRST_BOOT");
66✔
866
        if (e) {
66✔
867
                r = parse_boolean(e);
×
UNCOV
868
                if (r < 0)
×
UNCOV
869
                        log_debug_errno(r, "Failed to parse $SYSTEMD_FIRST_BOOT, ignoring: %m");
×
870
                else
871
                        return (first_boot = r);
×
872
        }
873

874
        r = RET_NERRNO(access("/run/systemd/first-boot", F_OK));
66✔
875
        if (r < 0 && r != -ENOENT)
66✔
UNCOV
876
                log_debug_errno(r, "Failed to check if /run/systemd/first-boot exists, assuming no: %m");
×
877
        return r >= 0;
66✔
878
}
879

880
static int condition_test_first_boot(Condition *c, char **env) {
66✔
881
        int r;
66✔
882

883
        assert(c);
66✔
884
        assert(c->parameter);
66✔
885
        assert(c->type == CONDITION_FIRST_BOOT);
66✔
886

887
        // TODO: Parse c->parameter immediately when reading the config.
888
        //       Apply negation when parsing too.
889

890
        r = parse_boolean(c->parameter);
66✔
891
        if (r < 0)
66✔
892
                return r;
893

894
        return in_first_boot() == r;
66✔
895
}
896

897
static int condition_test_environment(Condition *c, char **env) {
201✔
898
        bool equal;
201✔
899

900
        assert(c);
201✔
901
        assert(c->parameter);
201✔
902
        assert(c->type == CONDITION_ENVIRONMENT);
201✔
903

904
        equal = strchr(c->parameter, '=');
201✔
905

906
        STRV_FOREACH(i, env) {
2,338✔
907
                bool found;
2,162✔
908

909
                if (equal)
2,162✔
910
                        found = streq(c->parameter, *i);
55✔
911
                else {
912
                        const char *f;
2,107✔
913

914
                        f = startswith(*i, c->parameter);
2,107✔
915
                        found = f && IN_SET(*f, 0, '=');
2,107✔
916
                }
917

918
                if (found)
55✔
919
                        return true;
920
        }
921

922
        return false;
923
}
924

925
static int condition_test_path_exists(Condition *c, char **env) {
994✔
926
        assert(c);
994✔
927
        assert(c->parameter);
994✔
928
        assert(c->type == CONDITION_PATH_EXISTS);
994✔
929

930
        return access(c->parameter, F_OK) >= 0;
994✔
931
}
932

933
static int condition_test_path_exists_glob(Condition *c, char **env) {
2✔
934
        assert(c);
2✔
935
        assert(c->parameter);
2✔
936
        assert(c->type == CONDITION_PATH_EXISTS_GLOB);
2✔
937

938
        return glob_exists(c->parameter) > 0;
2✔
939
}
940

941
static int condition_test_path_is_directory(Condition *c, char **env) {
1✔
942
        assert(c);
1✔
943
        assert(c->parameter);
1✔
944
        assert(c->type == CONDITION_PATH_IS_DIRECTORY);
1✔
945

946
        return is_dir(c->parameter, true) > 0;
1✔
947
}
948

949
static int condition_test_path_is_symbolic_link(Condition *c, char **env) {
16✔
950
        assert(c);
16✔
951
        assert(c->parameter);
16✔
952
        assert(c->type == CONDITION_PATH_IS_SYMBOLIC_LINK);
16✔
953

954
        return is_symlink(c->parameter) > 0;
16✔
955
}
956

957
static int condition_test_path_is_mount_point(Condition *c, char **env) {
69✔
958
        assert(c);
69✔
959
        assert(c->parameter);
69✔
960
        assert(c->type == CONDITION_PATH_IS_MOUNT_POINT);
69✔
961

962
        return path_is_mount_point_full(c->parameter, /* root = */ NULL, AT_SYMLINK_FOLLOW) > 0;
69✔
963
}
964

965
static int condition_test_path_is_read_write(Condition *c, char **env) {
410✔
966
        int r;
410✔
967

968
        assert(c);
410✔
969
        assert(c->parameter);
410✔
970
        assert(c->type == CONDITION_PATH_IS_READ_WRITE);
410✔
971

972
        r = path_is_read_only_fs(c->parameter);
410✔
973

974
        return r <= 0 && r != -ENOENT;
410✔
975
}
976

977
static int condition_test_cpufeature(Condition *c, char **env) {
3✔
978
        assert(c);
3✔
979
        assert(c->parameter);
3✔
980
        assert(c->type == CONDITION_CPU_FEATURE);
3✔
981

982
        return has_cpu_with_flag(ascii_strlower(c->parameter));
3✔
983
}
984

985
static int condition_test_path_is_encrypted(Condition *c, char **env) {
1✔
986
        int r;
1✔
987

988
        assert(c);
1✔
989
        assert(c->parameter);
1✔
990
        assert(c->type == CONDITION_PATH_IS_ENCRYPTED);
1✔
991

992
        r = path_is_encrypted(c->parameter);
1✔
993
        if (r < 0 && r != -ENOENT)
1✔
UNCOV
994
                log_debug_errno(r, "Failed to determine if '%s' is encrypted: %m", c->parameter);
×
995

996
        return r > 0;
1✔
997
}
998

999
static int condition_test_directory_not_empty(Condition *c, char **env) {
835✔
1000
        int r;
835✔
1001

1002
        assert(c);
835✔
1003
        assert(c->parameter);
835✔
1004
        assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY);
835✔
1005

1006
        r = dir_is_empty(c->parameter, /* ignore_hidden_or_backup= */ true);
835✔
1007
        return r <= 0 && !IN_SET(r, -ENOENT, -ENOTDIR);
835✔
1008
}
1009

1010
static int condition_test_file_not_empty(Condition *c, char **env) {
67✔
1011
        struct stat st;
67✔
1012

1013
        assert(c);
67✔
1014
        assert(c->parameter);
67✔
1015
        assert(c->type == CONDITION_FILE_NOT_EMPTY);
67✔
1016

1017
        return (stat(c->parameter, &st) >= 0 &&
67✔
1018
                S_ISREG(st.st_mode) &&
67✔
1019
                st.st_size > 0);
49✔
1020
}
1021

1022
static int condition_test_file_is_executable(Condition *c, char **env) {
2✔
1023
        struct stat st;
2✔
1024

1025
        assert(c);
2✔
1026
        assert(c->parameter);
2✔
1027
        assert(c->type == CONDITION_FILE_IS_EXECUTABLE);
2✔
1028

1029
        return (stat(c->parameter, &st) >= 0 &&
2✔
1030
                S_ISREG(st.st_mode) &&
2✔
1031
                (st.st_mode & 0111));
2✔
1032
}
1033

1034
static int condition_test_psi(Condition *c, char **env) {
26✔
1035
        _cleanup_free_ char *first = NULL, *second = NULL, *third = NULL, *fourth = NULL, *pressure_path = NULL;
26✔
1036
        const char *p, *value, *pressure_type;
26✔
1037
        loadavg_t *current, limit;
26✔
1038
        ResourcePressure pressure;
26✔
1039
        PressureType preferred_pressure_type = PRESSURE_TYPE_FULL;
26✔
1040
        int r;
26✔
1041

1042
        assert(c);
26✔
1043
        assert(c->parameter);
26✔
1044
        assert(IN_SET(c->type, CONDITION_MEMORY_PRESSURE, CONDITION_CPU_PRESSURE, CONDITION_IO_PRESSURE));
26✔
1045

1046
        if (!is_pressure_supported()) {
26✔
UNCOV
1047
                log_debug("Pressure Stall Information (PSI) is not supported, skipping.");
×
UNCOV
1048
                return 1;
×
1049
        }
1050

1051
        pressure_type = c->type == CONDITION_MEMORY_PRESSURE ? "memory" :
26✔
1052
                        c->type == CONDITION_CPU_PRESSURE ? "cpu" :
18✔
1053
                        "io";
1054

1055
        p = c->parameter;
26✔
1056
        r = extract_many_words(&p, ":", 0, &first, &second);
26✔
1057
        if (r <= 0)
26✔
1058
                return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
1✔
1059
        /* If only one parameter is passed, then we look at the global system pressure rather than a specific cgroup. */
1060
        if (r == 1) {
25✔
1061
                /* cpu.pressure 'full' is reported but undefined at system level */
1062
                if (c->type == CONDITION_CPU_PRESSURE)
19✔
1063
                        preferred_pressure_type = PRESSURE_TYPE_SOME;
13✔
1064

1065
                pressure_path = path_join("/proc/pressure", pressure_type);
19✔
1066
                if (!pressure_path)
19✔
UNCOV
1067
                        return log_oom_debug();
×
1068

1069
                value = first;
19✔
1070
        } else {
1071
                const char *controller = strjoina(pressure_type, ".pressure");
30✔
1072
                _cleanup_free_ char *slice_path = NULL, *root_scope = NULL;
6✔
1073
                CGroupMask mask, required_mask;
6✔
1074
                char *slice, *e;
6✔
1075

1076
                required_mask = c->type == CONDITION_MEMORY_PRESSURE ? CGROUP_MASK_MEMORY :
6✔
1077
                                c->type == CONDITION_CPU_PRESSURE ? CGROUP_MASK_CPU :
1078
                                CGROUP_MASK_IO;
1079

1080
                slice = strstrip(first);
6✔
1081
                if (!slice)
6✔
UNCOV
1082
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
×
1083

1084
                r = cg_mask_supported(&mask);
6✔
1085
                if (r < 0)
6✔
1086
                        return log_debug_errno(r, "Failed to get supported cgroup controllers: %m");
×
1087

1088
                if (!FLAGS_SET(mask, required_mask)) {
6✔
UNCOV
1089
                        log_debug("Cgroup %s controller not available, skipping PSI condition check.", pressure_type);
×
1090
                        return 1;
×
1091
                }
1092

1093
                r = cg_slice_to_path(slice, &slice_path);
6✔
1094
                if (r < 0)
6✔
1095
                        return log_debug_errno(r, "Cannot determine slice \"%s\" cgroup path: %m", slice);
1✔
1096

1097
                /* We might be running under the user manager, so get the root path and prefix it accordingly. */
1098
                r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, getpid_cached(), &root_scope);
5✔
1099
                if (r < 0)
5✔
UNCOV
1100
                        return log_debug_errno(r, "Failed to get root cgroup path: %m");
×
1101

1102
                /* Drop init.scope, we want the parent. We could get an empty or / path, but that's fine,
1103
                 * just skip it in that case. */
1104
                e = endswith(root_scope, "/" SPECIAL_INIT_SCOPE);
5✔
1105
                if (e)
5✔
UNCOV
1106
                        *e = 0;
×
1107
                if (!empty_or_root(root_scope)) {
5✔
UNCOV
1108
                        _cleanup_free_ char *slice_joined = NULL;
×
1109

1110
                        slice_joined = path_join(root_scope, slice_path);
5✔
1111
                        if (!slice_joined)
5✔
1112
                                return log_oom_debug();
×
1113

1114
                        free_and_replace(slice_path, slice_joined);
5✔
1115
                }
1116

1117
                r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, slice_path, controller, &pressure_path);
5✔
1118
                if (r < 0)
5✔
UNCOV
1119
                        return log_debug_errno(r, "Error getting cgroup pressure path from %s: %m", slice_path);
×
1120

1121
                value = second;
5✔
1122
        }
1123

1124
        /* If a value including a specific timespan (in the intervals allowed by the kernel),
1125
         * parse it, otherwise we assume just a plain percentage that will be checked if it is
1126
         * smaller or equal to the current pressure average over 5 minutes. */
1127
        r = extract_many_words(&value, "/", 0, &third, &fourth);
24✔
1128
        if (r <= 0)
24✔
UNCOV
1129
                return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
×
1130
        if (r == 1)
24✔
1131
                current = &pressure.avg300;
1132
        else {
1133
                const char *timespan;
9✔
1134

1135
                timespan = skip_leading_chars(fourth, NULL);
9✔
1136
                if (!timespan)
9✔
UNCOV
1137
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
×
1138

1139
                if (startswith(timespan, "10sec"))
9✔
1140
                        current = &pressure.avg10;
1141
                else if (startswith(timespan, "1min"))
7✔
1142
                        current = &pressure.avg60;
1143
                else if (startswith(timespan, "5min"))
4✔
1144
                        current = &pressure.avg300;
1145
                else
1146
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
3✔
1147
        }
1148

1149
        value = strstrip(third);
21✔
1150
        if (!value)
21✔
UNCOV
1151
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
×
1152

1153
        r = parse_permyriad(value);
21✔
1154
        if (r < 0)
21✔
1155
                return log_debug_errno(r, "Failed to parse permyriad: %s", c->parameter);
6✔
1156

1157
        r = store_loadavg_fixed_point(r / 100LU, r % 100LU, &limit);
15✔
1158
        if (r < 0)
15✔
UNCOV
1159
                return log_debug_errno(r, "Failed to parse loadavg: %s", c->parameter);
×
1160

1161
        r = read_resource_pressure(pressure_path, preferred_pressure_type, &pressure);
15✔
1162
        /* cpu.pressure 'full' was recently added at cgroup level, fall back to 'some' */
1163
        if (r == -ENODATA && preferred_pressure_type == PRESSURE_TYPE_FULL)
15✔
UNCOV
1164
                r = read_resource_pressure(pressure_path, PRESSURE_TYPE_SOME, &pressure);
×
1165
        if (r == -ENOENT) {
15✔
1166
                /* We already checked that /proc/pressure exists, so this means we were given a cgroup
1167
                 * that doesn't exist or doesn't exist any longer. */
1168
                log_debug("\"%s\" not found, skipping PSI check.", pressure_path);
1✔
1169
                return 1;
1✔
1170
        }
1171
        if (r < 0)
14✔
UNCOV
1172
                return log_debug_errno(r, "Error parsing pressure from %s: %m", pressure_path);
×
1173

1174
        return *current <= limit;
14✔
1175
}
1176

1177
static int condition_test_kernel_module_loaded(Condition *c, char **env) {
209✔
1178
        int r;
209✔
1179

1180
        assert(c);
209✔
1181
        assert(c->parameter);
209✔
1182
        assert(c->type == CONDITION_KERNEL_MODULE_LOADED);
209✔
1183

1184
        /* Checks whether a specific kernel module is fully loaded (i.e. with the full initialization routine
1185
         * complete). */
1186

1187
        _cleanup_free_ char *normalized = strreplace(c->parameter, "-", "_");
418✔
1188
        if (!normalized)
209✔
UNCOV
1189
                return log_oom_debug();
×
1190

1191
        if (!filename_is_valid(normalized)) {
209✔
1192
                log_debug("Kernel module name '%s' is not valid, hence reporting it to not be loaded.", normalized);
2✔
1193
                return false;
2✔
1194
        }
1195

1196
        _cleanup_free_ char *p = path_join("/sys/module/", normalized);
414✔
1197
        if (!p)
207✔
UNCOV
1198
                return log_oom_debug();
×
1199

1200
        _cleanup_close_ int dir_fd = open(p, O_PATH|O_DIRECTORY|O_CLOEXEC);
414✔
1201
        if (dir_fd < 0) {
207✔
1202
                if (errno == ENOENT) {
132✔
1203
                        log_debug_errno(errno, "'%s/' does not exist, kernel module '%s' not loaded.", p, normalized);
132✔
1204
                        return false;
132✔
1205
                }
1206

UNCOV
1207
                return log_debug_errno(errno, "Failed to open directory '%s/': %m", p);
×
1208
        }
1209

1210
        _cleanup_free_ char *initstate = NULL;
75✔
1211
        r = read_virtual_file_at(dir_fd, "initstate", SIZE_MAX, &initstate, NULL);
75✔
1212
        if (r == -ENOENT) {
75✔
1213
                log_debug_errno(r, "'%s/' exists but '%s/initstate' does not, kernel module '%s' is built-in, hence loaded.", p, p, normalized);
51✔
1214
                return true;
51✔
1215
        }
1216
        if (r < 0)
24✔
UNCOV
1217
                return log_debug_errno(r, "Failed to open '%s/initstate': %m", p);
×
1218

1219
        delete_trailing_chars(initstate, WHITESPACE);
24✔
1220

1221
        if (!streq(initstate, "live")) {
24✔
UNCOV
1222
                log_debug("Kernel module '%s' is reported as '%s', hence not loaded.", normalized, initstate);
×
UNCOV
1223
                return false;
×
1224
        }
1225

1226
        log_debug("Kernel module '%s' detected as loaded.", normalized);
75✔
1227
        return true;
1228
}
1229

1230
int condition_test(Condition *c, char **env) {
5,680✔
1231

1232
        static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c, char **env) = {
5,680✔
1233
                [CONDITION_PATH_EXISTS]              = condition_test_path_exists,
1234
                [CONDITION_PATH_EXISTS_GLOB]         = condition_test_path_exists_glob,
1235
                [CONDITION_PATH_IS_DIRECTORY]        = condition_test_path_is_directory,
1236
                [CONDITION_PATH_IS_SYMBOLIC_LINK]    = condition_test_path_is_symbolic_link,
1237
                [CONDITION_PATH_IS_MOUNT_POINT]      = condition_test_path_is_mount_point,
1238
                [CONDITION_PATH_IS_READ_WRITE]       = condition_test_path_is_read_write,
1239
                [CONDITION_PATH_IS_ENCRYPTED]        = condition_test_path_is_encrypted,
1240
                [CONDITION_DIRECTORY_NOT_EMPTY]      = condition_test_directory_not_empty,
1241
                [CONDITION_FILE_NOT_EMPTY]           = condition_test_file_not_empty,
1242
                [CONDITION_FILE_IS_EXECUTABLE]       = condition_test_file_is_executable,
1243
                [CONDITION_KERNEL_COMMAND_LINE]      = condition_test_kernel_command_line,
1244
                [CONDITION_VERSION]                  = condition_test_version,
1245
                [CONDITION_CREDENTIAL]               = condition_test_credential,
1246
                [CONDITION_VIRTUALIZATION]           = condition_test_virtualization,
1247
                [CONDITION_SECURITY]                 = condition_test_security,
1248
                [CONDITION_CAPABILITY]               = condition_test_capability,
1249
                [CONDITION_HOST]                     = condition_test_host,
1250
                [CONDITION_AC_POWER]                 = condition_test_ac_power,
1251
                [CONDITION_ARCHITECTURE]             = condition_test_architecture,
1252
                [CONDITION_FIRMWARE]                 = condition_test_firmware,
1253
                [CONDITION_NEEDS_UPDATE]             = condition_test_needs_update,
1254
                [CONDITION_FIRST_BOOT]               = condition_test_first_boot,
1255
                [CONDITION_USER]                     = condition_test_user,
1256
                [CONDITION_GROUP]                    = condition_test_group,
1257
                [CONDITION_CONTROL_GROUP_CONTROLLER] = condition_test_control_group_controller,
1258
                [CONDITION_CPUS]                     = condition_test_cpus,
1259
                [CONDITION_MEMORY]                   = condition_test_memory,
1260
                [CONDITION_ENVIRONMENT]              = condition_test_environment,
1261
                [CONDITION_CPU_FEATURE]              = condition_test_cpufeature,
1262
                [CONDITION_OS_RELEASE]               = condition_test_osrelease,
1263
                [CONDITION_MEMORY_PRESSURE]          = condition_test_psi,
1264
                [CONDITION_CPU_PRESSURE]             = condition_test_psi,
1265
                [CONDITION_IO_PRESSURE]              = condition_test_psi,
1266
                [CONDITION_KERNEL_MODULE_LOADED]     = condition_test_kernel_module_loaded,
1267
        };
1268

1269
        int r, b;
5,680✔
1270

1271
        assert(c);
5,680✔
1272
        assert(c->type >= 0);
5,680✔
1273
        assert(c->type < _CONDITION_TYPE_MAX);
5,680✔
1274

1275
        r = condition_tests[c->type](c, env);
5,680✔
1276
        if (r < 0) {
5,680✔
1277
                c->result = CONDITION_ERROR;
28✔
1278
                return r;
28✔
1279
        }
1280

1281
        b = (r > 0) == !c->negate;
5,652✔
1282
        c->result = b ? CONDITION_SUCCEEDED : CONDITION_FAILED;
5,652✔
1283
        return b;
5,652✔
1284
}
1285

1286
bool condition_test_list(
24,570✔
1287
                Condition *first,
1288
                char **env,
1289
                condition_to_string_t to_string,
1290
                condition_test_logger_t logger,
1291
                void *userdata) {
1292

1293
        int triggered = -1;
24,570✔
1294

1295
        /* If the condition list is empty, then it is true */
1296
        if (!first)
24,570✔
1297
                return true;
1298

1299
        /* Otherwise, if all of the non-trigger conditions apply and
1300
         * if any of the trigger conditions apply (unless there are
1301
         * none) we return true */
1302
        LIST_FOREACH(conditions, c, first) {
6,739✔
1303
                int r;
5,394✔
1304

1305
                r = condition_test(c, env);
5,394✔
1306

1307
                if (logger) {
5,394✔
1308
                        if (r < 0)
4,286✔
UNCOV
1309
                                logger(userdata, LOG_WARNING, r, PROJECT_FILE, __LINE__, __func__,
×
1310
                                       "Couldn't determine result for %s=%s%s%s, assuming failed: %m",
UNCOV
1311
                                       to_string(c->type),
×
UNCOV
1312
                                       c->trigger ? "|" : "",
×
UNCOV
1313
                                       c->negate ? "!" : "",
×
1314
                                       c->parameter);
1315
                        else
1316
                                logger(userdata, LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__,
4,286✔
1317
                                       "%s=%s%s%s %s.",
1318
                                       to_string(c->type),
4,286✔
1319
                                       c->trigger ? "|" : "",
4,286✔
1320
                                       c->negate ? "!" : "",
4,286✔
1321
                                       c->parameter,
1322
                                       condition_result_to_string(c->result));
4,286✔
1323
                }
1324

1325
                if (!c->trigger && r <= 0)
5,394✔
1326
                        return false;
1327

1328
                if (c->trigger && triggered <= 0)
3,226✔
1329
                        triggered = r > 0;
1,191✔
1330
        }
1331

1332
        return triggered != 0;
1,345✔
1333
}
1334

UNCOV
1335
void condition_dump(Condition *c, FILE *f, const char *prefix, condition_to_string_t to_string) {
×
UNCOV
1336
        assert(c);
×
UNCOV
1337
        assert(f);
×
UNCOV
1338
        assert(to_string);
×
1339

UNCOV
1340
        prefix = strempty(prefix);
×
1341

UNCOV
1342
        fprintf(f,
×
1343
                "%s\t%s: %s%s%s %s\n",
1344
                prefix,
UNCOV
1345
                to_string(c->type),
×
UNCOV
1346
                c->trigger ? "|" : "",
×
1347
                c->negate ? "!" : "",
×
1348
                c->parameter,
1349
                condition_result_to_string(c->result));
×
1350
}
×
1351

1352
void condition_dump_list(Condition *first, FILE *f, const char *prefix, condition_to_string_t to_string) {
736✔
1353
        LIST_FOREACH(conditions, c, first)
736✔
1354
                condition_dump(c, f, prefix, to_string);
×
1355
}
736✔
1356

1357
static const char* const _condition_type_table[_CONDITION_TYPE_MAX] = {
1358
        [CONDITION_ARCHITECTURE]             = "ConditionArchitecture",
1359
        [CONDITION_FIRMWARE]                 = "ConditionFirmware",
1360
        [CONDITION_VIRTUALIZATION]           = "ConditionVirtualization",
1361
        [CONDITION_HOST]                     = "ConditionHost",
1362
        [CONDITION_KERNEL_COMMAND_LINE]      = "ConditionKernelCommandLine",
1363
        [CONDITION_VERSION]                  = "ConditionVersion",
1364
        [CONDITION_CREDENTIAL]               = "ConditionCredential",
1365
        [CONDITION_SECURITY]                 = "ConditionSecurity",
1366
        [CONDITION_CAPABILITY]               = "ConditionCapability",
1367
        [CONDITION_AC_POWER]                 = "ConditionACPower",
1368
        [CONDITION_NEEDS_UPDATE]             = "ConditionNeedsUpdate",
1369
        [CONDITION_FIRST_BOOT]               = "ConditionFirstBoot",
1370
        [CONDITION_PATH_EXISTS]              = "ConditionPathExists",
1371
        [CONDITION_PATH_EXISTS_GLOB]         = "ConditionPathExistsGlob",
1372
        [CONDITION_PATH_IS_DIRECTORY]        = "ConditionPathIsDirectory",
1373
        [CONDITION_PATH_IS_SYMBOLIC_LINK]    = "ConditionPathIsSymbolicLink",
1374
        [CONDITION_PATH_IS_MOUNT_POINT]      = "ConditionPathIsMountPoint",
1375
        [CONDITION_PATH_IS_READ_WRITE]       = "ConditionPathIsReadWrite",
1376
        [CONDITION_PATH_IS_ENCRYPTED]        = "ConditionPathIsEncrypted",
1377
        [CONDITION_DIRECTORY_NOT_EMPTY]      = "ConditionDirectoryNotEmpty",
1378
        [CONDITION_FILE_NOT_EMPTY]           = "ConditionFileNotEmpty",
1379
        [CONDITION_FILE_IS_EXECUTABLE]       = "ConditionFileIsExecutable",
1380
        [CONDITION_USER]                     = "ConditionUser",
1381
        [CONDITION_GROUP]                    = "ConditionGroup",
1382
        [CONDITION_CONTROL_GROUP_CONTROLLER] = "ConditionControlGroupController",
1383
        [CONDITION_CPUS]                     = "ConditionCPUs",
1384
        [CONDITION_MEMORY]                   = "ConditionMemory",
1385
        [CONDITION_ENVIRONMENT]              = "ConditionEnvironment",
1386
        [CONDITION_CPU_FEATURE]              = "ConditionCPUFeature",
1387
        [CONDITION_OS_RELEASE]               = "ConditionOSRelease",
1388
        [CONDITION_MEMORY_PRESSURE]          = "ConditionMemoryPressure",
1389
        [CONDITION_CPU_PRESSURE]             = "ConditionCPUPressure",
1390
        [CONDITION_IO_PRESSURE]              = "ConditionIOPressure",
1391
        [CONDITION_KERNEL_MODULE_LOADED]     = "ConditionKernelModuleLoaded",
1392
};
1393

1394
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(_condition_type, ConditionType);
5,609✔
1395

1396
const char* condition_type_to_string(ConditionType t) {
5,547✔
1397
        return _condition_type_to_string(t);
5,547✔
1398
}
1399

1400
ConditionType condition_type_from_string(const char *s) {
65✔
1401
        /* for backward compatibility */
1402
        if (streq_ptr(s, "ConditionKernelVersion"))
65✔
1403
                return CONDITION_VERSION;
1404

1405
        return _condition_type_from_string(s);
62✔
1406
}
1407

1408
static const char* const _assert_type_table[_CONDITION_TYPE_MAX] = {
1409
        [CONDITION_ARCHITECTURE]             = "AssertArchitecture",
1410
        [CONDITION_FIRMWARE]                 = "AssertFirmware",
1411
        [CONDITION_VIRTUALIZATION]           = "AssertVirtualization",
1412
        [CONDITION_HOST]                     = "AssertHost",
1413
        [CONDITION_KERNEL_COMMAND_LINE]      = "AssertKernelCommandLine",
1414
        [CONDITION_VERSION]                  = "AssertVersion",
1415
        [CONDITION_CREDENTIAL]               = "AssertCredential",
1416
        [CONDITION_SECURITY]                 = "AssertSecurity",
1417
        [CONDITION_CAPABILITY]               = "AssertCapability",
1418
        [CONDITION_AC_POWER]                 = "AssertACPower",
1419
        [CONDITION_NEEDS_UPDATE]             = "AssertNeedsUpdate",
1420
        [CONDITION_FIRST_BOOT]               = "AssertFirstBoot",
1421
        [CONDITION_PATH_EXISTS]              = "AssertPathExists",
1422
        [CONDITION_PATH_EXISTS_GLOB]         = "AssertPathExistsGlob",
1423
        [CONDITION_PATH_IS_DIRECTORY]        = "AssertPathIsDirectory",
1424
        [CONDITION_PATH_IS_SYMBOLIC_LINK]    = "AssertPathIsSymbolicLink",
1425
        [CONDITION_PATH_IS_MOUNT_POINT]      = "AssertPathIsMountPoint",
1426
        [CONDITION_PATH_IS_READ_WRITE]       = "AssertPathIsReadWrite",
1427
        [CONDITION_PATH_IS_ENCRYPTED]        = "AssertPathIsEncrypted",
1428
        [CONDITION_DIRECTORY_NOT_EMPTY]      = "AssertDirectoryNotEmpty",
1429
        [CONDITION_FILE_NOT_EMPTY]           = "AssertFileNotEmpty",
1430
        [CONDITION_FILE_IS_EXECUTABLE]       = "AssertFileIsExecutable",
1431
        [CONDITION_USER]                     = "AssertUser",
1432
        [CONDITION_GROUP]                    = "AssertGroup",
1433
        [CONDITION_CONTROL_GROUP_CONTROLLER] = "AssertControlGroupController",
1434
        [CONDITION_CPUS]                     = "AssertCPUs",
1435
        [CONDITION_MEMORY]                   = "AssertMemory",
1436
        [CONDITION_ENVIRONMENT]              = "AssertEnvironment",
1437
        [CONDITION_CPU_FEATURE]              = "AssertCPUFeature",
1438
        [CONDITION_OS_RELEASE]               = "AssertOSRelease",
1439
        [CONDITION_MEMORY_PRESSURE]          = "AssertMemoryPressure",
1440
        [CONDITION_CPU_PRESSURE]             = "AssertCPUPressure",
1441
        [CONDITION_IO_PRESSURE]              = "AssertIOPressure",
1442
        [CONDITION_KERNEL_MODULE_LOADED]     = "AssertKernelModuleLoaded",
1443
};
1444

1445
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(_assert_type, ConditionType);
248✔
1446

1447
const char* assert_type_to_string(ConditionType t) {
187✔
1448
        return _assert_type_to_string(t);
187✔
1449
}
1450

1451
ConditionType assert_type_from_string(const char *s) {
62✔
1452
        /* for backward compatibility */
1453
        if (streq_ptr(s, "AssertKernelVersion"))
62✔
1454
                return CONDITION_VERSION;
1455

1456
        return _assert_type_from_string(s);
61✔
1457
}
1458

1459
static const char* const condition_result_table[_CONDITION_RESULT_MAX] = {
1460
        [CONDITION_UNTESTED]  = "untested",
1461
        [CONDITION_SUCCEEDED] = "succeeded",
1462
        [CONDITION_FAILED]    = "failed",
1463
        [CONDITION_ERROR]     = "error",
1464
};
1465

1466
DEFINE_STRING_TABLE_LOOKUP(condition_result, ConditionResult);
4,340✔
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