summaryrefslogtreecommitdiffstats
path: root/src/shared/extension-release.c
blob: 2da8e7ea94bec49d261fac8891d42da32d8a7b7b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "alloc-util.h"
#include "architecture.h"
#include "env-util.h"
#include "extension-release.h"
#include "log.h"
#include "os-util.h"
#include "strv.h"

int 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) {

        const char *extension_release_id = NULL, *extension_release_sysext_level = NULL, *extension_architecture = NULL;

        assert(name);
        assert(!isempty(host_os_release_id));

        /* Now that we can look into the extension image, let's see if the OS version is compatible */
        if (strv_isempty(extension_release)) {
                log_debug("Extension '%s' carries no extension-release data, ignoring extension.", name);
                return 0;
        }

        if (host_sysext_scope) {
                _cleanup_strv_free_ char **extension_sysext_scope_list = NULL;
                const char *extension_sysext_scope;
                bool valid;

                extension_sysext_scope = strv_env_pairs_get(extension_release, "SYSEXT_SCOPE");
                if (extension_sysext_scope) {
                        extension_sysext_scope_list = strv_split(extension_sysext_scope, WHITESPACE);
                        if (!extension_sysext_scope_list)
                                return -ENOMEM;
                }

                /* by default extension are good for attachment in portable service and on the system */
                valid = strv_contains(
                                extension_sysext_scope_list ?: STRV_MAKE("system", "portable"),
                                host_sysext_scope);
                if (!valid) {
                        log_debug("Extension '%s' is not suitable for scope %s, ignoring extension.", name, host_sysext_scope);
                        return 0;
                }
        }

        /* When the architecture field is present and not '_any' it must match the host - for now just look at uname but in
         * the future we could check if the kernel also supports 32 bit or binfmt has a translator set up for the architecture */
        extension_architecture = strv_env_pairs_get(extension_release, "ARCHITECTURE");
        if (!isempty(extension_architecture) && !streq(extension_architecture, "_any") &&
            !streq(architecture_to_string(uname_architecture()), extension_architecture)) {
                log_debug("Extension '%s' is for architecture '%s', but deployed on top of '%s'.",
                          name, extension_architecture, architecture_to_string(uname_architecture()));
                return 0;
        }

        extension_release_id = strv_env_pairs_get(extension_release, "ID");
        if (isempty(extension_release_id)) {
                log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s' or be '_any'",
                          name, host_os_release_id);
                return 0;
        }

        /* A sysext with no host OS dependency (static binaries or scripts) can match
         * '_any' host OS, and VERSION_ID or SYSEXT_LEVEL are not required anywhere */
        if (streq(extension_release_id, "_any")) {
                log_debug("Extension '%s' matches '_any' OS.", name);
                return 1;
        }

        if (!streq(host_os_release_id, extension_release_id)) {
                log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
                          name, extension_release_id, host_os_release_id);
                return 0;
        }

        /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
        if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
                log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
                return 1;
        }

        /* If the extension has a sysext API level declared, then it must match the host API
         * level. Otherwise, compare OS version as a whole */
        extension_release_sysext_level = strv_env_pairs_get(extension_release, "SYSEXT_LEVEL");
        if (!isempty(host_os_release_sysext_level) && !isempty(extension_release_sysext_level)) {
                if (!streq_ptr(host_os_release_sysext_level, extension_release_sysext_level)) {
                        log_debug("Extension '%s' is for sysext API level '%s', but running on sysext API level '%s'",
                                  name, strna(extension_release_sysext_level), strna(host_os_release_sysext_level));
                        return 0;
                }
        } else if (!isempty(host_os_release_version_id)) {
                const char *extension_release_version_id;

                extension_release_version_id = strv_env_pairs_get(extension_release, "VERSION_ID");
                if (isempty(extension_release_version_id)) {
                        log_debug("Extension '%s' does not contain VERSION_ID in extension-release but requested to match '%s'",
                                  name, strna(host_os_release_version_id));
                        return 0;
                }

                if (!streq_ptr(host_os_release_version_id, extension_release_version_id)) {
                        log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
                                  name, strna(extension_release_version_id), strna(host_os_release_version_id));
                        return 0;
                }
        } else if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
                /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
                log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
                return 1;
        }

        log_debug("Version info of extension '%s' matches host.", name);
        return 1;
}

int parse_env_extension_hierarchies(char ***ret_hierarchies) {
        _cleanup_free_ char **l = NULL;
        int r;

        r = getenv_path_list("SYSTEMD_SYSEXT_HIERARCHIES", &l);
        if (r == -ENXIO) {
                /* Default when unset */
                l = strv_new("/usr", "/opt");
                if (!l)
                        return -ENOMEM;
        } else if (r < 0)
                return r;

        *ret_hierarchies = TAKE_PTR(l);
        return 0;
}