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

systemd / systemd / 14161316195

30 Mar 2025 08:18AM UTC coverage: 71.892% (-0.08%) from 71.976%
14161316195

push

github

DaanDeMeyer
test: skip networkd tests if networkd/resolved are disabled at build time

296399 of 412283 relevant lines covered (71.89%)

653258.53 hits per line

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

85.3
/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) {
22,049✔
72
        Condition *c;
22,049✔
73

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

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

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

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

94
        return c;
95
}
96

97
Condition* condition_free(Condition *c) {
22,049✔
98
        assert(c);
22,049✔
99

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

104
Condition* condition_free_list_type(Condition *head, ConditionType type) {
138,572✔
105
        LIST_FOREACH(conditions, c, head)
160,335✔
106
                if (type < 0 || c->type == type) {
21,763✔
107
                        LIST_REMOVE(conditions, head, c);
21,763✔
108
                        condition_free(c);
21,763✔
109
                }
110

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

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

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

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

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

129
        STRV_FOREACH(word, args) {
5,566✔
130
                bool found;
5,408✔
131

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

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

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

145
        return false;
146
}
147

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

151
        assert(c);
39✔
152
        assert(c->parameter);
39✔
153
        assert(c->type == CONDITION_CREDENTIAL);
39✔
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 */
39✔
160
                return false;
161

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

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

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

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

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

186
        return false;
36✔
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
        int r;
32✔
415
        CGroupMask system_mask, wanted_mask = 0;
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 cg_all_unified();
1✔
423
        if (streq(c->parameter, "v1")) {
31✔
424
                r = cg_all_unified();
1✔
425
                if (r < 0)
1✔
426
                        return r;
427
                return !r;
1✔
428
        }
429

430
        r = cg_mask_supported(&system_mask);
30✔
431
        if (r < 0)
30✔
432
                return log_debug_errno(r, "Failed to determine supported controllers: %m");
×
433

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

443
        return FLAGS_SET(system_mask, wanted_mask);
28✔
444
}
445

446
static int condition_test_group(Condition *c, char **env) {
4✔
447
        gid_t id;
4✔
448
        int r;
4✔
449

450
        assert(c);
4✔
451
        assert(c->parameter);
4✔
452
        assert(c->type == CONDITION_GROUP);
4✔
453

454
        r = parse_gid(c->parameter, &id);
4✔
455
        if (r >= 0)
4✔
456
                return in_gid(id);
2✔
457

458
        /* Avoid any NSS lookups if we are PID1 */
459
        if (getpid_cached() == 1)
2✔
460
                return streq(c->parameter, "root");
×
461

462
        return in_group(c->parameter) > 0;
2✔
463
}
464

465
static int condition_test_virtualization(Condition *c, char **env) {
1,355✔
466
        Virtualization v;
1,355✔
467
        int b;
1,355✔
468

469
        assert(c);
1,355✔
470
        assert(c->parameter);
1,355✔
471
        assert(c->type == CONDITION_VIRTUALIZATION);
1,355✔
472

473
        if (streq(c->parameter, "private-users"))
1,355✔
474
                return running_in_userns();
63✔
475

476
        v = detect_virtualization();
1,292✔
477
        if (v < 0)
1,292✔
478
                return v;
479

480
        /* First, compare with yes/no */
481
        b = parse_boolean(c->parameter);
1,292✔
482
        if (b >= 0)
1,292✔
483
                return b == (v != VIRTUALIZATION_NONE);
10✔
484

485
        /* Then, compare categorization */
486
        if (streq(c->parameter, "vm"))
1,282✔
487
                return VIRTUALIZATION_IS_VM(v);
1✔
488

489
        if (streq(c->parameter, "container"))
1,281✔
490
                return VIRTUALIZATION_IS_CONTAINER(v);
1,254✔
491

492
        /* Finally compare id */
493
        return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v));
27✔
494
}
495

496
static int condition_test_architecture(Condition *c, char **env) {
4✔
497
        Architecture a, b;
4✔
498

499
        assert(c);
4✔
500
        assert(c->parameter);
4✔
501
        assert(c->type == CONDITION_ARCHITECTURE);
4✔
502

503
        a = uname_architecture();
4✔
504
        if (a < 0)
4✔
505
                return a;
506

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

515
        return a == b;
3✔
516
}
517

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

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

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

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

