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

systemd / systemd / 19200508431

08 Nov 2025 07:53PM UTC coverage: 72.411% (+0.3%) from 72.098%
19200508431

push

github

yuwata
README: align features after 'for'

307177 of 424213 relevant lines covered (72.41%)

1185389.63 hits per line

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

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

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

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

29
#define MODULE_NAME_MAX_LEN (4096UL)
30

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

34
STATIC_DESTRUCTOR_REGISTER(arg_proc_cmdline_modules, strv_freep);
80✔
35

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

41
        assert(module_set);
20✔
42
        assert(mod);
20✔
43

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

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

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

58
        TAKE_PTR(mod);
59

60
        return 0;
61
}
62

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

66
        assert(module);
2✔
67

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

71
        return modules_list_append_suffix(module_set, TAKE_PTR(m));
2✔
72
}
73

74
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
3,359✔
75
        int r;
3,359✔
76

77
        if (proc_cmdline_key_streq(key, "modules_load")) {
3,359✔
78

79
                if (proc_cmdline_value_missing(key, value))
4✔
80
                        return 0;
81

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

87
        return 0;
88
}
89

90
static int apply_file(FILE *f, const char *filename, OrderedSet **module_set) {
78✔
91
        int ret = 0, r;
78✔
92

93
        assert(f);
78✔
94

95
        log_debug("apply: %s", filename);
78✔
96
        for (;;) {
174✔
97
                _cleanup_free_ char *line = NULL;
96✔
98

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

105
                if (isempty(line))
96✔
106
                        continue;
2✔
107
                if (strchr(COMMENTS, *line))
94✔
108
                        continue;
76✔
109

110
                RET_GATHER(ret, modules_list_append_suffix(module_set, TAKE_PTR(line)));
18✔
111
        }
112

113
        return ret;
78✔
114
}
115

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

121
        assert(path);
2✔
122

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

127
        return apply_file(f, pp, module_set);
1✔
128
}
129

130
static int apply_conf_file(ConfFile *c, OrderedSet **module_set) {
77✔
131
        _cleanup_fclose_ FILE *f = NULL;
77✔
132

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

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

141
        return apply_file(f, c->original_path, module_set);
77✔
142
}
143

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

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

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

159
        return ret;
75✔
160
}
161

162
static int enqueue_module_to_load(int sock, const char *module) {
10✔
163
        ssize_t bytes;
10✔
164

165
        assert(sock >= 0);
10✔
166
        assert(module);
10✔
167

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

172
        return 0;
173
}
174

175
static int dequeue_module_to_load(int sock, char *buffer, size_t buffer_len) {
14✔
176
        ssize_t bytes;
14✔
177

178
        assert(sock >= 0);
14✔
179
        assert(buffer);
14✔
180

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

191
        buffer[bytes] = '\0';
10✔
192

193
        return 1;
10✔
194
}
195

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

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

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

220
                r = module_load_and_warn(ctx, buffer, /* verbose = */ true);
10✔
221
                if (r != -ENOENT)
10✔
222
                        RET_GATHER(ret, r);
2✔
223
        }
224

225
        return ret;
226
}
227

228
static void *prober_thread(void *arg) {
2✔
229
        int sock = PTR_TO_FD(arg);
2✔
230
        return INT_TO_PTR(run_prober(sock));
2✔
231
}
232

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

239
        assert(ret_threads);
2✔
240
        assert(ret_n_threads);
2✔
241

242
        if (n_threads == 0) {
2✔
243
                *ret_threads = NULL;
×
244
                *ret_n_threads = 0;
×
245
                return 0;
×
246
        }
247

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

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

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

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

272
        *ret_threads = TAKE_PTR(new_threads);
2✔
273
        *ret_n_threads = created_threads;
2✔
274

275
        return 0;
2✔
276
}
277

278
static int destroy_worker_threads(pthread_t **threads, size_t n_threads) {
2✔
279
        int ret = 0, r;
2✔
280

281
        assert(threads);
2✔
282
        assert(n_threads == 0 || *threads);
2✔
283

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

293
        *threads = mfree(*threads);
2✔
294

295
        return ret;
2✔
296
}
297

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

303
        if (n_modules == 0)
77✔
304
                return 0;
77✔
305

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

313
        if (n_threads == UINT_MAX) {
4✔
314
                /* By default, use a number of worker threads equal the number of online CPUs,
315
                 * but clamp it to avoid a probing storm on machines with many CPUs. */
316
                long ncpus = sysconf(_SC_NPROCESSORS_ONLN);
4✔
317
                if (ncpus < 0) {
4✔
318
                        log_warning_errno(errno, "Failed to get number of online CPUs, ignoring: %m");
×
319
                        ncpus = 1;
320
                }
321

322
                n_threads = CLAMP((unsigned)ncpus, 1U, 16U);
4✔
323
        }
324

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

328
        /* One of the probe threads is the main process */
329
        return n_threads - 1U;
4✔
330
}
331

332
static int help(void) {
1✔
333
        _cleanup_free_ char *link = NULL;
1✔
334

335
        if (terminal_urlify_man("systemd-modules-load.service", "8", &link) < 0)
1✔
336
                return log_oom();
×
337

338
        printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1✔
339
               "Loads statically configured kernel modules.\n\n"
340
               "  -h --help             Show this help\n"
341
               "     --version          Show package version\n"
342
               "\nSee the %s for details.\n",
343
               program_invocation_short_name,
344
               link);
345

346
        return 0;
347
}
348

349
static int parse_argv(int argc, char *argv[]) {
80✔
350
        enum {
80✔
351
                ARG_VERSION = 0x100,
352
        };
353

354
        static const struct option options[] = {
80✔
355
                { "help",      no_argument,       NULL, 'h'           },
356
                { "version",   no_argument,       NULL, ARG_VERSION   },
357
                {}
358
        };
359

360
        int c;
80✔
361

362
        assert(argc >= 0);
80✔
363
        assert(argv);
80✔
364

365
        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
80✔
366
                switch (c) {
3✔
367

368
                case 'h':
1✔
369
                        return help();
1✔
370

371
                case ARG_VERSION:
1✔
372
                        return version();
1✔
373

374
                case '?':
375
                        return -EINVAL;
376

377
                default:
×
378
                        assert_not_reached();
×
379
                }
380

381
        return 1;
382
}
383

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

398
        r = parse_argv(argc, argv);
80✔
399
        if (r <= 0)
80✔
400
                return r;
401

402
        log_setup();
77✔
403

404
        umask(0022);
77✔
405

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

410
        if (argc > optind) {
77✔
411
                for (int i = optind; i < argc; i++) {
4✔
412
                        r = apply_file_from_path(argv[i], &module_set);
2✔
413
                        if (r < 0)
2✔
414
                                RET_GATHER(ret, r);
1✔
415
                }
416
        } else {
417
                ConfFile **files = NULL;
75✔
418
                size_t n_files = 0;
75✔
419

420
                CLEANUP_ARRAY(files, n_files, conf_file_free_many);
75✔
421

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

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

435
        n_threads = determine_num_worker_threads((size_t) ordered_set_size(module_set));
77✔
436

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

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

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

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

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

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

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

468
        return ret;
469
}
470

471
DEFINE_MAIN_FUNCTION(run);
80✔
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