]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/condition.c
cryptsetup: mention correct action in log message
[thirdparty/systemd.git] / 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 <limits.h>
8 #include <stdlib.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <sys/utsname.h>
12 #include <time.h>
13 #include <unistd.h>
14
15 #include "sd-id128.h"
16
17 #include "alloc-util.h"
18 #include "apparmor-util.h"
19 #include "architecture.h"
20 #include "audit-util.h"
21 #include "battery-util.h"
22 #include "blockdev-util.h"
23 #include "cap-list.h"
24 #include "cgroup-util.h"
25 #include "compare-operator.h"
26 #include "condition.h"
27 #include "confidential-virt.h"
28 #include "cpu-set-util.h"
29 #include "creds-util.h"
30 #include "efi-api.h"
31 #include "efi-loader.h"
32 #include "env-file.h"
33 #include "env-util.h"
34 #include "extract-word.h"
35 #include "fd-util.h"
36 #include "fileio.h"
37 #include "fs-util.h"
38 #include "glob-util.h"
39 #include "hostname-util.h"
40 #include "ima-util.h"
41 #include "initrd-util.h"
42 #include "limits-util.h"
43 #include "list.h"
44 #include "macro.h"
45 #include "mountpoint-util.h"
46 #include "nulstr-util.h"
47 #include "os-util.h"
48 #include "parse-util.h"
49 #include "path-util.h"
50 #include "percent-util.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 "tomoyo-util.h"
61 #include "tpm2-util.h"
62 #include "uid-classification.h"
63 #include "user-util.h"
64 #include "virt.h"
65
66 Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) {
67 Condition *c;
68
69 assert(type >= 0);
70 assert(type < _CONDITION_TYPE_MAX);
71 assert(parameter);
72
73 c = new(Condition, 1);
74 if (!c)
75 return NULL;
76
77 *c = (Condition) {
78 .type = type,
79 .trigger = trigger,
80 .negate = negate,
81 };
82
83 if (parameter) {
84 c->parameter = strdup(parameter);
85 if (!c->parameter)
86 return mfree(c);
87 }
88
89 return c;
90 }
91
92 Condition* condition_free(Condition *c) {
93 assert(c);
94
95 free(c->parameter);
96 return mfree(c);
97 }
98
99 Condition* condition_free_list_type(Condition *head, ConditionType type) {
100 LIST_FOREACH(conditions, c, head)
101 if (type < 0 || c->type == type) {
102 LIST_REMOVE(conditions, head, c);
103 condition_free(c);
104 }
105
106 assert(type >= 0 || !head);
107 return head;
108 }
109
110 static int condition_test_kernel_command_line(Condition *c, char **env) {
111 _cleanup_strv_free_ char **args = NULL;
112 int r;
113
114 assert(c);
115 assert(c->parameter);
116 assert(c->type == CONDITION_KERNEL_COMMAND_LINE);
117
118 r = proc_cmdline_strv(&args);
119 if (r < 0)
120 return r;
121
122 bool equal = strchr(c->parameter, '=');
123
124 STRV_FOREACH(word, args) {
125 bool found;
126
127 if (equal)
128 found = streq(*word, c->parameter);
129 else {
130 const char *f;
131
132 f = startswith(*word, c->parameter);
133 found = f && IN_SET(*f, 0, '=');
134 }
135
136 if (found)
137 return true;
138 }
139
140 return false;
141 }
142
143 static int condition_test_credential(Condition *c, char **env) {
144 int r;
145
146 assert(c);
147 assert(c->parameter);
148 assert(c->type == CONDITION_CREDENTIAL);
149
150 /* For now we'll do a very simple existence check and are happy with either a regular or an encrypted
151 * credential. Given that we check the syntax of the argument we have the option to later maybe allow
152 * contents checks too without breaking compatibility, but for now let's be minimalistic. */
153
154 if (!credential_name_valid(c->parameter)) /* credentials with invalid names do not exist */
155 return false;
156
157 int (*gd)(const char **ret);
158 FOREACH_ARGUMENT(gd, get_credentials_dir, get_encrypted_credentials_dir) {
159 _cleanup_free_ char *j = NULL;
160 const char *cd;
161
162 r = gd(&cd);
163 if (r == -ENXIO) /* no env var set */
164 continue;
165 if (r < 0)
166 return r;
167
168 j = path_join(cd, c->parameter);
169 if (!j)
170 return -ENOMEM;
171
172 if (laccess(j, F_OK) >= 0)
173 return true; /* yay! */
174 if (errno != ENOENT)
175 return -errno;
176
177 /* not found in this dir */
178 }
179
180 return false;
181 }
182
183 static int condition_test_kernel_version(Condition *c, char **env) {
184 CompareOperator operator;
185 struct utsname u;
186 bool first = true;
187
188 assert(c);
189 assert(c->parameter);
190 assert(c->type == CONDITION_KERNEL_VERSION);
191
192 assert_se(uname(&u) >= 0);
193
194 for (const char *p = c->parameter;;) {
195 _cleanup_free_ char *word = NULL;
196 const char *s;
197 int r;
198
199 r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE);
200 if (r < 0)
201 return log_debug_errno(r, "Failed to parse condition string \"%s\": %m", p);
202 if (r == 0)
203 break;
204
205 s = strstrip(word);
206 operator = parse_compare_operator(&s, COMPARE_ALLOW_FNMATCH|COMPARE_EQUAL_BY_STRING);
207 if (operator < 0) /* No prefix? Then treat as glob string */
208 operator = COMPARE_FNMATCH_EQUAL;
209
210 s += strspn(s, WHITESPACE);
211 if (isempty(s)) {
212 if (first) {
213 /* For backwards compatibility, allow whitespace between the operator and
214 * value, without quoting, but only in the first expression. */
215 word = mfree(word);
216 r = extract_first_word(&p, &word, NULL, 0);
217 if (r < 0)
218 return log_debug_errno(r, "Failed to parse condition string \"%s\": %m", p);
219 if (r == 0)
220 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected end of expression: %s", p);
221 s = word;
222 } else
223 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected end of expression: %s", p);
224 }
225
226 r = version_or_fnmatch_compare(operator, u.release, s);
227 if (r < 0)
228 return r;
229 if (!r)
230 return false;
231
232 first = false;
233 }
234
235 return true;
236 }
237
238 static int condition_test_osrelease(Condition *c, char **env) {
239 int r;
240
241 assert(c);
242 assert(c->type == CONDITION_OS_RELEASE);
243
244 for (const char *parameter = ASSERT_PTR(c->parameter);;) {
245 _cleanup_free_ char *key = NULL, *condition = NULL, *actual_value = NULL;
246 CompareOperator operator;
247 const char *word;
248
249 r = extract_first_word(&parameter, &condition, NULL, EXTRACT_UNQUOTE);
250 if (r < 0)
251 return log_debug_errno(r, "Failed to parse parameter: %m");
252 if (r == 0)
253 break;
254
255 /* parse_compare_operator() needs the string to start with the comparators */
256 word = condition;
257 r = extract_first_word(&word, &key, COMPARE_OPERATOR_WITH_FNMATCH_CHARS, EXTRACT_RETAIN_SEPARATORS);
258 if (r < 0)
259 return log_debug_errno(r, "Failed to parse parameter: %m");
260 /* The os-release spec mandates env-var-like key names */
261 if (r == 0 || isempty(word) || !env_name_is_valid(key))
262 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
263 "Failed to parse parameter, key/value format expected.");
264
265 /* Do not allow whitespace after the separator, as that's not a valid os-release format */
266 operator = parse_compare_operator(&word, COMPARE_ALLOW_FNMATCH|COMPARE_EQUAL_BY_STRING);
267 if (operator < 0 || isempty(word) || strchr(WHITESPACE, *word) != NULL)
268 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
269 "Failed to parse parameter, key/value format expected.");
270
271 r = parse_os_release(NULL, key, &actual_value);
272 if (r < 0)
273 return log_debug_errno(r, "Failed to parse os-release: %m");
274
275 r = version_or_fnmatch_compare(operator, actual_value, word);
276 if (r < 0)
277 return r;
278 if (!r)
279 return false;
280 }
281
282 return true;
283 }
284
285 static int condition_test_memory(Condition *c, char **env) {
286 CompareOperator operator;
287 uint64_t m, k;
288 const char *p;
289 int r;
290
291 assert(c);
292 assert(c->parameter);
293 assert(c->type == CONDITION_MEMORY);
294
295 m = physical_memory();
296
297 p = c->parameter;
298 operator = parse_compare_operator(&p, 0);
299 if (operator < 0)
300 operator = COMPARE_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */
301
302 r = parse_size(p, 1024, &k);
303 if (r < 0)
304 return log_debug_errno(r, "Failed to parse size '%s': %m", p);
305
306 return test_order(CMP(m, k), operator);
307 }
308
309 static int condition_test_cpus(Condition *c, char **env) {
310 CompareOperator operator;
311 const char *p;
312 unsigned k;
313 int r, n;
314
315 assert(c);
316 assert(c->parameter);
317 assert(c->type == CONDITION_CPUS);
318
319 n = cpus_in_affinity_mask();
320 if (n < 0)
321 return log_debug_errno(n, "Failed to determine CPUs in affinity mask: %m");
322
323 p = c->parameter;
324 operator = parse_compare_operator(&p, 0);
325 if (operator < 0)
326 operator = COMPARE_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */
327
328 r = safe_atou(p, &k);
329 if (r < 0)
330 return log_debug_errno(r, "Failed to parse number of CPUs: %m");
331
332 return test_order(CMP((unsigned) n, k), operator);
333 }
334
335 static int condition_test_user(Condition *c, char **env) {
336 uid_t id;
337 int r;
338
339 assert(c);
340 assert(c->parameter);
341 assert(c->type == CONDITION_USER);
342
343 /* Do the quick&easy comparisons first, and only parse the UID later. */
344 if (streq(c->parameter, "root"))
345 return getuid() == 0 || geteuid() == 0;
346 if (streq(c->parameter, NOBODY_USER_NAME))
347 return getuid() == UID_NOBODY || geteuid() == UID_NOBODY;
348 if (streq(c->parameter, "@system"))
349 return uid_is_system(getuid()) || uid_is_system(geteuid());
350
351 r = parse_uid(c->parameter, &id);
352 if (r >= 0)
353 return id == getuid() || id == geteuid();
354
355 if (getpid_cached() == 1) /* We already checked for "root" above, and we know that
356 * PID 1 is running as root, hence we know it cannot match. */
357 return false;
358
359 /* getusername_malloc() may do an nss lookup, which is not allowed in PID 1. */
360 _cleanup_free_ char *username = getusername_malloc();
361 if (!username)
362 return -ENOMEM;
363
364 if (streq(username, c->parameter))
365 return 1;
366
367 const char *u = c->parameter;
368 r = get_user_creds(&u, &id, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING);
369 if (r < 0)
370 return 0;
371
372 return id == getuid() || id == geteuid();
373 }
374
375 static int condition_test_control_group_controller(Condition *c, char **env) {
376 int r;
377 CGroupMask system_mask, wanted_mask = 0;
378
379 assert(c);
380 assert(c->parameter);
381 assert(c->type == CONDITION_CONTROL_GROUP_CONTROLLER);
382
383 if (streq(c->parameter, "v2"))
384 return cg_all_unified();
385 if (streq(c->parameter, "v1")) {
386 r = cg_all_unified();
387 if (r < 0)
388 return r;
389 return !r;
390 }
391
392 r = cg_mask_supported(&system_mask);
393 if (r < 0)
394 return log_debug_errno(r, "Failed to determine supported controllers: %m");
395
396 r = cg_mask_from_string(c->parameter, &wanted_mask);
397 if (r < 0 || wanted_mask <= 0) {
398 /* This won't catch the case that we have an unknown controller
399 * mixed in with valid ones -- these are only assessed on the
400 * validity of the valid controllers found. */
401 log_debug("Failed to parse cgroup string: %s", c->parameter);
402 return 1;
403 }
404
405 return FLAGS_SET(system_mask, wanted_mask);
406 }
407
408 static int condition_test_group(Condition *c, char **env) {
409 gid_t id;
410 int r;
411
412 assert(c);
413 assert(c->parameter);
414 assert(c->type == CONDITION_GROUP);
415
416 r = parse_gid(c->parameter, &id);
417 if (r >= 0)
418 return in_gid(id);
419
420 /* Avoid any NSS lookups if we are PID1 */
421 if (getpid_cached() == 1)
422 return streq(c->parameter, "root");
423
424 return in_group(c->parameter) > 0;
425 }
426
427 static int condition_test_virtualization(Condition *c, char **env) {
428 Virtualization v;
429 int b;
430
431 assert(c);
432 assert(c->parameter);
433 assert(c->type == CONDITION_VIRTUALIZATION);
434
435 if (streq(c->parameter, "private-users"))
436 return running_in_userns();
437
438 v = detect_virtualization();
439 if (v < 0)
440 return v;
441
442 /* First, compare with yes/no */
443 b = parse_boolean(c->parameter);
444 if (b >= 0)
445 return b == (v != VIRTUALIZATION_NONE);
446
447 /* Then, compare categorization */
448 if (streq(c->parameter, "vm"))
449 return VIRTUALIZATION_IS_VM(v);
450
451 if (streq(c->parameter, "container"))
452 return VIRTUALIZATION_IS_CONTAINER(v);
453
454 /* Finally compare id */
455 return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v));
456 }
457
458 static int condition_test_architecture(Condition *c, char **env) {
459 Architecture a, b;
460
461 assert(c);
462 assert(c->parameter);
463 assert(c->type == CONDITION_ARCHITECTURE);
464
465 a = uname_architecture();
466 if (a < 0)
467 return a;
468
469 if (streq(c->parameter, "native"))
470 b = native_architecture();
471 else {
472 b = architecture_from_string(c->parameter);
473 if (b < 0) /* unknown architecture? Then it's definitely not ours */
474 return false;
475 }
476
477 return a == b;
478 }
479
480 #define DTCOMPAT_FILE "/proc/device-tree/compatible"
481 static int condition_test_firmware_devicetree_compatible(const char *dtcarg) {
482 int r;
483 _cleanup_free_ char *dtcompat = NULL;
484 _cleanup_strv_free_ char **dtcompatlist = NULL;
485 size_t size;
486
487 r = read_full_virtual_file(DTCOMPAT_FILE, &dtcompat, &size);
488 if (r < 0) {
489 /* if the path doesn't exist it is incompatible */
490 if (r != -ENOENT)
491 log_debug_errno(r, "Failed to open() '%s', assuming machine is incompatible: %m", DTCOMPAT_FILE);
492 return false;
493 }
494
495 /* Not sure this can happen, but play safe. */
496 if (size == 0) {
497 log_debug("%s has zero length, assuming machine is incompatible", DTCOMPAT_FILE);
498 return false;
499 }
500
501 /* /proc/device-tree/compatible consists of one or more strings, each ending in '\0'.
502 * So the last character in dtcompat must be a '\0'. */
503 if (dtcompat[size - 1] != '\0') {
504 log_debug("%s is in an unknown format, assuming machine is incompatible", DTCOMPAT_FILE);
505 return false;
506 }
507
508 dtcompatlist = strv_parse_nulstr(dtcompat, size);
509 if (!dtcompatlist)
510 return -ENOMEM;
511
512 return strv_contains(dtcompatlist, dtcarg);
513 }
514
515 static int condition_test_firmware_smbios_field(const char *expression) {
516 _cleanup_free_ char *field = NULL, *expected_value = NULL, *actual_value = NULL;
517 CompareOperator operator;
518 int r;
519
520 assert(expression);
521
522 /* Parse SMBIOS field */
523 r = extract_first_word(&expression, &field, COMPARE_OPERATOR_WITH_FNMATCH_CHARS, EXTRACT_RETAIN_SEPARATORS);
524 if (r < 0)
525 return r;
526 if (r == 0 || isempty(expression))
527 return -EINVAL;
528
529 /* Remove trailing spaces from SMBIOS field */
530 delete_trailing_chars(field, WHITESPACE);
531
532 /* Parse operator */
533 operator = parse_compare_operator(&expression, COMPARE_ALLOW_FNMATCH|COMPARE_EQUAL_BY_STRING);
534 if (operator < 0)
535 return operator;
536
537 /* Parse expected value */
538 r = extract_first_word(&expression, &expected_value, NULL, EXTRACT_UNQUOTE);
539 if (r < 0)
540 return r;
541 if (r == 0 || !isempty(expression))
542 return -EINVAL;
543
544 /* Read actual value from sysfs */
545 if (!filename_is_valid(field))
546 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid SMBIOS field name.");
547
548 const char *p = strjoina("/sys/class/dmi/id/", field);
549 r = read_virtual_file(p, SIZE_MAX, &actual_value, NULL);
550 if (r < 0) {
551 log_debug_errno(r, "Failed to read %s: %m", p);
552 if (r == -ENOENT)
553 return false;
554 return r;
555 }
556
557 /* Remove trailing newline */
558 delete_trailing_chars(actual_value, WHITESPACE);
559
560 /* Finally compare actual and expected value */
561 return version_or_fnmatch_compare(operator, actual_value, expected_value);
562 }
563
564 static int condition_test_firmware(Condition *c, char **env) {
565 sd_char *arg;
566 int r;
567
568 assert(c);
569 assert(c->parameter);
570 assert(c->type == CONDITION_FIRMWARE);
571
572 if (streq(c->parameter, "device-tree")) {
573 if (access("/sys/firmware/devicetree/", F_OK) < 0) {
574 if (errno != ENOENT)
575 log_debug_errno(errno, "Unexpected error when checking for /sys/firmware/devicetree/: %m");
576 return false;
577 } else
578 return true;
579 } else if ((arg = startswith(c->parameter, "device-tree-compatible("))) {
580 _cleanup_free_ char *dtc_arg = NULL;
581 char *end;
582
583 end = strrchr(arg, ')');
584 if (!end || *(end + 1) != '\0') {
585 log_debug("Malformed ConditionFirmware=%s", c->parameter);
586 return false;
587 }
588
589 dtc_arg = strndup(arg, end - arg);
590 if (!dtc_arg)
591 return -ENOMEM;
592
593 return condition_test_firmware_devicetree_compatible(dtc_arg);
594 } else if (streq(c->parameter, "uefi"))
595 return is_efi_boot();
596 else if ((arg = startswith(c->parameter, "smbios-field("))) {
597 _cleanup_free_ char *smbios_arg = NULL;
598 char *end;
599
600 end = strrchr(arg, ')');
601 if (!end || *(end + 1) != '\0')
602 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed ConditionFirmware=%s.", c->parameter);
603
604 smbios_arg = strndup(arg, end - arg);
605 if (!smbios_arg)
606 return log_oom_debug();
607
608 r = condition_test_firmware_smbios_field(smbios_arg);
609 if (r < 0)
610 return log_debug_errno(r, "Malformed ConditionFirmware=%s: %m", c->parameter);
611 return r;
612 } else {
613 log_debug("Unsupported Firmware condition \"%s\"", c->parameter);
614 return false;
615 }
616 }
617
618 static int condition_test_host(Condition *c, char **env) {
619 _cleanup_free_ char *h = NULL;
620 sd_id128_t x, y;
621 int r;
622
623 assert(c);
624 assert(c->parameter);
625 assert(c->type == CONDITION_HOST);
626
627 if (sd_id128_from_string(c->parameter, &x) >= 0) {
628
629 r = sd_id128_get_machine(&y);
630 if (r < 0)
631 return r;
632
633 return sd_id128_equal(x, y);
634 }
635
636 h = gethostname_malloc();
637 if (!h)
638 return -ENOMEM;
639
640 r = fnmatch(c->parameter, h, FNM_CASEFOLD);
641 if (r == FNM_NOMATCH)
642 return false;
643 if (r != 0)
644 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "fnmatch() failed.");
645
646 return true;
647 }
648
649 static int condition_test_ac_power(Condition *c, char **env) {
650 int r;
651
652 assert(c);
653 assert(c->parameter);
654 assert(c->type == CONDITION_AC_POWER);
655
656 r = parse_boolean(c->parameter);
657 if (r < 0)
658 return r;
659
660 return (on_ac_power() != 0) == !!r;
661 }
662
663 static int has_tpm2(void) {
664 /* Checks whether the kernel has the TPM subsystem enabled and the firmware reports support. Note
665 * we don't check for actual TPM devices, since we might not have loaded the driver for it yet, i.e.
666 * during early boot where we very likely want to use this condition check).
667 *
668 * Note that we don't check if we ourselves are built with TPM2 support here! */
669
670 return FLAGS_SET(tpm2_support(), TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_FIRMWARE);
671 }
672
673 static int condition_test_security(Condition *c, char **env) {
674 assert(c);
675 assert(c->parameter);
676 assert(c->type == CONDITION_SECURITY);
677
678 if (streq(c->parameter, "selinux"))
679 return mac_selinux_use();
680 if (streq(c->parameter, "smack"))
681 return mac_smack_use();
682 if (streq(c->parameter, "apparmor"))
683 return mac_apparmor_use();
684 if (streq(c->parameter, "audit"))
685 return use_audit();
686 if (streq(c->parameter, "ima"))
687 return use_ima();
688 if (streq(c->parameter, "tomoyo"))
689 return mac_tomoyo_use();
690 if (streq(c->parameter, "uefi-secureboot"))
691 return is_efi_secure_boot();
692 if (streq(c->parameter, "tpm2"))
693 return has_tpm2();
694 if (streq(c->parameter, "cvm"))
695 return detect_confidential_virtualization() > 0;
696 if (streq(c->parameter, "measured-uki"))
697 return efi_measured_uki(LOG_DEBUG);
698
699 return false;
700 }
701
702 static int condition_test_capability(Condition *c, char **env) {
703 unsigned long long capabilities = (unsigned long long) -1;
704 _cleanup_fclose_ FILE *f = NULL;
705 int value, r;
706
707 assert(c);
708 assert(c->parameter);
709 assert(c->type == CONDITION_CAPABILITY);
710
711 /* If it's an invalid capability, we don't have it */
712 value = capability_from_name(c->parameter);
713 if (value < 0)
714 return -EINVAL;
715
716 /* If it's a valid capability we default to assume
717 * that we have it */
718
719 f = fopen("/proc/self/status", "re");
720 if (!f)
721 return -errno;
722
723 for (;;) {
724 _cleanup_free_ char *line = NULL;
725
726 r = read_line(f, LONG_LINE_MAX, &line);
727 if (r < 0)
728 return r;
729 if (r == 0)
730 break;
731
732 const char *p = startswith(line, "CapBnd:");
733 if (p) {
734 if (sscanf(p, "%llx", &capabilities) != 1)
735 return -EIO;
736
737 break;
738 }
739 }
740
741 return !!(capabilities & (1ULL << value));
742 }
743
744 static int condition_test_needs_update(Condition *c, char **env) {
745 struct stat usr, other;
746 const char *p;
747 bool b;
748 int r;
749
750 assert(c);
751 assert(c->parameter);
752 assert(c->type == CONDITION_NEEDS_UPDATE);
753
754 r = proc_cmdline_get_bool("systemd.condition_needs_update", /* flags = */ 0, &b);
755 if (r < 0)
756 log_debug_errno(r, "Failed to parse systemd.condition_needs_update= kernel command line argument, ignoring: %m");
757 if (r > 0)
758 return b;
759
760 if (in_initrd()) {
761 log_debug("We are in an initrd, not doing any updates.");
762 return false;
763 }
764
765 if (!path_is_absolute(c->parameter)) {
766 log_debug("Specified condition parameter '%s' is not absolute, assuming an update is needed.", c->parameter);
767 return true;
768 }
769
770 /* If the file system is read-only we shouldn't suggest an update */
771 r = path_is_read_only_fs(c->parameter);
772 if (r < 0)
773 log_debug_errno(r, "Failed to determine if '%s' is read-only, ignoring: %m", c->parameter);
774 if (r > 0)
775 return false;
776
777 /* Any other failure means we should allow the condition to be true, so that we rather invoke too
778 * many update tools than too few. */
779
780 p = strjoina(c->parameter, "/.updated");
781 if (lstat(p, &other) < 0) {
782 if (errno != ENOENT)
783 log_debug_errno(errno, "Failed to stat() '%s', assuming an update is needed: %m", p);
784 return true;
785 }
786
787 if (lstat("/usr/", &usr) < 0) {
788 log_debug_errno(errno, "Failed to stat() /usr/, assuming an update is needed: %m");
789 return true;
790 }
791
792 /*
793 * First, compare seconds as they are always accurate...
794 */
795 if (usr.st_mtim.tv_sec != other.st_mtim.tv_sec)
796 return usr.st_mtim.tv_sec > other.st_mtim.tv_sec;
797
798 /*
799 * ...then compare nanoseconds.
800 *
801 * A false positive is only possible when /usr's nanoseconds > 0
802 * (otherwise /usr cannot be strictly newer than the target file)
803 * AND the target file's nanoseconds == 0
804 * (otherwise the filesystem supports nsec timestamps, see stat(2)).
805 */
806 if (usr.st_mtim.tv_nsec == 0 || other.st_mtim.tv_nsec > 0)
807 return usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec;
808
809 _cleanup_free_ char *timestamp_str = NULL;
810 r = parse_env_file(NULL, p, "TIMESTAMP_NSEC", &timestamp_str);
811 if (r < 0) {
812 log_debug_errno(r, "Failed to parse timestamp file '%s', using mtime: %m", p);
813 return true;
814 }
815 if (isempty(timestamp_str)) {
816 log_debug("No data in timestamp file '%s', using mtime.", p);
817 return true;
818 }
819
820 uint64_t timestamp;
821 r = safe_atou64(timestamp_str, &timestamp);
822 if (r < 0) {
823 log_debug_errno(r, "Failed to parse timestamp value '%s' in file '%s', using mtime: %m", timestamp_str, p);
824 return true;
825 }
826
827 return timespec_load_nsec(&usr.st_mtim) > timestamp;
828 }
829
830 static bool in_first_boot(void) {
831 static int first_boot = -1;
832 int r;
833
834 if (first_boot >= 0)
835 return first_boot;
836
837 const char *e = secure_getenv("SYSTEMD_FIRST_BOOT");
838 if (e) {
839 r = parse_boolean(e);
840 if (r < 0)
841 log_debug_errno(r, "Failed to parse $SYSTEMD_FIRST_BOOT, ignoring: %m");
842 else
843 return (first_boot = r);
844 }
845
846 r = RET_NERRNO(access("/run/systemd/first-boot", F_OK));
847 if (r < 0 && r != -ENOENT)
848 log_debug_errno(r, "Failed to check if /run/systemd/first-boot exists, assuming no: %m");
849 return r >= 0;
850 }
851
852 static int condition_test_first_boot(Condition *c, char **env) {
853 int r;
854
855 assert(c);
856 assert(c->parameter);
857 assert(c->type == CONDITION_FIRST_BOOT);
858
859 // TODO: Parse c->parameter immediately when reading the config.
860 // Apply negation when parsing too.
861
862 r = parse_boolean(c->parameter);
863 if (r < 0)
864 return r;
865
866 return in_first_boot() == r;
867 }
868
869 static int condition_test_environment(Condition *c, char **env) {
870 bool equal;
871
872 assert(c);
873 assert(c->parameter);
874 assert(c->type == CONDITION_ENVIRONMENT);
875
876 equal = strchr(c->parameter, '=');
877
878 STRV_FOREACH(i, env) {
879 bool found;
880
881 if (equal)
882 found = streq(c->parameter, *i);
883 else {
884 const char *f;
885
886 f = startswith(*i, c->parameter);
887 found = f && IN_SET(*f, 0, '=');
888 }
889
890 if (found)
891 return true;
892 }
893
894 return false;
895 }
896
897 static int condition_test_path_exists(Condition *c, char **env) {
898 assert(c);
899 assert(c->parameter);
900 assert(c->type == CONDITION_PATH_EXISTS);
901
902 return access(c->parameter, F_OK) >= 0;
903 }
904
905 static int condition_test_path_exists_glob(Condition *c, char **env) {
906 assert(c);
907 assert(c->parameter);
908 assert(c->type == CONDITION_PATH_EXISTS_GLOB);
909
910 return glob_exists(c->parameter) > 0;
911 }
912
913 static int condition_test_path_is_directory(Condition *c, char **env) {
914 assert(c);
915 assert(c->parameter);
916 assert(c->type == CONDITION_PATH_IS_DIRECTORY);
917
918 return is_dir(c->parameter, true) > 0;
919 }
920
921 static int condition_test_path_is_symbolic_link(Condition *c, char **env) {
922 assert(c);
923 assert(c->parameter);
924 assert(c->type == CONDITION_PATH_IS_SYMBOLIC_LINK);
925
926 return is_symlink(c->parameter) > 0;
927 }
928
929 static int condition_test_path_is_mount_point(Condition *c, char **env) {
930 assert(c);
931 assert(c->parameter);
932 assert(c->type == CONDITION_PATH_IS_MOUNT_POINT);
933
934 return path_is_mount_point_full(c->parameter, /* root = */ NULL, AT_SYMLINK_FOLLOW) > 0;
935 }
936
937 static int condition_test_path_is_read_write(Condition *c, char **env) {
938 int r;
939
940 assert(c);
941 assert(c->parameter);
942 assert(c->type == CONDITION_PATH_IS_READ_WRITE);
943
944 r = path_is_read_only_fs(c->parameter);
945
946 return r <= 0 && r != -ENOENT;
947 }
948
949 static int condition_test_cpufeature(Condition *c, char **env) {
950 assert(c);
951 assert(c->parameter);
952 assert(c->type == CONDITION_CPU_FEATURE);
953
954 return has_cpu_with_flag(ascii_strlower(c->parameter));
955 }
956
957 static int condition_test_path_is_encrypted(Condition *c, char **env) {
958 int r;
959
960 assert(c);
961 assert(c->parameter);
962 assert(c->type == CONDITION_PATH_IS_ENCRYPTED);
963
964 r = path_is_encrypted(c->parameter);
965 if (r < 0 && r != -ENOENT)
966 log_debug_errno(r, "Failed to determine if '%s' is encrypted: %m", c->parameter);
967
968 return r > 0;
969 }
970
971 static int condition_test_directory_not_empty(Condition *c, char **env) {
972 int r;
973
974 assert(c);
975 assert(c->parameter);
976 assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY);
977
978 r = dir_is_empty(c->parameter, /* ignore_hidden_or_backup= */ true);
979 return r <= 0 && !IN_SET(r, -ENOENT, -ENOTDIR);
980 }
981
982 static int condition_test_file_not_empty(Condition *c, char **env) {
983 struct stat st;
984
985 assert(c);
986 assert(c->parameter);
987 assert(c->type == CONDITION_FILE_NOT_EMPTY);
988
989 return (stat(c->parameter, &st) >= 0 &&
990 S_ISREG(st.st_mode) &&
991 st.st_size > 0);
992 }
993
994 static int condition_test_file_is_executable(Condition *c, char **env) {
995 struct stat st;
996
997 assert(c);
998 assert(c->parameter);
999 assert(c->type == CONDITION_FILE_IS_EXECUTABLE);
1000
1001 return (stat(c->parameter, &st) >= 0 &&
1002 S_ISREG(st.st_mode) &&
1003 (st.st_mode & 0111));
1004 }
1005
1006 static int condition_test_psi(Condition *c, char **env) {
1007 _cleanup_free_ char *first = NULL, *second = NULL, *third = NULL, *fourth = NULL, *pressure_path = NULL;
1008 const char *p, *value, *pressure_type;
1009 loadavg_t *current, limit;
1010 ResourcePressure pressure;
1011 int r;
1012
1013 assert(c);
1014 assert(c->parameter);
1015 assert(IN_SET(c->type, CONDITION_MEMORY_PRESSURE, CONDITION_CPU_PRESSURE, CONDITION_IO_PRESSURE));
1016
1017 if (!is_pressure_supported()) {
1018 log_debug("Pressure Stall Information (PSI) is not supported, skipping.");
1019 return 1;
1020 }
1021
1022 pressure_type = c->type == CONDITION_MEMORY_PRESSURE ? "memory" :
1023 c->type == CONDITION_CPU_PRESSURE ? "cpu" :
1024 "io";
1025
1026 p = c->parameter;
1027 r = extract_many_words(&p, ":", 0, &first, &second);
1028 if (r <= 0)
1029 return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
1030 /* If only one parameter is passed, then we look at the global system pressure rather than a specific cgroup. */
1031 if (r == 1) {
1032 pressure_path = path_join("/proc/pressure", pressure_type);
1033 if (!pressure_path)
1034 return log_oom_debug();
1035
1036 value = first;
1037 } else {
1038 const char *controller = strjoina(pressure_type, ".pressure");
1039 _cleanup_free_ char *slice_path = NULL, *root_scope = NULL;
1040 CGroupMask mask, required_mask;
1041 char *slice, *e;
1042
1043 required_mask = c->type == CONDITION_MEMORY_PRESSURE ? CGROUP_MASK_MEMORY :
1044 c->type == CONDITION_CPU_PRESSURE ? CGROUP_MASK_CPU :
1045 CGROUP_MASK_IO;
1046
1047 slice = strstrip(first);
1048 if (!slice)
1049 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
1050
1051 r = cg_all_unified();
1052 if (r < 0)
1053 return log_debug_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m");
1054 if (r == 0) {
1055 log_debug("PSI condition check requires the unified cgroups hierarchy, skipping.");
1056 return 1;
1057 }
1058
1059 r = cg_mask_supported(&mask);
1060 if (r < 0)
1061 return log_debug_errno(r, "Failed to get supported cgroup controllers: %m");
1062
1063 if (!FLAGS_SET(mask, required_mask)) {
1064 log_debug("Cgroup %s controller not available, skipping PSI condition check.", pressure_type);
1065 return 1;
1066 }
1067
1068 r = cg_slice_to_path(slice, &slice_path);
1069 if (r < 0)
1070 return log_debug_errno(r, "Cannot determine slice \"%s\" cgroup path: %m", slice);
1071
1072 /* We might be running under the user manager, so get the root path and prefix it accordingly. */
1073 r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, getpid_cached(), &root_scope);
1074 if (r < 0)
1075 return log_debug_errno(r, "Failed to get root cgroup path: %m");
1076
1077 /* Drop init.scope, we want the parent. We could get an empty or / path, but that's fine,
1078 * just skip it in that case. */
1079 e = endswith(root_scope, "/" SPECIAL_INIT_SCOPE);
1080 if (e)
1081 *e = 0;
1082 if (!empty_or_root(root_scope)) {
1083 _cleanup_free_ char *slice_joined = NULL;
1084
1085 slice_joined = path_join(root_scope, slice_path);
1086 if (!slice_joined)
1087 return log_oom_debug();
1088
1089 free_and_replace(slice_path, slice_joined);
1090 }
1091
1092 r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, slice_path, controller, &pressure_path);
1093 if (r < 0)
1094 return log_debug_errno(r, "Error getting cgroup pressure path from %s: %m", slice_path);
1095
1096 value = second;
1097 }
1098
1099 /* If a value including a specific timespan (in the intervals allowed by the kernel),
1100 * parse it, otherwise we assume just a plain percentage that will be checked if it is
1101 * smaller or equal to the current pressure average over 5 minutes. */
1102 r = extract_many_words(&value, "/", 0, &third, &fourth);
1103 if (r <= 0)
1104 return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter);
1105 if (r == 1)
1106 current = &pressure.avg300;
1107 else {
1108 const char *timespan;
1109
1110 timespan = skip_leading_chars(fourth, NULL);
1111 if (!timespan)
1112 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
1113
1114 if (startswith(timespan, "10sec"))
1115 current = &pressure.avg10;
1116 else if (startswith(timespan, "1min"))
1117 current = &pressure.avg60;
1118 else if (startswith(timespan, "5min"))
1119 current = &pressure.avg300;
1120 else
1121 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
1122 }
1123
1124 value = strstrip(third);
1125 if (!value)
1126 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter);
1127
1128 r = parse_permyriad(value);
1129 if (r < 0)
1130 return log_debug_errno(r, "Failed to parse permyriad: %s", c->parameter);
1131
1132 r = store_loadavg_fixed_point(r / 100LU, r % 100LU, &limit);
1133 if (r < 0)
1134 return log_debug_errno(r, "Failed to parse loadavg: %s", c->parameter);
1135
1136 r = read_resource_pressure(pressure_path, PRESSURE_TYPE_FULL, &pressure);
1137 if (r == -ENODATA) /* cpu.pressure 'full' was added recently, fall back to 'some'. */
1138 r = read_resource_pressure(pressure_path, PRESSURE_TYPE_SOME, &pressure);
1139 if (r == -ENOENT) {
1140 /* We already checked that /proc/pressure exists, so this means we were given a cgroup
1141 * that doesn't exist or doesn't exist any longer. */
1142 log_debug("\"%s\" not found, skipping PSI check.", pressure_path);
1143 return 1;
1144 }
1145 if (r < 0)
1146 return log_debug_errno(r, "Error parsing pressure from %s: %m", pressure_path);
1147
1148 return *current <= limit;
1149 }
1150
1151 int condition_test(Condition *c, char **env) {
1152
1153 static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c, char **env) = {
1154 [CONDITION_PATH_EXISTS] = condition_test_path_exists,
1155 [CONDITION_PATH_EXISTS_GLOB] = condition_test_path_exists_glob,
1156 [CONDITION_PATH_IS_DIRECTORY] = condition_test_path_is_directory,
1157 [CONDITION_PATH_IS_SYMBOLIC_LINK] = condition_test_path_is_symbolic_link,
1158 [CONDITION_PATH_IS_MOUNT_POINT] = condition_test_path_is_mount_point,
1159 [CONDITION_PATH_IS_READ_WRITE] = condition_test_path_is_read_write,
1160 [CONDITION_PATH_IS_ENCRYPTED] = condition_test_path_is_encrypted,
1161 [CONDITION_DIRECTORY_NOT_EMPTY] = condition_test_directory_not_empty,
1162 [CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty,
1163 [CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable,
1164 [CONDITION_KERNEL_COMMAND_LINE] = condition_test_kernel_command_line,
1165 [CONDITION_KERNEL_VERSION] = condition_test_kernel_version,
1166 [CONDITION_CREDENTIAL] = condition_test_credential,
1167 [CONDITION_VIRTUALIZATION] = condition_test_virtualization,
1168 [CONDITION_SECURITY] = condition_test_security,
1169 [CONDITION_CAPABILITY] = condition_test_capability,
1170 [CONDITION_HOST] = condition_test_host,
1171 [CONDITION_AC_POWER] = condition_test_ac_power,
1172 [CONDITION_ARCHITECTURE] = condition_test_architecture,
1173 [CONDITION_FIRMWARE] = condition_test_firmware,
1174 [CONDITION_NEEDS_UPDATE] = condition_test_needs_update,
1175 [CONDITION_FIRST_BOOT] = condition_test_first_boot,
1176 [CONDITION_USER] = condition_test_user,
1177 [CONDITION_GROUP] = condition_test_group,
1178 [CONDITION_CONTROL_GROUP_CONTROLLER] = condition_test_control_group_controller,
1179 [CONDITION_CPUS] = condition_test_cpus,
1180 [CONDITION_MEMORY] = condition_test_memory,
1181 [CONDITION_ENVIRONMENT] = condition_test_environment,
1182 [CONDITION_CPU_FEATURE] = condition_test_cpufeature,
1183 [CONDITION_OS_RELEASE] = condition_test_osrelease,
1184 [CONDITION_MEMORY_PRESSURE] = condition_test_psi,
1185 [CONDITION_CPU_PRESSURE] = condition_test_psi,
1186 [CONDITION_IO_PRESSURE] = condition_test_psi,
1187 };
1188
1189 int r, b;
1190
1191 assert(c);
1192 assert(c->type >= 0);
1193 assert(c->type < _CONDITION_TYPE_MAX);
1194
1195 r = condition_tests[c->type](c, env);
1196 if (r < 0) {
1197 c->result = CONDITION_ERROR;
1198 return r;
1199 }
1200
1201 b = (r > 0) == !c->negate;
1202 c->result = b ? CONDITION_SUCCEEDED : CONDITION_FAILED;
1203 return b;
1204 }
1205
1206 bool condition_test_list(
1207 Condition *first,
1208 char **env,
1209 condition_to_string_t to_string,
1210 condition_test_logger_t logger,
1211 void *userdata) {
1212
1213 int triggered = -1;
1214
1215 /* If the condition list is empty, then it is true */
1216 if (!first)
1217 return true;
1218
1219 /* Otherwise, if all of the non-trigger conditions apply and
1220 * if any of the trigger conditions apply (unless there are
1221 * none) we return true */
1222 LIST_FOREACH(conditions, c, first) {
1223 int r;
1224
1225 r = condition_test(c, env);
1226
1227 if (logger) {
1228 if (r < 0)
1229 logger(userdata, LOG_WARNING, r, PROJECT_FILE, __LINE__, __func__,
1230 "Couldn't determine result for %s=%s%s%s, assuming failed: %m",
1231 to_string(c->type),
1232 c->trigger ? "|" : "",
1233 c->negate ? "!" : "",
1234 c->parameter);
1235 else
1236 logger(userdata, LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__,
1237 "%s=%s%s%s %s.",
1238 to_string(c->type),
1239 c->trigger ? "|" : "",
1240 c->negate ? "!" : "",
1241 c->parameter,
1242 condition_result_to_string(c->result));
1243 }
1244
1245 if (!c->trigger && r <= 0)
1246 return false;
1247
1248 if (c->trigger && triggered <= 0)
1249 triggered = r > 0;
1250 }
1251
1252 return triggered != 0;
1253 }
1254
1255 void condition_dump(Condition *c, FILE *f, const char *prefix, condition_to_string_t to_string) {
1256 assert(c);
1257 assert(f);
1258 assert(to_string);
1259
1260 prefix = strempty(prefix);
1261
1262 fprintf(f,
1263 "%s\t%s: %s%s%s %s\n",
1264 prefix,
1265 to_string(c->type),
1266 c->trigger ? "|" : "",
1267 c->negate ? "!" : "",
1268 c->parameter,
1269 condition_result_to_string(c->result));
1270 }
1271
1272 void condition_dump_list(Condition *first, FILE *f, const char *prefix, condition_to_string_t to_string) {
1273 LIST_FOREACH(conditions, c, first)
1274 condition_dump(c, f, prefix, to_string);
1275 }
1276
1277 static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
1278 [CONDITION_ARCHITECTURE] = "ConditionArchitecture",
1279 [CONDITION_FIRMWARE] = "ConditionFirmware",
1280 [CONDITION_VIRTUALIZATION] = "ConditionVirtualization",
1281 [CONDITION_HOST] = "ConditionHost",
1282 [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine",
1283 [CONDITION_KERNEL_VERSION] = "ConditionKernelVersion",
1284 [CONDITION_CREDENTIAL] = "ConditionCredential",
1285 [CONDITION_SECURITY] = "ConditionSecurity",
1286 [CONDITION_CAPABILITY] = "ConditionCapability",
1287 [CONDITION_AC_POWER] = "ConditionACPower",
1288 [CONDITION_NEEDS_UPDATE] = "ConditionNeedsUpdate",
1289 [CONDITION_FIRST_BOOT] = "ConditionFirstBoot",
1290 [CONDITION_PATH_EXISTS] = "ConditionPathExists",
1291 [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob",
1292 [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory",
1293 [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink",
1294 [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint",
1295 [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite",
1296 [CONDITION_PATH_IS_ENCRYPTED] = "ConditionPathIsEncrypted",
1297 [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty",
1298 [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty",
1299 [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable",
1300 [CONDITION_USER] = "ConditionUser",
1301 [CONDITION_GROUP] = "ConditionGroup",
1302 [CONDITION_CONTROL_GROUP_CONTROLLER] = "ConditionControlGroupController",
1303 [CONDITION_CPUS] = "ConditionCPUs",
1304 [CONDITION_MEMORY] = "ConditionMemory",
1305 [CONDITION_ENVIRONMENT] = "ConditionEnvironment",
1306 [CONDITION_CPU_FEATURE] = "ConditionCPUFeature",
1307 [CONDITION_OS_RELEASE] = "ConditionOSRelease",
1308 [CONDITION_MEMORY_PRESSURE] = "ConditionMemoryPressure",
1309 [CONDITION_CPU_PRESSURE] = "ConditionCPUPressure",
1310 [CONDITION_IO_PRESSURE] = "ConditionIOPressure",
1311 };
1312
1313 DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
1314
1315 static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
1316 [CONDITION_ARCHITECTURE] = "AssertArchitecture",
1317 [CONDITION_FIRMWARE] = "AssertFirmware",
1318 [CONDITION_VIRTUALIZATION] = "AssertVirtualization",
1319 [CONDITION_HOST] = "AssertHost",
1320 [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine",
1321 [CONDITION_KERNEL_VERSION] = "AssertKernelVersion",
1322 [CONDITION_CREDENTIAL] = "AssertCredential",
1323 [CONDITION_SECURITY] = "AssertSecurity",
1324 [CONDITION_CAPABILITY] = "AssertCapability",
1325 [CONDITION_AC_POWER] = "AssertACPower",
1326 [CONDITION_NEEDS_UPDATE] = "AssertNeedsUpdate",
1327 [CONDITION_FIRST_BOOT] = "AssertFirstBoot",
1328 [CONDITION_PATH_EXISTS] = "AssertPathExists",
1329 [CONDITION_PATH_EXISTS_GLOB] = "AssertPathExistsGlob",
1330 [CONDITION_PATH_IS_DIRECTORY] = "AssertPathIsDirectory",
1331 [CONDITION_PATH_IS_SYMBOLIC_LINK] = "AssertPathIsSymbolicLink",
1332 [CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint",
1333 [CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite",
1334 [CONDITION_PATH_IS_ENCRYPTED] = "AssertPathIsEncrypted",
1335 [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty",
1336 [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty",
1337 [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable",
1338 [CONDITION_USER] = "AssertUser",
1339 [CONDITION_GROUP] = "AssertGroup",
1340 [CONDITION_CONTROL_GROUP_CONTROLLER] = "AssertControlGroupController",
1341 [CONDITION_CPUS] = "AssertCPUs",
1342 [CONDITION_MEMORY] = "AssertMemory",
1343 [CONDITION_ENVIRONMENT] = "AssertEnvironment",
1344 [CONDITION_CPU_FEATURE] = "AssertCPUFeature",
1345 [CONDITION_OS_RELEASE] = "AssertOSRelease",
1346 [CONDITION_MEMORY_PRESSURE] = "AssertMemoryPressure",
1347 [CONDITION_CPU_PRESSURE] = "AssertCPUPressure",
1348 [CONDITION_IO_PRESSURE] = "AssertIOPressure",
1349 };
1350
1351 DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType);
1352
1353 static const char* const condition_result_table[_CONDITION_RESULT_MAX] = {
1354 [CONDITION_UNTESTED] = "untested",
1355 [CONDITION_SUCCEEDED] = "succeeded",
1356 [CONDITION_FAILED] = "failed",
1357 [CONDITION_ERROR] = "error",
1358 };
1359
1360 DEFINE_STRING_TABLE_LOOKUP(condition_result, ConditionResult);