546
        dtcompatlist = strv_parse_nulstr(dtcompat, size);
×
547
        if (!dtcompatlist)
×
548
                return -ENOMEM;
549

550
        return strv_contains(dtcompatlist, dtcarg);
×
551
}
552

553
static int condition_test_firmware_smbios_field(const char *expression) {
17✔
554
        _cleanup_free_ char *field = NULL, *expected_value = NULL, *actual_value = NULL;
17✔
555
        CompareOperator operator;
17✔
556
        int r;
17✔
557

558
        assert(expression);
17✔
559

560
        /* Parse SMBIOS field */
561
        r = extract_first_word(&expression, &field, COMPARE_OPERATOR_WITH_FNMATCH_CHARS, EXTRACT_RETAIN_SEPARATORS);
17✔
562
        if (r < 0)
17✔
563
                return r;
564
        if (r == 0 || isempty(expression))
17✔
565
                return -EINVAL;
566

567
        /* Remove trailing spaces from SMBIOS field */
568
        delete_trailing_chars(field, WHITESPACE);
15✔
569

570
        /* Parse operator */
571
        operator = parse_compare_operator(&expression, COMPARE_ALLOW_FNMATCH|COMPARE_EQUAL_BY_STRING);
15✔
572
        if (operator < 0)
15✔
573
                return operator;
574

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

582
        /* Read actual value from sysfs */
583
        if (!filename_is_valid(field))
12✔
584
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid SMBIOS field name.");
×
585

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

595
        /* Remove trailing newline */
596
        delete_trailing_chars(actual_value, WHITESPACE);
11✔
597

598
        /* Finally compare actual and expected value */
599
        return version_or_fnmatch_compare(operator, actual_value, expected_value);
11✔
600
}
601

602
static int condition_test_firmware(Condition *c, char **env) {
22✔
603
        sd_char *arg;
22✔
604
        int r;
22✔
605

606
        assert(c);
22✔
607
        assert(c->parameter);
22✔
608
        assert(c->type == CONDITION_FIRMWARE);
22✔
609

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

621
                end = strrchr(arg, ')');
1✔
622
                if (!end || *(end + 1) != '\0') {
1✔
623
                        log_debug("Malformed ConditionFirmware=%s", c->parameter);
×
624
                        return false;
×
625
                }
626

627
                dtc_arg = strndup(arg, end - arg);
1✔
628
                if (!dtc_arg)
1✔
629
                        return -ENOMEM;
630

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

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

642
                smbios_arg = strndup(arg, end - arg);
17✔
643
                if (!smbios_arg)
17✔
644
                        return log_oom_debug();
×
645

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

656
static int condition_test_host(Condition *c, char **env) {
4✔
657
        _cleanup_free_ char *h = NULL;
4✔
658
        int r;
4✔
659

660
        assert(c);
4✔
661
        assert(c->parameter);
4✔
662
        assert(c->type == CONDITION_HOST);
4✔
663

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

675
                /* If this is a UUID, check if this matches the machine ID, boot ID or product UUID */
676
                FOREACH_ELEMENT(i, table) {
2✔
677
                        sd_id128_t y;
2✔
678

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

686
                /* Fall through, also allow setups where people set hostnames to UUIDs. Kinda weird, but no
687
                 * reason not to allow that */
688
        }
689

690
        h = gethostname_malloc();
2✔
691
        if (!h)
2✔
692
                return -ENOMEM;
693

694
        r = fnmatch(c->parameter, h, FNM_CASEFOLD);
2✔
695
        if (r == FNM_NOMATCH)
2✔
696
                return false;
697
        if (r != 0)
1✔
698
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "fnmatch() failed.");
×
699

700
        return true;
701
}
702

703
static int condition_test_ac_power(Condition *c, char **env) {
4✔
704
        int r;
4✔
705

706
        assert(c);
4✔
707
        assert(c->parameter);
4✔
708
        assert(c->type == CONDITION_AC_POWER);
4✔
709

710
        r = parse_boolean(c->parameter);
4✔
711
        if (r < 0)
4✔
712
                return r;
713

714
        return (on_ac_power() != 0) == !!r;
4✔
715
}
716

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

