1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "alloc-util.h"
4 #include "architecture.h"
7 #include "extension-util.h"
12 int extension_release_validate(
14 const char *host_os_release_id
,
15 const char *host_os_release_version_id
,
16 const char *host_os_extension_release_level
,
17 const char *host_extension_scope
,
18 char **extension_release
,
19 ImageClass image_class
) {
21 const char *extension_release_id
= NULL
, *extension_release_level
= NULL
, *extension_architecture
= NULL
;
22 const char *extension_level
= image_class
== IMAGE_CONFEXT
? "CONFEXT_LEVEL" : "SYSEXT_LEVEL";
23 const char *extension_scope
= image_class
== IMAGE_CONFEXT
? "CONFEXT_SCOPE" : "SYSEXT_SCOPE";
26 assert(!isempty(host_os_release_id
));
28 /* Now that we can look into the extension/confext image, let's see if the OS version is compatible */
29 if (strv_isempty(extension_release
)) {
30 log_debug("Extension '%s' carries no release data, ignoring.", name
);
34 if (host_extension_scope
) {
35 _cleanup_strv_free_
char **scope_list
= NULL
;
39 scope
= strv_env_pairs_get(extension_release
, extension_scope
);
41 scope_list
= strv_split(scope
, WHITESPACE
);
46 /* By default extension are good for attachment in portable service and on the system */
47 valid
= strv_contains(
48 scope_list
?: STRV_MAKE("system", "portable"),
49 host_extension_scope
);
51 log_debug("Extension '%s' is not suitable for scope %s, ignoring.", name
, host_extension_scope
);
56 /* When the architecture field is present and not '_any' it must match the host - for now just look at uname but in
57 * the future we could check if the kernel also supports 32 bit or binfmt has a translator set up for the architecture */
58 extension_architecture
= strv_env_pairs_get(extension_release
, "ARCHITECTURE");
59 if (!isempty(extension_architecture
) && !streq(extension_architecture
, "_any") &&
60 !streq(architecture_to_string(uname_architecture()), extension_architecture
)) {
61 log_debug("Extension '%s' is for architecture '%s', but deployed on top of '%s'.",
62 name
, extension_architecture
, architecture_to_string(uname_architecture()));
66 extension_release_id
= strv_env_pairs_get(extension_release
, "ID");
67 if (isempty(extension_release_id
)) {
68 log_debug("Extension '%s' does not contain ID in release file but requested to match '%s' or be '_any'",
69 name
, host_os_release_id
);
73 /* A sysext(or confext) with no host OS dependency (static binaries or scripts) can match
74 * '_any' host OS, and VERSION_ID or SYSEXT_LEVEL(or CONFEXT_LEVEL) are not required anywhere */
75 if (streq(extension_release_id
, "_any")) {
76 log_debug("Extension '%s' matches '_any' OS.", name
);
80 if (!streq(host_os_release_id
, extension_release_id
)) {
81 log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
82 name
, extension_release_id
, host_os_release_id
);
86 /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
87 if (isempty(host_os_release_version_id
) && isempty(host_os_extension_release_level
)) {
88 log_debug("No version info on the host (rolling release?), but ID in %s matched.", name
);
92 /* If the extension has a sysext API level declared, then it must match the host API
93 * level. Otherwise, compare OS version as a whole */
94 extension_release_level
= strv_env_pairs_get(extension_release
, extension_level
);
95 if (!isempty(host_os_extension_release_level
) && !isempty(extension_release_level
)) {
96 if (!streq_ptr(host_os_extension_release_level
, extension_release_level
)) {
97 log_debug("Extension '%s' is for API level '%s', but running on API level '%s'",
98 name
, strna(extension_release_level
), strna(host_os_extension_release_level
));
101 } else if (!isempty(host_os_release_version_id
)) {
102 const char *extension_release_version_id
;
104 extension_release_version_id
= strv_env_pairs_get(extension_release
, "VERSION_ID");
105 if (isempty(extension_release_version_id
)) {
106 log_debug("Extension '%s' does not contain VERSION_ID in release file but requested to match '%s'",
107 name
, strna(host_os_release_version_id
));
111 if (!streq_ptr(host_os_release_version_id
, extension_release_version_id
)) {
112 log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
113 name
, strna(extension_release_version_id
), strna(host_os_release_version_id
));
116 } else if (isempty(host_os_release_version_id
) && isempty(host_os_extension_release_level
)) {
117 /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
118 log_debug("No version info on the host (rolling release?), but ID in %s matched.", name
);
122 log_debug("Version info of extension '%s' matches host.", name
);
126 int parse_env_extension_hierarchies(char ***ret_hierarchies
, const char *hierarchy_env
) {
127 _cleanup_free_
char **l
= NULL
;
130 assert(hierarchy_env
);
131 r
= getenv_path_list(hierarchy_env
, &l
);
133 if (streq(hierarchy_env
, "SYSTEMD_CONFEXT_HIERARCHIES"))
134 /* Default for confext when unset */
135 l
= strv_new("/etc");
136 else if (streq(hierarchy_env
, "SYSTEMD_SYSEXT_HIERARCHIES"))
137 /* Default for sysext when unset */
138 l
= strv_new("/usr", "/opt");
139 else if (streq(hierarchy_env
, "SYSTEMD_SYSEXT_AND_CONFEXT_HIERARCHIES"))
140 /* Combined sysext and confext directories */
141 l
= strv_new("/usr", "/opt", "/etc");
147 *ret_hierarchies
= TAKE_PTR(l
);
151 int extension_has_forbidden_content(const char *root
) {
154 /* Insist that extension images do not overwrite the underlying OS release file (it's fine if
155 * they place one in /etc/os-release, i.e. where things don't matter, as they aren't
157 r
= chase("/usr/lib/os-release", root
, CHASE_PREFIX_ROOT
, NULL
, NULL
);
159 log_debug("Extension contains '/usr/lib/os-release', which is not allowed, refusing.");
162 if (r
< 0 && r
!= -ENOENT
)
163 return log_debug_errno(r
, "Failed to determine whether '/usr/lib/os-release' exists in the extension: %m");