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

systemd / systemd / 17813902210

17 Sep 2025 11:56PM UTC coverage: 72.24% (-0.04%) from 72.281%
17813902210

push

github

web-flow
sysupdate: use conf_files_list_strv_full() where possible (#38198)

37 of 44 new or added lines in 1 file covered. (84.09%)

6236 existing lines in 79 files now uncovered.

302695 of 419015 relevant lines covered (72.24%)

1046897.1 hits per line

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

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

3
#include <fcntl.h>
4
#include <fnmatch.h>
5
#include <gnu/libc-version.h>
6
#include <sys/stat.h>
7
#include <sys/utsname.h>
8
#include <time.h>
9
#include <unistd.h>
10

11
#include "sd-id128.h"
12

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

68
Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) {
21,989✔
69
        Condition *c;
21,989✔
70

71
        assert(type >= 0);
21,989✔
72
        assert(type < _CONDITION_TYPE_MAX);
21,989✔
73
        assert(parameter);
21,989✔
74

75
        c = new(Condition, 1);
21,989✔
76
        if (!c)
21,989✔
77
                return NULL;
78

79
        *c = (Condition) {
21,989✔
80
                .type = type,
81
                .trigger = trigger,
82
                .negate = negate,
83
        };
84

85
        if (parameter) {
21,989✔
86
                c->parameter = strdup(parameter);
21,989✔
87
                if (!c->parameter)
21,989✔
88
                        return mfree(c);
×
89
        }
90

91
        return c;
92
}
93

94
Condition* condition_free(Condition *c) {
21,989✔
95
        assert(c);
21,989✔
96

97
        free(c->parameter);
21,989✔
98
        return mfree(c);
21,989✔
99
}
100

101
Condition* condition_free_list_type(Condition *head, ConditionType type) {
171,828✔
102
        LIST_FOREACH(conditions, c, head)
193,531✔
103
                if (type < 0 || c->type == type) {
21,703✔
104
                        LIST_REMOVE(conditions, head, c);
21,703✔
105
                        condition_free(c);
21,703✔
106
                }
107

108
        assert(type >= 0 || !head);
171,828✔
109
        return head;
171,828✔
110
}
111

112
static int condition_test_kernel_command_line(Condition *c, char **env) {
164✔
113
        _cleanup_strv_free_ char **args = NULL;
164✔
114
        int r;
164✔
115

116
        assert(c);
164✔
117
        assert(c->parameter);
164✔
118
        assert(c->type == CONDITION_KERNEL_COMMAND_LINE);
164✔
119

120
        r = proc_cmdline_strv(&args);
164✔
121
        if (r < 0)
164✔
122
                return r;
123

124
        bool equal = strchr(c->parameter, '=');
164✔
125

126
        STRV_FOREACH(word, args) {
5,962✔
127
                bool found;
5,798✔
128

129
                if (equal)
5,798✔
130
                        found = streq(*word, c->parameter);
1,012✔
131
                else {
132
                        const char *f;
4,786✔
133

134
                        f = startswith(*word, c->parameter);
4,786✔
135
                        found = f && IN_SET(*f, 0, '=');
4,786✔
136
                }
137

138
                if (found)
1,012✔
139
                        return true;
140
        }
141

142
        return false;
143
}
144

145
static int condition_test_credential(Condition *c, char **env) {
40✔
146
        int r;
40✔
147

148
        assert(c);
40✔
149
        assert(c->parameter);
40✔
150
        assert(c->type == CONDITION_CREDENTIAL);
40✔
151

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

156
        if (!credential_name_valid(c->parameter)) /* credentials with invalid names do not exist */
40✔
157
                return false;
158

159
        int (*gd)(const char **ret);
39✔
160
        FOREACH_ARGUMENT(gd, get_credentials_dir, get_encrypted_credentials_dir) {
114✔
161
                _cleanup_free_ char *j = NULL;
40✔
162
                const char *cd;
77✔
163

164
                r = gd(&cd);
77✔
165
                if (r == -ENXIO) /* no env var set */
77✔
166
                        continue;
37✔
167
                if (r < 0)
40✔
168
                        return r;
169

170
                j = path_join(cd, c->parameter);
40✔
171
                if (!j)
40✔
172
                        return -ENOMEM;
173

174
                r = access_nofollow(j, F_OK);
40✔
175
                if (r >= 0)
40✔
176
                        return true; /* yay! */
177
                if (r != -ENOENT)
38✔
178
                        return r;
179

180
                /* not found in this dir */
181
        }
182

183
        return false;
37✔
184
}
185

