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

systemd / systemd / 24809677062

22 Apr 2026 10:05PM UTC coverage: 71.989% (-0.2%) from 72.23%
24809677062

push

github

bluca
ukify: fix default path for hwids

The documentation and commit that added this seem to suggest this should
be under /usr/lib/systemd

fixes 117ec9db7

321862 of 447097 relevant lines covered (71.99%)

1201427.36 hits per line

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

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

30
#define MODULE_NAME_MAX_LEN (4096UL)
31

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

35
STATIC_DESTRUCTOR_REGISTER(arg_proc_cmdline_modules, strv_freep);
73✔
36

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

42
        assert(module_set);
88✔
43
        assert(mod);
88✔
44

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

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

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

59
        TAKE_PTR(mod);
60

61
        return 0;
62
}
63

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

67
        assert(module);
2✔
68

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

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

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

78
        if (proc_cmdline_key_streq(key, "modules_load")) {
3,051✔
79

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

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

88
        return 0;
89
}
90

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

94
        assert(f);
71✔
95

96
        log_debug("apply: %s", filename);
71✔
97
        for (;;) {
772✔
98
                _cleanup_free_ char *line = NULL;
701✔
99

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

106
                if (isempty(line))
701✔
107
                        continue;
2✔
108
                if (strchr(COMMENTS, *line))
699✔
109
                        continue;
613✔
110

111
                RET_GATHER(ret, modules_list_append_suffix(module_set, TAKE_PTR(line)));
86✔
112
        }
113

114
        return ret;
71✔
115
}
116

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

122
        assert(path);
2✔
123

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

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

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

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

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

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

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

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

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

160
        return ret;
67✔
161
}
162

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

166
        assert(sock >= 0);
14✔
167
        assert(module);
14✔
168

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

173
        return 0;
174
}
175

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

179
        assert(sock >= 0);
20✔
180
        assert(buffer);
20✔
181

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

192
        buffer[bytes] = '\0';
14✔
193

194
        return 1;
14✔
195
}
196

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

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

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

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

226
        return ret;
227
}
228

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

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

240
        assert(ret_threads);
3✔
241
        assert(ret_n_threads);
3✔
242

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

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

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

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

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

273
        *ret_threads = TAKE_PTR(new_threads);
3✔
274
        *ret_n_threads = created_threads;
3✔
275

276
        return 0;
3✔
277
}
278

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

282
        assert(threads);
3✔
283
        assert(n_threads == 0 || *threads);
3✔
284

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

294
        *threads = mfree(*threads);
3✔
295

296
        return ret;
3✔
297
}
298

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

304
        if (n_modules == 0)
70✔
305
                return 0;
70✔
306

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

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

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

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

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

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

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

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

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

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

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

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

362
        OptionParser state = { argc, argv };
73✔
363

364
        FOREACH_OPTION(&state, c, /* arg= */ NULL, /* on_error= */ return c)
73✔
365
                switch (c) {
2✔
366

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

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

374
        *ret_args = option_parser_get_args(&state);
70✔
375
        return 1;
70✔
376
}
377

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

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

397
        log_setup();
70✔
398

399
        umask(0022);
70✔
400

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

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

415
                CLEANUP_ARRAY(files, n_files, conf_file_free_array);
68✔
416

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

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

430
        n_threads = determine_num_worker_threads((size_t) ordered_set_size(module_set));
70✔
431

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

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

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

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

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

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

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

463
        return ret;
464
}
465

466
DEFINE_MAIN_FUNCTION(run);
73✔
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