]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
b77c08e0 | 2 | |
b77c08e0 | 3 | #include <errno.h> |
a8fbdf54 | 4 | #include <fcntl.h> |
84ac7bea | 5 | #include <fnmatch.h> |
a8fbdf54 | 6 | #include <limits.h> |
84ac7bea | 7 | #include <stdlib.h> |
c465a29f | 8 | #include <sys/types.h> |
5022f08a | 9 | #include <sys/utsname.h> |
a8fbdf54 | 10 | #include <time.h> |
b77c08e0 | 11 | #include <unistd.h> |
b77c08e0 | 12 | |
d1bddcec | 13 | #include "sd-id128.h" |
84ac7bea | 14 | |
b5efdb8a | 15 | #include "alloc-util.h" |
d1bddcec | 16 | #include "apparmor-util.h" |
84ac7bea | 17 | #include "architecture.h" |
430f0182 | 18 | #include "audit-util.h" |
2822da4f | 19 | #include "cap-list.h" |
e16647c3 | 20 | #include "cgroup-util.h" |
3ffd4af2 | 21 | #include "condition.h" |
f44b3035 | 22 | #include "cpu-set-util.h" |
0bb2f0f1 | 23 | #include "efi-loader.h" |
78d76525 | 24 | #include "env-file.h" |
84ac7bea | 25 | #include "extract-word.h" |
3ffd4af2 | 26 | #include "fd-util.h" |
fb8b0869 | 27 | #include "fileio.h" |
7f19247b | 28 | #include "fs-util.h" |
7d50b32a | 29 | #include "glob-util.h" |
958b66ea | 30 | #include "hostname-util.h" |
84ac7bea | 31 | #include "ima-util.h" |
754f719a | 32 | #include "limits-util.h" |
a8fbdf54 TA |
33 | #include "list.h" |
34 | #include "macro.h" | |
049af8ad | 35 | #include "mountpoint-util.h" |
6bedfcbb | 36 | #include "parse-util.h" |
84ac7bea | 37 | #include "path-util.h" |
4e731273 | 38 | #include "proc-cmdline.h" |
df0ff127 | 39 | #include "process-util.h" |
84ac7bea LP |
40 | #include "selinux-util.h" |
41 | #include "smack-util.h" | |
8fcde012 | 42 | #include "stat-util.h" |
8b43440b | 43 | #include "string-table.h" |
07630cea | 44 | #include "string-util.h" |
ed440f6b | 45 | #include "tomoyo-util.h" |
98dcb8f4 | 46 | #include "user-record.h" |
c465a29f | 47 | #include "user-util.h" |
84ac7bea LP |
48 | #include "util.h" |
49 | #include "virt.h" | |
b77c08e0 TG |
50 | |
51 | Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) { | |
52 | Condition *c; | |
53 | ||
b80ba1da | 54 | assert(type >= 0); |
b77c08e0 | 55 | assert(type < _CONDITION_TYPE_MAX); |
476cfe62 | 56 | assert(parameter); |
b77c08e0 | 57 | |
78d76525 | 58 | c = new(Condition, 1); |
b77c08e0 TG |
59 | if (!c) |
60 | return NULL; | |
61 | ||
78d76525 LP |
62 | *c = (Condition) { |
63 | .type = type, | |
64 | .trigger = trigger, | |
65 | .negate = negate, | |
66 | }; | |
b77c08e0 | 67 | |
78d76525 LP |
68 | if (parameter) { |
69 | c->parameter = strdup(parameter); | |
70 | if (!c->parameter) | |
71 | return mfree(c); | |
b77c08e0 TG |
72 | } |
73 | ||
74 | return c; | |
75 | } | |
76 | ||
411e835c | 77 | Condition* condition_free(Condition *c) { |
b77c08e0 TG |
78 | assert(c); |
79 | ||
80 | free(c->parameter); | |
411e835c | 81 | return mfree(c); |
b77c08e0 TG |
82 | } |
83 | ||
7bb55ed0 YW |
84 | Condition* condition_free_list_type(Condition *head, ConditionType type) { |
85 | Condition *c, *n; | |
b77c08e0 | 86 | |
7bb55ed0 YW |
87 | LIST_FOREACH_SAFE(conditions, c, n, head) |
88 | if (type < 0 || c->type == type) { | |
89 | LIST_REMOVE(conditions, head, c); | |
c4f58dea | 90 | condition_free(c); |
7bb55ed0 | 91 | } |
447021aa | 92 | |
7bb55ed0 YW |
93 | assert(type >= 0 || !head); |
94 | return head; | |
b77c08e0 TG |
95 | } |
96 | ||
a0b191b7 | 97 | static int condition_test_kernel_command_line(Condition *c, char **env) { |
07318c29 LP |
98 | _cleanup_free_ char *line = NULL; |
99 | const char *p; | |
b77c08e0 TG |
100 | bool equal; |
101 | int r; | |
b77c08e0 TG |
102 | |
103 | assert(c); | |
104 | assert(c->parameter); | |
105 | assert(c->type == CONDITION_KERNEL_COMMAND_LINE); | |
106 | ||
107 | r = proc_cmdline(&line); | |
108 | if (r < 0) | |
592fd144 | 109 | return r; |
b77c08e0 | 110 | |
5d904a6a | 111 | equal = strchr(c->parameter, '='); |
07318c29 | 112 | |
c58bd76a | 113 | for (p = line;;) { |
07318c29 LP |
114 | _cleanup_free_ char *word = NULL; |
115 | bool found; | |
116 | ||
4ec85141 | 117 | r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX); |
592fd144 LP |
118 | if (r < 0) |
119 | return r; | |
120 | if (r == 0) | |
a4705396 | 121 | break; |
07318c29 LP |
122 | |
123 | if (equal) | |
124 | found = streq(word, c->parameter); | |
125 | else { | |
126 | const char *f; | |
127 | ||
128 | f = startswith(word, c->parameter); | |
4c701096 | 129 | found = f && IN_SET(*f, 0, '='); |
b77c08e0 TG |
130 | } |
131 | ||
07318c29 | 132 | if (found) |
a4705396 | 133 | return true; |
b77c08e0 | 134 | } |
b77c08e0 | 135 | |
a4705396 | 136 | return false; |
b77c08e0 TG |
137 | } |
138 | ||
ba22ff13 LP |
139 | typedef enum { |
140 | /* Listed in order of checking. Note that some comparators are prefixes of others, hence the longest | |
141 | * should be listed first. */ | |
142 | ORDER_LOWER_OR_EQUAL, | |
143 | ORDER_GREATER_OR_EQUAL, | |
144 | ORDER_LOWER, | |
145 | ORDER_GREATER, | |
146 | ORDER_EQUAL, | |
2877d428 | 147 | ORDER_UNEQUAL, |
ba22ff13 LP |
148 | _ORDER_MAX, |
149 | _ORDER_INVALID = -1 | |
150 | } OrderOperator; | |
151 | ||
152 | static OrderOperator parse_order(const char **s) { | |
68c58c67 LP |
153 | |
154 | static const char *const prefix[_ORDER_MAX] = { | |
ba22ff13 LP |
155 | [ORDER_LOWER_OR_EQUAL] = "<=", |
156 | [ORDER_GREATER_OR_EQUAL] = ">=", | |
157 | [ORDER_LOWER] = "<", | |
158 | [ORDER_GREATER] = ">", | |
159 | [ORDER_EQUAL] = "=", | |
2877d428 | 160 | [ORDER_UNEQUAL] = "!=", |
68c58c67 | 161 | }; |
5022f08a | 162 | |
ba22ff13 | 163 | OrderOperator i; |
5022f08a | 164 | |
68c58c67 | 165 | for (i = 0; i < _ORDER_MAX; i++) { |
ba22ff13 LP |
166 | const char *e; |
167 | ||
168 | e = startswith(*s, prefix[i]); | |
169 | if (e) { | |
170 | *s = e; | |
171 | return i; | |
172 | } | |
68c58c67 LP |
173 | } |
174 | ||
ba22ff13 LP |
175 | return _ORDER_INVALID; |
176 | } | |
68c58c67 | 177 | |
ba22ff13 | 178 | static bool test_order(int k, OrderOperator p) { |
68c58c67 | 179 | |
ba22ff13 | 180 | switch (p) { |
68c58c67 | 181 | |
ba22ff13 | 182 | case ORDER_LOWER: |
68c58c67 LP |
183 | return k < 0; |
184 | ||
ba22ff13 | 185 | case ORDER_LOWER_OR_EQUAL: |
68c58c67 LP |
186 | return k <= 0; |
187 | ||
ba22ff13 | 188 | case ORDER_EQUAL: |
68c58c67 LP |
189 | return k == 0; |
190 | ||
2877d428 LP |
191 | case ORDER_UNEQUAL: |
192 | return k != 0; | |
193 | ||
ba22ff13 | 194 | case ORDER_GREATER_OR_EQUAL: |
68c58c67 LP |
195 | return k >= 0; |
196 | ||
ba22ff13 | 197 | case ORDER_GREATER: |
68c58c67 LP |
198 | return k > 0; |
199 | ||
200 | default: | |
ba22ff13 LP |
201 | assert_not_reached("unknown order"); |
202 | ||
68c58c67 | 203 | } |
5022f08a LP |
204 | } |
205 | ||
a0b191b7 | 206 | static int condition_test_kernel_version(Condition *c, char **env) { |
ba22ff13 LP |
207 | OrderOperator order; |
208 | struct utsname u; | |
209 | const char *p; | |
910c6d09 | 210 | bool first = true; |
ba22ff13 LP |
211 | |
212 | assert(c); | |
213 | assert(c->parameter); | |
214 | assert(c->type == CONDITION_KERNEL_VERSION); | |
215 | ||
216 | assert_se(uname(&u) >= 0); | |
217 | ||
218 | p = c->parameter; | |
ba22ff13 | 219 | |
910c6d09 ZJS |
220 | for (;;) { |
221 | _cleanup_free_ char *word = NULL; | |
222 | const char *s; | |
223 | int r; | |
224 | ||
225 | r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE); | |
226 | if (r < 0) | |
227 | return log_debug_errno(r, "Failed to parse condition string \"%s\": %m", p); | |
228 | if (r == 0) | |
229 | break; | |
230 | ||
231 | s = strstrip(word); | |
232 | order = parse_order(&s); | |
233 | if (order >= 0) { | |
234 | s += strspn(s, WHITESPACE); | |
235 | if (isempty(s)) { | |
236 | if (first) { | |
237 | /* For backwards compatibility, allow whitespace between the operator and | |
238 | * value, without quoting, but only in the first expression. */ | |
239 | word = mfree(word); | |
240 | r = extract_first_word(&p, &word, NULL, 0); | |
241 | if (r < 0) | |
242 | return log_debug_errno(r, "Failed to parse condition string \"%s\": %m", p); | |
243 | if (r == 0) | |
244 | return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected end of expression: %s", p); | |
245 | s = word; | |
246 | } else | |
247 | return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected end of expression: %s", p); | |
248 | } | |
249 | ||
250 | r = test_order(str_verscmp(u.release, s), order); | |
251 | } else | |
252 | /* No prefix? Then treat as glob string */ | |
253 | r = fnmatch(s, u.release, 0) == 0; | |
254 | ||
255 | if (r == 0) | |
256 | return false; | |
257 | ||
258 | first = false; | |
259 | } | |
ba22ff13 | 260 | |
910c6d09 | 261 | return true; |
ba22ff13 LP |
262 | } |
263 | ||
a0b191b7 | 264 | static int condition_test_memory(Condition *c, char **env) { |
754f719a LP |
265 | OrderOperator order; |
266 | uint64_t m, k; | |
267 | const char *p; | |
268 | int r; | |
269 | ||
270 | assert(c); | |
271 | assert(c->parameter); | |
272 | assert(c->type == CONDITION_MEMORY); | |
273 | ||
274 | m = physical_memory(); | |
275 | ||
276 | p = c->parameter; | |
277 | order = parse_order(&p); | |
278 | if (order < 0) | |
279 | order = ORDER_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */ | |
280 | ||
281 | r = safe_atou64(p, &k); | |
282 | if (r < 0) | |
283 | return log_debug_errno(r, "Failed to parse size: %m"); | |
284 | ||
285 | return test_order(CMP(m, k), order); | |
286 | } | |
287 | ||
a0b191b7 | 288 | static int condition_test_cpus(Condition *c, char **env) { |
754f719a LP |
289 | OrderOperator order; |
290 | const char *p; | |
291 | unsigned k; | |
292 | int r, n; | |
293 | ||
294 | assert(c); | |
295 | assert(c->parameter); | |
296 | assert(c->type == CONDITION_CPUS); | |
297 | ||
298 | n = cpus_in_affinity_mask(); | |
299 | if (n < 0) | |
300 | return log_debug_errno(n, "Failed to determine CPUs in affinity mask: %m"); | |
301 | ||
302 | p = c->parameter; | |
303 | order = parse_order(&p); | |
304 | if (order < 0) | |
305 | order = ORDER_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */ | |
306 | ||
307 | r = safe_atou(p, &k); | |
308 | if (r < 0) | |
309 | return log_debug_errno(r, "Failed to parse number of CPUs: %m"); | |
310 | ||
311 | return test_order(CMP((unsigned) n, k), order); | |
312 | } | |
313 | ||
a0b191b7 | 314 | static int condition_test_user(Condition *c, char **env) { |
c465a29f FS |
315 | uid_t id; |
316 | int r; | |
317 | _cleanup_free_ char *username = NULL; | |
318 | const char *u; | |
319 | ||
320 | assert(c); | |
321 | assert(c->parameter); | |
322 | assert(c->type == CONDITION_USER); | |
323 | ||
324 | r = parse_uid(c->parameter, &id); | |
325 | if (r >= 0) | |
326 | return id == getuid() || id == geteuid(); | |
327 | ||
534bab66 | 328 | if (streq("@system", c->parameter)) |
ece877d4 | 329 | return uid_is_system(getuid()) || uid_is_system(geteuid()); |
534bab66 | 330 | |
c465a29f FS |
331 | username = getusername_malloc(); |
332 | if (!username) | |
333 | return -ENOMEM; | |
334 | ||
335 | if (streq(username, c->parameter)) | |
336 | return 1; | |
337 | ||
df0ff127 | 338 | if (getpid_cached() == 1) |
c465a29f FS |
339 | return streq(c->parameter, "root"); |
340 | ||
341 | u = c->parameter; | |
fafff8f1 | 342 | r = get_user_creds(&u, &id, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); |
c465a29f FS |
343 | if (r < 0) |
344 | return 0; | |
345 | ||
346 | return id == getuid() || id == geteuid(); | |
347 | } | |
348 | ||
a0b191b7 | 349 | static int condition_test_control_group_controller(Condition *c, char **env) { |
e16647c3 CD |
350 | int r; |
351 | CGroupMask system_mask, wanted_mask = 0; | |
352 | ||
353 | assert(c); | |
354 | assert(c->parameter); | |
355 | assert(c->type == CONDITION_CONTROL_GROUP_CONTROLLER); | |
356 | ||
357 | r = cg_mask_supported(&system_mask); | |
358 | if (r < 0) | |
359 | return log_debug_errno(r, "Failed to determine supported controllers: %m"); | |
360 | ||
361 | r = cg_mask_from_string(c->parameter, &wanted_mask); | |
362 | if (r < 0 || wanted_mask <= 0) { | |
363 | /* This won't catch the case that we have an unknown controller | |
364 | * mixed in with valid ones -- these are only assessed on the | |
365 | * validity of the valid controllers found. */ | |
366 | log_debug("Failed to parse cgroup string: %s", c->parameter); | |
367 | return 1; | |
368 | } | |
369 | ||
d94a24ca | 370 | return FLAGS_SET(system_mask, wanted_mask); |
e16647c3 CD |
371 | } |
372 | ||
a0b191b7 | 373 | static int condition_test_group(Condition *c, char **env) { |
c465a29f FS |
374 | gid_t id; |
375 | int r; | |
376 | ||
377 | assert(c); | |
378 | assert(c->parameter); | |
379 | assert(c->type == CONDITION_GROUP); | |
380 | ||
381 | r = parse_gid(c->parameter, &id); | |
382 | if (r >= 0) | |
383 | return in_gid(id); | |
384 | ||
385 | /* Avoid any NSS lookups if we are PID1 */ | |
df0ff127 | 386 | if (getpid_cached() == 1) |
c465a29f FS |
387 | return streq(c->parameter, "root"); |
388 | ||
389 | return in_group(c->parameter) > 0; | |
390 | } | |
391 | ||
a0b191b7 | 392 | static int condition_test_virtualization(Condition *c, char **env) { |
248fab74 | 393 | int b, v; |
b77c08e0 TG |
394 | |
395 | assert(c); | |
396 | assert(c->parameter); | |
397 | assert(c->type == CONDITION_VIRTUALIZATION); | |
398 | ||
239a5707 ZJS |
399 | if (streq(c->parameter, "private-users")) |
400 | return running_in_userns(); | |
401 | ||
75f86906 | 402 | v = detect_virtualization(); |
592fd144 LP |
403 | if (v < 0) |
404 | return v; | |
b77c08e0 TG |
405 | |
406 | /* First, compare with yes/no */ | |
407 | b = parse_boolean(c->parameter); | |
0809d774 ZJS |
408 | if (b >= 0) |
409 | return b == !!v; | |
b77c08e0 TG |
410 | |
411 | /* Then, compare categorization */ | |
0809d774 ZJS |
412 | if (streq(c->parameter, "vm")) |
413 | return VIRTUALIZATION_IS_VM(v); | |
b77c08e0 | 414 | |
0809d774 ZJS |
415 | if (streq(c->parameter, "container")) |
416 | return VIRTUALIZATION_IS_CONTAINER(v); | |
b77c08e0 TG |
417 | |
418 | /* Finally compare id */ | |
75f86906 | 419 | return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v)); |
b77c08e0 TG |
420 | } |
421 | ||
a0b191b7 | 422 | static int condition_test_architecture(Condition *c, char **env) { |
592fd144 | 423 | int a, b; |
099524d7 LP |
424 | |
425 | assert(c); | |
426 | assert(c->parameter); | |
427 | assert(c->type == CONDITION_ARCHITECTURE); | |
428 | ||
429 | a = uname_architecture(); | |
430 | if (a < 0) | |
592fd144 | 431 | return a; |
099524d7 LP |
432 | |
433 | if (streq(c->parameter, "native")) | |
434 | b = native_architecture(); | |
2cb62395 | 435 | else { |
099524d7 | 436 | b = architecture_from_string(c->parameter); |
2cb62395 LP |
437 | if (b < 0) /* unknown architecture? Then it's definitely not ours */ |
438 | return false; | |
439 | } | |
099524d7 | 440 | |
a4705396 | 441 | return a == b; |
099524d7 LP |
442 | } |
443 | ||
a0b191b7 | 444 | static int condition_test_host(Condition *c, char **env) { |
dc92e62c | 445 | _cleanup_free_ char *h = NULL; |
b77c08e0 | 446 | sd_id128_t x, y; |
b77c08e0 | 447 | int r; |
b77c08e0 TG |
448 | |
449 | assert(c); | |
450 | assert(c->parameter); | |
451 | assert(c->type == CONDITION_HOST); | |
452 | ||
453 | if (sd_id128_from_string(c->parameter, &x) >= 0) { | |
454 | ||
455 | r = sd_id128_get_machine(&y); | |
456 | if (r < 0) | |
592fd144 | 457 | return r; |
b77c08e0 | 458 | |
a4705396 | 459 | return sd_id128_equal(x, y); |
b77c08e0 TG |
460 | } |
461 | ||
462 | h = gethostname_malloc(); | |
463 | if (!h) | |
592fd144 | 464 | return -ENOMEM; |
b77c08e0 | 465 | |
a4705396 | 466 | return fnmatch(c->parameter, h, FNM_CASEFOLD) == 0; |
b77c08e0 TG |
467 | } |
468 | ||
a0b191b7 | 469 | static int condition_test_ac_power(Condition *c, char **env) { |
b77c08e0 TG |
470 | int r; |
471 | ||
472 | assert(c); | |
473 | assert(c->parameter); | |
474 | assert(c->type == CONDITION_AC_POWER); | |
475 | ||
476 | r = parse_boolean(c->parameter); | |
477 | if (r < 0) | |
592fd144 | 478 | return r; |
b77c08e0 | 479 | |
a4705396 | 480 | return (on_ac_power() != 0) == !!r; |
b77c08e0 TG |
481 | } |
482 | ||
a0b191b7 | 483 | static int condition_test_security(Condition *c, char **env) { |
d1bddcec LP |
484 | assert(c); |
485 | assert(c->parameter); | |
486 | assert(c->type == CONDITION_SECURITY); | |
487 | ||
488 | if (streq(c->parameter, "selinux")) | |
6d395665 | 489 | return mac_selinux_use(); |
d1bddcec | 490 | if (streq(c->parameter, "smack")) |
a4705396 | 491 | return mac_smack_use(); |
d1bddcec | 492 | if (streq(c->parameter, "apparmor")) |
a4705396 | 493 | return mac_apparmor_use(); |
d1bddcec | 494 | if (streq(c->parameter, "audit")) |
a4705396 | 495 | return use_audit(); |
d1bddcec | 496 | if (streq(c->parameter, "ima")) |
a4705396 | 497 | return use_ima(); |
ed440f6b SL |
498 | if (streq(c->parameter, "tomoyo")) |
499 | return mac_tomoyo_use(); | |
be405b90 LP |
500 | if (streq(c->parameter, "uefi-secureboot")) |
501 | return is_efi_secure_boot(); | |
d1bddcec | 502 | |
a4705396 | 503 | return false; |
d1bddcec LP |
504 | } |
505 | ||
a0b191b7 | 506 | static int condition_test_capability(Condition *c, char **env) { |
9c6f9786 | 507 | unsigned long long capabilities = (unsigned long long) -1; |
d1bddcec | 508 | _cleanup_fclose_ FILE *f = NULL; |
9c6f9786 | 509 | int value, r; |
d1bddcec LP |
510 | |
511 | assert(c); | |
512 | assert(c->parameter); | |
513 | assert(c->type == CONDITION_CAPABILITY); | |
514 | ||
515 | /* If it's an invalid capability, we don't have it */ | |
2822da4f LP |
516 | value = capability_from_name(c->parameter); |
517 | if (value < 0) | |
d1bddcec LP |
518 | return -EINVAL; |
519 | ||
520 | /* If it's a valid capability we default to assume | |
521 | * that we have it */ | |
522 | ||
523 | f = fopen("/proc/self/status", "re"); | |
524 | if (!f) | |
525 | return -errno; | |
526 | ||
9c6f9786 LP |
527 | for (;;) { |
528 | _cleanup_free_ char *line = NULL; | |
529 | const char *p; | |
530 | ||
531 | r = read_line(f, LONG_LINE_MAX, &line); | |
532 | if (r < 0) | |
533 | return r; | |
534 | if (r == 0) | |
535 | break; | |
536 | ||
537 | p = startswith(line, "CapBnd:"); | |
538 | if (p) { | |
539 | if (sscanf(line+7, "%llx", &capabilities) != 1) | |
540 | return -EIO; | |
d1bddcec | 541 | |
d1bddcec LP |
542 | break; |
543 | } | |
544 | } | |
545 | ||
a4705396 | 546 | return !!(capabilities & (1ULL << value)); |
d1bddcec LP |
547 | } |
548 | ||
a0b191b7 | 549 | static int condition_test_needs_update(Condition *c, char **env) { |
d1bddcec | 550 | struct stat usr, other; |
df1f5dc1 | 551 | const char *p; |
f8b4ae29 | 552 | bool b; |
df1f5dc1 | 553 | int r; |
d1bddcec LP |
554 | |
555 | assert(c); | |
556 | assert(c->parameter); | |
557 | assert(c->type == CONDITION_NEEDS_UPDATE); | |
558 | ||
f8b4ae29 LP |
559 | r = proc_cmdline_get_bool("systemd.condition-needs-update", &b); |
560 | if (r < 0) | |
561 | log_debug_errno(r, "Failed to parse systemd.condition-needs-update= kernel command line argument, ignoring: %m"); | |
562 | if (r > 0) | |
563 | return b; | |
564 | ||
df1f5dc1 LP |
565 | if (!path_is_absolute(c->parameter)) { |
566 | log_debug("Specified condition parameter '%s' is not absolute, assuming an update is needed.", c->parameter); | |
841c0987 | 567 | return true; |
df1f5dc1 | 568 | } |
841c0987 | 569 | |
d1bddcec | 570 | /* If the file system is read-only we shouldn't suggest an update */ |
df1f5dc1 LP |
571 | r = path_is_read_only_fs(c->parameter); |
572 | if (r < 0) | |
573 | log_debug_errno(r, "Failed to determine if '%s' is read-only, ignoring: %m", c->parameter); | |
574 | if (r > 0) | |
a4705396 | 575 | return false; |
d1bddcec | 576 | |
841c0987 LP |
577 | /* Any other failure means we should allow the condition to be true, so that we rather invoke too |
578 | * many update tools than too few. */ | |
d1bddcec | 579 | |
63c372cb | 580 | p = strjoina(c->parameter, "/.updated"); |
df1f5dc1 LP |
581 | if (lstat(p, &other) < 0) { |
582 | if (errno != ENOENT) | |
583 | log_debug_errno(errno, "Failed to stat() '%s', assuming an update is needed: %m", p); | |
a4705396 | 584 | return true; |
df1f5dc1 | 585 | } |
d1bddcec | 586 | |
df1f5dc1 LP |
587 | if (lstat("/usr/", &usr) < 0) { |
588 | log_debug_errno(errno, "Failed to stat() /usr/, assuming an update is needed: %m"); | |
a4705396 | 589 | return true; |
df1f5dc1 | 590 | } |
d1bddcec | 591 | |
fb8b0869 IS |
592 | /* |
593 | * First, compare seconds as they are always accurate... | |
594 | */ | |
595 | if (usr.st_mtim.tv_sec != other.st_mtim.tv_sec) | |
596 | return usr.st_mtim.tv_sec > other.st_mtim.tv_sec; | |
597 | ||
598 | /* | |
599 | * ...then compare nanoseconds. | |
600 | * | |
601 | * A false positive is only possible when /usr's nanoseconds > 0 | |
602 | * (otherwise /usr cannot be strictly newer than the target file) | |
603 | * AND the target file's nanoseconds == 0 | |
604 | * (otherwise the filesystem supports nsec timestamps, see stat(2)). | |
605 | */ | |
ce0f7f55 LP |
606 | if (usr.st_mtim.tv_nsec == 0 || other.st_mtim.tv_nsec > 0) |
607 | return usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec; | |
fb8b0869 | 608 | |
ce0f7f55 LP |
609 | _cleanup_free_ char *timestamp_str = NULL; |
610 | r = parse_env_file(NULL, p, "TIMESTAMP_NSEC", ×tamp_str); | |
611 | if (r < 0) { | |
612 | log_debug_errno(r, "Failed to parse timestamp file '%s', using mtime: %m", p); | |
613 | return true; | |
614 | } else if (r == 0) { | |
615 | log_debug("No data in timestamp file '%s', using mtime.", p); | |
616 | return true; | |
617 | } | |
fb8b0869 | 618 | |
ce0f7f55 LP |
619 | uint64_t timestamp; |
620 | r = safe_atou64(timestamp_str, ×tamp); | |
621 | if (r < 0) { | |
622 | log_debug_errno(r, "Failed to parse timestamp value '%s' in file '%s', using mtime: %m", timestamp_str, p); | |
623 | return true; | |
fb8b0869 IS |
624 | } |
625 | ||
ce0f7f55 | 626 | return timespec_load_nsec(&usr.st_mtim) > timestamp; |
d1bddcec LP |
627 | } |
628 | ||
a0b191b7 | 629 | static int condition_test_first_boot(Condition *c, char **env) { |
5439d821 | 630 | int r, q; |
814872e9 | 631 | bool b; |
d1bddcec LP |
632 | |
633 | assert(c); | |
634 | assert(c->parameter); | |
635 | assert(c->type == CONDITION_FIRST_BOOT); | |
636 | ||
814872e9 LP |
637 | r = proc_cmdline_get_bool("systemd.condition-first-boot", &b); |
638 | if (r < 0) | |
639 | log_debug_errno(r, "Failed to parse systemd.condition-first-boot= kernel command line argument, ignoring: %m"); | |
640 | if (r > 0) | |
641 | return b == !!r; | |
642 | ||
d1bddcec LP |
643 | r = parse_boolean(c->parameter); |
644 | if (r < 0) | |
645 | return r; | |
646 | ||
5439d821 LP |
647 | q = access("/run/systemd/first-boot", F_OK); |
648 | if (q < 0 && errno != ENOENT) | |
649 | log_debug_errno(errno, "Failed to check if /run/systemd/first-boot exists, ignoring: %m"); | |
650 | ||
651 | return (q >= 0) == !!r; | |
d1bddcec LP |
652 | } |
653 | ||
a0b191b7 LP |
654 | static int condition_test_environment(Condition *c, char **env) { |
655 | bool equal; | |
656 | char **i; | |
657 | ||
658 | assert(c); | |
659 | assert(c->parameter); | |
660 | assert(c->type == CONDITION_ENVIRONMENT); | |
661 | ||
662 | equal = strchr(c->parameter, '='); | |
663 | ||
664 | STRV_FOREACH(i, env) { | |
665 | bool found; | |
666 | ||
667 | if (equal) | |
668 | found = streq(c->parameter, *i); | |
669 | else { | |
670 | const char *f; | |
671 | ||
672 | f = startswith(*i, c->parameter); | |
673 | found = f && IN_SET(*f, 0, '='); | |
674 | } | |
675 | ||
676 | if (found) | |
677 | return true; | |
678 | } | |
679 | ||
680 | return false; | |
681 | } | |
682 | ||
683 | static int condition_test_path_exists(Condition *c, char **env) { | |
d1bddcec LP |
684 | assert(c); |
685 | assert(c->parameter); | |
686 | assert(c->type == CONDITION_PATH_EXISTS); | |
687 | ||
a4705396 | 688 | return access(c->parameter, F_OK) >= 0; |
d1bddcec LP |
689 | } |
690 | ||
a0b191b7 | 691 | static int condition_test_path_exists_glob(Condition *c, char **env) { |
d1bddcec LP |
692 | assert(c); |
693 | assert(c->parameter); | |
694 | assert(c->type == CONDITION_PATH_EXISTS_GLOB); | |
695 | ||
a4705396 | 696 | return glob_exists(c->parameter) > 0; |
d1bddcec LP |
697 | } |
698 | ||
a0b191b7 | 699 | static int condition_test_path_is_directory(Condition *c, char **env) { |
d1bddcec LP |
700 | assert(c); |
701 | assert(c->parameter); | |
702 | assert(c->type == CONDITION_PATH_IS_DIRECTORY); | |
703 | ||
a4705396 | 704 | return is_dir(c->parameter, true) > 0; |
d1bddcec LP |
705 | } |
706 | ||
a0b191b7 | 707 | static int condition_test_path_is_symbolic_link(Condition *c, char **env) { |
d1bddcec LP |
708 | assert(c); |
709 | assert(c->parameter); | |
710 | assert(c->type == CONDITION_PATH_IS_SYMBOLIC_LINK); | |
711 | ||
a4705396 | 712 | return is_symlink(c->parameter) > 0; |
d1bddcec LP |
713 | } |
714 | ||
a0b191b7 | 715 | static int condition_test_path_is_mount_point(Condition *c, char **env) { |
d1bddcec LP |
716 | assert(c); |
717 | assert(c->parameter); | |
718 | assert(c->type == CONDITION_PATH_IS_MOUNT_POINT); | |
719 | ||
e1873695 | 720 | return path_is_mount_point(c->parameter, NULL, AT_SYMLINK_FOLLOW) > 0; |
d1bddcec LP |
721 | } |
722 | ||
a0b191b7 | 723 | static int condition_test_path_is_read_write(Condition *c, char **env) { |
d1bddcec LP |
724 | assert(c); |
725 | assert(c->parameter); | |
726 | assert(c->type == CONDITION_PATH_IS_READ_WRITE); | |
727 | ||
a4705396 | 728 | return path_is_read_only_fs(c->parameter) <= 0; |
d1bddcec LP |
729 | } |
730 | ||
a0b191b7 | 731 | static int condition_test_path_is_encrypted(Condition *c, char **env) { |
7f19247b LP |
732 | int r; |
733 | ||
734 | assert(c); | |
735 | assert(c->parameter); | |
736 | assert(c->type == CONDITION_PATH_IS_ENCRYPTED); | |
737 | ||
738 | r = path_is_encrypted(c->parameter); | |
739 | if (r < 0 && r != -ENOENT) | |
740 | log_debug_errno(r, "Failed to determine if '%s' is encrypted: %m", c->parameter); | |
741 | ||
742 | return r > 0; | |
743 | } | |
744 | ||
a0b191b7 | 745 | static int condition_test_directory_not_empty(Condition *c, char **env) { |
d1bddcec LP |
746 | int r; |
747 | ||
748 | assert(c); | |
749 | assert(c->parameter); | |
750 | assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY); | |
751 | ||
752 | r = dir_is_empty(c->parameter); | |
a4705396 | 753 | return r <= 0 && r != -ENOENT; |
d1bddcec LP |
754 | } |
755 | ||
a0b191b7 | 756 | static int condition_test_file_not_empty(Condition *c, char **env) { |
d1bddcec LP |
757 | struct stat st; |
758 | ||
759 | assert(c); | |
760 | assert(c->parameter); | |
761 | assert(c->type == CONDITION_FILE_NOT_EMPTY); | |
762 | ||
763 | return (stat(c->parameter, &st) >= 0 && | |
764 | S_ISREG(st.st_mode) && | |
a4705396 | 765 | st.st_size > 0); |
d1bddcec LP |
766 | } |
767 | ||
a0b191b7 | 768 | static int condition_test_file_is_executable(Condition *c, char **env) { |
d1bddcec LP |
769 | struct stat st; |
770 | ||
771 | assert(c); | |
772 | assert(c->parameter); | |
773 | assert(c->type == CONDITION_FILE_IS_EXECUTABLE); | |
774 | ||
775 | return (stat(c->parameter, &st) >= 0 && | |
776 | S_ISREG(st.st_mode) && | |
a4705396 | 777 | (st.st_mode & 0111)); |
d1bddcec LP |
778 | } |
779 | ||
a0b191b7 | 780 | int condition_test(Condition *c, char **env) { |
d1bddcec | 781 | |
a0b191b7 | 782 | static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c, char **env) = { |
cae90de3 ZJS |
783 | [CONDITION_PATH_EXISTS] = condition_test_path_exists, |
784 | [CONDITION_PATH_EXISTS_GLOB] = condition_test_path_exists_glob, | |
785 | [CONDITION_PATH_IS_DIRECTORY] = condition_test_path_is_directory, | |
786 | [CONDITION_PATH_IS_SYMBOLIC_LINK] = condition_test_path_is_symbolic_link, | |
787 | [CONDITION_PATH_IS_MOUNT_POINT] = condition_test_path_is_mount_point, | |
788 | [CONDITION_PATH_IS_READ_WRITE] = condition_test_path_is_read_write, | |
7f19247b | 789 | [CONDITION_PATH_IS_ENCRYPTED] = condition_test_path_is_encrypted, |
cae90de3 ZJS |
790 | [CONDITION_DIRECTORY_NOT_EMPTY] = condition_test_directory_not_empty, |
791 | [CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty, | |
792 | [CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable, | |
793 | [CONDITION_KERNEL_COMMAND_LINE] = condition_test_kernel_command_line, | |
794 | [CONDITION_KERNEL_VERSION] = condition_test_kernel_version, | |
795 | [CONDITION_VIRTUALIZATION] = condition_test_virtualization, | |
796 | [CONDITION_SECURITY] = condition_test_security, | |
797 | [CONDITION_CAPABILITY] = condition_test_capability, | |
798 | [CONDITION_HOST] = condition_test_host, | |
799 | [CONDITION_AC_POWER] = condition_test_ac_power, | |
800 | [CONDITION_ARCHITECTURE] = condition_test_architecture, | |
801 | [CONDITION_NEEDS_UPDATE] = condition_test_needs_update, | |
802 | [CONDITION_FIRST_BOOT] = condition_test_first_boot, | |
803 | [CONDITION_USER] = condition_test_user, | |
804 | [CONDITION_GROUP] = condition_test_group, | |
e16647c3 | 805 | [CONDITION_CONTROL_GROUP_CONTROLLER] = condition_test_control_group_controller, |
cae90de3 ZJS |
806 | [CONDITION_CPUS] = condition_test_cpus, |
807 | [CONDITION_MEMORY] = condition_test_memory, | |
a0b191b7 | 808 | [CONDITION_ENVIRONMENT] = condition_test_environment, |
d1bddcec | 809 | }; |
cc50ef13 LP |
810 | |
811 | int r, b; | |
d1bddcec LP |
812 | |
813 | assert(c); | |
814 | assert(c->type >= 0); | |
815 | assert(c->type < _CONDITION_TYPE_MAX); | |
816 | ||
a0b191b7 | 817 | r = condition_tests[c->type](c, env); |
cc50ef13 LP |
818 | if (r < 0) { |
819 | c->result = CONDITION_ERROR; | |
a4705396 | 820 | return r; |
cc50ef13 | 821 | } |
a4705396 | 822 | |
cc50ef13 LP |
823 | b = (r > 0) == !c->negate; |
824 | c->result = b ? CONDITION_SUCCEEDED : CONDITION_FAILED; | |
825 | return b; | |
d1bddcec LP |
826 | } |
827 | ||
dce719f6 LP |
828 | bool condition_test_list( |
829 | Condition *first, | |
a0b191b7 | 830 | char **env, |
dce719f6 LP |
831 | condition_to_string_t to_string, |
832 | condition_test_logger_t logger, | |
833 | void *userdata) { | |
834 | ||
828fa610 YW |
835 | Condition *c; |
836 | int triggered = -1; | |
837 | ||
838 | assert(!!logger == !!to_string); | |
839 | ||
840 | /* If the condition list is empty, then it is true */ | |
841 | if (!first) | |
842 | return true; | |
843 | ||
844 | /* Otherwise, if all of the non-trigger conditions apply and | |
845 | * if any of the trigger conditions apply (unless there are | |
846 | * none) we return true */ | |
847 | LIST_FOREACH(conditions, c, first) { | |
848 | int r; | |
849 | ||
a0b191b7 | 850 | r = condition_test(c, env); |
828fa610 YW |
851 | |
852 | if (logger) { | |
853 | if (r < 0) | |
62c6bbbc | 854 | logger(userdata, LOG_WARNING, r, PROJECT_FILE, __LINE__, __func__, |
828fa610 YW |
855 | "Couldn't determine result for %s=%s%s%s, assuming failed: %m", |
856 | to_string(c->type), | |
857 | c->trigger ? "|" : "", | |
858 | c->negate ? "!" : "", | |
476cfe62 | 859 | c->parameter); |
828fa610 | 860 | else |
62c6bbbc | 861 | logger(userdata, LOG_DEBUG, 0, PROJECT_FILE, __LINE__, __func__, |
828fa610 YW |
862 | "%s=%s%s%s %s.", |
863 | to_string(c->type), | |
864 | c->trigger ? "|" : "", | |
865 | c->negate ? "!" : "", | |
476cfe62 | 866 | c->parameter, |
828fa610 YW |
867 | condition_result_to_string(c->result)); |
868 | } | |
869 | ||
870 | if (!c->trigger && r <= 0) | |
871 | return false; | |
872 | ||
873 | if (c->trigger && triggered <= 0) | |
874 | triggered = r > 0; | |
875 | } | |
876 | ||
877 | return triggered != 0; | |
878 | } | |
879 | ||
dce719f6 | 880 | void condition_dump(Condition *c, FILE *f, const char *prefix, condition_to_string_t to_string) { |
b77c08e0 TG |
881 | assert(c); |
882 | assert(f); | |
dce719f6 | 883 | assert(to_string); |
b77c08e0 | 884 | |
ad5d4b17 | 885 | prefix = strempty(prefix); |
b77c08e0 TG |
886 | |
887 | fprintf(f, | |
888 | "%s\t%s: %s%s%s %s\n", | |
889 | prefix, | |
59fccdc5 | 890 | to_string(c->type), |
b77c08e0 TG |
891 | c->trigger ? "|" : "", |
892 | c->negate ? "!" : "", | |
893 | c->parameter, | |
cc50ef13 | 894 | condition_result_to_string(c->result)); |
b77c08e0 TG |
895 | } |
896 | ||
dce719f6 | 897 | void condition_dump_list(Condition *first, FILE *f, const char *prefix, condition_to_string_t to_string) { |
b77c08e0 TG |
898 | Condition *c; |
899 | ||
900 | LIST_FOREACH(conditions, c, first) | |
59fccdc5 | 901 | condition_dump(c, f, prefix, to_string); |
b77c08e0 TG |
902 | } |
903 | ||
904 | static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { | |
651c3318 LP |
905 | [CONDITION_ARCHITECTURE] = "ConditionArchitecture", |
906 | [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", | |
907 | [CONDITION_HOST] = "ConditionHost", | |
908 | [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", | |
5022f08a | 909 | [CONDITION_KERNEL_VERSION] = "ConditionKernelVersion", |
651c3318 LP |
910 | [CONDITION_SECURITY] = "ConditionSecurity", |
911 | [CONDITION_CAPABILITY] = "ConditionCapability", | |
912 | [CONDITION_AC_POWER] = "ConditionACPower", | |
913 | [CONDITION_NEEDS_UPDATE] = "ConditionNeedsUpdate", | |
914 | [CONDITION_FIRST_BOOT] = "ConditionFirstBoot", | |
b77c08e0 TG |
915 | [CONDITION_PATH_EXISTS] = "ConditionPathExists", |
916 | [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob", | |
917 | [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory", | |
918 | [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink", | |
919 | [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint", | |
920 | [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite", | |
7f19247b | 921 | [CONDITION_PATH_IS_ENCRYPTED] = "ConditionPathIsEncrypted", |
b77c08e0 TG |
922 | [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty", |
923 | [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty", | |
924 | [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable", | |
c465a29f FS |
925 | [CONDITION_USER] = "ConditionUser", |
926 | [CONDITION_GROUP] = "ConditionGroup", | |
e16647c3 | 927 | [CONDITION_CONTROL_GROUP_CONTROLLER] = "ConditionControlGroupController", |
754f719a LP |
928 | [CONDITION_CPUS] = "ConditionCPUs", |
929 | [CONDITION_MEMORY] = "ConditionMemory", | |
a0b191b7 | 930 | [CONDITION_ENVIRONMENT] = "ConditionEnvironment", |
b77c08e0 TG |
931 | }; |
932 | ||
933 | DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType); | |
cc50ef13 | 934 | |
59fccdc5 | 935 | static const char* const assert_type_table[_CONDITION_TYPE_MAX] = { |
651c3318 LP |
936 | [CONDITION_ARCHITECTURE] = "AssertArchitecture", |
937 | [CONDITION_VIRTUALIZATION] = "AssertVirtualization", | |
938 | [CONDITION_HOST] = "AssertHost", | |
939 | [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine", | |
5022f08a | 940 | [CONDITION_KERNEL_VERSION] = "AssertKernelVersion", |
651c3318 LP |
941 | [CONDITION_SECURITY] = "AssertSecurity", |
942 | [CONDITION_CAPABILITY] = "AssertCapability", | |
943 | [CONDITION_AC_POWER] = "AssertACPower", | |
944 | [CONDITION_NEEDS_UPDATE] = "AssertNeedsUpdate", | |
945 | [CONDITION_FIRST_BOOT] = "AssertFirstBoot", | |
59fccdc5 LP |
946 | [CONDITION_PATH_EXISTS] = "AssertPathExists", |
947 | [CONDITION_PATH_EXISTS_GLOB] = "AssertPathExistsGlob", | |
948 | [CONDITION_PATH_IS_DIRECTORY] = "AssertPathIsDirectory", | |
949 | [CONDITION_PATH_IS_SYMBOLIC_LINK] = "AssertPathIsSymbolicLink", | |
950 | [CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint", | |
951 | [CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite", | |
7f19247b | 952 | [CONDITION_PATH_IS_ENCRYPTED] = "AssertPathIsEncrypted", |
59fccdc5 LP |
953 | [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty", |
954 | [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty", | |
955 | [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable", | |
c465a29f FS |
956 | [CONDITION_USER] = "AssertUser", |
957 | [CONDITION_GROUP] = "AssertGroup", | |
e16647c3 | 958 | [CONDITION_CONTROL_GROUP_CONTROLLER] = "AssertControlGroupController", |
754f719a LP |
959 | [CONDITION_CPUS] = "AssertCPUs", |
960 | [CONDITION_MEMORY] = "AssertMemory", | |
a0b191b7 | 961 | [CONDITION_ENVIRONMENT] = "AssertEnvironment", |
59fccdc5 LP |
962 | }; |
963 | ||
964 | DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType); | |
965 | ||
cc50ef13 LP |
966 | static const char* const condition_result_table[_CONDITION_RESULT_MAX] = { |
967 | [CONDITION_UNTESTED] = "untested", | |
968 | [CONDITION_SUCCEEDED] = "succeeded", | |
969 | [CONDITION_FAILED] = "failed", | |
970 | [CONDITION_ERROR] = "error", | |
971 | }; | |
972 | ||
973 | DEFINE_STRING_TABLE_LOOKUP(condition_result, ConditionResult); |