186
static int condition_test_version_cmp(const char *condition, const char *ver) {
55✔
187
        CompareOperator operator;
55✔
188
        bool first = true;
55✔
189

190
        assert(ver);
55✔
191

192
        for (const char *p = condition;;) {
55✔
193
                _cleanup_free_ char *word = NULL;
59✔
194
                const char *s;
86✔
195
                int r;
86✔
196

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

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

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

224
                r = version_or_fnmatch_compare(operator, ver, s);
55✔
225
                if (r < 0)
55✔
226
                        return r;
227
                if (!r)
55✔
228
                        return false;
229

230
                first = false;
31✔
231
        }
232

233
        return true;
27✔
234
}
235

236
static int condition_test_version(Condition *c, char **env) {
57✔
237
        int r;
57✔
238

239
        assert(c);
57✔
240
        assert(c->type == CONDITION_VERSION);
57✔
241

242
        /* An empty condition is considered true. */
243
        if (isempty(c->parameter))
57✔
244
                return true;
57✔
245

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

255
        if (streq(word, "systemd"))
55✔
256
                return condition_test_version_cmp(p, STRINGIFY(PROJECT_VERSION));
21✔
257

258
        if (streq(word, "glibc"))
34✔
259
                return condition_test_version_cmp(p, gnu_get_libc_version());
6✔
260

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

265
        struct utsname u;
28✔
266
        assert_se(uname(&u) >= 0);
28✔
267
        return condition_test_version_cmp(p, u.release);
28✔
268
}
269

