1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "alloc-util.h"
5 #include "dirent-util.h"
11 #include "glyph-util.h"
14 #include "parse-util.h"
15 #include "path-util.h"
16 #include "stat-util.h"
17 #include "string-table.h"
18 #include "string-util.h"
21 #include "xattr-util.h"
23 static const char* const image_class_table
[_IMAGE_CLASS_MAX
] = {
24 [IMAGE_MACHINE
] = "machine",
25 [IMAGE_PORTABLE
] = "portable",
26 [IMAGE_SYSEXT
] = "sysext",
27 [IMAGE_CONFEXT
] = "confext",
30 DEFINE_STRING_TABLE_LOOKUP(image_class
, ImageClass
);
32 /* Helper struct for naming simplicity and reusability */
34 const char *release_file_directory
;
35 const char *release_file_path_prefix
;
36 } image_class_release_info
[_IMAGE_CLASS_MAX
] = {
38 .release_file_directory
= "/usr/lib/extension-release.d/",
39 .release_file_path_prefix
= "/usr/lib/extension-release.d/extension-release.",
42 .release_file_directory
= "/etc/extension-release.d/",
43 .release_file_path_prefix
= "/etc/extension-release.d/extension-release.",
47 bool image_name_is_valid(const char *s
) {
48 if (!filename_is_valid(s
))
51 if (string_has_cc(s
, NULL
))
54 if (!utf8_is_valid(s
))
57 /* Temporary files for atomically creating new files */
58 if (startswith(s
, ".#"))
64 int path_is_extension_tree(ImageClass image_class
, const char *path
, const char *extension
, bool relax_extension_release_check
) {
69 /* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
70 * always results in -ENOENT, and we can properly distinguish the case where the whole root doesn't exist from
71 * the case where just the os-release file is missing. */
72 if (laccess(path
, F_OK
) < 0)
75 /* We use /usr/lib/extension-release.d/extension-release[.NAME] as flag for something being a system extension,
76 * /etc/extension-release.d/extension-release[.NAME] as flag for something being a system configuration, and finally,
77 * and {/etc|/usr/lib}/os-release as a flag for something being an OS (when not an extension). */
78 r
= open_extension_release(path
, image_class
, extension
, relax_extension_release_check
, NULL
, NULL
);
79 if (r
== -ENOENT
) /* We got nothing */
87 static int extension_release_strict_xattr_value(int extension_release_fd
, const char *extension_release_dir_path
, const char *filename
) {
90 assert(extension_release_fd
>= 0);
91 assert(extension_release_dir_path
);
94 /* No xattr or cannot parse it? Then skip this. */
95 _cleanup_free_
char *extension_release_xattr
= NULL
;
96 r
= fgetxattr_malloc(extension_release_fd
, "user.extension-release.strict", &extension_release_xattr
);
98 if (!ERRNO_IS_XATTR_ABSENT(r
))
99 return log_debug_errno(r
,
100 "%s/%s: Failed to read 'user.extension-release.strict' extended attribute from file, ignoring: %m",
101 extension_release_dir_path
, filename
);
103 return log_debug_errno(r
, "%s/%s does not have user.extension-release.strict xattr, ignoring.", extension_release_dir_path
, filename
);
106 /* Explicitly set to request strict matching? Skip it. */
107 r
= parse_boolean(extension_release_xattr
);
109 return log_debug_errno(r
,
110 "%s/%s: Failed to parse 'user.extension-release.strict' extended attribute from file, ignoring: %m",
111 extension_release_dir_path
, filename
);
113 log_debug("%s/%s: 'user.extension-release.strict' attribute is true, ignoring file.",
114 extension_release_dir_path
, filename
);
118 log_debug("%s/%s: 'user.extension-release.strict' attribute is false%s",
119 extension_release_dir_path
, filename
,
120 special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
125 int open_os_release_at(int rfd
, char **ret_path
, int *ret_fd
) {
129 assert(rfd
>= 0 || rfd
== AT_FDCWD
);
131 e
= secure_getenv("SYSTEMD_OS_RELEASE");
133 return chaseat(rfd
, e
, CHASE_AT_RESOLVE_IN_ROOT
, ret_path
, ret_fd
);
135 FOREACH_STRING(path
, "/etc/os-release", "/usr/lib/os-release") {
136 r
= chaseat(rfd
, path
, CHASE_AT_RESOLVE_IN_ROOT
, ret_path
, ret_fd
);
144 int open_os_release(const char *root
, char **ret_path
, int *ret_fd
) {
145 _cleanup_close_
int rfd
= -EBADF
, fd
= -EBADF
;
146 _cleanup_free_
char *p
= NULL
;
149 rfd
= open(empty_to_root(root
), O_CLOEXEC
| O_DIRECTORY
| O_PATH
);
153 r
= open_os_release_at(rfd
, ret_path
? &p
: NULL
, ret_fd
? &fd
: NULL
);
158 r
= chaseat_prefix_root(p
, root
, ret_path
);
164 *ret_fd
= TAKE_FD(fd
);
169 int open_extension_release_at(
171 ImageClass image_class
,
172 const char *extension
,
173 bool relax_extension_release_check
,
177 _cleanup_free_
char *dir_path
= NULL
, *path_found
= NULL
;
178 _cleanup_close_
int fd_found
= -EBADF
;
179 _cleanup_closedir_
DIR *dir
= NULL
;
184 assert(rfd
>= 0 || rfd
== AT_FDCWD
);
185 assert(!extension
|| (image_class
>= 0 && image_class
< _IMAGE_CLASS_MAX
));
188 return open_os_release_at(rfd
, ret_path
, ret_fd
);
190 if (!IN_SET(image_class
, IMAGE_SYSEXT
, IMAGE_CONFEXT
))
193 if (!image_name_is_valid(extension
))
194 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
), "The extension name %s is invalid.", extension
);
196 p
= strjoina(image_class_release_info
[image_class
].release_file_path_prefix
, extension
);
197 r
= chaseat(rfd
, p
, CHASE_AT_RESOLVE_IN_ROOT
, ret_path
, ret_fd
);
198 log_full_errno_zerook(LOG_DEBUG
, MIN(r
, 0), "Checking for %s: %m", p
);
202 /* Cannot find the expected extension-release file? The image filename might have been mangled on
203 * deployment, so fallback to checking for any file in the extension-release.d directory, and return
204 * the first one with a user.extension-release xattr instead. The user.extension-release.strict
205 * xattr is checked to ensure the author of the image considers it OK if names do not match. */
207 p
= image_class_release_info
[image_class
].release_file_directory
;
208 r
= chase_and_opendirat(rfd
, p
, CHASE_AT_RESOLVE_IN_ROOT
, &dir_path
, &dir
);
210 return log_debug_errno(r
, "Cannot open %s, ignoring: %m", p
);
212 FOREACH_DIRENT(de
, dir
, return -errno
) {
213 _cleanup_close_
int fd
= -EBADF
;
214 const char *image_name
;
216 if (!IN_SET(de
->d_type
, DT_REG
, DT_UNKNOWN
))
219 image_name
= startswith(de
->d_name
, "extension-release.");
223 if (!image_name_is_valid(image_name
)) {
224 log_debug("%s/%s is not a valid release file name, ignoring.", dir_path
, de
->d_name
);
228 /* We already chased the directory, and checked that this is a real file, so we shouldn't
229 * fail to open it. */
230 fd
= openat(dirfd(dir
), de
->d_name
, O_PATH
|O_CLOEXEC
|O_NOFOLLOW
);
232 return log_debug_errno(errno
, "Failed to open release file %s/%s: %m", dir_path
, de
->d_name
);
234 /* Really ensure it is a regular file after we open it. */
235 r
= fd_verify_regular(fd
);
237 log_debug_errno(r
, "%s/%s is not a regular file, ignoring: %m", dir_path
, de
->d_name
);
241 if (!relax_extension_release_check
&&
242 extension_release_strict_xattr_value(fd
, dir_path
, de
->d_name
) != 0)
245 /* We already found what we were looking for, but there's another candidate? We treat this as
246 * an error, as we want to enforce that there are no ambiguities in case we are in the
254 fd_found
= TAKE_FD(fd
);
257 path_found
= path_join(dir_path
, de
->d_name
);
266 *ret_fd
= TAKE_FD(fd_found
);
268 *ret_path
= TAKE_PTR(path_found
);
273 int open_extension_release(
275 ImageClass image_class
,
276 const char *extension
,
277 bool relax_extension_release_check
,
281 _cleanup_close_
int rfd
= -EBADF
, fd
= -EBADF
;
282 _cleanup_free_
char *p
= NULL
;
285 rfd
= open(empty_to_root(root
), O_CLOEXEC
| O_DIRECTORY
| O_PATH
);
289 r
= open_extension_release_at(rfd
, image_class
, extension
, relax_extension_release_check
,
290 ret_path
? &p
: NULL
, ret_fd
? &fd
: NULL
);
295 r
= chaseat_prefix_root(p
, root
, ret_path
);
301 *ret_fd
= TAKE_FD(fd
);
306 static int parse_extension_release_atv(
308 ImageClass image_class
,
309 const char *extension
,
310 bool relax_extension_release_check
,
313 _cleanup_close_
int fd
= -EBADF
;
314 _cleanup_free_
char *p
= NULL
;
317 assert(rfd
>= 0 || rfd
== AT_FDCWD
);
319 r
= open_extension_release_at(rfd
, image_class
, extension
, relax_extension_release_check
, &p
, &fd
);
323 return parse_env_file_fdv(fd
, p
, ap
);
326 int parse_extension_release_at_sentinel(
328 ImageClass image_class
,
329 bool relax_extension_release_check
,
330 const char *extension
,
336 assert(rfd
>= 0 || rfd
== AT_FDCWD
);
338 va_start(ap
, extension
);
339 r
= parse_extension_release_atv(rfd
, image_class
, extension
, relax_extension_release_check
, ap
);
344 int parse_extension_release_sentinel(
346 ImageClass image_class
,
347 bool relax_extension_release_check
,
348 const char *extension
,
351 _cleanup_close_
int rfd
= -EBADF
;
355 rfd
= open(empty_to_root(root
), O_CLOEXEC
| O_DIRECTORY
| O_PATH
);
359 va_start(ap
, extension
);
360 r
= parse_extension_release_atv(rfd
, image_class
, extension
, relax_extension_release_check
, ap
);
365 int load_os_release_pairs_with_prefix(const char *root
, const char *prefix
, char ***ret
) {
366 _cleanup_strv_free_
char **os_release_pairs
= NULL
, **os_release_pairs_prefixed
= NULL
;
369 r
= load_os_release_pairs(root
, &os_release_pairs
);
373 STRV_FOREACH_PAIR(p
, q
, os_release_pairs
) {
376 /* We strictly return only the four main ID fields and ignore the rest */
377 if (!STR_IN_SET(*p
, "ID", "VERSION_ID", "BUILD_ID", "VARIANT_ID"))
381 line
= strjoin(prefix
, *p
, "=", *q
);
384 r
= strv_consume(&os_release_pairs_prefixed
, line
);
389 *ret
= TAKE_PTR(os_release_pairs_prefixed
);
394 int load_extension_release_pairs(const char *root
, ImageClass image_class
, const char *extension
, bool relax_extension_release_check
, char ***ret
) {
395 _cleanup_close_
int fd
= -EBADF
;
396 _cleanup_free_
char *p
= NULL
;
399 r
= open_extension_release(root
, image_class
, extension
, relax_extension_release_check
, &p
, &fd
);
403 return load_env_file_pairs_fd(fd
, p
, ret
);
406 int os_release_support_ended(const char *support_end
, bool quiet
, usec_t
*ret_eol
) {
407 _cleanup_free_
char *_support_end_alloc
= NULL
;
411 /* If the caller has the variably handy, they can pass it in. If not, we'll read it
414 r
= parse_os_release(NULL
,
415 "SUPPORT_END", &_support_end_alloc
);
416 if (r
< 0 && r
!= -ENOENT
)
417 return log_full_errno(quiet
? LOG_DEBUG
: LOG_WARNING
, r
,
418 "Failed to read os-release file, ignoring: %m");
420 support_end
= _support_end_alloc
;
423 if (isempty(support_end
)) /* An empty string is a explicit way to say "no EOL exists" */
424 return false; /* no end date defined */
427 const char *k
= strptime(support_end
, "%Y-%m-%d", &tm
);
429 return log_full_errno(quiet
? LOG_DEBUG
: LOG_WARNING
, SYNTHETIC_ERRNO(EINVAL
),
430 "Failed to parse SUPPORT_END= in os-release file, ignoring: %m");
432 time_t eol
= timegm(&tm
);
433 if (eol
== (time_t) -1)
434 return log_full_errno(quiet
? LOG_DEBUG
: LOG_WARNING
, SYNTHETIC_ERRNO(EINVAL
),
435 "Failed to convert SUPPORT_END= in os-release file, ignoring: %m");
438 *ret_eol
= eol
* USEC_PER_SEC
;
440 return DIV_ROUND_UP(now(CLOCK_REALTIME
), USEC_PER_SEC
) > (usec_t
) eol
;
443 const char *os_release_pretty_name(const char *pretty_name
, const char *name
) {
444 /* Distills a "pretty" name to show from os-release data. First argument is supposed to be the
445 * PRETTY_NAME= field, the second one the NAME= field. This function is trivial, of course, and
446 * exists mostly to ensure we use the same logic wherever possible. */
448 return empty_to_null(pretty_name
) ?:
449 empty_to_null(name
) ?: "Linux";