724
        return FLAGS_SET(tpm2_support_full(TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_FIRMWARE), TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_FIRMWARE);
×
725
}
726

727
static int condition_test_security(Condition *c, char **env) {
273✔
728
        assert(c);
273✔
729
        assert(c->parameter);
273✔
730
        assert(c->type == CONDITION_SECURITY);
273✔
731

732
        if (streq(c->parameter, "selinux"))
273✔
733
                return mac_selinux_use();
1✔
734
        if (streq(c->parameter, "smack"))
272✔
735
                return mac_smack_use();
1✔
736
        if (streq(c->parameter, "apparmor"))
271✔
737
                return mac_apparmor_use();
1✔
738
        if (streq(c->parameter, "audit"))
270✔
739
                return use_audit();
14✔
740
        if (streq(c->parameter, "ima"))
256✔
741
                return use_ima();
1✔
742
        if (streq(c->parameter, "tomoyo"))
255✔
743
                return mac_tomoyo_use();
1✔
744
        if (streq(c->parameter, "uefi-secureboot"))
254✔
745
                return is_efi_secure_boot();
1✔
746
        if (streq(c->parameter, "tpm2"))
253✔
747
                return has_tpm2();
×
748
        if (streq(c->parameter, "cvm"))
253✔
749
                return detect_confidential_virtualization() > 0;
1✔
750
        if (streq(c->parameter, "measured-uki"))
252✔
751
                return efi_measured_uki(LOG_DEBUG);
251✔
752

753
        return false;
754
}
755

756
static int condition_test_capability(Condition *c, char **env) {
584✔
757
        int r;
584✔
758

759
        assert(c);
584✔
760
        assert(c->parameter);
584✔
761
        assert(c->type == CONDITION_CAPABILITY);
584✔
762

763
        /* If it's an invalid capability, we don't have it */
764
        int value = capability_from_name(c->parameter);
584✔
765
        if (value < 0)
584✔
766
                return -EINVAL;
584✔
767

768
        CapabilityQuintet q;
584✔
769
        r = pidref_get_capability(&PIDREF_MAKE_FROM_PID(getpid_cached()), &q);
584✔
770
        if (r < 0)
584✔
771
                return r;
772

773
        return BIT_SET(q.bounding, value);
584✔
774
}
775

776
static int condition_test_needs_update(Condition *c, char **env) {
204✔
777
        struct stat usr, other;
204✔
778
        const char *p;
204✔
779
        bool b;
204✔
780
        int r;
204✔
781

782
        assert(c);
204✔
783
        assert(c->parameter);
204✔
784
        assert(c->type == CONDITION_NEEDS_UPDATE);
204✔
785

786
        r = proc_cmdline_get_bool("systemd.condition_needs_update", /* flags = */ 0, &b);
204✔
787
        if (r < 0)
204✔
788
                log_debug_errno(r, "Failed to parse systemd.condition_needs_update= kernel command line argument, ignoring: %m");
×
789
        if (r > 0)
204✔
790
                return b;
204✔
791

792
        if (in_initrd()) {
204✔
793
                log_debug("We are in an initrd, not doing any updates.");
60✔
794
                return false;
60✔
795
        }
796

797
        if (!path_is_absolute(c->parameter)) {
144✔
798
                log_debug("Specified condition parameter '%s' is not absolute, assuming an update is needed.", c->parameter);
×
799
                return true;
×
800
        }
801

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

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

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

819
        if (lstat("/usr/", &usr) < 0) {
144✔
820
                log_debug_errno(errno, "Failed to stat() /usr/, assuming an update is needed: %m");
×
821
                return true;
×
822
        }
823

824
        /*
825
         * First, compare seconds as they are always accurate...
826
         */
827
        if (usr.st_mtim.tv_sec != other.st_mtim.tv_sec)
144✔
828
                return usr.st_mtim.tv_sec > other.st_mtim.tv_sec;
×
829

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

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

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

859
        return timespec_load_nsec(&usr.st_mtim) > timestamp;
×
860
}
861

