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