270
static int condition_test_osrelease(Condition *c, char **env) {
17✔
271
        int r;
17✔
272

273
        assert(c);
17✔
274
        assert(c->type == CONDITION_OS_RELEASE);
17✔
275

276
        for (const char *parameter = ASSERT_PTR(c->parameter);;) {
17✔
277
                _cleanup_free_ char *key = NULL, *condition = NULL, *actual_value = NULL;
16✔
278
                CompareOperator operator;
21✔
279
                const char *word;
21✔
280

281
                r = extract_first_word(&parameter, &condition, NULL, EXTRACT_UNQUOTE);
21✔
282
                if (r < 0)
21✔
283
                        return log_debug_errno(r, "Failed to parse parameter: %m");
×
284
                if (r == 0)
21✔
285
                        break;
286

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

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

303
                r = parse_os_release(NULL, key, &actual_value);
9✔
304
                if (r < 0)
9✔
305
                        return log_debug_errno(r, "Failed to parse os-release: %m");
×
306

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

316
        return true;
5✔
317
}
318

319
static int condition_test_memory(Condition *c, char **env) {
36✔
320
        CompareOperator operator;
36✔
321
        uint64_t m, k;
36✔
322
        const char *p;
36✔
323
        int r;
36✔
324

325
        assert(c);
36✔
326
        assert(c->parameter);
36✔
327
        assert(c->type == CONDITION_MEMORY);
36✔
328

329
        m = physical_memory();
36✔
330

331
        p = c->parameter;
36✔
332
        operator = parse_compare_operator(&p, 0);
36✔
333
        if (operator < 0)
36✔
334
                operator = COMPARE_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */
×
335

336
        r = parse_size(p, 1024, &k);
36✔
337
        if (r < 0)
36✔
338
                return log_debug_errno(r, "Failed to parse size '%s': %m", p);
×
339

340
        return test_order(CMP(m, k), operator);
36✔
341
}
342

343
static int condition_test_cpus(Condition *c, char **env) {
18✔
344
        CompareOperator operator;
18✔
345
        const char *p;
18✔
346
        unsigned k;
18✔
347
        int r, n;
18✔
348

349
        assert(c);
18✔
350
        assert(c->parameter);
18✔
351
        assert(c->type == CONDITION_CPUS);
18✔
352

353
        n = cpus_in_affinity_mask();
18✔
354
        if (n < 0)
18✔
355
                return log_debug_errno(n, "Failed to determine CPUs in affinity mask: %m");
×
356

357
        p = c->parameter;
18✔
358
        operator = parse_compare_operator(&p, 0);
18✔
359
        if (operator < 0)
18✔
360
                operator = COMPARE_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */
×
361

362
        r = safe_atou(p, &k);
18✔
363
        if (r < 0)
18✔
364
                return log_debug_errno(r, "Failed to parse number of CPUs: %m");
×
365

366
        return test_order(CMP((unsigned) n, k), operator);
18✔
367
}
368

369
static int condition_test_user(Condition *c, char **env) {
7✔
370
        uid_t id;
7✔
371
        int r;
7✔
372

373
        assert(c);
7✔
374
        assert(c->parameter);
7✔
375
        assert(c->type == CONDITION_USER);
7✔
376

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

385
        r = parse_uid(c->parameter, &id);
4✔
386
        if (r >= 0)
4✔
387
                return id == getuid() || id == geteuid();
2✔
388

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

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

398
        if (streq(username, c->parameter))
2✔
399
                return 1;
400

401
        const char *u = c->parameter;
2✔
402
        r = get_user_creds(&u, &id, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING);
2✔
403
        if (r < 0)
2✔
404
                return 0;
405

406
        return id == getuid() || id == geteuid();
×
407
}
408

409
static int condition_test_control_group_controller(Condition *c, char **env) {
32✔
410
        CGroupMask system_mask, wanted_mask;
32✔
411
        int r;
32✔
412

413
        assert(c);
32✔
414
        assert(c->parameter);
32✔
415
        assert(c->type == CONDITION_CONTROL_GROUP_CONTROLLER);
32✔
416

417
        if (streq(c->parameter, "v2"))
32✔
418
                return true;
32✔
419
        if (streq(c->parameter, "v1"))
31✔
420
                return false;
421

422
        r = cg_mask_supported(&system_mask);
30✔
423
        if (r < 0)
30✔
424
                return log_debug_errno(r, "Failed to determine supported controllers: %m");
×
425

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

435
        return FLAGS_SET(system_mask, wanted_mask);
28✔
436
}
437

438
static int condition_test_group(Condition *c, char **env) {
4✔
439
        gid_t id;
4✔
440
        int r;
4✔
441

442
        assert(c);
4✔
443
        assert(c->parameter);
4✔
444
        assert(c->type == CONDITION_GROUP);
4✔
445

446
        r = parse_gid(c->parameter, &id);
4✔
447
        if (r >= 0)
4✔
448
                return in_gid(id);
2✔
449

450
        /* Avoid any NSS lookups if we are PID1 */
451
        if (getpid_cached() == 1)
2✔
452
                return streq(c->parameter, "root");
×
453

454
        return in_group(c->parameter) > 0;
2✔
455
}
456

457
static int condition_test_virtualization(Condition *c, char **env) {
1,413✔
458
        Virtualization v;
1,413✔
459
        int b;
1,413✔
460

461
        assert(c);
1,413✔
462
        assert(c->parameter);
1,413✔
463
        assert(c->type == CONDITION_VIRTUALIZATION);
1,413✔
464

465
        if (streq(c->parameter, "private-users"))
1,413✔
466
                return running_in_userns();
65✔
467

468
        v = detect_virtualization();
1,348✔
469
        if (v < 0)
1,348✔
470
                return v;
471

472
        /* First, compare with yes/no */
473
        b = parse_boolean(c->parameter);
1,348✔
474
        if (b >= 0)
1,348✔
475
                return b == (v != VIRTUALIZATION_NONE);
11✔
476

477
        /* Then, compare categorization */
478
        if (streq(c->parameter, "vm"))
1,337✔
479
                return VIRTUALIZATION_IS_VM(v);
1✔
480

481
        if (streq(c->parameter, "container"))
1,336✔
482
                return VIRTUALIZATION_IS_CONTAINER(v);
1,308✔
483

484
        /* Finally compare id */
485
        return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v));
28✔
486
}
487

488
static int condition_test_architecture(Condition *c, char **env) {
4✔
489
        Architecture a, b;
4✔
490

491
        assert(c);
4✔
492
        assert(c->parameter);
4✔
493
        assert(c->type == CONDITION_ARCHITECTURE);
4✔
494

495
        a = uname_architecture();
4✔
496
        if (a < 0)
4✔
497
                return a;
498

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

507
        return a == b;
3✔
508
}
509

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

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

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

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

538
        dtcompatlist = strv_parse_nulstr(dtcompat, size);
×
539
        if (!dtcompatlist)
×
540
                return -ENOMEM;
541

542
        return strv_contains(dtcompatlist, dtcarg);
×
543
}
544