862
static bool in_first_boot(void) {
68✔
863
        static int first_boot = -1;
68✔
864
        int r;
68✔
865

866
        if (first_boot >= 0)
68✔
867
                return first_boot;
×
868

869
        const char *e = secure_getenv("SYSTEMD_FIRST_BOOT");
68✔
870
        if (e) {
68✔
871
                r = parse_boolean(e);
×
872
                if (r < 0)
×
873
                        log_debug_errno(r, "Failed to parse $SYSTEMD_FIRST_BOOT, ignoring: %m");
×
874
                else
875
                        return (first_boot = r);
×
876
        }
877

878
        r = RET_NERRNO(access("/run/systemd/first-boot", F_OK));
68✔
879
        if (r < 0 && r != -ENOENT)
68✔
880
                log_debug_errno(r, "Failed to check if /run/systemd/first-boot exists, assuming no: %m");
×
881
        return r >= 0;
68✔
882
}
883

884
static int condition_test_first_boot(Condition *c, char **env) {
68✔
885
        int r;
68✔
886

887
        assert(c);
68✔
888
        assert(c->parameter);
68✔
889
        assert(c->type == CONDITION_FIRST_BOOT);
68✔
890

891
        // TODO: Parse c->parameter immediately when reading the config.
892
        //       Apply negation when parsing too.
893

894
        r = parse_boolean(c->parameter);
68✔
895
        if (r < 0)
68✔
896
                return r;
897

898
        return in_first_boot() == r;
68✔
899
}
900

901
static int condition_test_environment(Condition *c, char **env) {
168✔
902
        bool equal;
168✔
903

904
        assert(c);
168✔
905
        assert(c->parameter);
168✔
906
        assert(c->type == CONDITION_ENVIRONMENT);
168✔
907

908
        equal = strchr(c->parameter, '=');
168✔
909

910
        STRV_FOREACH(i, env) {
1,898✔
911
                bool found;
1,756✔
912

913
                if (equal)
1,756✔
914
                        found = streq(c->parameter, *i);
55✔
915
                else {
916
                        const char *f;
1,701✔
917

918
                        f = startswith(*i, c->parameter);
1,701✔
919
                        found = f && IN_SET(*f, 0, '=');
1,701✔
920
                }
921

922
                if (found)
55✔
923
                        return true;
924
        }
925

926
        return false;
927
}
928

929
static int condition_test_path_exists(Condition *c, char **env) {
1,038✔
930
        assert(c);
1,038✔
931
        assert(c->parameter);
1,038✔
932
        assert(c->type == CONDITION_PATH_EXISTS);
1,038✔
933

934
        return access(c->parameter, F_OK) >= 0;
1,038✔
935
}
936

937
static int condition_test_path_exists_glob(Condition *c, char **env) {
2✔
938
        assert(c);
2✔
939
        assert(c->parameter);
2✔
940
        assert(c->type == CONDITION_PATH_EXISTS_GLOB);
2✔
941

942
        return glob_exists(c->parameter) > 0;
2✔
943
}
944

945
static int condition_test_path_is_directory(Condition *c, char **env) {
1✔
946
        assert(c);
1✔
947
        assert(c->parameter);
1✔
948
        assert(c->type == CONDITION_PATH_IS_DIRECTORY);
1✔
949

950
        return is_dir(c->parameter, true) > 0;
1✔
951
}
952

953
static int condition_test_path_is_symbolic_link(Condition *c, char **env) {
17✔
954
        assert(c);
17✔
955
        assert(c->parameter);
17✔
956
        assert(c->type == CONDITION_PATH_IS_SYMBOLIC_LINK);
17✔
957

958
        return is_symlink(c->parameter) > 0;
17✔
959
}
960

961
static int condition_test_path_is_mount_point(Condition *c, char **env) {
71✔
962
        assert(c);
71✔
963
        assert(c->parameter);
71✔
964
        assert(c->type == CONDITION_PATH_IS_MOUNT_POINT);
71✔
965

966
        return path_is_mount_point_full(c->parameter, /* root = */ NULL, AT_SYMLINK_FOLLOW) > 0;
71✔
967
}
968

