summaryrefslogtreecommitdiff
path: root/src/core/kmod-setup.c
blob: c09e17f568bac85a981511deef0f32dfe78393a1 (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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <unistd.h>

#include "alloc-util.h"
#include "bus-util.h"
#include "capability-util.h"
#include "efi-api.h"
#include "fileio.h"
#include "kmod-setup.h"
#include "macro.h"
#include "recurse-dir.h"
#include "string-util.h"
#include "strv.h"
#include "virt.h"

#if HAVE_KMOD
#include "module-util.h"

static void systemd_kmod_log(
                void *data,
                int priority,
                const char *file, int line,
                const char *fn,
                const char *format,
                va_list args) {

        /* library logging is enabled at debug only */
        DISABLE_WARNING_FORMAT_NONLITERAL;
        log_internalv(LOG_DEBUG, 0, file, line, fn, format, args);
        REENABLE_WARNING;
}

static int match_modalias_recurse_dir_cb(
                RecurseDirEvent event,
                const char *path,
                int dir_fd,
                int inode_fd,
                const struct dirent *de,
                const struct statx *sx,
                void *userdata) {

        _cleanup_free_ char *alias = NULL;
        char **modaliases = ASSERT_PTR(userdata);
        int r;

        if (event != RECURSE_DIR_ENTRY)
                return RECURSE_DIR_CONTINUE;

        if (de->d_type != DT_REG)
                return RECURSE_DIR_CONTINUE;

        if (!streq(de->d_name, "modalias"))
                return RECURSE_DIR_CONTINUE;

        r = read_one_line_file(path, &alias);
        if (r < 0) {
                log_debug_errno(r, "Failed to read %s, ignoring: %m", path);
                return RECURSE_DIR_LEAVE_DIRECTORY;
        }

        if (startswith_strv(alias, modaliases))
                return 1;

        return RECURSE_DIR_LEAVE_DIRECTORY;
}

static bool has_virtio_rng(void) {
        int r;

        /* Directory traversal might be slow, hence let's do a cheap check first if it's even worth it */
        if (detect_vm() == VIRTUALIZATION_NONE)
                return false;

        r = recurse_dir_at(
                        AT_FDCWD,
                        "/sys/devices/pci0000:00",
                        /* statx_mask= */ 0,
                        /* n_depth_max= */ 2,
                        RECURSE_DIR_ENSURE_TYPE,
                        match_modalias_recurse_dir_cb,
                        STRV_MAKE("pci:v00001AF4d00001005", "pci:v00001AF4d00001044"));
        if (r < 0)
                log_debug_errno(r, "Failed to determine whether host has virtio-rng device, ignoring: %m");

        return r > 0;
}

static bool has_virtio_console(void) {
        int r;

        /* Directory traversal might be slow, hence let's do a cheap check first if it's even worth it */
        if (detect_vm() == VIRTUALIZATION_NONE)
                return false;

        r = recurse_dir_at(
                        AT_FDCWD,
                        "/sys/devices/pci0000:00",
                        /* statx_mask= */ 0,
                        /* n_depth_max= */ 3,
                        RECURSE_DIR_ENSURE_TYPE,
                        match_modalias_recurse_dir_cb,
                        STRV_MAKE("virtio:d00000003v", "virtio:d0000000Bv"));
        if (r < 0)
                log_debug_errno(r, "Failed to determine whether host has virtio-console device, ignoring: %m");

        return r > 0;
}

static bool in_qemu(void) {
        return IN_SET(detect_vm(), VIRTUALIZATION_KVM, VIRTUALIZATION_QEMU);
}
#endif

int kmod_setup(void) {
#if HAVE_KMOD

        static const struct {
                const char *module;
                const char *path;
                bool warn_if_unavailable:1;
                bool warn_if_module:1;
                bool (*condition_fn)(void);
        } kmod_table[] = {
                /* This one we need to load explicitly, since auto-loading on use doesn't work
                 * before udev created the ghost device nodes, and we need it earlier than that. */
                { "autofs4",         "/sys/class/misc/autofs",    true,  false, NULL               },

                /* This one we need to load explicitly, since auto-loading of IPv6 is not done when
                 * we try to configure ::1 on the loopback device. */
                { "ipv6",            "/sys/module/ipv6",          false, true,  NULL               },

                /* This should never be a module */
                { "unix",            "/proc/net/unix",            true,  true,  NULL               },

#if HAVE_LIBIPTC
                /* netfilter is needed by networkd, nspawn among others, and cannot be autoloaded */
                { "ip_tables",       "/proc/net/ip_tables_names", false, false, NULL               },
#endif
                /* virtio_rng would be loaded by udev later, but real entropy might be needed very early */
                { "virtio_rng",      NULL,                        false, false, has_virtio_rng     },

                /* we want early logging to hvc consoles if possible, and make sure systemd-getty-generator
                 * can rely on all consoles being probed already.*/
                { "virtio_console",  NULL,                        false, false, has_virtio_console },

                /* qemu_fw_cfg would be loaded by udev later, but we want to import credentials from it super early */
                { "qemu_fw_cfg",     "/sys/firmware/qemu_fw_cfg", false, false, in_qemu            },

                /* dmi-sysfs is needed to import credentials from it super early */
                { "dmi-sysfs",       "/sys/firmware/dmi/entries", false, false, NULL               },

#if HAVE_TPM2
                /* Make sure the tpm subsystem is available which ConditionSecurity=tpm2 depends on. */
                { "tpm",             "/sys/class/tpmrm",          false, false, efi_has_tpm2       },
#endif
        };
        _cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL;
        unsigned i;

        if (have_effective_cap(CAP_SYS_MODULE) <= 0)
                return 0;

        for (i = 0; i < ELEMENTSOF(kmod_table); i++) {
                if (kmod_table[i].path && access(kmod_table[i].path, F_OK) >= 0)
                        continue;

                if (kmod_table[i].condition_fn && !kmod_table[i].condition_fn())
                        continue;

                if (kmod_table[i].warn_if_module)
                        log_debug("Your kernel apparently lacks built-in %s support. Might be "
                                  "a good idea to compile it in. We'll now try to work around "
                                  "this by loading the module...", kmod_table[i].module);

                if (!ctx) {
                        ctx = kmod_new(NULL, NULL);
                        if (!ctx)
                                return log_oom();

                        kmod_set_log_fn(ctx, systemd_kmod_log, NULL);
                        kmod_load_resources(ctx);
                }

                (void) module_load_and_warn(ctx, kmod_table[i].module, kmod_table[i].warn_if_unavailable);
        }

#endif
        return 0;
}