545
static int condition_test_firmware_smbios_field(const char *expression) {
17✔
546
        _cleanup_free_ char *field = NULL, *expected_value = NULL, *actual_value = NULL;
17✔
547
        CompareOperator operator;
17✔
548
        int r;
17✔
549

550
        assert(expression);
17✔
551

552
        /* Parse SMBIOS field */
553
        r = extract_first_word(&expression, &field, COMPARE_OPERATOR_WITH_FNMATCH_CHARS, EXTRACT_RETAIN_SEPARATORS);
17✔
554
        if (r < 0)
17✔
555
                return r;
556
        if (r == 0 || isempty(expression))
17✔
557
                return -EINVAL;
558

559
        /* Remove trailing spaces from SMBIOS field */
560
        delete_trailing_chars(field, WHITESPACE);
15✔
561

562
        /* Parse operator */
563
        operator = parse_compare_operator(&expression, COMPARE_ALLOW_FNMATCH|COMPARE_EQUAL_BY_STRING);
15✔
564
        if (operator < 0)
15✔
565
                return operator;
566

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

574
        /* Read actual value from sysfs */
575
        if (!filename_is_valid(field))
12✔
576
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid SMBIOS field name.");
×
577

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

587
        /* Remove trailing newline */
588
        delete_trailing_chars(actual_value, WHITESPACE);
11✔
589

590
        /* Finally compare actual and expected value */
591
        return version_or_fnmatch_compare(operator, actual_value, expected_value);
11✔
592
}
593

594
static int condition_test_firmware(Condition *c, char **env) {
22✔
595
        sd_char *arg;
22✔
596
        int r;
22✔
597

598
        assert(c);
22✔
599
        assert(c->parameter);
22✔
600
        assert(c->type == CONDITION_FIRMWARE);
22✔
601

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

613
                end = strrchr(arg, ')');
1✔
614
                if (!end || *(end + 1) != '\0') {
1✔
615
                        log_debug("Malformed ConditionFirmware=%s", c->parameter);
×
616
                        return false;
×
617
                }
618

619
                dtc_arg = strndup(arg, end - arg);
1✔
620
                if (!dtc_arg)
1✔
621
                        return -ENOMEM;
622

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

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

634
                smbios_arg = strndup(arg, end - arg);
17✔
635
                if (!smbios_arg)
17✔
636
                        return log_oom_debug();
×
637

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

648
static int condition_test_host(Condition *c, char **env) {
4✔
649
        _cleanup_free_ char *h = NULL;
4✔
650
        int r;
4✔
651

652
        assert(c);
4✔
653
        assert(c->parameter);
4✔
654
        assert(c->type == CONDITION_HOST);
4✔
655

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

667
                /* If this is a UUID, check if this matches the machine ID, boot ID or product UUID */
668
                FOREACH_ELEMENT(i, table) {
2✔
669
                        sd_id128_t y;
2✔
670

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

678
                /* Fall through, also allow setups where people set hostnames to UUIDs. Kinda weird, but no
679
                 * reason not to allow that */
680
        }
681

682
        h = gethostname_malloc();
2✔
683
        if (!h)
2✔
684
                return -ENOMEM;
685

686
        r = fnmatch(c->parameter, h, FNM_CASEFOLD);
2✔
687
        if (r == FNM_NOMATCH)
2✔
688
                return false;
689
        if (r != 0)
1✔
690
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "fnmatch() failed.");
×
691

692
        return true;
693
}
694

695
static int condition_test_ac_power(Condition *c, char **env) {
4✔
696
        int r;
4✔
697

698
        assert(c);
4✔
699
        assert(c->parameter);
4✔
700
        assert(c->type == CONDITION_AC_POWER);
4✔
701

702
        r = parse_boolean(c->parameter);
4✔
703
        if (r < 0)
4✔
704
                return r;
705

706
        return (on_ac_power() != 0) == !!r;
4✔
707
}
708

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

716
        return FLAGS_SET(tpm2_support_full(TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_FIRMWARE), TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_FIRMWARE);
×
717
}
718

719
static int condition_test_security(Condition *c, char **env) {
283✔
720
        assert(c);
283✔
721
        assert(c->parameter);
283✔
722
        assert(c->type == CONDITION_SECURITY);
283✔
723

724
        if (streq(c->parameter, "selinux"))
283✔
725
                return mac_selinux_use();
1✔
726
        if (streq(c->parameter, "smack"))
282✔
727
                return mac_smack_use();
1✔
728
        if (streq(c->parameter, "apparmor"))
281✔
729
                return mac_apparmor_use();
1✔
730
        if (streq(c->parameter, "audit"))
280✔
731
                return use_audit();
15✔
732
        if (streq(c->parameter, "ima"))
265✔
733
                return use_ima();
1✔
734
        if (streq(c->parameter, "tomoyo"))
264✔
735
                return mac_tomoyo_use();
1✔
736
        if (streq(c->parameter, "uefi-secureboot"))
263✔
737
                return is_efi_secure_boot();
1✔
738
        if (streq(c->parameter, "tpm2"))
262✔
739
                return has_tpm2();
×
740
        if (streq(c->parameter, "cvm"))
262✔
741
                return detect_confidential_virtualization() > 0;
1✔
742
        if (streq(c->parameter, "measured-uki"))
261✔
743
                return efi_measured_uki(LOG_DEBUG);
260✔
744

745
        return false;
746
}
747