969
static int condition_test_path_is_read_write(Condition *c, char **env) {
440✔
970
        int r;
440✔
971

972
        assert(c);
440✔
973
        assert(c->parameter);
440✔
974
        assert(c->type == CONDITION_PATH_IS_READ_WRITE);
440✔
975

976
        r = path_is_read_only_fs(c->parameter);
440✔
977

978
        return r <= 0 && r != -ENOENT;
440✔
979
}
980

981
static int condition_test_cpufeature(Condition *c, char **env) {
3✔
982
        assert(c);
3✔
983
        assert(c->parameter);
3✔
984
        assert(c->type == CONDITION_CPU_FEATURE);
3✔
985

986
        return has_cpu_with_flag(ascii_strlower(c->parameter));
3✔
987
}
988

989
static int condition_test_path_is_encrypted(Condition *c, char **env) {
1✔
990
        int r;
1✔
991

992
        assert(c);
1✔
993
        assert(c->parameter);
1✔
994
        assert(c->type == CONDITION_PATH_IS_ENCRYPTED);
1✔
995

996
        r = path_is_encrypted(c->parameter);
1✔
997
        if (r < 0 && r != -ENOENT)
1✔
998
                log_debug_errno(r, "Failed to determine if '%s' is encrypted: %m", c->parameter);
×
999

1000
        return r > 0;
1✔
1001
}
1002

1003
static int condition_test_directory_not_empty(Condition *c, char **env) {
863✔
1004
        int r;
863✔
1005

1006
        assert(c);
863✔
1007
        assert(c->parameter);
863✔
1008
        assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY);
863✔
1009

1010
        r = dir_is_empty(c->parameter, /* ignore_hidden_or_backup= */ true);
863✔
1011
        return r <= 0 && !IN_SET(r, -ENOENT, -ENOTDIR);
863✔
1012
}
1013

1014
static int condition_test_file_not_empty(Condition *c, char **env) {
69✔
1015
        struct stat st;
69✔
1016

1017
        assert(c);
69✔
1018
        assert(c->parameter);
69✔
1019
        assert(c->type == CONDITION_FILE_NOT_EMPTY);
69✔
1020

1021
        return (stat(c->parameter, &st) >= 0 &&
69✔
1022
                S_ISREG(st.st_mode) &&
69✔
1023
                st.st_size > 0);
51✔
1024
}
1025

1026
static int condition_test_file_is_executable(Condition *c, char **env) {
2✔
1027
        struct stat st;
2✔
1028

1029
        assert(c);
2✔
1030
        assert(c->parameter);
2✔
1031
        assert(c->type == CONDITION_FILE_IS_EXECUTABLE);
2✔
1032

1033
        return (stat(c->parameter, &st) >= 0 &&
2✔
1034
                S_ISREG(st.st_mode) &&
2✔
1035
                (st.st_mode & 0111));
2✔
1036
}
1037

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

1046
        assert(c);
26✔
1047
        assert(c->parameter);
26✔
1048
        assert(IN_SET(c->type, CONDITION_MEMORY_PRESSURE, CONDITION_CPU_PRESSURE, CONDITION_IO_PRESSURE));
26✔
1049

1050
        if (!is_pressure_supported()) {
26✔
1051
                log_debug("Pressure Stall Information (PSI) is not supported, skipping.");
×
1052
                return 1;
×
1053
        }
1054

1055
        pressure_type = c->type == CONDITION_MEMORY_PRESSURE ? "memory" :
26✔
1056
                        c->type == CONDITION_CPU_PRESSURE ? "cpu" :
18✔
1057
                        "io";
1058

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

1069
                pressure_path = path_join("/proc/pressure", pressure_type);
19✔
1070
                if (!pressure_path)
19✔
1071
                        return log_oom_debug();
×
1072

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

1080
                required_mask = c->type == CONDITION_MEMORY_PRESSURE ? CGROUP_MASK_MEMORY :
6✔
1081
                                c->type == CONDITION_CPU_PRESSURE ? CGROUP_MASK_CPU :
1082
                                CGROUP_MASK_IO;
1083

