]> git.ipfire.org Git - thirdparty/kmod.git/blob - libkmod/libkmod-config.c
kmod_config: fix kcmd line parser with dots after =
[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 struct kmod_softdep {
52 char *name;
53 const char **pre;
54 const char **post;
55 unsigned int n_pre;
56 unsigned int n_post;
57 };
58
59 const char *kmod_alias_get_name(const struct kmod_list *l) {
60 const struct kmod_alias *alias = l->data;
61 return alias->name;
62 }
63
64 const char *kmod_alias_get_modname(const struct kmod_list *l) {
65 const struct kmod_alias *alias = l->data;
66 return alias->modname;
67 }
68
69 const char *kmod_option_get_options(const struct kmod_list *l) {
70 const struct kmod_options *alias = l->data;
71 return alias->options;
72 }
73
74 const char *kmod_option_get_modname(const struct kmod_list *l) {
75 const struct kmod_options *alias = l->data;
76 return alias->modname;
77 }
78
79 const char *kmod_command_get_command(const struct kmod_list *l) {
80 const struct kmod_command *alias = l->data;
81 return alias->command;
82 }
83
84 const char *kmod_command_get_modname(const struct kmod_list *l) {
85 const struct kmod_command *alias = l->data;
86 return alias->modname;
87 }
88
89 const char *kmod_softdep_get_name(const struct kmod_list *l) {
90 const struct kmod_softdep *dep = l->data;
91 return dep->name;
92 }
93
94 const char * const *kmod_softdep_get_pre(const struct kmod_list *l, unsigned int *count) {
95 const struct kmod_softdep *dep = l->data;
96 *count = dep->n_pre;
97 return dep->pre;
98 }
99
100 const char * const *kmod_softdep_get_post(const struct kmod_list *l, unsigned int *count) {
101 const struct kmod_softdep *dep = l->data;
102 *count = dep->n_post;
103 return dep->post;
104 }
105
106 static int kmod_config_add_command(struct kmod_config *config,
107 const char *modname,
108 const char *command,
109 const char *command_name,
110 struct kmod_list **list)
111 {
112 struct kmod_command *cmd;
113 struct kmod_list *l;
114 size_t modnamelen = strlen(modname) + 1;
115 size_t commandlen = strlen(command) + 1;
116
117 DBG(config->ctx, "modname='%s' cmd='%s %s'\n", modname, command_name,
118 command);
119
120 cmd = malloc(sizeof(*cmd) + modnamelen + commandlen);
121 if (cmd == NULL)
122 goto oom_error_init;
123
124 cmd->command = sizeof(*cmd) + modnamelen + (char *)cmd;
125 memcpy(cmd->modname, modname, modnamelen);
126 memcpy(cmd->command, command, commandlen);
127
128 l = kmod_list_append(*list, cmd);
129 if (l == NULL)
130 goto oom_error;
131
132 *list = l;
133 return 0;
134
135 oom_error:
136 free(cmd);
137 oom_error_init:
138 ERR(config->ctx, "out-of-memory\n");
139 return -ENOMEM;
140 }
141
142 static void kmod_config_free_command(struct kmod_config *config,
143 struct kmod_list *l,
144 struct kmod_list **list)
145 {
146 struct kmod_command *cmd = l->data;
147
148 free(cmd);
149 *list = kmod_list_remove(l);
150 }
151
152 static int kmod_config_add_options(struct kmod_config *config,
153 const char *modname, const char *options)
154 {
155 struct kmod_options *opt;
156 struct kmod_list *list;
157 size_t modnamelen = strlen(modname) + 1;
158 size_t optionslen = strlen(options) + 1;
159
160 DBG(config->ctx, "modname='%s' options='%s'\n", modname, options);
161
162 opt = malloc(sizeof(*opt) + modnamelen + optionslen);
163 if (opt == NULL)
164 goto oom_error_init;
165
166 opt->options = sizeof(*opt) + modnamelen + (char *)opt;
167
168 memcpy(opt->modname, modname, modnamelen);
169 memcpy(opt->options, options, optionslen);
170 strchr_replace(opt->options, '\t', ' ');
171
172 list = kmod_list_append(config->options, opt);
173 if (list == NULL)
174 goto oom_error;
175
176 config->options = list;
177 return 0;
178
179 oom_error:
180 free(opt);
181 oom_error_init:
182 ERR(config->ctx, "out-of-memory\n");
183 return -ENOMEM;
184 }
185
186 static void kmod_config_free_options(struct kmod_config *config,
187 struct kmod_list *l)
188 {
189 struct kmod_options *opt = l->data;
190
191 free(opt);
192
193 config->options = kmod_list_remove(l);
194 }
195
196 static int kmod_config_add_alias(struct kmod_config *config,
197 const char *name, const char *modname)
198 {
199 struct kmod_alias *alias;
200 struct kmod_list *list;
201 size_t namelen = strlen(name) + 1, modnamelen = strlen(modname) + 1;
202
203 DBG(config->ctx, "name=%s modname=%s\n", name, modname);
204
205 alias = malloc(sizeof(*alias) + namelen + modnamelen);
206 if (!alias)
207 goto oom_error_init;
208
209 alias->name = sizeof(*alias) + modnamelen + (char *)alias;
210
211 memcpy(alias->modname, modname, modnamelen);
212 memcpy(alias->name, name, namelen);
213
214 list = kmod_list_append(config->aliases, alias);
215 if (!list)
216 goto oom_error;
217
218 config->aliases = list;
219 return 0;
220
221 oom_error:
222 free(alias);
223 oom_error_init:
224 ERR(config->ctx, "out-of-memory name=%s modname=%s\n", name, modname);
225 return -ENOMEM;
226 }
227
228 static void kmod_config_free_alias(struct kmod_config *config,
229 struct kmod_list *l)
230 {
231 struct kmod_alias *alias = l->data;
232
233 free(alias);
234
235 config->aliases = kmod_list_remove(l);
236 }
237
238 static int kmod_config_add_blacklist(struct kmod_config *config,
239 const char *modname)
240 {
241 char *p;
242 struct kmod_list *list;
243
244 DBG(config->ctx, "modname=%s\n", modname);
245
246 p = strdup(modname);
247 if (!p)
248 goto oom_error_init;
249
250 list = kmod_list_append(config->blacklists, p);
251 if (!list)
252 goto oom_error;
253 config->blacklists = list;
254 return 0;
255
256 oom_error:
257 free(p);
258 oom_error_init:
259 ERR(config->ctx, "out-of-memory modname=%s\n", modname);
260 return -ENOMEM;
261 }
262
263 static void kmod_config_free_blacklist(struct kmod_config *config,
264 struct kmod_list *l)
265 {
266 free(l->data);
267 config->blacklists = kmod_list_remove(l);
268 }
269
270 static int kmod_config_add_softdep(struct kmod_config *config,
271 const char *modname,
272 const char *line)
273 {
274 struct kmod_list *list;
275 struct kmod_softdep *dep;
276 const char *s, *p;
277 char *itr;
278 unsigned int n_pre = 0, n_post = 0;
279 size_t modnamelen = strlen(modname) + 1;
280 size_t buflen = 0;
281 bool was_space = false;
282 enum { S_NONE, S_PRE, S_POST } mode = S_NONE;
283
284 DBG(config->ctx, "modname=%s\n", modname);
285
286 /* analyze and count */
287 for (p = s = line; ; s++) {
288 size_t plen;
289
290 if (*s != '\0') {
291 if (!isspace(*s)) {
292 was_space = false;
293 continue;
294 }
295
296 if (was_space) {
297 p = s + 1;
298 continue;
299 }
300 was_space = true;
301
302 if (p >= s)
303 continue;
304 }
305 plen = s - p;
306
307 if (plen == sizeof("pre:") - 1 &&
308 memcmp(p, "pre:", sizeof("pre:") - 1) == 0)
309 mode = S_PRE;
310 else if (plen == sizeof("post:") - 1 &&
311 memcmp(p, "post:", sizeof("post:") - 1) == 0)
312 mode = S_POST;
313 else if (*s != '\0' || (*s == '\0' && !was_space)) {
314 if (mode == S_PRE) {
315 buflen += plen + 1;
316 n_pre++;
317 } else if (mode == S_POST) {
318 buflen += plen + 1;
319 n_post++;
320 }
321 }
322 p = s + 1;
323 if (*s == '\0')
324 break;
325 }
326
327 DBG(config->ctx, "%u pre, %u post\n", n_pre, n_post);
328
329 dep = malloc(sizeof(struct kmod_softdep) + modnamelen +
330 n_pre * sizeof(const char *) +
331 n_post * sizeof(const char *) +
332 buflen);
333 if (dep == NULL) {
334 ERR(config->ctx, "out-of-memory modname=%s\n", modname);
335 return -ENOMEM;
336 }
337 dep->n_pre = n_pre;
338 dep->n_post = n_post;
339 dep->pre = (const char **)((char *)dep + sizeof(struct kmod_softdep));
340 dep->post = dep->pre + n_pre;
341 dep->name = (char *)(dep->post + n_post);
342
343 memcpy(dep->name, modname, modnamelen);
344
345 /* copy strings */
346 itr = dep->name + modnamelen;
347 n_pre = 0;
348 n_post = 0;
349 mode = S_NONE;
350 for (p = s = line; ; s++) {
351 size_t plen;
352
353 if (*s != '\0') {
354 if (!isspace(*s)) {
355 was_space = false;
356 continue;
357 }
358
359 if (was_space) {
360 p = s + 1;
361 continue;
362 }
363 was_space = true;
364
365 if (p >= s)
366 continue;
367 }
368 plen = s - p;
369
370 if (plen == sizeof("pre:") - 1 &&
371 memcmp(p, "pre:", sizeof("pre:") - 1) == 0)
372 mode = S_PRE;
373 else if (plen == sizeof("post:") - 1 &&
374 memcmp(p, "post:", sizeof("post:") - 1) == 0)
375 mode = S_POST;
376 else if (*s != '\0' || (*s == '\0' && !was_space)) {
377 if (mode == S_PRE) {
378 dep->pre[n_pre] = itr;
379 memcpy(itr, p, plen);
380 itr[plen] = '\0';
381 itr += plen + 1;
382 n_pre++;
383 } else if (mode == S_POST) {
384 dep->post[n_post] = itr;
385 memcpy(itr, p, plen);
386 itr[plen] = '\0';
387 itr += plen + 1;
388 n_post++;
389 }
390 }
391 p = s + 1;
392 if (*s == '\0')
393 break;
394 }
395
396 list = kmod_list_append(config->softdeps, dep);
397 if (list == NULL) {
398 free(dep);
399 return -ENOMEM;
400 }
401 config->softdeps = list;
402
403 return 0;
404 }
405
406 static void kmod_config_free_softdep(struct kmod_config *config,
407 struct kmod_list *l)
408 {
409 free(l->data);
410 config->softdeps = kmod_list_remove(l);
411 }
412
413 static void kcmdline_parse_result(struct kmod_config *config, char *modname,
414 char *param, char *value)
415 {
416 if (modname == NULL || param == NULL || value == NULL)
417 return;
418
419 DBG(config->ctx, "%s %s\n", modname, param);
420
421 if (streq(modname, "modprobe") && !strncmp(param, "blacklist=", 10)) {
422 for (;;) {
423 char *t = strsep(&value, ",");
424 if (t == NULL)
425 break;
426
427 kmod_config_add_blacklist(config, t);
428 }
429 } else {
430 kmod_config_add_options(config,
431 underscores(config->ctx, modname), param);
432 }
433 }
434
435 static int kmod_config_parse_kcmdline(struct kmod_config *config)
436 {
437 char buf[KCMD_LINE_SIZE];
438 int fd, err;
439 char *p, *modname, *param = NULL, *value = NULL;
440
441 fd = open("/proc/cmdline", O_RDONLY|O_CLOEXEC);
442 err = read_str_safe(fd, buf, sizeof(buf));
443 close(fd);
444 if (err < 0) {
445 ERR(config->ctx, "could not read from '/proc/cmdline': %s\n",
446 strerror(-err));
447 return err;
448 }
449
450 for (p = buf, modname = buf; *p != '\0' && *p != '\n'; p++) {
451 switch (*p) {
452 case ' ':
453 *p = '\0';
454 kcmdline_parse_result(config, modname, param, value);
455 param = value = NULL;
456 modname = p + 1;
457 break;
458 case '.':
459 *p = '\0';
460 param = p + 1;
461 break;
462 case '=':
463 if (param != NULL)
464 value = p + 1;
465 break;
466 }
467 }
468
469 *p = '\0';
470 kcmdline_parse_result(config, modname, param, value);
471
472 return 0;
473 }
474
475 /*
476 * Take an fd and own it. It will be closed on return. filename is used only
477 * for debug messages
478 */
479 static int kmod_config_parse(struct kmod_config *config, int fd,
480 const char *filename)
481 {
482 struct kmod_ctx *ctx = config->ctx;
483 char *line;
484 FILE *fp;
485 unsigned int linenum;
486 int err;
487
488 fp = fdopen(fd, "r");
489 if (fp == NULL) {
490 err = -errno;
491 ERR(config->ctx, "fd %d: %m", fd);
492 close(fd);
493 return err;
494 }
495
496 while ((line = getline_wrapped(fp, &linenum)) != NULL) {
497 char *cmd, *saveptr;
498
499 if (line[0] == '\0' || line[0] == '#')
500 goto done_next;
501
502 cmd = strtok_r(line, "\t ", &saveptr);
503 if (cmd == NULL)
504 goto done_next;
505
506 if (streq(cmd, "alias")) {
507 char *alias = strtok_r(NULL, "\t ", &saveptr);
508 char *modname = strtok_r(NULL, "\t ", &saveptr);
509
510 if (alias == NULL || modname == NULL)
511 goto syntax_error;
512
513 kmod_config_add_alias(config,
514 underscores(ctx, alias),
515 underscores(ctx, modname));
516 } else if (streq(cmd, "blacklist")) {
517 char *modname = strtok_r(NULL, "\t ", &saveptr);
518
519 if (modname == NULL)
520 goto syntax_error;
521
522 kmod_config_add_blacklist(config,
523 underscores(ctx, modname));
524 } else if (streq(cmd, "options")) {
525 char *modname = strtok_r(NULL, "\t ", &saveptr);
526
527 if (modname == NULL)
528 goto syntax_error;
529
530 kmod_config_add_options(config,
531 underscores(ctx, modname),
532 strtok_r(NULL, "\0", &saveptr));
533 } else if streq(cmd, "install") {
534 char *modname = strtok_r(NULL, "\t ", &saveptr);
535
536 if (modname == NULL)
537 goto syntax_error;
538
539 kmod_config_add_command(config,
540 underscores(ctx, modname),
541 strtok_r(NULL, "\0", &saveptr),
542 cmd, &config->install_commands);
543 } else if streq(cmd, "remove") {
544 char *modname = strtok_r(NULL, "\t ", &saveptr);
545
546 if (modname == NULL)
547 goto syntax_error;
548
549 kmod_config_add_command(config,
550 underscores(ctx, modname),
551 strtok_r(NULL, "\0", &saveptr),
552 cmd, &config->remove_commands);
553 } else if streq(cmd, "softdep") {
554 char *modname = strtok_r(NULL, "\t ", &saveptr);
555
556 if (modname == NULL)
557 goto syntax_error;
558
559 kmod_config_add_softdep(config,
560 underscores(ctx, modname),
561 strtok_r(NULL, "\0", &saveptr));
562 } else if (streq(cmd, "include")
563 || streq(cmd, "config")) {
564 INFO(ctx, "%s: command %s not implemented yet\n",
565 filename, cmd);
566 } else {
567 syntax_error:
568 ERR(ctx, "%s line %u: ignoring bad line starting with '%s'\n",
569 filename, linenum, cmd);
570 }
571
572 done_next:
573 free(line);
574 }
575
576 fclose(fp);
577
578 return 0;
579 }
580
581 void kmod_config_free(struct kmod_config *config)
582 {
583 while (config->aliases)
584 kmod_config_free_alias(config, config->aliases);
585
586 while (config->blacklists)
587 kmod_config_free_blacklist(config, config->blacklists);
588
589 while (config->options)
590 kmod_config_free_options(config, config->options);
591
592 while (config->install_commands) {
593 kmod_config_free_command(config, config->install_commands,
594 &config->install_commands);
595 }
596
597 while (config->remove_commands) {
598 kmod_config_free_command(config, config->remove_commands,
599 &config->remove_commands);
600 }
601
602 while (config->softdeps)
603 kmod_config_free_softdep(config, config->softdeps);
604
605 free(config);
606 }
607
608 static bool conf_files_filter_out(struct kmod_ctx *ctx, DIR *d,
609 const char *path, const char *fn)
610 {
611 size_t len = strlen(fn);
612 struct stat st;
613
614 if (fn[0] == '.')
615 return true;
616
617 if (len < 6 || (!streq(&fn[len - 5], ".conf")
618 && !streq(&fn[len - 6], ".alias"))) {
619 INFO(ctx, "All config files need .conf: %s/%s, "
620 "it will be ignored in a future release\n",
621 path, fn);
622 return true;
623 }
624
625 fstatat(dirfd(d), fn, &st, 0);
626
627 if (S_ISDIR(st.st_mode)) {
628 ERR(ctx, "Directories inside directories are not supported: "
629 "%s/%s\n", path, fn);
630 return true;
631 }
632
633 return false;
634 }
635
636 /*
637 * Iterate over a directory (given by @path) and save the list of
638 * configuration files in @list.
639 */
640 static DIR *conf_files_list(struct kmod_ctx *ctx, struct kmod_list **list,
641 const char *path)
642 {
643 DIR *d;
644 int err;
645
646 *list = NULL;
647
648 d = opendir(path);
649 if (d == NULL) {
650 ERR(ctx, "%m\n");
651 return NULL;
652 }
653
654 for (;;) {
655 struct dirent ent, *entp;
656 struct kmod_list *l, *tmp;
657 const char *dname;
658
659 err = readdir_r(d, &ent, &entp);
660 if (err != 0) {
661 ERR(ctx, "reading entry %s\n", strerror(-err));
662 goto fail_read;
663 }
664
665 if (entp == NULL)
666 break;
667
668 if (conf_files_filter_out(ctx, d, path, entp->d_name))
669 continue;
670
671 /* insert sorted */
672 kmod_list_foreach(l, *list) {
673 if (strcmp(entp->d_name, l->data) < 0)
674 break;
675 }
676
677 dname = strdup(entp->d_name);
678 if (dname == NULL)
679 goto fail_oom;
680
681 if (l == NULL)
682 tmp = kmod_list_append(*list, dname);
683 else if (l == *list)
684 tmp = kmod_list_prepend(*list, dname);
685 else
686 tmp = kmod_list_insert_before(l, dname);
687
688 if (tmp == NULL)
689 goto fail_oom;
690
691 if (l == NULL || l == *list)
692 *list = tmp;
693 }
694
695 return d;
696
697 fail_oom:
698 ERR(ctx, "out of memory while scanning '%s'\n", path);
699 fail_read:
700 for (; *list != NULL; *list = kmod_list_remove(*list))
701 free((*list)->data);
702 closedir(d);
703 return NULL;
704 }
705
706 int kmod_config_new(struct kmod_ctx *ctx, struct kmod_config **p_config,
707 const char * const *config_paths)
708 {
709 struct kmod_config *config;
710 size_t i;
711
712 *p_config = config = calloc(1, sizeof(struct kmod_config));
713 if (config == NULL)
714 return -ENOMEM;
715
716 config->ctx = ctx;
717
718 for (i = 0; config_paths[i] != NULL; i++) {
719 const char *path = config_paths[i];
720 struct kmod_list *list;
721 struct stat st;
722 DIR *d;
723
724 if (stat(path, &st) != 0) {
725 DBG(ctx, "could not load '%s': %s\n",
726 path, strerror(errno));
727 continue;
728 }
729
730 if (S_ISREG(st.st_mode)) {
731 int fd = open(path, O_RDONLY|O_CLOEXEC);
732 DBG(ctx, "parsing file '%s': %d\n", path, fd);
733 if (fd >= 0)
734 kmod_config_parse(config, fd, path);
735 continue;
736 } else if (!S_ISDIR(st.st_mode)) {
737 ERR(ctx, "unsupported file mode %s: %#x\n",
738 path, st.st_mode);
739 continue;
740 }
741
742 d = conf_files_list(ctx, &list, path);
743
744 for (; list != NULL; list = kmod_list_remove(list)) {
745 int fd = openat(dirfd(d), list->data, O_RDONLY|O_CLOEXEC);
746 DBG(ctx, "parsing file '%s/%s': %d\n", path,
747 (const char *) list->data, fd);
748 if (fd >= 0)
749 kmod_config_parse(config, fd, list->data);
750
751 free(list->data);
752 }
753
754 closedir(d);
755 }
756
757 kmod_config_parse_kcmdline(config);
758
759 return 0;
760 }