748
static int condition_test_capability(Condition *c, char **env) {
599✔
749
        int r;
599✔
750

751
        assert(c);
599✔
752
        assert(c->parameter);
599✔
753
        assert(c->type == CONDITION_CAPABILITY);
599✔
754

755
        /* If it's an invalid capability, we don't have it */
756
        int value = capability_from_name(c->parameter);
599✔
757
        if (value < 0)
599✔
758
                return -EINVAL;
599✔
759

760
        CapabilityQuintet q;
599✔
761
        r = pidref_get_capability(&PIDREF_MAKE_FROM_PID(getpid_cached()), &q);
599✔
762
        if (r < 0)
599✔
763
                return r;
764

765
        return BIT_SET(q.bounding, value);
599✔
766
}
767

768
static int condition_test_needs_update(Condition *c, char **env) {
210✔
769
        struct stat usr, other;
210✔
770
        const char *p;
210✔
771
        bool b;
210✔
772
        int r;
210✔
773

774
        assert(c);
210✔
775
        assert(c->parameter);
210✔
776
        assert(c->type == CONDITION_NEEDS_UPDATE);
210✔
777

778
        r = proc_cmdline_get_bool("systemd.condition_needs_update", /* flags = */ 0, &b);
210✔
779
        if (r < 0)
210✔
780
                log_debug_errno(r, "Failed to parse systemd.condition_needs_update= kernel command line argument, ignoring: %m");
×
781
        if (r > 0)
210✔
782
                return b;
210✔
783

784
        if (in_initrd()) {
210✔
785
                log_debug("We are in an initrd, not doing any updates.");
66✔
786
                return false;
66✔
787
        }
788

789
        if (!path_is_absolute(c->parameter)) {
144✔
790
                log_debug("Specified condition parameter '%s' is not absolute, assuming an update is needed.", c->parameter);
×
791
                return true;
×
792
        }
793

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

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

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

811
        if (lstat("/usr/", &usr) < 0) {
144✔
812
                log_debug_errno(errno, "Failed to stat() /usr/, assuming an update is needed: %m");
×
813
                return true;
×
814
        }
815

816
        /*
817
         * First, compare seconds as they are always accurate...
818
         */
819
        if (usr.st_mtim.tv_sec != other.st_mtim.tv_sec)
144✔
820
                return usr.st_mtim.tv_sec > other.st_mtim.tv_sec;
×
821

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

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

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

851
        return timespec_load_nsec(&usr.st_mtim) > timestamp;
×
852
}
853

854
static bool in_first_boot(void) {
70✔
855
        static int first_boot = -1;
70✔
856
        int r;
70✔
857

858
        if (first_boot >= 0)
70✔
859
                return first_boot;
×
860

861
        const char *e = secure_getenv("SYSTEMD_FIRST_BOOT");
70✔
862
        if (e) {
70✔
863
                r = parse_boolean(e);
×
864
                if (r < 0)
×
865
                        log_debug_errno(r, "Failed to parse $SYSTEMD_FIRST_BOOT, ignoring: %m");
×
866
                else
867
                        return (first_boot = r);
×
868
        }
869

870
        r = RET_NERRNO(access("/run/systemd/first-boot", F_OK));
70✔
871
        if (r < 0 && r != -ENOENT)
70✔
872
                log_debug_errno(r, "Failed to check if /run/systemd/first-boot exists, assuming no: %m");
×
873
        return r >= 0;
70✔
874
}
875

876
static int condition_test_first_boot(Condition *c, char **env) {
70✔
877
        int r;
70✔
878

879
        assert(c);
70✔
880
        assert(c->parameter);
70✔
881
        assert(c->type == CONDITION_FIRST_BOOT);
70✔
882

883
        // TODO: Parse c->parameter immediately when reading the config.
884
        //       Apply negation when parsing too.
885

886
        r = parse_boolean(c->parameter);
70✔
887
        if (r < 0)
70✔
888
                return r;
889

890
        return in_first_boot() == r;
70✔
891
}
892

