• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 25893429527

14 May 2026 09:08PM UTC coverage: 72.364% (-0.2%) from 72.584%
25893429527

push

github

bluca
ci: switch SUSE mkosi mirror to cdn.o.o

The cdn mirror is preferred by SUSE for clouds/CIs. There have been issues with some
mirrors, which fail to download from GHA quite often lately, so hopefully this will
make it reliable again.

328159 of 453485 relevant lines covered (72.36%)

1405869.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.32
/src/modules-load/modules-load.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <limits.h>
4
#include <pthread.h>
5
#include <signal.h>
6
#include <sys/socket.h>
7
#include <unistd.h>
8

9
#include "alloc-util.h"
10
#include "assert.h"
11
#include "build.h"
12
#include "conf-files.h"
13
#include "constants.h"
14
#include "cpu-set-util.h"
15
#include "errno-util.h"
16
#include "fd-util.h"
17
#include "fileio.h"
18
#include "format-table.h"
19
#include "log.h"
20
#include "macro.h"
21
#include "main-func.h"
22
#include "module-util.h"
23
#include "options.h"
24
#include "ordered-set.h"
25
#include "parse-util.h"
26
#include "pretty-print.h"
27
#include "proc-cmdline.h"
28
#include "string-util.h"
29
#include "strv.h"
30

31
#define MODULE_NAME_MAX_LEN (4096UL)
32

33
static char **arg_proc_cmdline_modules = NULL;
34
static const char conf_file_dirs[] = CONF_PATHS_NULSTR("modules-load.d");
35

36
STATIC_DESTRUCTOR_REGISTER(arg_proc_cmdline_modules, strv_freep);
75✔
37

38
static int modules_list_append_suffix(OrderedSet **module_set, char *modp) {
90✔
39
        /* take possession of string no matter what */
40
        _cleanup_free_ char *mod = modp;
90✔
41
        int r;
90✔
42

43
        assert(module_set);
90✔
44
        assert(mod);
90✔
45

46
        if (strlen(mod) > MODULE_NAME_MAX_LEN)
90✔
47
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Module name max length exceeded (%lu): %s",
×
48
                                       MODULE_NAME_MAX_LEN, mod);
49

50
        /* kmod will do it anyway later, so replace now dashes with
51
           underscores to detect duplications due to different spelling. */
52
        string_replace_char(mod, '-', '_');
90✔
53

54
        r = ordered_set_ensure_put(module_set, &string_hash_ops_free, mod);
90✔
55
        if (r == -EEXIST)
90✔
56
                return 0;
57
        if (r < 0)
82✔
58
                return log_error_errno(r, "Failed to enqueue module '%s': %m", mod);
×
59

60
        TAKE_PTR(mod);
61

62
        return 0;
63
}
64

65
static int modules_list_append_dup(OrderedSet **module_set, const char *module) {
2✔
66
        _cleanup_free_ char *m = NULL;
2✔
67

68
        assert(module);
2✔
69

70
        if (strdup_to(&m, module) < 0)
2✔
71
                return log_oom();
×
72

73
        return modules_list_append_suffix(module_set, TAKE_PTR(m));
2✔
74
}
75

76
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
3,149✔
77
        int r;
3,149✔
78

79
        if (proc_cmdline_key_streq(key, "modules_load")) {
3,149✔
80

81
                if (proc_cmdline_value_missing(key, value))
4✔
82
                        return 0;
83

84
                r = strv_split_and_extend(&arg_proc_cmdline_modules, value, ",", /* filter_duplicates= */ true);
4✔
85
                if (r < 0)
4✔
86
                        return log_error_errno(r, "Failed to parse modules_load= kernel command line option: %m");
×
87
        }
88

89
        return 0;
90
}
91

92
static int apply_file(FILE *f, const char *filename, OrderedSet **module_set) {
73✔
93
        int ret = 0, r;
73✔
94

95
        assert(f);
73✔
96

97
        log_debug("apply: %s", filename);
73✔
98
        for (;;) {
794✔
99
                _cleanup_free_ char *line = NULL;
721✔
100

101
                r = read_stripped_line(f, LONG_LINE_MAX, &line);
794✔
102
                if (r < 0)
794✔
103
                        return log_error_errno(r, "Failed to read file '%s': %m", filename);
×
104
                if (r == 0)
794✔
105
                        break;
106

107
                if (isempty(line))
721✔
108
                        continue;
2✔
109
                if (strchr(COMMENTS, *line))
719✔
110
                        continue;
631✔
111

112
                RET_GATHER(ret, modules_list_append_suffix(module_set, TAKE_PTR(line)));
88✔
113
        }
114

115
        return ret;
73✔
116
}
117