1084
                slice = strstrip(first);
6✔
1085
                if (!slice)
6✔
1086
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
×
1087

1088
                r = cg_all_unified();
6✔
1089
                if (r < 0)
6✔
1090
                        return log_debug_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m");
×
1091
                if (r == 0) {
6✔
1092
                        log_debug("PSI condition check requires the unified cgroups hierarchy, skipping.");
×
1093
                        return 1;
×
1094
                }
1095

1096
                r = cg_mask_supported(&mask);
6✔
1097
                if (r < 0)
6✔
1098
                        return log_debug_errno(r, "Failed to get supported cgroup controllers: %m");
×
1099

1100
                if (!FLAGS_SET(mask, required_mask)) {
6✔
1101
                        log_debug("Cgroup %s controller not available, skipping PSI condition check.", pressure_type);
×
1102
                        return 1;
×
1103
                }
1104

1105
                r = cg_slice_to_path(slice, &slice_path);
6✔
1106
                if (r < 0)
6✔
1107
                        return log_debug_errno(r, "Cannot determine slice \"%s\" cgroup path: %m", slice);
1✔
1108

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

1114
                /* Drop init.scope, we want the parent. We could get an empty or / path, but that's fine,
1115
                 * just skip it in that case. */
1116
                e = endswith(root_scope, "/" SPECIAL_INIT_SCOPE);
5✔
1117
                if (e)
5✔
1118
                        *e = 0;
×
1119
                if (!empty_or_root(root_scope)) {
5✔
1120
                        _cleanup_free_ char *slice_joined = NULL;
×
1121

1122
                        slice_joined = path_join(root_scope, slice_path);
5✔
1123
                        if (!slice_joined)
5✔
1124
                                return log_oom_debug();
×
1125

1126
                        free_and_replace(slice_path, slice_joined);
5✔
1127
                }
1128

1129
                r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, slice_path, controller, &pressure_path);
5✔
1130
                if (r < 0)
5✔
1131
                        return log_debug_errno(r, "Error getting cgroup pressure path from %s: %m", slice_path);
×
1132

1133
                value = second;
5✔
1134
        }
1135

1136
        /* If a value including a specific timespan (in the intervals allowed by the kernel),
1137
         * parse it, otherwise we assume just a plain percentage that will be checked if it is
1138
         * smaller or equal to the current pressure average over 5 minutes. */
1139
        r = extract_many_words(&value, "/", 0, &third, &fourth);
24✔
1140
        if (r <= 0)
24✔
1141
                return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
×
1142
        if (r == 1)
24✔
1143
                current = &pressure.avg300;
1144
        else {
1145
                const char *timespan;
9✔
1146

1147
                timespan = skip_leading_chars(fourth, NULL);
9✔
1148
                if (!timespan)
9✔
1149
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
×
1150

1151
                if (startswith(timespan, "10sec"))
9✔
1152
                        current = &pressure.avg10;
1153
                else if (startswith(timespan, "1min"))
7✔
1154
                        current = &pressure.avg60;
1155
                else if (startswith(timespan, "5min"))
4✔
1156
                        current = &pressure.avg300;
1157
                else
1158
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
3✔
1159
        }
1160

1161
        value = strstrip(third);
21✔
1162
        if (!value)
21✔
1163
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
×
1164

1165
        r = parse_permyriad(value);
21✔
1166
        if (r < 0)
21✔
1167
                return log_debug_errno(r, "Failed to parse permyriad: %s", c->parameter);
6✔
1168

1169
        r = store_loadavg_fixed_point(r / 100LU, r % 100LU, &limit);
15✔
1170
        if (r < 0)
15✔
1171
                return log_debug_errno(r, "Failed to parse loadavg: %s", c->parameter);
×
1172

1173
        r = read_resource_pressure(pressure_path, preferred_pressure_type, &pressure);
15✔
1174
        /* cpu.pressure 'full' was recently added at cgroup level, fall back to 'some' */
1175
        if (r == -ENODATA && preferred_pressure_type == PRESSURE_TYPE_FULL)
15✔
1176
                r = read_resource_pressure(pressure_path, PRESSURE_TYPE_SOME, &pressure);