893
static int condition_test_environment(Condition *c, char **env) {
207✔
894
        bool equal;
207✔
895

896
        assert(c);
207✔
897
        assert(c->parameter);
207✔
898
        assert(c->type == CONDITION_ENVIRONMENT);
207✔
899

900
        equal = strchr(c->parameter, '=');
207✔
901

902
        STRV_FOREACH(i, env) {
2,338✔
903
                bool found;
2,147✔
904

905
                if (equal)
2,147✔
906
                        found = streq(c->parameter, *i);
55✔
907
                else {
908
                        const char *f;
2,092✔
909

910
                        f = startswith(*i, c->parameter);
2,092✔
911
                        found = f && IN_SET(*f, 0, '=');
2,092✔
912
                }
913

914
                if (found)
55✔
915
                        return true;
916
        }
917

918
        return false;
919
}
920

921
static int condition_test_path_exists(Condition *c, char **env) {
1,085✔
922
        assert(c);
1,085✔
923
        assert(c->parameter);
1,085✔
924
        assert(c->type == CONDITION_PATH_EXISTS);
1,085✔
925

926
        return access(c->parameter, F_OK) >= 0;
1,085✔
927
}
928

929
static int condition_test_path_exists_glob(Condition *c, char **env) {
2✔
930
        assert(c);
2✔
931
        assert(c->parameter);
2✔
932
        assert(c->type == CONDITION_PATH_EXISTS_GLOB);
2✔
933

934
        return glob_exists(c->parameter) > 0;
2✔
935
}
936

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

942
        return is_dir(c->parameter, true) > 0;
1✔
943
}
944

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

950
        return is_symlink(c->parameter) > 0;
18✔
951
}
952

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

958
        return path_is_mount_point_full(c->parameter, /* root = */ NULL, AT_SYMLINK_FOLLOW) > 0;
73✔
959
}
960

961
static int condition_test_path_is_read_write(Condition *c, char **env) {
421✔
962
        int r;
421✔
963

964
        assert(c);
421✔
965
        assert(c->parameter);
421✔
966
        assert(c->type == CONDITION_PATH_IS_READ_WRITE);
421✔
967

968
        r = path_is_read_only_fs(c->parameter);
421✔
969

970
        return r <= 0 && r != -ENOENT;
421✔
971
}
972

973
static int condition_test_cpufeature(Condition *c, char **env) {
3✔
974
        assert(c);
3✔
975
        assert(c->parameter);
3✔
976
        assert(c->type == CONDITION_CPU_FEATURE);
3✔
977

978
        return has_cpu_with_flag(ascii_strlower(c->parameter));
3✔
979
}
980

981
static int condition_test_path_is_encrypted(Condition *c, char **env) {
1✔
982
        int r;
1✔
983

984
        assert(c);
1✔
985
        assert(c->parameter);
1✔
986
        assert(c->type == CONDITION_PATH_IS_ENCRYPTED);
1✔
987

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

992
        return r > 0;
1✔
993
}
994

995
static int condition_test_directory_not_empty(Condition *c, char **env) {
913✔
996
        int r;
913✔
997

998
        assert(c);
913✔
999
        assert(c->parameter);
913✔
1000
        assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY);
913✔
1001

1002
        r = dir_is_empty(c->parameter, /* ignore_hidden_or_backup= */ true);
913✔
1003
        return r <= 0 && !IN_SET(r, -ENOENT, -ENOTDIR);
913✔
1004
}
1005

1006
static int condition_test_file_not_empty(Condition *c, char **env) {
71✔
1007
        struct stat st;
71✔
1008

1009
        assert(c);
71✔
1010
        assert(c->parameter);
71✔
1011
        assert(c->type == CONDITION_FILE_NOT_EMPTY);
71✔
1012

1013
        return (stat(c->parameter, &st) >= 0 &&
71✔
1014
                S_ISREG(st.st_mode) &&
71✔
1015
                st.st_size > 0);
53✔
1016
}
1017

1018
static int condition_test_file_is_executable(Condition *c, char **env) {
2✔
1019
        struct stat st;
2✔
1020

1021
        assert(c);
2✔
1022
        assert(c->parameter);
2✔
1023
        assert(c->type == CONDITION_FILE_IS_EXECUTABLE);
2✔
1024

1025
        return (stat(c->parameter, &st) >= 0 &&
2✔
1026
                S_ISREG(st.st_mode) &&
2✔
1027
                (st.st_mode & 0111));
2✔
1028
}
1029

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

1038
        assert(c);
26✔
1039
        assert(c->parameter);
26✔
1040
        assert(IN_SET(c->type, CONDITION_MEMORY_PRESSURE, CONDITION_CPU_PRESSURE, CONDITION_IO_PRESSURE));
