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