]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/os-util.c
basic/include: replace _Static_assert() with static_assert()
[thirdparty/systemd.git] / src / basic / os-util.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
d58ad743 2
0c15577a
DDM
3#include <stdlib.h>
4
d58ad743 5#include "alloc-util.h"
f461a28d 6#include "chase.h"
9a4b883b 7#include "dirent-util.h"
686d13b9 8#include "env-file.h"
fda65211 9#include "errno-util.h"
d58ad743
LP
10#include "fd-util.h"
11#include "fs-util.h"
28e5e1e9 12#include "glyph-util.h"
93a1f792 13#include "log.h"
d58ad743 14#include "os-util.h"
6ef06723 15#include "path-util.h"
9a4b883b 16#include "stat-util.h"
c2cd9508 17#include "string-table.h"
d58ad743 18#include "string-util.h"
686d13b9 19#include "strv.h"
0c15577a 20#include "time-util.h"
6ef06723 21#include "utf8.h"
9a4b883b 22#include "xattr-util.h"
6ef06723 23
c2cd9508
YW
24static const char* const image_class_table[_IMAGE_CLASS_MAX] = {
25 [IMAGE_MACHINE] = "machine",
26 [IMAGE_PORTABLE] = "portable",
8a324d16 27 [IMAGE_SYSEXT] = "sysext",
c2cd9508
YW
28 [IMAGE_CONFEXT] = "confext",
29};
30
31DEFINE_STRING_TABLE_LOOKUP(image_class, ImageClass);
32
b60e0f57 33/* Helper struct for naming simplicity and reusability */
34static const struct {
35 const char *release_file_directory;
36 const char *release_file_path_prefix;
37} image_class_release_info[_IMAGE_CLASS_MAX] = {
38 [IMAGE_SYSEXT] = {
39 .release_file_directory = "/usr/lib/extension-release.d/",
40 .release_file_path_prefix = "/usr/lib/extension-release.d/extension-release.",
41 },
42 [IMAGE_CONFEXT] = {
43 .release_file_directory = "/etc/extension-release.d/",
44 .release_file_path_prefix = "/etc/extension-release.d/extension-release.",
45 }
46};
47
6ef06723
ZJS
48bool image_name_is_valid(const char *s) {
49 if (!filename_is_valid(s))
50 return false;
51
52 if (string_has_cc(s, NULL))
53 return false;
54
55 if (!utf8_is_valid(s))
56 return false;
57
58 /* Temporary files for atomically creating new files */
59 if (startswith(s, ".#"))
60 return false;
61
62 return true;
63}
d58ad743 64
37543971
LB
65int path_extract_image_name(const char *path, char **ret) {
66 _cleanup_free_ char *fn = NULL;
67 int r;
68
69 assert(path);
70 assert(ret);
71
72 /* Extract last component from path, without any "/" suffixes. */
73 r = path_extract_filename(path, &fn);
74 if (r < 0)
75 return r;
37543971 76 if (r != O_DIRECTORY) {
99f3b67f
MY
77 /* Chop off any image suffixes we recognize (unless we already know this must refer to some dir) */
78 char *m = ENDSWITH_SET(fn, ".sysext.raw", ".confext.raw", ".raw");
79 if (m)
80 *m = 0;
37543971
LB
81 }
82
83 /* Truncate the version/counting suffixes */
84 fn[strcspn(fn, "_+")] = 0;
85
86 if (!image_name_is_valid(fn))
87 return -EINVAL;
88
89 *ret = TAKE_PTR(fn);
90 return 0;
91}
92
b60e0f57 93int path_is_extension_tree(ImageClass image_class, const char *path, const char *extension, bool relax_extension_release_check) {
d58ad743
LP
94 int r;
95
96 assert(path);
97
98 /* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
5238e957 99 * always results in -ENOENT, and we can properly distinguish the case where the whole root doesn't exist from
d58ad743 100 * the case where just the os-release file is missing. */
3f8999a7 101 r = access_nofollow(path, F_OK);
7c1dd9e2
MY
102 if (r < 0)
103 return r;
d58ad743 104
9a4b883b 105 /* We use /usr/lib/extension-release.d/extension-release[.NAME] as flag for something being a system extension,
b60e0f57 106 * /etc/extension-release.d/extension-release[.NAME] as flag for something being a system configuration, and finally,
9a4b883b 107 * and {/etc|/usr/lib}/os-release as a flag for something being an OS (when not an extension). */
b60e0f57 108 r = open_extension_release(path, image_class, extension, relax_extension_release_check, NULL, NULL);
d58ad743
LP
109 if (r == -ENOENT) /* We got nothing */
110 return 0;
111 if (r < 0)
112 return r;
113
114 return 1;
115}
116
192a9b70
LB
117static int extension_release_strict_xattr_value(int extension_release_fd, const char *extension_release_dir_path, const char *filename) {
118 int r;
119
120 assert(extension_release_fd >= 0);
121 assert(extension_release_dir_path);
122 assert(filename);
123
124 /* No xattr or cannot parse it? Then skip this. */
09755ba5 125 r = getxattr_at_bool(extension_release_fd, /* path= */ NULL, "user.extension-release.strict", /* at_flags= */ 0);
bb44fd07
ZJS
126 if (ERRNO_IS_NEG_XATTR_ABSENT(r))
127 return log_debug_errno(r, "%s/%s does not have user.extension-release.strict xattr, ignoring.",
128 extension_release_dir_path, filename);
129 if (r < 0)
130 return log_debug_errno(r, "%s/%s: Failed to read 'user.extension-release.strict' extended attribute from file, ignoring: %m",
131 extension_release_dir_path, filename);
192a9b70
LB
132
133 /* Explicitly set to request strict matching? Skip it. */
192a9b70
LB
134 if (r > 0) {
135 log_debug("%s/%s: 'user.extension-release.strict' attribute is true, ignoring file.",
136 extension_release_dir_path, filename);
137 return true;
138 }
139
140 log_debug("%s/%s: 'user.extension-release.strict' attribute is false%s",
141 extension_release_dir_path, filename,
1ae9b0cf 142 glyph(GLYPH_ELLIPSIS));
192a9b70
LB
143
144 return false;
145}
146
538d878d 147int open_os_release_at(int rfd, char **ret_path, int *ret_fd) {
a84677e0
YW
148 const char *e;
149 int r;
150
538d878d
YW
151 assert(rfd >= 0 || rfd == AT_FDCWD);
152
a84677e0
YW
153 e = secure_getenv("SYSTEMD_OS_RELEASE");
154 if (e)
538d878d 155 return chaseat(rfd, e, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
a84677e0
YW
156
157 FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") {
538d878d 158 r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
a84677e0
YW
159 if (r != -ENOENT)
160 return r;
161 }
162
163 return -ENOENT;
164}
165
538d878d
YW
166int open_os_release(const char *root, char **ret_path, int *ret_fd) {
167 _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
168 _cleanup_free_ char *p = NULL;
169 int r;
170
171 rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
172 if (rfd < 0)
173 return -errno;
174
175 r = open_os_release_at(rfd, ret_path ? &p : NULL, ret_fd ? &fd : NULL);
176 if (r < 0)
177 return r;
178
179 if (ret_path) {
60e761d8 180 r = chaseat_prefix_root(p, root, ret_path);
538d878d
YW
181 if (r < 0)
182 return r;
183 }
184
185 if (ret_fd)
186 *ret_fd = TAKE_FD(fd);
187
188 return 0;
189}
190
191int open_extension_release_at(
192 int rfd,
a84677e0
YW
193 ImageClass image_class,
194 const char *extension,
195 bool relax_extension_release_check,
196 char **ret_path,
197 int *ret_fd) {
198
61acfd83
YW
199 _cleanup_free_ char *dir_path = NULL, *path_found = NULL;
200 _cleanup_close_ int fd_found = -EBADF;
201 _cleanup_closedir_ DIR *dir = NULL;
c9d64f8a 202 bool found = false;
61acfd83 203 const char *p;
6f0f4d14 204 int r;
d58ad743 205
538d878d 206 assert(rfd >= 0 || rfd == AT_FDCWD);
a84677e0 207 assert(!extension || (image_class >= 0 && image_class < _IMAGE_CLASS_MAX));
b60e0f57 208
a84677e0 209 if (!extension)
538d878d 210 return open_os_release_at(rfd, ret_path, ret_fd);
6ddd0511 211
7421f20c
YW
212 if (!IN_SET(image_class, IMAGE_SYSEXT, IMAGE_CONFEXT))
213 return -EINVAL;
214
a84677e0
YW
215 if (!image_name_is_valid(extension))
216 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The extension name %s is invalid.", extension);
ed15f8bc 217
61acfd83 218 p = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension);
538d878d 219 r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd);
61acfd83 220 log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p);
7213c750
YW
221 if (r != -ENOENT)
222 return r;
9a4b883b 223
a84677e0
YW
224 /* Cannot find the expected extension-release file? The image filename might have been mangled on
225 * deployment, so fallback to checking for any file in the extension-release.d directory, and return
226 * the first one with a user.extension-release xattr instead. The user.extension-release.strict
227 * xattr is checked to ensure the author of the image considers it OK if names do not match. */
9a4b883b 228
61acfd83 229 p = image_class_release_info[image_class].release_file_directory;
538d878d 230 r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir);
7213c750 231 if (r < 0)
538d878d 232 return log_debug_errno(r, "Cannot open %s, ignoring: %m", p);
9a4b883b 233
61acfd83
YW
234 FOREACH_DIRENT(de, dir, return -errno) {
235 _cleanup_close_ int fd = -EBADF;
236 const char *image_name;
9a4b883b 237
7213c750
YW
238 if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
239 continue;
a84677e0 240
61acfd83 241 image_name = startswith(de->d_name, "extension-release.");
7213c750
YW
242 if (!image_name)
243 continue;
244
245 if (!image_name_is_valid(image_name)) {
61acfd83 246 log_debug("%s/%s is not a valid release file name, ignoring.", dir_path, de->d_name);
7213c750
YW
247 continue;
248 }
249
250 /* We already chased the directory, and checked that this is a real file, so we shouldn't
251 * fail to open it. */
61acfd83
YW
252 fd = openat(dirfd(dir), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW);
253 if (fd < 0)
254 return log_debug_errno(errno, "Failed to open release file %s/%s: %m", dir_path, de->d_name);
7213c750
YW
255
256 /* Really ensure it is a regular file after we open it. */
61acfd83 257 r = fd_verify_regular(fd);
59c47075 258 if (r < 0) {
61acfd83 259 log_debug_errno(r, "%s/%s is not a regular file, ignoring: %m", dir_path, de->d_name);
7213c750
YW
260 continue;
261 }
262
37543971 263 if (!relax_extension_release_check) {
92d1fe3e 264 _cleanup_free_ char *base_extension = NULL;
37543971
LB
265
266 r = path_extract_image_name(extension, &base_extension);
267 if (r < 0) {
268 log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", extension);
269 continue;
270 }
271
92d1fe3e 272 if (!streq(image_name, base_extension) &&
37543971
LB
273 extension_release_strict_xattr_value(fd, dir_path, image_name) != 0)
274 continue;
275 }
7213c750
YW
276
277 /* We already found what we were looking for, but there's another candidate? We treat this as
278 * an error, as we want to enforce that there are no ambiguities in case we are in the
279 * fallback path. */
c9d64f8a
YW
280 if (found)
281 return -ENOTUNIQ;
7213c750 282
c9d64f8a 283 found = true;
7213c750
YW
284
285 if (ret_fd)
61acfd83 286 fd_found = TAKE_FD(fd);
7213c750
YW
287
288 if (ret_path) {
61acfd83
YW
289 path_found = path_join(dir_path, de->d_name);
290 if (!path_found)
7213c750 291 return -ENOMEM;
a84677e0 292 }
d58ad743 293 }
c9d64f8a
YW
294 if (!found)
295 return -ENOENT;
d58ad743 296
396ec958 297 if (ret_fd)
61acfd83 298 *ret_fd = TAKE_FD(fd_found);
d58ad743 299 if (ret_path)
61acfd83 300 *ret_path = TAKE_PTR(path_found);
d58ad743
LP
301
302 return 0;
303}
304
538d878d 305int open_extension_release(
f4a1d32c
YW
306 const char *root,
307 ImageClass image_class,
538d878d 308 const char *extension,
f4a1d32c 309 bool relax_extension_release_check,
538d878d
YW
310 char **ret_path,
311 int *ret_fd) {
312
313 _cleanup_close_ int rfd = -EBADF, fd = -EBADF;
314 _cleanup_free_ char *p = NULL;
315 int r;
316
317 rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
318 if (rfd < 0)
319 return -errno;
320
321 r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check,
322 ret_path ? &p : NULL, ret_fd ? &fd : NULL);
323 if (r < 0)
324 return r;
325
326 if (ret_path) {
60e761d8 327 r = chaseat_prefix_root(p, root, ret_path);
538d878d
YW
328 if (r < 0)
329 return r;
330 }
331
332 if (ret_fd)
333 *ret_fd = TAKE_FD(fd);
334
335 return 0;
336}
337
338static int parse_extension_release_atv(
339 int rfd,
340 ImageClass image_class,
f4a1d32c 341 const char *extension,
538d878d
YW
342 bool relax_extension_release_check,
343 va_list ap) {
f4a1d32c 344
53cbf5f9 345 _cleanup_close_ int fd = -EBADF;
d58ad743 346 _cleanup_free_ char *p = NULL;
d58ad743
LP
347 int r;
348
538d878d
YW
349 assert(rfd >= 0 || rfd == AT_FDCWD);
350
351 r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check, &p, &fd);
d58ad743
LP
352 if (r < 0)
353 return r;
354
538d878d
YW
355 return parse_env_file_fdv(fd, p, ap);
356}
357
358int parse_extension_release_at_sentinel(
359 int rfd,
360 ImageClass image_class,
361 bool relax_extension_release_check,
362 const char *extension,
363 ...) {
364
365 va_list ap;
366 int r;
367
368 assert(rfd >= 0 || rfd == AT_FDCWD);
369
370 va_start(ap, extension);
371 r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
372 va_end(ap);
373 return r;
374}
375
376int parse_extension_release_sentinel(
377 const char *root,
378 ImageClass image_class,
379 bool relax_extension_release_check,
380 const char *extension,
381 ...) {
382
383 _cleanup_close_ int rfd = -EBADF;
384 va_list ap;
385 int r;
386
387 rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
388 if (rfd < 0)
389 return -errno;
390
6ddd0511 391 va_start(ap, extension);
538d878d 392 r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap);
d58ad743 393 va_end(ap);
d58ad743
LP
394 return r;
395}
396
e1bb4b0d
LB
397int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret) {
398 _cleanup_strv_free_ char **os_release_pairs = NULL, **os_release_pairs_prefixed = NULL;
e1bb4b0d
LB
399 int r;
400
401 r = load_os_release_pairs(root, &os_release_pairs);
402 if (r < 0)
403 return r;
404
405 STRV_FOREACH_PAIR(p, q, os_release_pairs) {
406 char *line;
407
2094cd49 408 /* We strictly return only the four main ID fields and ignore the rest */
e1bb4b0d
LB
409 if (!STR_IN_SET(*p, "ID", "VERSION_ID", "BUILD_ID", "VARIANT_ID"))
410 continue;
411
412 ascii_strlower(*p);
413 line = strjoin(prefix, *p, "=", *q);
414 if (!line)
415 return -ENOMEM;
416 r = strv_consume(&os_release_pairs_prefixed, line);
417 if (r < 0)
418 return r;
419 }
420
421 *ret = TAKE_PTR(os_release_pairs_prefixed);
422
423 return 0;
424}
eb590035 425
b60e0f57 426int load_extension_release_pairs(const char *root, ImageClass image_class, const char *extension, bool relax_extension_release_check, char ***ret) {
53cbf5f9 427 _cleanup_close_ int fd = -EBADF;
eb590035
LB
428 _cleanup_free_ char *p = NULL;
429 int r;
430
53cbf5f9 431 r = open_extension_release(root, image_class, extension, relax_extension_release_check, &p, &fd);
eb590035
LB
432 if (r < 0)
433 return r;
434
53cbf5f9 435 return load_env_file_pairs_fd(fd, p, ret);
eb590035 436}
4bd03515 437
6bfe9b3b 438int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol) {
4bd03515
ZJS
439 _cleanup_free_ char *_support_end_alloc = NULL;
440 int r;
441
442 if (!support_end) {
443 /* If the caller has the variably handy, they can pass it in. If not, we'll read it
444 * ourselves. */
445
446 r = parse_os_release(NULL,
447 "SUPPORT_END", &_support_end_alloc);
469af08f
LP
448 if (r < 0 && r != -ENOENT)
449 return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, r,
4bd03515 450 "Failed to read os-release file, ignoring: %m");
4bd03515
ZJS
451
452 support_end = _support_end_alloc;
453 }
454
6f5cf415
LP
455 if (isempty(support_end)) { /* An empty string is a explicit way to say "no EOL exists" */
456 if (ret_eol)
457 *ret_eol = USEC_INFINITY;
458
a9bd4b4e 459 return false; /* no end date defined */
6f5cf415 460 }
4bd03515 461
a9bd4b4e 462 struct tm tm = {};
4bd03515
ZJS
463 const char *k = strptime(support_end, "%Y-%m-%d", &tm);
464 if (!k || *k)
465 return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, SYNTHETIC_ERRNO(EINVAL),
6f5cf415 466 "Failed to parse SUPPORT_END= from os-release file, ignoring: %s", support_end);
4bd03515 467
6f5cf415
LP
468 usec_t eol;
469 r = mktime_or_timegm_usec(&tm, /* utc= */ true, &eol);
470 if (r < 0)
471 return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING, r,
472 "Failed to convert SUPPORT_END= time from os-release file, ignoring: %m");
4bd03515 473
6bfe9b3b 474 if (ret_eol)
6f5cf415 475 *ret_eol = eol;
6bfe9b3b 476
6f5cf415 477 return now(CLOCK_REALTIME) > eol;
4bd03515 478}
02b7005e 479
bfd5a068 480const char* os_release_pretty_name(const char *pretty_name, const char *name) {
02b7005e
LP
481 /* Distills a "pretty" name to show from os-release data. First argument is supposed to be the
482 * PRETTY_NAME= field, the second one the NAME= field. This function is trivial, of course, and
483 * exists mostly to ensure we use the same logic wherever possible. */
484
485 return empty_to_null(pretty_name) ?:
486 empty_to_null(name) ?: "Linux";
487}