26✔
1041

1042
        if (!is_pressure_supported()) {
26✔
1043
                log_debug("Pressure Stall Information (PSI) is not supported, skipping.");
×
1044
                return 1;
×
1045
        }
1046

1047
        pressure_type = c->type == CONDITION_MEMORY_PRESSURE ? "memory" :
29✔
1048
                        c->type == CONDITION_CPU_PRESSURE ? "cpu" :
18✔
1049
                        "io";
1050

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

1061
                pressure_path = path_join("/proc/pressure", pressure_type);
19✔
1062
                if (!pressure_path)
19✔
1063
                        return log_oom_debug();
×
1064

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

1072
                required_mask = c->type == CONDITION_MEMORY_PRESSURE ? CGROUP_MASK_MEMORY :
6✔
1073
                                c->type == CONDITION_CPU_PRESSURE ? CGROUP_MASK_CPU :
1074
                                CGROUP_MASK_IO;
1075

1076
                slice = strstrip(first);
6✔
1077
                if (!slice)
6✔
1078
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
×
1079

1080
                r = cg_mask_supported(&mask);
6✔
1081
                if (r < 0)
6✔
1082
                        return log_debug_errno(r, "Failed to get supported cgroup controllers: %m");
×
1083

1084
                if (!FLAGS_SET(mask, required_mask)) {
6✔
1085
                        log_debug("Cgroup %s controller not available, skipping PSI condition check.", pressure_type);
×
1086
                        return 1;
×
1087
                }
1088

1089
                r = cg_slice_to_path(slice, &slice_path);
6✔
1090
                if (r < 0)
6✔
1091
                        return log_debug_errno(r, "Cannot determine slice \"%s\" cgroup path: %m", slice);
1✔
1092

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

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

1106
                        slice_joined = path_join(root_scope, slice_path);
5✔
1107
                        if (!slice_joined)
5✔
1108
                                return log_oom_debug();
×
1109

1110
                        free_and_replace(slice_path, slice_joined);
5✔
1111
                }
1112

1113
                r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, slice_path, controller, &pressure_path);
5✔
1114
                if (r < 0)
5✔
1115
                        return log_debug_errno(r, "Error getting cgroup pressure path from %s: %m", slice_path);
×
1116

1117
                value = second;
5✔
1118
        }
1119

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

1131
                timespan = skip_leading_chars(fourth, NULL);
9✔
1132
                if (!timespan)
9✔
1133
                        return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
×
1134

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

1145
        value = strstrip(third);
21✔
1146
        if (!value)
21✔
1147
                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
×
1148

1149
        r = parse_permyriad(value);
21✔
1150
        if (r < 0)
21✔
1151
                return log_debug_errno(r, "Failed to parse permyriad: %s", c->parameter);
6✔
1152

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

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

1170
        return *current <= limit;
14✔
1171
}
1172

1173
static int condition_test_kernel_module_loaded(Condition *c, char **env) {
177✔
1174
        int r;
177✔
1175

1176
        assert(c);
177✔
1177
        assert(c->parameter);
177✔
1178
        assert(c->type == CONDITION_KERNEL_MODULE_LOADED);
177✔
1179

1180
        /* Checks whether a specific kernel module is fully loaded (i.e. with the full initialization routine
1181
         * complete). */
1182

1183
        _cleanup_free_ char *normalized = strreplace(c->parameter, "-", "_");
354✔
1184
        if (!normalized)
177✔
1185
                return log_oom_debug();
×
1186

1187
        if (!filename_is_valid(normalized)) {
177✔
1188
                log_debug("Kernel module name '%s' is not valid, hence reporting it to not be loaded.", normalized);
2✔
1189
                return false;
2✔
1190
        }
1191

1192
        _cleanup_free_ char *p = path_join("/sys/module/", normalized);
350✔
1193
        if (!p)
175✔
1194
                return log_oom_debug();
×
1195

1196
        _cleanup_close_ int dir_fd = open(p, O_PATH|O_DIRECTORY|O_CLOEXEC);
350✔
1197
        if (dir_fd < 0) {
175✔
1198
                if (errno == ENOENT) {
118✔
1199
                        log_debug_errno(errno, "'%s/' does not exist, kernel module '%s' not loaded.", p, normalized);
118✔
1200
                        return false;
118✔
1201
                }
1202

1203
                return log_debug_errno(errno, "Failed to open directory '%s/': %m", p);
×
1204
        }
1205

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

UNCOV
1215
        delete_trailing_chars(initstate, WHITESPACE);
×
1216

UNCOV
1217
        if (!streq(initstate, "live")) {
×
1218
                log_debug("Kernel module '%s' is reported as '%s', hence not loaded.", normalized, initstate);
×
1219
                return false;
×
1220
        }
1221

UNCOV
1222
        log_debug("Kernel module '%s' detected as loaded.", normalized);
×
1223
        return true;
1224
}
1225