118
static int apply_file_from_path(const char *path, OrderedSet **module_set) {
2✔
119
        _cleanup_fclose_ FILE *f = NULL;
2✔
120
        _cleanup_free_ char *pp = NULL;
2✔
121
        int r;
2✔
122

123
        assert(path);
2✔
124

125
        r = search_and_fopen_nulstr(path, "re", NULL, conf_file_dirs, &f, &pp);
2✔
126
        if (r < 0)
2✔
127
                return log_error_errno(r, "Failed to open %s: %m", path);
1✔
128

129
        return apply_file(f, pp, module_set);
1✔
130
}
131

132
static int apply_conf_file(ConfFile *c, OrderedSet **module_set) {
72✔
133
        _cleanup_fclose_ FILE *f = NULL;
72✔
134

135
        f = fopen(FORMAT_PROC_FD_PATH(c->fd), "re");
72✔
136
        if (!f) {
72✔
137
                if (errno == ENOENT)
×
138
                        return 0;
139

140
                return log_error_errno(errno, "Failed to open %s: %m", c->original_path);
×
141
        }
142

143
        return apply_file(f, c->original_path, module_set);
72✔
144
}
145

146
static int do_direct_probe(OrderedSet *module_set) {
72✔
147
        _cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL;
72✔
148
        char *module;
72✔
149
        int ret = 0, r;
72✔
150

151
        r = module_setup_context(&ctx);
72✔
152
        if (r < 0)
72✔
153
                return log_error_errno(r, "Failed to initialize libkmod context: %m");
×
154

155
        ORDERED_SET_FOREACH(module, module_set) {
154✔
156
                r = module_load_and_warn(ctx, module, /* verbose= */ true);
82✔
157
                if (r != -ENOENT)
82✔
158
                        RET_GATHER(ret, r);
74✔
159
        }
160

161
        return ret;
72✔
162
}
163

164
static int enqueue_module_to_load(int sock, const char *module) {
×
165
        ssize_t bytes;
×
166

167
        assert(sock >= 0);
×
168
        assert(module);
×
169

170
        bytes = send(sock, module, strlen(module), /* flags= */ 0);
×
171
        if (bytes < 0)
×
172
                return log_error_errno(errno, "Failed to send '%s' to thread pool: %m", module);
×
173

174
        return 0;
175
}
176

177
static int dequeue_module_to_load(int sock, char *buffer, size_t buffer_len) {
×
178
        ssize_t bytes;
×
179

180
        assert(sock >= 0);
×
181
        assert(buffer);
×
182

183
        /* Dequeue one module to be loaded from the socket pair. In case no more
184
         * modules are present, recv() will return 0. */
185
        bytes = recv(sock, buffer, buffer_len, /* flags= */ 0);
×
186
        if (bytes == 0)
×
187
                return 0;
188
        if (bytes < 0)
×
189
                return negative_errno();
×
190
        if ((size_t)bytes == buffer_len)
×
191
                return -E2BIG;
192

193
        buffer[bytes] = '\0';
×
194

195
        return 1;
×
196
}
197

198
static int run_prober(int sock) {
×
199
        _cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL;
×
200
        char buffer[MODULE_NAME_MAX_LEN + 1];
×
201
        int ret = 0, r;
×
202

203
        r = module_setup_context(&ctx);
×
204
        if (r < 0)
×
205
                return log_error_errno(r, "Failed to initialize libkmod context: %m");
×
206

207
        for (;;) {
×
208
                r = dequeue_module_to_load(sock, buffer, sizeof(buffer));
×
209
                if (ERRNO_IS_NEG_TRANSIENT(r))
×
210
                        continue;
×
211
                if (r == -E2BIG) {
×
212
                        log_warning_errno(SYNTHETIC_ERRNO(E2BIG), "Dequeued module name too long, proceeding anyway");
×
213
                        continue;
×
214
                }
215
                if (r < 0)
×
216
                        return log_error_errno(r, "Failed to receive from module queue: %m");
×
217
                if (r == 0) {
×
218
                        log_debug("No more queued modules, terminate thread");
×
219
                        break;
220
                }
221

222
                r = module_load_and_warn(ctx, buffer, /* verbose= */ true);
×
223
                if (r != -ENOENT)
×
224
                        RET_GATHER(ret, r);
×
225
        }
226

227
        return ret;
228
}
229

230
static void *prober_thread(void *arg) {
×
231
        int sock = PTR_TO_FD(arg);
×
232
        return INT_TO_PTR(run_prober(sock));
×
233
}
234

