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

systemd / systemd / 29132483780

10 Jul 2026 08:43PM UTC coverage: 72.912% (+0.2%) from 72.702%
29132483780

push

github

bluca
man: run forgotten 'update-man-rules'

344600 of 472622 relevant lines covered (72.91%)

1365091.83 hits per line

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

54.51
/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 "dlopen-note.h"
16
#include "errno-util.h"
17
#include "fd-util.h"
18
#include "fileio.h"
19
#include "format-table.h"
20
#include "log.h"
21
#include "macro.h"
22
#include "main-func.h"
23
#include "module-util.h"
24
#include "options.h"
25
#include "ordered-set.h"
26
#include "parse-util.h"
27
#include "pretty-print.h"
28
#include "proc-cmdline.h"
29
#include "string-util.h"
30
#include "strv.h"
31

32
#define MODULE_NAME_MAX_LEN (4096UL)
33

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

37
STATIC_DESTRUCTOR_REGISTER(arg_proc_cmdline_modules, strv_freep);
85✔
38

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

44
        assert(module_set);
100✔
45
        assert(mod);
100✔
46

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

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

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

61
        TAKE_PTR(mod);
62

63
        return 0;
64
}
65

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

69
        assert(module);
2✔
70

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

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

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

80
        if (proc_cmdline_key_streq(key, "modules_load")) {
3,610✔
81

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

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

90
        return 0;
91
}
92

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

96
        assert(f);
83✔
97

98
        log_debug("apply: %s", filename);
83✔
99
        for (;;) {
904✔
100
                _cleanup_free_ char *line = NULL;
821✔
101

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

108
                if (isempty(line))
821✔
109
                        continue;
2✔
110
                if (strchr(COMMENTS, *line))
819✔
111
                        continue;
721✔
112

113
                RET_GATHER(ret, modules_list_append_suffix(module_set, TAKE_PTR(line)));
98✔
114
        }
115

116
        return ret;
83✔
117
}
118

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

124
        assert(path);
2✔
125

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

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

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

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

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

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

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

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

156
        ORDERED_SET_FOREACH(module, module_set) {
174✔
157
                r = module_load_and_warn(ctx, module, /* verbose= */ true);
92✔
158
                if (r != -ENOENT)
92✔
159
                        RET_GATHER(ret, r);
84✔
160
        }
161

162
        return ret;
82✔
163
}
164

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

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

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

175
        return 0;
176
}
177

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

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

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

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

196
        return 1;
×
197
}
198

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

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

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

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

228
        return ret;
229
}
230

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

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

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

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

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

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

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

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

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

278
        return 0;
×
279
}
280

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

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

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

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

298
        return ret;
×
299
}
300

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

306
        if (n_modules == 0)
82✔
307
                return 0;
82✔
308

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

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

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

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

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

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

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

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

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

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

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

364
        OptionParser opts = { argc, argv };
85✔
365

366
        FOREACH_OPTION_OR_RETURN(c, &opts)
85✔
367
                switch (c) {
2✔
368

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

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

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

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

394
        LIBKMOD_NOTE(required);
85✔
395

396
        char **args = NULL;
85✔
397
        r = parse_argv(argc, argv, &args);
85✔
398
        if (r <= 0)
85✔
399
                return r;
400

401
        log_setup();
82✔
402

403
        umask(0022);
82✔
404

405
        r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, PROC_CMDLINE_STRIP_RD_PREFIX);
82✔
406
        if (r < 0)
82✔
407
                log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
×
408

409
        if (!strv_isempty(args)) {
82✔
410
                STRV_FOREACH(i, args) {
4✔
411
                        r = apply_file_from_path(*i, &module_set);
2✔
412
                        if (r < 0)
2✔
413
                                RET_GATHER(ret, r);
1✔
414
                }
415
        } else {
416
                ConfFile **files = NULL;
80✔
417
                size_t n_files = 0;
80✔
418

419
                CLEANUP_ARRAY(files, n_files, conf_file_free_array);
80✔
420

421
                STRV_FOREACH(i, arg_proc_cmdline_modules)
82✔
422
                        RET_GATHER(ret, modules_list_append_dup(&module_set, *i));
2✔
423

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

434
        n_threads = determine_num_worker_threads((size_t) ordered_set_size(module_set));
82✔
435

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

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

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

454
        /* Send modules to be probed */
455
        ORDERED_SET_FOREACH(module, module_set)
×
456
                RET_GATHER(ret, enqueue_module_to_load(pair[0], module));
×
457

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

461
        /* Run the prober function also in original thread */
462
        RET_GATHER(ret, run_prober(pair[1]));
×
463

464
        /* Wait for all threads (if any) to finish and gather errors */
465
        RET_GATHER(ret, destroy_worker_threads(&threads, n_threads));
×
466

467
        return ret;
468
}
469

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

© 2026 Coveralls, Inc