1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "alloc-util.h"
4 #include "env-util.h"
5 #include "extension-release.h"
6 #include "log.h"
7 #include "os-util.h"
8 #include "strv.h"
9
extension_release_validate(const char * name,const char * host_os_release_id,const char * host_os_release_version_id,const char * host_os_release_sysext_level,const char * host_sysext_scope,char ** extension_release)10 int extension_release_validate(
11 const char *name,
12 const char *host_os_release_id,
13 const char *host_os_release_version_id,
14 const char *host_os_release_sysext_level,
15 const char *host_sysext_scope,
16 char **extension_release) {
17
18 const char *extension_release_id = NULL, *extension_release_sysext_level = NULL;
19
20 assert(name);
21 assert(!isempty(host_os_release_id));
22
23 /* Now that we can look into the extension image, let's see if the OS version is compatible */
24 if (strv_isempty(extension_release)) {
25 log_debug("Extension '%s' carries no extension-release data, ignoring extension.", name);
26 return 0;
27 }
28
29 if (host_sysext_scope) {
30 _cleanup_strv_free_ char **extension_sysext_scope_list = NULL;
31 const char *extension_sysext_scope;
32 bool valid;
33
34 extension_sysext_scope = strv_env_pairs_get(extension_release, "SYSEXT_SCOPE");
35 if (extension_sysext_scope) {
36 extension_sysext_scope_list = strv_split(extension_sysext_scope, WHITESPACE);
37 if (!extension_sysext_scope_list)
38 return -ENOMEM;
39 }
40
41 /* by default extension are good for attachment in portable service and on the system */
42 valid = strv_contains(
43 extension_sysext_scope_list ?: STRV_MAKE("system", "portable"),
44 host_sysext_scope);
45 if (!valid) {
46 log_debug("Extension '%s' is not suitable for scope %s, ignoring extension.", name, host_sysext_scope);
47 return 0;
48 }
49 }
50
51 extension_release_id = strv_env_pairs_get(extension_release, "ID");
52 if (isempty(extension_release_id)) {
53 log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s'",
54 name, host_os_release_id);
55 return 0;
56 }
57
58 if (!streq(host_os_release_id, extension_release_id)) {
59 log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
60 name, extension_release_id, host_os_release_id);
61 return 0;
62 }
63
64 /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
65 if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
66 log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
67 return 1;
68 }
69
70 /* If the extension has a sysext API level declared, then it must match the host API
71 * level. Otherwise, compare OS version as a whole */
72 extension_release_sysext_level = strv_env_pairs_get(extension_release, "SYSEXT_LEVEL");
73 if (!isempty(host_os_release_sysext_level) && !isempty(extension_release_sysext_level)) {
74 if (!streq_ptr(host_os_release_sysext_level, extension_release_sysext_level)) {
75 log_debug("Extension '%s' is for sysext API level '%s', but running on sysext API level '%s'",
76 name, strna(extension_release_sysext_level), strna(host_os_release_sysext_level));
77 return 0;
78 }
79 } else if (!isempty(host_os_release_version_id)) {
80 const char *extension_release_version_id;
81
82 extension_release_version_id = strv_env_pairs_get(extension_release, "VERSION_ID");
83 if (isempty(extension_release_version_id)) {
84 log_debug("Extension '%s' does not contain VERSION_ID in extension-release but requested to match '%s'",
85 name, strna(host_os_release_version_id));
86 return 0;
87 }
88
89 if (!streq_ptr(host_os_release_version_id, extension_release_version_id)) {
90 log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
91 name, strna(extension_release_version_id), strna(host_os_release_version_id));
92 return 0;
93 }
94 } else if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
95 /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
96 log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
97 return 1;
98 }
99
100 log_debug("Version info of extension '%s' matches host.", name);
101 return 1;
102 }
103
parse_env_extension_hierarchies(char *** ret_hierarchies)104 int parse_env_extension_hierarchies(char ***ret_hierarchies) {
105 _cleanup_free_ char **l = NULL;
106 int r;
107
108 r = getenv_path_list("SYSTEMD_SYSEXT_HIERARCHIES", &l);
109 if (r == -ENXIO) {
110 /* Default when unset */
111 l = strv_new("/usr", "/opt");
112 if (!l)
113 return -ENOMEM;
114 } else if (r < 0)
115 return r;
116
117 *ret_hierarchies = TAKE_PTR(l);
118 return 0;
119 }
120