]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/extension-util.c
mkosi: update arch commit reference
[thirdparty/systemd.git] / src / shared / extension-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "alloc-util.h"
4 #include "architecture.h"
5 #include "chase.h"
6 #include "env-util.h"
7 #include "extension-util.h"
8 #include "log.h"
9 #include "os-util.h"
10 #include "strv.h"
11
12 int extension_release_validate(
13 const char *name,
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) {
20
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";
24
25 assert(name);
26 assert(!isempty(host_os_release_id));
27
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);
31 return 0;
32 }
33
34 if (host_extension_scope) {
35 _cleanup_strv_free_ char **scope_list = NULL;
36 const char *scope;
37 bool valid;
38
39 scope = strv_env_pairs_get(extension_release, extension_scope);
40 if (scope) {
41 scope_list = strv_split(scope, WHITESPACE);
42 if (!scope_list)
43 return -ENOMEM;
44 }
45
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);
50 if (!valid) {
51 log_debug("Extension '%s' is not suitable for scope %s, ignoring.", name, host_extension_scope);
52 return 0;
53 }
54 }
55
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()));
63 return 0;
64 }
65
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);
70 return 0;
71 }
72
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);
77 return 1;
78 }
79
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);
83 return 0;
84 }
85
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);
89 return 1;
90 }
91
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));
99 return 0;
100 }
101 } else if (!isempty(host_os_release_version_id)) {
102 const char *extension_release_version_id;
103
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));
108 return 0;
109 }
110
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));
114 return 0;
115 }
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);
119 return 1;
120 }
121
122 log_debug("Version info of extension '%s' matches host.", name);
123 return 1;
124 }
125
126 int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarchy_env) {
127 _cleanup_free_ char **l = NULL;
128 int r;
129
130 assert(hierarchy_env);
131 r = getenv_path_list(hierarchy_env, &l);
132 if (r == -ENXIO) {
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");
142 else
143 return -ENXIO;
144 } else if (r < 0)
145 return r;
146
147 *ret_hierarchies = TAKE_PTR(l);
148 return 0;
149 }
150
151 int extension_has_forbidden_content(const char *root) {
152 int r;
153
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
156 * merged.) */
157 r = chase("/usr/lib/os-release", root, CHASE_PREFIX_ROOT, NULL, NULL);
158 if (r > 0) {
159 log_debug("Extension contains '/usr/lib/os-release', which is not allowed, refusing.");
160 return 1;
161 }
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");
164
165 return 0;
166 }