×
1177
        if (r == -ENOENT) {
15✔
1178
                /* We already checked that /proc/pressure exists, so this means we were given a cgroup
1179
                 * that doesn't exist or doesn't exist any longer. */
1180
                log_debug("\"%s\" not found, skipping PSI check.", pressure_path);
1✔
1181
                return 1;
1✔
1182
        }
1183
        if (r < 0)
14✔
1184
                return log_debug_errno(r, "Error parsing pressure from %s: %m", pressure_path);
×
1185

1186
        return *current <= limit;
14✔
1187
}
1188

1189
static int condition_test_kernel_module_loaded(Condition *c, char **env) {
216✔
1190
        int r;
216✔
1191

1192
        assert(c);
216✔
1193
        assert(c->parameter);
216✔
1194
        assert(c->type == CONDITION_KERNEL_MODULE_LOADED);
216✔
1195

1196
        /* Checks whether a specific kernel module is fully loaded (i.e. with the full initialization routine
1197
         * complete). */
1198

1199
        _cleanup_free_ char *normalized = strreplace(c->parameter, "-", "_");
432✔
1200
        if (!normalized)
216✔
1201
                return log_oom_debug();
×
1202

1203
        if (!filename_is_valid(normalized)) {
216✔
1204
                log_debug("Kernel module name '%s' is not valid, hence reporting it to not be loaded.", normalized);
2✔
1205
                return false;
2✔
1206
        }
1207

1208
        _cleanup_free_ char *p = path_join("/sys/module/", normalized);
428✔
1209
        if (!p)
214✔
1210
                return log_oom_debug();
×
1211

1212
        _cleanup_close_ int dir_fd = open(p, O_PATH|O_DIRECTORY|O_CLOEXEC);
428✔
1213
        if (dir_fd < 0) {
214✔
1214
                if (errno == ENOENT) {
134✔
1215
                        log_debug_errno(errno, "'%s/' does not exist, kernel module '%s' not loaded.", p, normalized);
134✔
1216
                        return false;
134✔
1217
                }
1218

1219
                return log_debug_errno(errno, "Failed to open directory '%s/': %m", p);
×
1220
        }
1221

1222
        _cleanup_free_ char *initstate = NULL;
80✔
1223
        r = read_virtual_file_at(dir_fd, "initstate", SIZE_MAX, &initstate, NULL);
80✔
1224
        if (r == -ENOENT) {
80✔
1225
                log_debug_errno(r, "'%s/' exists but '%s/initstate' does not, kernel module '%s' is built-in, hence loaded.", p, p, normalized);
54✔
1226
                return true;
54✔
1227
        }
1228
        if (r < 0)
26✔
1229
                return log_debug_errno(r, "Failed to open '%s/initstate': %m", p);
×
1230

1231
        delete_trailing_chars(initstate, WHITESPACE);
26✔
1232

1233
        if (!streq(initstate, "live")) {
26✔
1234
                log_debug("Kernel module '%s' is reported as '%s', hence not loaded.", normalized, initstate);
×
1235
                return false;
×
1236
        }
1237

1238
        log_debug("Kernel module '%s' detected as loaded.", normalized);
80✔
1239
        return true;
1240
}
1241

1242
int condition_test(Condition *c, char **env) {
5,803✔
1243

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

1281
        int r, b;
5,803✔
1282

1283
        assert(c);
5,803✔
1284
        assert(c->type >= 0);
5,803✔
1285
        assert(c->type < _CONDITION_TYPE_MAX);
5,803✔
1286

1287
        r = condition_tests[c->type](c, env);
5,803✔
1288
        if (r < 0) {
5,803✔
1289
                c->result = CONDITION_ERROR;
28✔
1290
                return r;
28✔
1291
        }
1292

1293
        b = (r > 0) == !c->negate;
5,775✔
1294
        c->result = b ? CONDITION_SUCCEEDED : CONDITION_FAILED;
5,775✔
1295
        return b;
5,775✔
1296
}
1297

