]> git.ipfire.org Git - thirdparty/kmod.git/blob - libkmod/libkmod-config.c
kmod_config: parse kernel command line for options and blacklist
[thirdparty/kmod.git] / libkmod / libkmod-config.c
1 /*
2 * libkmod - interface to kernel module operations
3 *
4 * Copyright (C) 2011 ProFUSION embedded systems
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stddef.h>
24 #include <stdarg.h>
25 #include <unistd.h>
26 #include <errno.h>
27 #include <string.h>
28 #include <ctype.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <dirent.h>
32
33 #include "libkmod.h"
34 #include "libkmod-private.h"
35
36 struct kmod_alias {
37 char *name;
38 char modname[];
39 };
40
41 struct kmod_options {
42 char *options;
43 char modname[];
44 };
45
46 struct kmod_command {
47 char *command;
48 char modname[];
49 };
50
51 const char *kmod_alias_get_name(const struct kmod_list *l) {
52 const struct kmod_alias *alias = l->data;
53 return alias->name;
54 }
55
56 const char *kmod_alias_get_modname(const struct kmod_list *l) {
57 const struct kmod_alias *alias = l->data;
58 return alias->modname;
59 }
60
61 const char *kmod_option_get_options(const struct kmod_list *l) {
62 const struct kmod_options *alias = l->data;
63 return alias->options;
64 }
65
66 const char *kmod_option_get_modname(const struct kmod_list *l) {
67 const struct kmod_options *alias = l->data;
68 return alias->modname;
69 }
70
71 const char *kmod_command_get_command(const struct kmod_list *l) {
72 const struct kmod_command *alias = l->data;
73 return alias->command;
74 }
75
76 const char *kmod_command_get_modname(const struct kmod_list *l) {
77 const struct kmod_command *alias = l->data;
78 return alias->modname;
79 }
80
81 static int kmod_config_add_command(struct kmod_config *config,
82 const char *modname,
83 const char *command,
84 const char *command_name,
85 struct kmod_list **list)
86 {
87 struct kmod_command *cmd;
88 struct kmod_list *l;
89 size_t modnamelen = strlen(modname) + 1;
90 size_t commandlen = strlen(command) + 1;
91
92 DBG(config->ctx, "modname'%s' cmd='%s %s'\n", modname, command_name,
93 command);
94
95 cmd = malloc(sizeof(*cmd) + modnamelen + commandlen);
96 if (cmd == NULL)
97 goto oom_error_init;
98
99 cmd->command = sizeof(*cmd) + modnamelen + (char *)cmd;
100 memcpy(cmd->modname, modname, modnamelen);
101 memcpy(cmd->command, command, commandlen);
102
103 l = kmod_list_append(*list, cmd);
104 if (l == NULL)
105 goto oom_error;
106
107 *list = l;
108 return 0;
109
110 oom_error:
111 free(cmd);
112 oom_error_init:
113 ERR(config->ctx, "out-of-memory\n");
114 return -ENOMEM;
115 }
116
117 static void kmod_config_free_command(struct kmod_config *config,
118 struct kmod_list *l,
119 struct kmod_list **list)
120 {
121 struct kmod_command *cmd = l->data;
122
123 free(cmd);
124 *list = kmod_list_remove(l);
125 }
126
127 static int kmod_config_add_options(struct kmod_config *config,
128 const char *modname, const char *options)
129 {
130 struct kmod_options *opt;
131 struct kmod_list *list;
132 size_t modnamelen = strlen(modname) + 1;
133 size_t optionslen = strlen(options) + 1;
134
135 DBG(config->ctx, "modname'%s' options='%s'\n", modname, options);
136
137 opt = malloc(sizeof(*opt) + modnamelen + optionslen);
138 if (opt == NULL)
139 goto oom_error_init;
140
141 opt->options = sizeof(*opt) + modnamelen + (char *)opt;
142
143 memcpy(opt->modname, modname, modnamelen);
144 memcpy(opt->options, options, optionslen);
145 strchr_replace(opt->options, '\t', ' ');
146
147 list = kmod_list_append(config->options, opt);
148 if (list == NULL)
149 goto oom_error;
150
151 config->options = list;
152 return 0;
153
154 oom_error:
155 free(opt);
156 oom_error_init:
157 ERR(config->ctx, "out-of-memory\n");
158 return -ENOMEM;
159 }
160
161 static void kmod_config_free_options(struct kmod_config *config,
162 struct kmod_list *l)
163 {
164 struct kmod_options *opt = l->data;
165
166 free(opt);
167
168 config->options = kmod_list_remove(l);
169 }
170
171 static int kmod_config_add_alias(struct kmod_config *config,
172 const char *name, const char *modname)
173 {
174 struct kmod_alias *alias;
175 struct kmod_list *list;
176 size_t namelen = strlen(name) + 1, modnamelen = strlen(modname) + 1;
177
178 DBG(config->ctx, "name=%s modname=%s\n", name, modname);
179
180 alias = malloc(sizeof(*alias) + namelen + modnamelen);
181 if (!alias)
182 goto oom_error_init;
183
184 alias->name = sizeof(*alias) + modnamelen + (char *)alias;
185
186 memcpy(alias->modname, modname, modnamelen);
187 memcpy(alias->name, name, namelen);
188
189 list = kmod_list_append(config->aliases, alias);
190 if (!list)
191 goto oom_error;
192
193 config->aliases = list;
194 return 0;
195
196 oom_error:
197 free(alias);
198 oom_error_init:
199 ERR(config->ctx, "out-of-memory name=%s modname=%s\n", name, modname);
200 return -ENOMEM;
201 }
202
203 static void kmod_config_free_alias(struct kmod_config *config,
204 struct kmod_list *l)
205 {
206 struct kmod_alias *alias = l->data;
207
208 free(alias);
209
210 config->aliases = kmod_list_remove(l);
211 }
212
213 static int kmod_config_add_blacklist(struct kmod_config *config,
214 const char *modname)
215 {
216 char *p;
217 struct kmod_list *list;
218
219 DBG(config->ctx, "modname=%s\n", modname);
220
221 p = strdup(modname);
222 if (!p)
223 goto oom_error_init;
224
225 list = kmod_list_append(config->blacklists, p);
226 if (!list)
227 goto oom_error;
228 config->blacklists = list;
229 return 0;
230
231 oom_error:
232 free(p);
233 oom_error_init:
234 ERR(config->ctx, "out-of-memory modname=%s\n", modname);
235 return -ENOMEM;
236 }
237
238 static void kmod_config_free_blacklist(struct kmod_config *config,
239 struct kmod_list *l)
240 {
241 free(l->data);
242 config->blacklists = kmod_list_remove(l);
243 }
244
245 static void kcmdline_parse_result(struct kmod_config *config, char *modname,
246 char *param, char *value)
247 {
248 if (modname == NULL || param == NULL || value == NULL)
249 return;
250
251 DBG(config->ctx, "%s %s\n", modname, param);
252
253 if (streq(modname, "modprobe") && !strncmp(param, "blacklist=", 10)) {
254 for (;;) {
255 char *t = strsep(&value, ",");
256 if (t == NULL)
257 break;
258
259 kmod_config_add_blacklist(config, t);
260 }
261 } else {
262 kmod_config_add_options(config,
263 underscores(config->ctx, modname), param);
264 }
265 }
266
267 static int kmod_config_parse_kcmdline(struct kmod_config *config)
268 {
269 char buf[KCMD_LINE_SIZE];
270 int fd, err;
271 char *p, *modname, *param = NULL, *value = NULL;
272
273 fd = open("/proc/cmdline", O_RDONLY);
274 err = read_str_safe(fd, buf, sizeof(buf));
275 close(fd);
276 if (err < 0) {
277 ERR(config->ctx, "could not read from '/proc/cmdline': %s\n",
278 strerror(-err));
279 return err;
280 }
281
282 for (p = buf, modname = buf; *p != '\0' && *p != '\n'; p++) {
283 switch (*p) {
284 case ' ':
285 *p = '\0';
286 kcmdline_parse_result(config, modname, param, value);
287 param = value = NULL;
288 modname = p + 1;
289 break;
290 case '.':
291 *p = '\0';
292 param = p + 1;
293 break;
294 case '=':
295 value = p + 1;
296 break;
297 }
298 }
299
300 *p = '\0';
301 kcmdline_parse_result(config, modname, param, value);
302
303 return 0;
304 }
305
306 /*
307 * Take an fd and own it. It will be closed on return. filename is used only
308 * for debug messages
309 */
310 static int kmod_config_parse(struct kmod_config *config, int fd,
311 const char *filename)
312 {
313 struct kmod_ctx *ctx = config->ctx;
314 char *line;
315 FILE *fp;
316 unsigned int linenum;
317 int err;
318
319 fp = fdopen(fd, "r");
320 if (fp == NULL) {
321 err = -errno;
322 ERR(config->ctx, "fd %d: %m", fd);
323 close(fd);
324 return err;
325 }
326
327 while ((line = getline_wrapped(fp, &linenum)) != NULL) {
328 char *cmd, *saveptr;
329
330 if (line[0] == '\0' || line[0] == '#')
331 goto done_next;
332
333 cmd = strtok_r(line, "\t ", &saveptr);
334 if (cmd == NULL)
335 goto done_next;
336
337 if (streq(cmd, "alias")) {
338 char *alias = strtok_r(NULL, "\t ", &saveptr);
339 char *modname = strtok_r(NULL, "\t ", &saveptr);
340
341 if (alias == NULL || modname == NULL)
342 goto syntax_error;
343
344 kmod_config_add_alias(config,
345 underscores(ctx, alias),
346 underscores(ctx, modname));
347 } else if (streq(cmd, "blacklist")) {
348 char *modname = strtok_r(NULL, "\t ", &saveptr);
349
350 if (modname == NULL)
351 goto syntax_error;
352
353 kmod_config_add_blacklist(config,
354 underscores(ctx, modname));
355 } else if (streq(cmd, "options")) {
356 char *modname = strtok_r(NULL, "\t ", &saveptr);
357
358 if (modname == NULL)
359 goto syntax_error;
360
361 kmod_config_add_options(config,
362 underscores(ctx, modname),
363 strtok_r(NULL, "\0", &saveptr));
364 } else if streq(cmd, "install") {
365 char *modname = strtok_r(NULL, "\t ", &saveptr);
366
367 if (modname == NULL)
368 goto syntax_error;
369
370 kmod_config_add_command(config,
371 underscores(ctx, modname),
372 strtok_r(NULL, "\0", &saveptr),
373 cmd, &config->install_commands);
374 } else if streq(cmd, "remove") {
375 char *modname = strtok_r(NULL, "\t ", &saveptr);
376
377 if (modname == NULL)
378 goto syntax_error;
379
380 kmod_config_add_command(config,
381 underscores(ctx, modname),
382 strtok_r(NULL, "\0", &saveptr),
383 cmd, &config->remove_commands);
384 } else if (streq(cmd, "include")
385 || streq(cmd, "softdep")
386 || streq(cmd, "config")) {
387 INFO(ctx, "%s: command %s not implemented yet\n",
388 filename, cmd);
389 } else {
390 syntax_error:
391 ERR(ctx, "%s line %u: ignoring bad line starting with '%s'\n",
392 filename, linenum, cmd);
393 }
394
395 done_next:
396 free(line);
397 }
398
399 fclose(fp);
400
401 return 0;
402 }
403
404 void kmod_config_free(struct kmod_config *config)
405 {
406 while (config->aliases)
407 kmod_config_free_alias(config, config->aliases);
408
409 while (config->blacklists)
410 kmod_config_free_blacklist(config, config->blacklists);
411
412 while (config->options)
413 kmod_config_free_options(config, config->options);
414
415 while (config->install_commands) {
416 kmod_config_free_command(config, config->install_commands,
417 &config->install_commands);
418 }
419
420 while (config->remove_commands) {
421 kmod_config_free_command(config, config->remove_commands,
422 &config->remove_commands);
423 }
424
425 free(config);
426 }
427
428 static bool conf_files_filter_out(struct kmod_ctx *ctx, DIR *d,
429 const char *path, const char *fn)
430 {
431 size_t len = strlen(fn);
432 struct stat st;
433
434 if (fn[0] == '.')
435 return true;
436
437 if (len < 6 || (!streq(&fn[len - 5], ".conf")
438 && !streq(&fn[len - 6], ".alias"))) {
439 INFO(ctx, "All config files need .conf: %s/%s, "
440 "it will be ignored in a future release\n",
441 path, fn);
442 return true;
443 }
444
445 fstatat(dirfd(d), fn, &st, 0);
446
447 if (S_ISDIR(st.st_mode)) {
448 ERR(ctx, "Directories inside directories are not supported: "
449 "%s/%s\n", path, fn);
450 return true;
451 }
452
453 return false;
454 }
455
456 /*
457 * Iterate over a directory (given by @path) and save the list of
458 * configuration files in @list.
459 */
460 static DIR *conf_files_list(struct kmod_ctx *ctx, struct kmod_list **list,
461 const char *path)
462 {
463 DIR *d;
464 int err;
465
466 *list = NULL;
467
468 d = opendir(path);
469 if (d == NULL) {
470 ERR(ctx, "%m\n");
471 return NULL;
472 }
473
474 for (;;) {
475 struct dirent ent, *entp;
476 struct kmod_list *l, *tmp;
477 const char *dname;
478
479 err = readdir_r(d, &ent, &entp);
480 if (err != 0) {
481 ERR(ctx, "reading entry %s\n", strerror(-err));
482 goto fail_read;
483 }
484
485 if (entp == NULL)
486 break;
487
488 if (conf_files_filter_out(ctx, d, path, entp->d_name))
489 continue;
490
491 /* insert sorted */
492 kmod_list_foreach(l, *list) {
493 if (strcmp(entp->d_name, l->data) < 0)
494 break;
495 }
496
497 dname = strdup(entp->d_name);
498 if (dname == NULL)
499 goto fail_oom;
500
501 if (l == NULL)
502 tmp = kmod_list_append(*list, dname);
503 else if (l == *list)
504 tmp = kmod_list_prepend(*list, dname);
505 else
506 tmp = kmod_list_insert_before(l, dname);
507
508 if (tmp == NULL)
509 goto fail_oom;
510
511 if (l == NULL || l == *list)
512 *list = tmp;
513 }
514
515 return d;
516
517 fail_oom:
518 ERR(ctx, "out of memory while scanning '%s'\n", path);
519 fail_read:
520 for (; *list != NULL; *list = kmod_list_remove(*list))
521 free((*list)->data);
522 closedir(d);
523 return NULL;
524 }
525
526 int kmod_config_new(struct kmod_ctx *ctx, struct kmod_config **p_config,
527 const char * const *config_paths)
528 {
529 struct kmod_config *config;
530 size_t i;
531
532 *p_config = config = calloc(1, sizeof(struct kmod_config));
533 if (config == NULL)
534 return -ENOMEM;
535
536 config->ctx = ctx;
537
538 for (i = 0; config_paths[i] != NULL; i++) {
539 const char *path = config_paths[i];
540 struct kmod_list *list;
541 struct stat st;
542 DIR *d;
543
544 if (stat(path, &st) != 0) {
545 DBG(ctx, "could not load '%s': %s\n",
546 path, strerror(errno));
547 continue;
548 }
549
550 if (S_ISREG(st.st_mode)) {
551 int fd = open(path, O_RDONLY);
552 DBG(ctx, "parsing file '%s': %d\n", path, fd);
553 if (fd >= 0)
554 kmod_config_parse(config, fd, path);
555 continue;
556 } else if (!S_ISDIR(st.st_mode)) {
557 ERR(ctx, "unsupported file mode %s: %#x\n",
558 path, st.st_mode);
559 continue;
560 }
561
562 d = conf_files_list(ctx, &list, path);
563
564 for (; list != NULL; list = kmod_list_remove(list)) {
565 int fd = openat(dirfd(d), list->data, O_RDONLY);
566 DBG(ctx, "parsing file '%s/%s': %d\n", path,
567 (const char *) list->data, fd);
568 if (fd >= 0)
569 kmod_config_parse(config, fd, list->data);
570
571 free(list->data);
572 }
573
574 closedir(d);
575 }
576
577 kmod_config_parse_kcmdline(config);
578
579 return 0;
580 }