235
static int create_worker_threads(size_t n_threads, void *arg, pthread_t **ret_threads, size_t *ret_n_threads) {
×
236
        _cleanup_free_ pthread_t *new_threads = NULL;
×
237
        size_t created_threads;
×
238
        sigset_t ss, saved_ss;
×
239
        int r;
×
240

241
        assert(ret_threads);
×
242
        assert(ret_n_threads);
×
243

244
        if (n_threads == 0) {
×
245
                *ret_threads = NULL;
×
246
                *ret_n_threads = 0;
×
247
                return 0;
×
248
        }
249

250
        /* Create worker threads with masked signals */
251
        new_threads = new(pthread_t, n_threads);
×
252
        if (!new_threads)
×
253
                return log_oom();
×
254

255
        /* No signals in worker threads. */
256
        assert_se(sigfillset(&ss) >= 0);
×
257
        r = pthread_sigmask(SIG_BLOCK, &ss, &saved_ss);
×
258
        if (r != 0)
×
259
                return log_error_errno(r, "Failed to mask signals for workers: %m");
×
260

261
        for (created_threads = 0; created_threads < n_threads; ++created_threads) {
×
262
                r = pthread_create(&new_threads[created_threads], /* attr= */ NULL, prober_thread, arg);
×
263
                if (r != 0) {
×
264
                        log_error_errno(r, "Failed to create worker thread %zu: %m", created_threads);
×
265
                        break;
266
                }
267
        }
268

269
        /* Restore the signal mask */
270
        r = pthread_sigmask(SIG_SETMASK, &saved_ss, NULL);
×
271
        if (r != 0)
×
272
                log_warning_errno(r, "Failed to restore signal mask, ignoring: %m");
×
273

274
        *ret_threads = TAKE_PTR(new_threads);
×
275
        *ret_n_threads = created_threads;
×
276

277
        return 0;
×
278
}
279

280
static int destroy_worker_threads(pthread_t **threads, size_t n_threads) {
×
281
        int ret = 0, r;
×
282

283
        assert(threads);
×
284
        assert(n_threads == 0 || *threads);
×
285

286
        for (size_t i = 0; i < n_threads; ++i) {
×
287
                void *p;
×
288
                r = pthread_join((*threads)[i], &p);
×
289
                if (r != 0)
×
290
                        RET_GATHER(ret, log_warning_errno(r, "Failed to join worker thread, proceeding anyway: %m"));
×
291
                else
292
                        RET_GATHER(ret, PTR_TO_INT(p));
×
293
        }
294

295
        *threads = mfree(*threads);
×
296

297
        return ret;
×
298
}
299

300
/* Determine number of workers, either from env or from online CPUs */
301
static unsigned determine_num_worker_threads(unsigned n_modules) {
72✔
302
        unsigned n_threads = UINT_MAX;
72✔
303
        int r;
72✔
304

305
        if (n_modules == 0)
72✔
306
                return 0;
72✔
307

308
        const char *e = secure_getenv("SYSTEMD_MODULES_LOAD_NUM_THREADS");
71✔
309
        if (e) {
71✔
310
                r = safe_atou(e, &n_threads);
×
311
                if (r < 0)
×
312
                        log_debug("Invalid value in $SYSTEMD_MODULES_LOAD_NUM_THREADS, ignoring: %s", e);
×
313
        }
314

315
        if (n_threads == UINT_MAX) {
71✔
316
                /* By default, use a number of worker threads equal the number of online CPUs,
317
                 * but clamp it to avoid a probing storm on machines with many CPUs. */
318
                unsigned n_cpus;
71✔
319
                r = cpus_online(&n_cpus);
71✔
320
                if (r < 0) {
71✔
321
                        log_warning_errno(r, "Failed to get number of online CPUs, ignoring: %m");
×
322
                        n_threads = 1;
×
323
                } else
324
                        n_threads = CLAMP(n_cpus, 1U, 16U);
71✔
325
        }
326

327
        /* There's no reason to spawn more threads than the modules that need to be loaded */
328
        n_threads = CLAMP(n_threads, 1U, n_modules);
71✔
329

330
        /* One of the probe threads is the main process */
331
        return n_threads - 1U;
71✔
332
}
333

334
static int help(void) {
1✔
335
        _cleanup_free_ char *link = NULL;
1✔
336
        _cleanup_(table_unrefp) Table *options = NULL;
1✔
337
        int r;
1✔
338

339
        if (terminal_urlify_man("systemd-modules-load.service", "8", &link) < 0)
1✔
340
                return log_oom();
×
341

342
        r = option_parser_get_help_table(&options);
1✔
343
        if (r < 0)
1✔
344
                return r;
345

346
        printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1✔
347
               "Loads statically configured kernel modules.\n\n",
348
               program_invocation_short_name);
349

350
        r = table_print_or_warn(options);
1✔
351
        if (r < 0)
1✔
352
                return r;
353

354
        printf("\nSee the %s for details.\n", link);
1✔
355
        return 0;
356
}
357