1226
int condition_test(Condition *c, char **env) {
5,984✔
1227

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

1265
        int r, b;
5,984✔
1266

1267
        assert(c);
5,984✔
1268
        assert(c->type >= 0);
5,984✔
1269
        assert(c->type < _CONDITION_TYPE_MAX);
5,984✔
1270

1271
        r = condition_tests[c->type](c, env);
5,984✔
1272
        if (r < 0) {
5,984✔
1273
                c->result = CONDITION_ERROR;
28✔
1274
                return r;
28✔
1275
        }
1276

1277
        b = (r > 0) == !c->negate;
5,956✔
1278
        c->result = b ? CONDITION_SUCCEEDED : CONDITION_FAILED;
5,956✔
1279
        return b;
5,956✔
1280
}
1281

1282
bool condition_test_list(
27,043✔
1283
                Condition *first,
1284
                char **env,
1285
                condition_to_string_t to_string,
1286
                condition_test_logger_t logger,
1287
                void *userdata) {
1288

1289
        int triggered = -1;
27,043✔
1290

1291
        /* If the condition list is empty, then it is true */
1292
        if (!first)
27,043✔
1293
                return true;
1294

1295
        /* Otherwise, if all of the non-trigger conditions apply and
1296
         * if any of the trigger conditions apply (unless there are
1297
         * none) we return true */
1298
        LIST_FOREACH(conditions, c, first) {
7,158✔
1299
                int r;
5,698✔
1300

1301
                r = condition_test(c, env);
5,698✔
1302

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

1321
                if (!c->trigger && r <= 0)
5,698✔
1322
                        return false;
1323

1324
                if (c->trigger && triggered <= 0)
3,479✔
1325
                        triggered = r > 0;
1,291✔
1326
        }
1327

1328
        return triggered != 0;
1,460✔
1329
}
1330

1331
void condition_dump(Condition *c, FILE *f, const char *prefix, condition_to_string_t to_string) {
×
1332
        assert(c);
×
1333
        assert(f);
×
1334
        assert(to_string);
×
1335

1336
        prefix = strempty(prefix);
×
1337

1338
        fprintf(f,
×
1339
                "%s\t%s: %s%s%s %s\n",
1340
                prefix,
1341
                to_string(c->type),
×
1342
                c->trigger ? "|" : "",
×
1343
                c->negate ? "!" : "",
×
1344
                c->parameter,
1345
                condition_result_to_string(c->result));
×
1346
}
×
1347

1348
void condition_dump_list(Condition *first, FILE *f, const char *prefix, condition_to_string_t to_string) {
744✔
1349
        LIST_FOREACH(conditions, c, first)
744✔
1350
                condition_dump(c, f, prefix, to_string);
×
1351
}
744✔
1352

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

1390
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(_condition_type, ConditionType);
6,922✔
1391

1392
const char* condition_type_to_string(ConditionType t) {
5,598✔
1393
        return _condition_type_to_string(t);
5,598✔
1394
}
1395

1396
ConditionType condition_type_from_string(const char *s) {
137✔
1397
        /* for backward compatibility */
1398
        if (streq_ptr(s, "ConditionKernelVersion"))
137✔
1399
                return CONDITION_VERSION;
1400

1401
        return _condition_type_from_string(s);
134✔
1402
}
1403

1404
void condition_types_list(void) {
35✔
1405
        DUMP_STRING_TABLE(_condition_type, ConditionType, _CONDITION_TYPE_MAX);
1,225✔
1406
}
35✔
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);
1,480✔
1446

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

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

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

1459
void assert_types_list(void) {
35✔
1460
        DUMP_STRING_TABLE(_assert_type, ConditionType, _CONDITION_TYPE_MAX);
1,225✔
1461
}
35✔
1462

1463
static const char* const condition_result_table[_CONDITION_RESULT_MAX] = {
1464
        [CONDITION_UNTESTED]  = "untested",
1465
        [CONDITION_SUCCEEDED] = "succeeded",
1466
        [CONDITION_FAILED]    = "failed",
1467
        [CONDITION_ERROR]     = "error",
1468
};
1469

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