1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.h"
7 #include "dirent-util.h"
9 #include "errno-util.h"
12 #include "glyph-util.h"
15 #include "path-util.h"
16 #include "stat-util.h"
17 #include "string-table.h"
18 #include "string-util.h"
20 #include "time-util.h"
22 #include "xattr-util.h"
24 static const char* const image_class_table
[_IMAGE_CLASS_MAX
] = {
25 [IMAGE_MACHINE
] = "machine",
26 [IMAGE_PORTABLE
] = "portable",
27 [IMAGE_SYSEXT
] = "sysext",
28 [IMAGE_CONFEXT
] = "confext",
31 DEFINE_STRING_TABLE_LOOKUP(image_class
, ImageClass
);
33 /* Helper struct for naming simplicity and reusability */
35 const char *release_file_directory
;
36 const char *release_file_path_prefix
;
37 } image_class_release_info
[_IMAGE_CLASS_MAX
] = {
39 .release_file_directory
= "/usr/lib/extension-release.d/",
40 .release_file_path_prefix
= "/usr/lib/extension-release.d/extension-release.",
43 .release_file_directory
= "/etc/extension-release.d/",
44 .release_file_path_prefix
= "/etc/extension-release.d/extension-release.",
48 bool image_name_is_valid(const char *s
) {
49 if (!filename_is_valid(s
))
52 if (string_has_cc(s
, NULL
))
55 if (!utf8_is_valid(s
))
58 /* Temporary files for atomically creating new files */
59 if (startswith(s
, ".#"))
65 int path_extract_image_name(const char *path
, char **ret
) {
66 _cleanup_free_
char *fn
= NULL
;
72 /* Extract last component from path, without any "/" suffixes. */
73 r
= path_extract_filename(path
, &fn
);
76 if (r
!= O_DIRECTORY
) {
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");
83 /* Truncate the version/counting suffixes */
84 fn
[strcspn(fn
, "_+")] = 0;
86 if (!image_name_is_valid(fn
))
93 int path_is_extension_tree(ImageClass image_class
, const char *path
, const char *extension
, bool relax_extension_release_check
) {
98 /* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
99 * always results in -ENOENT, and we can properly distinguish the case where the whole root doesn't exist from
100 * the case where just the os-release file is missing. */
101 r
= access_nofollow(path
, F_OK
);
105 /* We use /usr/lib/extension-release.d/extension-release[.NAME] as flag for something being a system extension,
106 * /etc/extension-release.d/extension-release[.NAME] as flag for something being a system configuration, and finally,
107 * and {/etc|/usr/lib}/os-release as a flag for something being an OS (when not an extension). */
108 r
= open_extension_release(path
, image_class
, extension
, relax_extension_release_check
, NULL
, NULL
);
109 if (r
== -ENOENT
) /* We got nothing */
117 static int extension_release_strict_xattr_value(int extension_release_fd
, const char *extension_release_dir_path
, const char *filename
) {
120 assert(extension_release_fd
>= 0);
121 assert(extension_release_dir_path
);
124 /* No xattr or cannot parse it? Then skip this. */
125 r
= getxattr_at_bool(extension_release_fd
, /* path= */ NULL
, "user.extension-release.strict", /* at_flags= */ 0);
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
);
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
);
133 /* Explicitly set to request strict matching? Skip it. */
135 log_debug("%s/%s: 'user.extension-release.strict' attribute is true, ignoring file.",
136 extension_release_dir_path
, filename
);
140 log_debug("%s/%s: 'user.extension-release.strict' attribute is false%s",
141 extension_release_dir_path
, filename
,
142 glyph(GLYPH_ELLIPSIS
));
147 int open_os_release_at(int rfd
, char **ret_path
, int *ret_fd
) {
151 assert(rfd
>= 0 || rfd
== AT_FDCWD
);
153 e
= secure_getenv("SYSTEMD_OS_RELEASE");
155 return chaseat(rfd
, e
, CHASE_AT_RESOLVE_IN_ROOT
, ret_path
, ret_fd
);
157 FOREACH_STRING(path
, "/etc/os-release", "/usr/lib/os-release") {
158 r
= chaseat(rfd
, path
, CHASE_AT_RESOLVE_IN_ROOT
, ret_path
, ret_fd
);
166 int 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
;
171 rfd
= open(empty_to_root(root
), O_CLOEXEC
| O_DIRECTORY
| O_PATH
);
175 r
= open_os_release_at(rfd
, ret_path
? &p
: NULL
, ret_fd
? &fd
: NULL
);
180 r
= chaseat_prefix_root(p
, root
, ret_path
);
186 *ret_fd
= TAKE_FD(fd
);
191 int open_extension_release_at(
193 ImageClass image_class
,
194 const char *extension
,
195 bool relax_extension_release_check
,
199 _cleanup_free_
char *dir_path
= NULL
, *path_found
= NULL
;
200 _cleanup_close_
int fd_found
= -EBADF
;
201 _cleanup_closedir_
DIR *dir
= NULL
;
206 assert(rfd
>= 0 || rfd
== AT_FDCWD
);
207 assert(!extension
|| (image_class
>= 0 && image_class
< _IMAGE_CLASS_MAX
));
210 return open_os_release_at(rfd
, ret_path
, ret_fd
);
212 if (!IN_SET(image_class
, IMAGE_SYSEXT
, IMAGE_CONFEXT
))
215 if (!image_name_is_valid(extension
))
216 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "The extension name %s is invalid.", extension
);
218 p
= strjoina(image_class_release_info
[image_class
].release_file_path_prefix
, extension
);
219 r
= chaseat(rfd
, p
, CHASE_AT_RESOLVE_IN_ROOT
, ret_path
, ret_fd
);
220 log_full_errno_zerook(LOG_DEBUG
, MIN(r
, 0), "Checking for %s: %m", p
);
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. */
229 p
= image_class_release_info
[image_class
].release_file_directory
;
230 r
= chase_and_opendirat(rfd
, p
, CHASE_AT_RESOLVE_IN_ROOT
, &dir_path
, &dir
);
232 return log_debug_errno(r
, "Cannot open %s, ignoring: %m", p
);
234 FOREACH_DIRENT(de
, dir
, return -errno
) {
235 _cleanup_close_
int fd
= -EBADF
;
236 const char *image_name
;
238 if (!IN_SET(de
->d_type
, DT_REG
, DT_UNKNOWN
))
241 image_name
= startswith(de
->d_name
, "extension-release.");
245 if (!image_name_is_valid(image_name
)) {
246 log_debug("%s/%s is not a valid release file name, ignoring.", dir_path
, de
->d_name
);
250 /* We already chased the directory, and checked that this is a real file, so we shouldn't
251 * fail to open it. */
252 fd
= openat(dirfd(dir
), de
->d_name
, O_PATH
|O_CLOEXEC
|O_NOFOLLOW
);
254 return log_debug_errno(errno
, "Failed to open release file %s/%s: %m", dir_path
, de
->d_name
);
256 /* Really ensure it is a regular file after we open it. */
257 r
= fd_verify_regular(fd
);
259 log_debug_errno(r
, "%s/%s is not a regular file, ignoring: %m", dir_path
, de
->d_name
);
263 if (!relax_extension_release_check
) {
264 _cleanup_free_
char *base_extension
= NULL
;
266 r
= path_extract_image_name(extension
, &base_extension
);
268 log_debug_errno(r
, "Failed to extract image name from %s, ignoring: %m", extension
);
272 if (!streq(image_name
, base_extension
) &&
273 extension_release_strict_xattr_value(fd
, dir_path
, image_name
) != 0)
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
286 fd_found
= TAKE_FD(fd
);
289 path_found
= path_join(dir_path
, de
->d_name
);
298 *ret_fd
= TAKE_FD(fd_found
);
300 *ret_path
= TAKE_PTR(path_found
);
305 int open_extension_release(
307 ImageClass image_class
,
308 const char *extension
,
309 bool relax_extension_release_check
,
313 _cleanup_close_
int rfd
= -EBADF
, fd
= -EBADF
;
314 _cleanup_free_
char *p
= NULL
;
317 rfd
= open(empty_to_root(root
), O_CLOEXEC
| O_DIRECTORY
| O_PATH
);
321 r
= open_extension_release_at(rfd
, image_class
, extension
, relax_extension_release_check
,
322 ret_path
? &p
: NULL
, ret_fd
? &fd
: NULL
);
327 r
= chaseat_prefix_root(p
, root
, ret_path
);
333 *ret_fd
= TAKE_FD(fd
);
338 static int parse_extension_release_atv(
340 ImageClass image_class
,
341 const char *extension
,
342 bool relax_extension_release_check
,
345 _cleanup_close_
int fd
= -EBADF
;
346 _cleanup_free_
char *p
= NULL
;
349 assert(rfd
>= 0 || rfd
== AT_FDCWD
);
351 r
= open_extension_release_at(rfd
, image_class
, extension
, relax_extension_release_check
, &p
, &fd
);
355 return parse_env_file_fdv(fd
, p
, ap
);
358 int parse_extension_release_at_sentinel(
360 ImageClass image_class
,
361 bool relax_extension_release_check
,
362 const char *extension
,
368 assert(rfd
>= 0 || rfd
== AT_FDCWD
);
370 va_start(ap
, extension
);
371 r
= parse_extension_release_atv(rfd
, image_class
, extension
, relax_extension_release_check
, ap
);
376 int parse_extension_release_sentinel(
378 ImageClass image_class
,
379 bool relax_extension_release_check
,
380 const char *extension
,
383 _cleanup_close_
int rfd
= -EBADF
;
387 rfd
= open(empty_to_root(root
), O_CLOEXEC
| O_DIRECTORY
| O_PATH
);
391 va_start(ap
, extension
);
392 r
= parse_extension_release_atv(rfd
, image_class
, extension
, relax_extension_release_check
, ap
);
397 int 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
;
401 r
= load_os_release_pairs(root
, &os_release_pairs
);
405 STRV_FOREACH_PAIR(p
, q
, os_release_pairs
) {
408 /* We strictly return only the four main ID fields and ignore the rest */
409 if (!STR_IN_SET(*p
, "ID", "VERSION_ID", "BUILD_ID", "VARIANT_ID"))
413 line
= strjoin(prefix
, *p
, "=", *q
);
416 r
= strv_consume(&os_release_pairs_prefixed
, line
);
421 *ret
= TAKE_PTR(os_release_pairs_prefixed
);
426 int load_extension_release_pairs(const char *root
, ImageClass image_class
, const char *extension
, bool relax_extension_release_check
, char ***ret
) {
427 _cleanup_close_
int fd
= -EBADF
;
428 _cleanup_free_
char *p
= NULL
;
431 r
= open_extension_release(root
, image_class
, extension
, relax_extension_release_check
, &p
, &fd
);
435 return load_env_file_pairs_fd(fd
, p
, ret
);
438 int os_release_support_ended(const char *support_end
, bool quiet
, usec_t
*ret_eol
) {
439 _cleanup_free_
char *_support_end_alloc
= NULL
;
443 /* If the caller has the variably handy, they can pass it in. If not, we'll read it
446 r
= parse_os_release(NULL
,
447 "SUPPORT_END", &_support_end_alloc
);
448 if (r
< 0 && r
!= -ENOENT
)
449 return log_full_errno(quiet
? LOG_DEBUG
: LOG_WARNING
, r
,
450 "Failed to read os-release file, ignoring: %m");
452 support_end
= _support_end_alloc
;
455 if (isempty(support_end
)) { /* An empty string is a explicit way to say "no EOL exists" */
457 *ret_eol
= USEC_INFINITY
;
459 return false; /* no end date defined */
463 const char *k
= strptime(support_end
, "%Y-%m-%d", &tm
);
465 return log_full_errno(quiet
? LOG_DEBUG
: LOG_WARNING
, SYNTHETIC_ERRNO(EINVAL
),
466 "Failed to parse SUPPORT_END= from os-release file, ignoring: %s", support_end
);
469 r
= mktime_or_timegm_usec(&tm
, /* utc= */ true, &eol
);
471 return log_full_errno(quiet
? LOG_DEBUG
: LOG_WARNING
, r
,
472 "Failed to convert SUPPORT_END= time from os-release file, ignoring: %m");
477 return now(CLOCK_REALTIME
) > eol
;
480 const char* os_release_pretty_name(const char *pretty_name
, const char *name
) {
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. */
485 return empty_to_null(pretty_name
) ?:
486 empty_to_null(name
) ?: "Linux";