1298
bool condition_test_list(
23,067✔
1299
                Condition *first,
1300
                char **env,
1301
                condition_to_string_t to_string,
1302
                condition_test_logger_t logger,
1303
                void *userdata) {
1304

1305
        int triggered = -1;
23,067✔
1306

1307
        /* If the condition list is empty, then it is true */
1308
        if (!first)
23,067✔
1309
                return true;
1310

1311
        /* Otherwise, if all of the non-trigger conditions apply and
1312
         * if any of the trigger conditions apply (unless there are
1313
         * none) we return true */
1314
        LIST_FOREACH(conditions, c, first) {
6,878✔
1315
                int r;
5,517✔
1316

1317
                r = condition_test(c, env);
5,517✔
1318

1319
                if (logger) {
5,517✔
1320
                        if (r < 0)
4,415✔
1321
                                logger(userdata, LOG_WARNING, r, PROJECT_FILE, __LINE__, __func__,
×
1322
                                       "Couldn't determine result for %s=%s%s%s, assuming failed: %m",
1323
                                       to_string(c->type),
×
1324
                                       c->trigger ? "|" : "",
×
1325
                                       c->negate ? "!" : "",
×
1326
                                       c->parameter);
1327
                        else
1328
                                logger(userdata, LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__,
4,415✔
1329
                                       "%s=%s%s%s %s.",
1330
                                       to_string(c->type),
4,415✔
1331
                                       c->trigger ? "|" : "",
4,415✔
1332
                                       c->negate ? "!" : "",
4,415✔
1333
                                       c->parameter,
1334
                                       condition_result_to_string(c->result));
4,415✔
1335
                }
1336

1337
                if (!c->trigger && r <= 0)
5,517✔
1338
                        return false;
1339

1340
                if (c->trigger && triggered <= 0)
3,306✔
1341
                        triggered = r > 0;
1,230✔
1342
        }
1343

1344
        return triggered != 0;
1,361✔
1345
}
1346

1347
void condition_dump(Condition *c, FILE *f, const char *prefix, condition_to_string_t to_string) {
×
1348
        assert(c);
×
1349
        assert(f);
×
1350
        assert(to_string);
×
1351

1352
        prefix = strempty(prefix);
×
1353

1354
        fprintf(f,
×
1355
                "%s\t%s: %s%s%s %s\n",
1356
                prefix,
1357
                to_string(c->type),
×
1358
                c->trigger ? "|" : "",
×
1359
                c->negate ? "!" : "",
×
1360
                c->parameter,
1361
                condition_result_to_string(c->result));
×
1362
}
×
1363

1364
void condition_dump_list(Condition *first, FILE *f, const char *prefix, condition_to_string_t to_string) {
736✔
1365
        LIST_FOREACH(conditions, c, first)
736✔
1366
                condition_dump(c, f, prefix, to_string);
×
1367
}
736✔
1368

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

1406
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(_condition_type, ConditionType);
5,785✔
1407

1408
const char* condition_type_to_string(ConditionType t) {
5,723✔
1409
        return _condition_type_to_string(t);
5,723✔
1410
}
1411

1412
ConditionType condition_type_from_string(const char *s) {
65✔
1413
        /* for backward compatibility */
1414
        if (streq_ptr(s, "ConditionKernelVersion"))
65✔
1415
                return CONDITION_VERSION;
1416

1417
        return _condition_type_from_string(s);
62✔
1418
}
1419

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

1457
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(_assert_type, ConditionType);
258✔
1458

1459
const char* assert_type_to_string(ConditionType t) {
197✔
1460
        return _assert_type_to_string(t);
197✔
1461
}
1462

1463
ConditionType assert_type_from_string(const char *s) {
62✔
1464
        /* for backward compatibility */
1465
        if (streq_ptr(s, "AssertKernelVersion"))
62✔
1466
                return CONDITION_VERSION;
1467

1468
        return _assert_type_from_string(s);
61✔
1469
}
1470

1471
static const char* const condition_result_table[_CONDITION_RESULT_MAX] = {
1472
        [CONDITION_UNTESTED]  = "untested",
1473
        [CONDITION_SUCCEEDED] = "succeeded",
1474
        [CONDITION_FAILED]    = "failed",
1475
        [CONDITION_ERROR]     = "error",
1476
};
1477

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