358
static int parse_argv(int argc, char *argv[], char ***ret_args) {
75✔
359
        assert(argc >= 0);
75✔
360
        assert(argv);
75✔
361
        assert(ret_args);
75✔
362

363
        OptionParser opts = { argc, argv };
75✔
364

365
        FOREACH_OPTION_OR_RETURN(c, &opts)
75✔
366
                switch (c) {
2✔
367

368
                OPTION_COMMON_HELP:
1✔
369
                        return help();
1✔
370

371
                OPTION_COMMON_VERSION:
1✔
372
                        return version();
1✔
373
                }
374

375
        *ret_args = option_parser_get_args(&opts);
72✔
376
        return 1;
72✔
377
}
378

379
static int run(int argc, char *argv[]) {
75✔
380
        /* A OrderedSet instead of a plain Set is used here to make debug easier:
381
         * in case a race condition is observed during module probing, the threading
382
         * can be disabled through the SYSTEMD_MODULES_LOAD_NUM_THREADS environment
383
         * variable and the modules reordered at will by the user that is debugging it.
384
         * In that case, the probing order would be the same in which the modules
385
         * appear inside the modules-load.d files (this wouldn't be true with a Set). */
386
        _cleanup_ordered_set_free_ OrderedSet *module_set = NULL;
×
387
        _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
75✔
388
        _cleanup_free_ pthread_t *threads = NULL;
75✔
389
        size_t n_threads = 0;
75✔
390
        char *module;
75✔
391
        int ret = 0, r;
75✔
392

393
        char **args = NULL;
75✔
394
        r = parse_argv(argc, argv, &args);
75✔
395
        if (r <= 0)
75✔
396
                return r;
397

398
        log_setup();
72✔
399

400
        umask(0022);
72✔
401

402
        r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, PROC_CMDLINE_STRIP_RD_PREFIX);
72✔
403
        if (r < 0)
72✔
404
                log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
×
405

406
        if (!strv_isempty(args)) {
72✔
407
                STRV_FOREACH(i, args) {
4✔
408
                        r = apply_file_from_path(*i, &module_set);
2✔
409
                        if (r < 0)
2✔
410
                                RET_GATHER(ret, r);
1✔
411
                }
412
        } else {
413
                ConfFile **files = NULL;
70✔
414
                size_t n_files = 0;
70✔
415

416
                CLEANUP_ARRAY(files, n_files, conf_file_free_array);
70✔
417

418
                STRV_FOREACH(i, arg_proc_cmdline_modules)
72✔
419
                        RET_GATHER(ret, modules_list_append_dup(&module_set, *i));
2✔
420

421
                r = conf_files_list_nulstr_full(".conf", /* root= */ NULL,
70✔
422
                                                CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED | CONF_FILES_WARN,
423
                                                conf_file_dirs, &files, &n_files);
424
                if (r < 0)
70✔
425
                        RET_GATHER(ret, log_error_errno(r, "Failed to enumerate modules-load.d files: %m"));
×
426
                else
427
                        FOREACH_ARRAY(cf, files, n_files)
142✔
428
                                RET_GATHER(ret, apply_conf_file(*cf, &module_set));
72✔
429
        }
430

431
        n_threads = determine_num_worker_threads((size_t) ordered_set_size(module_set));
72✔
432

433
        /* If no additional thread is required, there is no need to create the
434
         * thread pool or the mean to communicate with its members. */
435
        if (n_threads == 0) {
72✔
436
                log_debug("Single-threaded probe");
72✔
437
                return RET_GATHER(ret, do_direct_probe(module_set));
72✔
438
        }
439

440
        /* Create a socketpair for communication with probe workers */
441
        r = RET_NERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, /* protocol= */ 0, pair));
×
442
        if (r < 0)
×
443
                return log_error_errno(r, "Failed to create socket pair: %m");
×
444

445
        /* Create threads, which will then wait for modules to probe. */
446
        log_info("Using %zu probe threads", n_threads + 1);
×
447
        r = create_worker_threads(n_threads, FD_TO_PTR(pair[1]), &threads, &n_threads);
×
448
        if (r < 0)
×
449
                log_warning_errno(r, "Failed to create probe threads, continuing as single-threaded probe");
×
450

451
        /* Send modules to be probed */
452
        ORDERED_SET_FOREACH(module, module_set)
×
453
                RET_GATHER(ret, enqueue_module_to_load(pair[0], module));
×
454

455
        /* Close one end of the socketpair; workers will run until the queue is empty */
456
        pair[0] = safe_close(pair[0]);
×
457

458
        /* Run the prober function also in original thread */
459
        RET_GATHER(ret, run_prober(pair[1]));
×
460

461
        /* Wait for all threads (if any) to finish and gather errors */
462
        RET_GATHER(ret, destroy_worker_threads(&threads, n_threads));
×
463

464
        return ret;
465
}
466

467
DEFINE_MAIN_FUNCTION(run);
75✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc