1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
10 #include <sys/types.h>
11 #include <sys/utsname.h>
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"
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"
31 #include "efi-loader.h"
34 #include "extract-word.h"
38 #include "glob-util.h"
39 #include "hostname-util.h"
41 #include "initrd-util.h"
42 #include "limits-util.h"
45 #include "mountpoint-util.h"
46 #include "nulstr-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"
54 #include "selinux-util.h"
55 #include "smack-util.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"
66 Condition
* condition_new(ConditionType type
, const char *parameter
, bool trigger
, bool negate
) {
70 assert(type
< _CONDITION_TYPE_MAX
);
73 c
= new(Condition
, 1);
84 c
->parameter
= strdup(parameter
);
92 Condition
* condition_free(Condition
*c
) {
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
);
106 assert(type
>= 0 || !head
);
110 static int condition_test_kernel_command_line(Condition
*c
, char **env
) {
111 _cleanup_strv_free_
char **args
= NULL
;
115 assert(c
->parameter
);
116 assert(c
->type
== CONDITION_KERNEL_COMMAND_LINE
);
118 r
= proc_cmdline_strv(&args
);
122 bool equal
= strchr(c
->parameter
, '=');
124 STRV_FOREACH(word
, args
) {
128 found
= streq(*word
, c
->parameter
);
132 f
= startswith(*word
, c
->parameter
);
133 found
= f
&& IN_SET(*f
, 0, '=');
143 static int condition_test_credential(Condition
*c
, char **env
) {
147 assert(c
->parameter
);
148 assert(c
->type
== CONDITION_CREDENTIAL
);
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. */
154 if (!credential_name_valid(c
->parameter
)) /* credentials with invalid names do not exist */
157 int (*gd
)(const char **ret
);
158 FOREACH_ARGUMENT(gd
, get_credentials_dir
, get_encrypted_credentials_dir
) {
159 _cleanup_free_
char *j
= NULL
;
163 if (r
== -ENXIO
) /* no env var set */
168 j
= path_join(cd
, c
->parameter
);
172 if (laccess(j
, F_OK
) >= 0)
173 return true; /* yay! */
177 /* not found in this dir */
183 static int condition_test_kernel_version(Condition
*c
, char **env
) {
184 CompareOperator
operator;
189 assert(c
->parameter
);
190 assert(c
->type
== CONDITION_KERNEL_VERSION
);
192 assert_se(uname(&u
) >= 0);
194 for (const char *p
= c
->parameter
;;) {
195 _cleanup_free_
char *word
= NULL
;
199 r
= extract_first_word(&p
, &word
, NULL
, EXTRACT_UNQUOTE
);
201 return log_debug_errno(r
, "Failed to parse condition string \"%s\": %m", p
);
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
;
210 s
+= strspn(s
, WHITESPACE
);
213 /* For backwards compatibility, allow whitespace between the operator and
214 * value, without quoting, but only in the first expression. */
216 r
= extract_first_word(&p
, &word
, NULL
, 0);
218 return log_debug_errno(r
, "Failed to parse condition string \"%s\": %m", p
);
220 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Unexpected end of expression: %s", p
);
223 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Unexpected end of expression: %s", p
);
226 r
= version_or_fnmatch_compare(operator, u
.release
, s
);
238 static int condition_test_osrelease(Condition
*c
, char **env
) {
242 assert(c
->type
== CONDITION_OS_RELEASE
);
244 for (const char *parameter
= ASSERT_PTR(c
->parameter
);;) {
245 _cleanup_free_
char *key
= NULL
, *condition
= NULL
, *actual_value
= NULL
;
246 CompareOperator
operator;
249 r
= extract_first_word(¶meter
, &condition
, NULL
, EXTRACT_UNQUOTE
);
251 return log_debug_errno(r
, "Failed to parse parameter: %m");
255 /* parse_compare_operator() needs the string to start with the comparators */
257 r
= extract_first_word(&word
, &key
, COMPARE_OPERATOR_WITH_FNMATCH_CHARS
, EXTRACT_RETAIN_SEPARATORS
);
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.");
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.");
271 r
= parse_os_release(NULL
, key
, &actual_value
);
273 return log_debug_errno(r
, "Failed to parse os-release: %m");
275 r
= version_or_fnmatch_compare(operator, actual_value
, word
);
285 static int condition_test_memory(Condition
*c
, char **env
) {
286 CompareOperator
operator;
292 assert(c
->parameter
);
293 assert(c
->type
== CONDITION_MEMORY
);
295 m
= physical_memory();
298 operator = parse_compare_operator(&p
, 0);
300 operator = COMPARE_GREATER_OR_EQUAL
; /* default to >= check, if nothing is specified. */
302 r
= parse_size(p
, 1024, &k
);
304 return log_debug_errno(r
, "Failed to parse size '%s': %m", p
);
306 return test_order(CMP(m
, k
), operator);
309 static int condition_test_cpus(Condition
*c
, char **env
) {
310 CompareOperator
operator;
316 assert(c
->parameter
);
317 assert(c
->type
== CONDITION_CPUS
);
319 n
= cpus_in_affinity_mask();
321 return log_debug_errno(n
, "Failed to determine CPUs in affinity mask: %m");
324 operator = parse_compare_operator(&p
, 0);
326 operator = COMPARE_GREATER_OR_EQUAL
; /* default to >= check, if nothing is specified. */
328 r
= safe_atou(p
, &k
);
330 return log_debug_errno(r
, "Failed to parse number of CPUs: %m");
332 return test_order(CMP((unsigned) n
, k
), operator);
335 static int condition_test_user(Condition
*c
, char **env
) {
340 assert(c
->parameter
);
341 assert(c
->type
== CONDITION_USER
);
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());
351 r
= parse_uid(c
->parameter
, &id
);
353 return id
== getuid() || id
== geteuid();
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. */
359 /* getusername_malloc() may do an nss lookup, which is not allowed in PID 1. */
360 _cleanup_free_
char *username
= getusername_malloc();
364 if (streq(username
, c
->parameter
))
367 const char *u
= c
->parameter
;
368 r
= get_user_creds(&u
, &id
, NULL
, NULL
, NULL
, USER_CREDS_ALLOW_MISSING
);
372 return id
== getuid() || id
== geteuid();
375 static int condition_test_control_group_controller(Condition
*c
, char **env
) {
377 CGroupMask system_mask
, wanted_mask
= 0;
380 assert(c
->parameter
);
381 assert(c
->type
== CONDITION_CONTROL_GROUP_CONTROLLER
);
383 if (streq(c
->parameter
, "v2"))
384 return cg_all_unified();
385 if (streq(c
->parameter
, "v1")) {
386 r
= cg_all_unified();
392 r
= cg_mask_supported(&system_mask
);
394 return log_debug_errno(r
, "Failed to determine supported controllers: %m");
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
);
405 return FLAGS_SET(system_mask
, wanted_mask
);
408 static int condition_test_group(Condition
*c
, char **env
) {
413 assert(c
->parameter
);
414 assert(c
->type
== CONDITION_GROUP
);
416 r
= parse_gid(c
->parameter
, &id
);
420 /* Avoid any NSS lookups if we are PID1 */
421 if (getpid_cached() == 1)
422 return streq(c
->parameter
, "root");
424 return in_group(c
->parameter
) > 0;
427 static int condition_test_virtualization(Condition
*c
, char **env
) {
432 assert(c
->parameter
);
433 assert(c
->type
== CONDITION_VIRTUALIZATION
);
435 if (streq(c
->parameter
, "private-users"))
436 return running_in_userns();
438 v
= detect_virtualization();
442 /* First, compare with yes/no */
443 b
= parse_boolean(c
->parameter
);
445 return b
== (v
!= VIRTUALIZATION_NONE
);
447 /* Then, compare categorization */
448 if (streq(c
->parameter
, "vm"))
449 return VIRTUALIZATION_IS_VM(v
);
451 if (streq(c
->parameter
, "container"))
452 return VIRTUALIZATION_IS_CONTAINER(v
);
454 /* Finally compare id */
455 return v
!= VIRTUALIZATION_NONE
&& streq(c
->parameter
, virtualization_to_string(v
));
458 static int condition_test_architecture(Condition
*c
, char **env
) {
462 assert(c
->parameter
);
463 assert(c
->type
== CONDITION_ARCHITECTURE
);
465 a
= uname_architecture();
469 if (streq(c
->parameter
, "native"))
470 b
= native_architecture();
472 b
= architecture_from_string(c
->parameter
);
473 if (b
< 0) /* unknown architecture? Then it's definitely not ours */
480 #define DTCOMPAT_FILE "/proc/device-tree/compatible"
481 static int condition_test_firmware_devicetree_compatible(const char *dtcarg
) {
483 _cleanup_free_
char *dtcompat
= NULL
;
484 _cleanup_strv_free_
char **dtcompatlist
= NULL
;
487 r
= read_full_virtual_file(DTCOMPAT_FILE
, &dtcompat
, &size
);
489 /* if the path doesn't exist it is incompatible */
491 log_debug_errno(r
, "Failed to open() '%s', assuming machine is incompatible: %m", DTCOMPAT_FILE
);
495 /* Not sure this can happen, but play safe. */
497 log_debug("%s has zero length, assuming machine is incompatible", DTCOMPAT_FILE
);
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
);
508 dtcompatlist
= strv_parse_nulstr(dtcompat
, size
);
512 return strv_contains(dtcompatlist
, dtcarg
);
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;
522 /* Parse SMBIOS field */
523 r
= extract_first_word(&expression
, &field
, COMPARE_OPERATOR_WITH_FNMATCH_CHARS
, EXTRACT_RETAIN_SEPARATORS
);
526 if (r
== 0 || isempty(expression
))
529 /* Remove trailing spaces from SMBIOS field */
530 delete_trailing_chars(field
, WHITESPACE
);
533 operator = parse_compare_operator(&expression
, COMPARE_ALLOW_FNMATCH
|COMPARE_EQUAL_BY_STRING
);
537 /* Parse expected value */
538 r
= extract_first_word(&expression
, &expected_value
, NULL
, EXTRACT_UNQUOTE
);
541 if (r
== 0 || !isempty(expression
))
544 /* Read actual value from sysfs */
545 if (!filename_is_valid(field
))
546 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid SMBIOS field name.");
548 const char *p
= strjoina("/sys/class/dmi/id/", field
);
549 r
= read_virtual_file(p
, SIZE_MAX
, &actual_value
, NULL
);
551 log_debug_errno(r
, "Failed to read %s: %m", p
);
557 /* Remove trailing newline */
558 delete_trailing_chars(actual_value
, WHITESPACE
);
560 /* Finally compare actual and expected value */
561 return version_or_fnmatch_compare(operator, actual_value
, expected_value
);
564 static int condition_test_firmware(Condition
*c
, char **env
) {
569 assert(c
->parameter
);
570 assert(c
->type
== CONDITION_FIRMWARE
);
572 if (streq(c
->parameter
, "device-tree")) {
573 if (access("/sys/firmware/devicetree/", F_OK
) < 0) {
575 log_debug_errno(errno
, "Unexpected error when checking for /sys/firmware/devicetree/: %m");
579 } else if ((arg
= startswith(c
->parameter
, "device-tree-compatible("))) {
580 _cleanup_free_
char *dtc_arg
= NULL
;
583 end
= strrchr(arg
, ')');
584 if (!end
|| *(end
+ 1) != '\0') {
585 log_debug("Malformed ConditionFirmware=%s", c
->parameter
);
589 dtc_arg
= strndup(arg
, end
- arg
);
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
;
600 end
= strrchr(arg
, ')');
601 if (!end
|| *(end
+ 1) != '\0')
602 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Malformed ConditionFirmware=%s.", c
->parameter
);
604 smbios_arg
= strndup(arg
, end
- arg
);
606 return log_oom_debug();
608 r
= condition_test_firmware_smbios_field(smbios_arg
);
610 return log_debug_errno(r
, "Malformed ConditionFirmware=%s: %m", c
->parameter
);
613 log_debug("Unsupported Firmware condition \"%s\"", c
->parameter
);
618 static int condition_test_host(Condition
*c
, char **env
) {
619 _cleanup_free_
char *h
= NULL
;
624 assert(c
->parameter
);
625 assert(c
->type
== CONDITION_HOST
);
627 if (sd_id128_from_string(c
->parameter
, &x
) >= 0) {
629 r
= sd_id128_get_machine(&y
);
633 return sd_id128_equal(x
, y
);
636 h
= gethostname_malloc();
640 r
= fnmatch(c
->parameter
, h
, FNM_CASEFOLD
);
641 if (r
== FNM_NOMATCH
)
644 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "fnmatch() failed.");
649 static int condition_test_ac_power(Condition
*c
, char **env
) {
653 assert(c
->parameter
);
654 assert(c
->type
== CONDITION_AC_POWER
);
656 r
= parse_boolean(c
->parameter
);
660 return (on_ac_power() != 0) == !!r
;
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).
668 * Note that we don't check if we ourselves are built with TPM2 support here! */
670 return FLAGS_SET(tpm2_support(), TPM2_SUPPORT_SUBSYSTEM
|TPM2_SUPPORT_FIRMWARE
);
673 static int condition_test_security(Condition
*c
, char **env
) {
675 assert(c
->parameter
);
676 assert(c
->type
== CONDITION_SECURITY
);
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"))
686 if (streq(c
->parameter
, "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"))
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
);
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
;
708 assert(c
->parameter
);
709 assert(c
->type
== CONDITION_CAPABILITY
);
711 /* If it's an invalid capability, we don't have it */
712 value
= capability_from_name(c
->parameter
);
716 /* If it's a valid capability we default to assume
719 f
= fopen("/proc/self/status", "re");
724 _cleanup_free_
char *line
= NULL
;
726 r
= read_line(f
, LONG_LINE_MAX
, &line
);
732 const char *p
= startswith(line
, "CapBnd:");
734 if (sscanf(p
, "%llx", &capabilities
) != 1)
741 return !!(capabilities
& (1ULL << value
));
744 static int condition_test_needs_update(Condition
*c
, char **env
) {
745 struct stat usr
, other
;
751 assert(c
->parameter
);
752 assert(c
->type
== CONDITION_NEEDS_UPDATE
);
754 r
= proc_cmdline_get_bool("systemd.condition_needs_update", /* flags = */ 0, &b
);
756 log_debug_errno(r
, "Failed to parse systemd.condition_needs_update= kernel command line argument, ignoring: %m");
761 log_debug("We are in an initrd, not doing any updates.");
765 if (!path_is_absolute(c
->parameter
)) {
766 log_debug("Specified condition parameter '%s' is not absolute, assuming an update is needed.", c
->parameter
);
770 /* If the file system is read-only we shouldn't suggest an update */
771 r
= path_is_read_only_fs(c
->parameter
);
773 log_debug_errno(r
, "Failed to determine if '%s' is read-only, ignoring: %m", c
->parameter
);
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. */
780 p
= strjoina(c
->parameter
, "/.updated");
781 if (lstat(p
, &other
) < 0) {
783 log_debug_errno(errno
, "Failed to stat() '%s', assuming an update is needed: %m", p
);
787 if (lstat("/usr/", &usr
) < 0) {
788 log_debug_errno(errno
, "Failed to stat() /usr/, assuming an update is needed: %m");
793 * First, compare seconds as they are always accurate...
795 if (usr
.st_mtim
.tv_sec
!= other
.st_mtim
.tv_sec
)
796 return usr
.st_mtim
.tv_sec
> other
.st_mtim
.tv_sec
;
799 * ...then compare nanoseconds.
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)).
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
;
809 _cleanup_free_
char *timestamp_str
= NULL
;
810 r
= parse_env_file(NULL
, p
, "TIMESTAMP_NSEC", ×tamp_str
);
812 log_debug_errno(r
, "Failed to parse timestamp file '%s', using mtime: %m", p
);
815 if (isempty(timestamp_str
)) {
816 log_debug("No data in timestamp file '%s', using mtime.", p
);
821 r
= safe_atou64(timestamp_str
, ×tamp
);
823 log_debug_errno(r
, "Failed to parse timestamp value '%s' in file '%s', using mtime: %m", timestamp_str
, p
);
827 return timespec_load_nsec(&usr
.st_mtim
) > timestamp
;
830 static bool in_first_boot(void) {
831 static int first_boot
= -1;
837 const char *e
= secure_getenv("SYSTEMD_FIRST_BOOT");
839 r
= parse_boolean(e
);
841 log_debug_errno(r
, "Failed to parse $SYSTEMD_FIRST_BOOT, ignoring: %m");
843 return (first_boot
= r
);
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");
852 static int condition_test_first_boot(Condition
*c
, char **env
) {
856 assert(c
->parameter
);
857 assert(c
->type
== CONDITION_FIRST_BOOT
);
859 // TODO: Parse c->parameter immediately when reading the config.
860 // Apply negation when parsing too.
862 r
= parse_boolean(c
->parameter
);
866 return in_first_boot() == r
;
869 static int condition_test_environment(Condition
*c
, char **env
) {
873 assert(c
->parameter
);
874 assert(c
->type
== CONDITION_ENVIRONMENT
);
876 equal
= strchr(c
->parameter
, '=');
878 STRV_FOREACH(i
, env
) {
882 found
= streq(c
->parameter
, *i
);
886 f
= startswith(*i
, c
->parameter
);
887 found
= f
&& IN_SET(*f
, 0, '=');
897 static int condition_test_path_exists(Condition
*c
, char **env
) {
899 assert(c
->parameter
);
900 assert(c
->type
== CONDITION_PATH_EXISTS
);
902 return access(c
->parameter
, F_OK
) >= 0;
905 static int condition_test_path_exists_glob(Condition
*c
, char **env
) {
907 assert(c
->parameter
);
908 assert(c
->type
== CONDITION_PATH_EXISTS_GLOB
);
910 return glob_exists(c
->parameter
) > 0;
913 static int condition_test_path_is_directory(Condition
*c
, char **env
) {
915 assert(c
->parameter
);
916 assert(c
->type
== CONDITION_PATH_IS_DIRECTORY
);
918 return is_dir(c
->parameter
, true) > 0;
921 static int condition_test_path_is_symbolic_link(Condition
*c
, char **env
) {
923 assert(c
->parameter
);
924 assert(c
->type
== CONDITION_PATH_IS_SYMBOLIC_LINK
);
926 return is_symlink(c
->parameter
) > 0;
929 static int condition_test_path_is_mount_point(Condition
*c
, char **env
) {
931 assert(c
->parameter
);
932 assert(c
->type
== CONDITION_PATH_IS_MOUNT_POINT
);
934 return path_is_mount_point_full(c
->parameter
, /* root = */ NULL
, AT_SYMLINK_FOLLOW
) > 0;
937 static int condition_test_path_is_read_write(Condition
*c
, char **env
) {
941 assert(c
->parameter
);
942 assert(c
->type
== CONDITION_PATH_IS_READ_WRITE
);
944 r
= path_is_read_only_fs(c
->parameter
);
946 return r
<= 0 && r
!= -ENOENT
;
949 static int condition_test_cpufeature(Condition
*c
, char **env
) {
951 assert(c
->parameter
);
952 assert(c
->type
== CONDITION_CPU_FEATURE
);
954 return has_cpu_with_flag(ascii_strlower(c
->parameter
));
957 static int condition_test_path_is_encrypted(Condition
*c
, char **env
) {
961 assert(c
->parameter
);
962 assert(c
->type
== CONDITION_PATH_IS_ENCRYPTED
);
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
);
971 static int condition_test_directory_not_empty(Condition
*c
, char **env
) {
975 assert(c
->parameter
);
976 assert(c
->type
== CONDITION_DIRECTORY_NOT_EMPTY
);
978 r
= dir_is_empty(c
->parameter
, /* ignore_hidden_or_backup= */ true);
979 return r
<= 0 && !IN_SET(r
, -ENOENT
, -ENOTDIR
);
982 static int condition_test_file_not_empty(Condition
*c
, char **env
) {
986 assert(c
->parameter
);
987 assert(c
->type
== CONDITION_FILE_NOT_EMPTY
);
989 return (stat(c
->parameter
, &st
) >= 0 &&
990 S_ISREG(st
.st_mode
) &&
994 static int condition_test_file_is_executable(Condition
*c
, char **env
) {
998 assert(c
->parameter
);
999 assert(c
->type
== CONDITION_FILE_IS_EXECUTABLE
);
1001 return (stat(c
->parameter
, &st
) >= 0 &&
1002 S_ISREG(st
.st_mode
) &&
1003 (st
.st_mode
& 0111));
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
;
1014 assert(c
->parameter
);
1015 assert(IN_SET(c
->type
, CONDITION_MEMORY_PRESSURE
, CONDITION_CPU_PRESSURE
, CONDITION_IO_PRESSURE
));
1017 if (!is_pressure_supported()) {
1018 log_debug("Pressure Stall Information (PSI) is not supported, skipping.");
1022 pressure_type
= c
->type
== CONDITION_MEMORY_PRESSURE
? "memory" :
1023 c
->type
== CONDITION_CPU_PRESSURE
? "cpu" :
1027 r
= extract_many_words(&p
, ":", 0, &first
, &second
);
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. */
1032 pressure_path
= path_join("/proc/pressure", pressure_type
);
1034 return log_oom_debug();
1038 const char *controller
= strjoina(pressure_type
, ".pressure");
1039 _cleanup_free_
char *slice_path
= NULL
, *root_scope
= NULL
;
1040 CGroupMask mask
, required_mask
;
1043 required_mask
= c
->type
== CONDITION_MEMORY_PRESSURE
? CGROUP_MASK_MEMORY
:
1044 c
->type
== CONDITION_CPU_PRESSURE
? CGROUP_MASK_CPU
:
1047 slice
= strstrip(first
);
1049 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Failed to parse condition parameter %s.", c
->parameter
);
1051 r
= cg_all_unified();
1053 return log_debug_errno(r
, "Failed to determine whether the unified cgroups hierarchy is used: %m");
1055 log_debug("PSI condition check requires the unified cgroups hierarchy, skipping.");
1059 r
= cg_mask_supported(&mask
);
1061 return log_debug_errno(r
, "Failed to get supported cgroup controllers: %m");
1063 if (!FLAGS_SET(mask
, required_mask
)) {
1064 log_debug("Cgroup %s controller not available, skipping PSI condition check.", pressure_type
);
1068 r
= cg_slice_to_path(slice
, &slice_path
);
1070 return log_debug_errno(r
, "Cannot determine slice \"%s\" cgroup path: %m", slice
);
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
);
1075 return log_debug_errno(r
, "Failed to get root cgroup path: %m");
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
);
1082 if (!empty_or_root(root_scope
)) {
1083 _cleanup_free_
char *slice_joined
= NULL
;
1085 slice_joined
= path_join(root_scope
, slice_path
);
1087 return log_oom_debug();
1089 free_and_replace(slice_path
, slice_joined
);
1092 r
= cg_get_path(SYSTEMD_CGROUP_CONTROLLER
, slice_path
, controller
, &pressure_path
);
1094 return log_debug_errno(r
, "Error getting cgroup pressure path from %s: %m", slice_path
);
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
);
1104 return log_debug_errno(r
< 0 ? r
: SYNTHETIC_ERRNO(EINVAL
), "Failed to parse condition parameter %s: %m", c
->parameter
);
1106 current
= &pressure
.avg300
;
1108 const char *timespan
;
1110 timespan
= skip_leading_chars(fourth
, NULL
);
1112 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Failed to parse condition parameter %s.", c
->parameter
);
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
;
1121 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Failed to parse condition parameter %s.", c
->parameter
);
1124 value
= strstrip(third
);
1126 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "Failed to parse condition parameter %s.", c
->parameter
);
1128 r
= parse_permyriad(value
);
1130 return log_debug_errno(r
, "Failed to parse permyriad: %s", c
->parameter
);
1132 r
= store_loadavg_fixed_point(r
/ 100LU, r
% 100LU, &limit
);
1134 return log_debug_errno(r
, "Failed to parse loadavg: %s", c
->parameter
);
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
);
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
);
1146 return log_debug_errno(r
, "Error parsing pressure from %s: %m", pressure_path
);
1148 return *current
<= limit
;
1151 int condition_test(Condition
*c
, char **env
) {
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
,
1192 assert(c
->type
>= 0);
1193 assert(c
->type
< _CONDITION_TYPE_MAX
);
1195 r
= condition_tests
[c
->type
](c
, env
);
1197 c
->result
= CONDITION_ERROR
;
1201 b
= (r
> 0) == !c
->negate
;
1202 c
->result
= b
? CONDITION_SUCCEEDED
: CONDITION_FAILED
;
1206 bool condition_test_list(
1209 condition_to_string_t to_string
,
1210 condition_test_logger_t logger
,
1215 /* If the condition list is empty, then it is true */
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
) {
1225 r
= condition_test(c
, env
);
1229 logger(userdata
, LOG_WARNING
, r
, PROJECT_FILE
, __LINE__
, __func__
,
1230 "Couldn't determine result for %s=%s%s%s, assuming failed: %m",
1232 c
->trigger
? "|" : "",
1233 c
->negate
? "!" : "",
1236 logger(userdata
, LOG_DEBUG
, 0, PROJECT_FILE
, __LINE__
, __func__
,
1239 c
->trigger
? "|" : "",
1240 c
->negate
? "!" : "",
1242 condition_result_to_string(c
->result
));
1245 if (!c
->trigger
&& r
<= 0)
1248 if (c
->trigger
&& triggered
<= 0)
1252 return triggered
!= 0;
1255 void condition_dump(Condition
*c
, FILE *f
, const char *prefix
, condition_to_string_t to_string
) {
1260 prefix
= strempty(prefix
);
1263 "%s\t%s: %s%s%s %s\n",
1266 c
->trigger
? "|" : "",
1267 c
->negate
? "!" : "",
1269 condition_result_to_string(c
->result
));
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
);
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",
1313 DEFINE_STRING_TABLE_LOOKUP(condition_type
, ConditionType
);
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",
1351 DEFINE_STRING_TABLE_LOOKUP(assert_type
, ConditionType
);
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",
1360 DEFINE_STRING_TABLE_LOOKUP(condition_result
, ConditionResult
);