]>
Commit | Line | Data |
---|---|---|
7c2ab358 LDM |
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 | |
cb451f35 LDM |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2.1 of the License, or (at your option) any later version. | |
7c2ab358 LDM |
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 | ||
7c2ab358 LDM |
36 | struct kmod_alias { |
37 | char *name; | |
43c29d10 | 38 | char modname[]; |
7c2ab358 LDM |
39 | }; |
40 | ||
615c42be LDM |
41 | struct kmod_options { |
42 | char *options; | |
43 | char modname[]; | |
44 | }; | |
45 | ||
a5cce6d6 LDM |
46 | struct kmod_command { |
47 | char *command; | |
48 | char modname[]; | |
49 | }; | |
50 | ||
b0ef19f7 | 51 | const char *kmod_alias_get_name(const struct kmod_list *l) { |
1ce08a56 | 52 | const struct kmod_alias *alias = l->data; |
b0ef19f7 LDM |
53 | return alias->name; |
54 | } | |
55 | ||
56 | const char *kmod_alias_get_modname(const struct kmod_list *l) { | |
1ce08a56 | 57 | const struct kmod_alias *alias = l->data; |
b0ef19f7 LDM |
58 | return alias->modname; |
59 | } | |
60 | ||
bd3f5535 GSB |
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 | ||
a5cce6d6 LDM |
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 | ||
615c42be LDM |
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 | ||
c35347f1 LDM |
161 | static void kmod_config_free_options(struct kmod_config *config, |
162 | struct kmod_list *l) | |
615c42be LDM |
163 | { |
164 | struct kmod_options *opt = l->data; | |
165 | ||
166 | free(opt); | |
167 | ||
168 | config->options = kmod_list_remove(l); | |
169 | } | |
170 | ||
d13e606f | 171 | static int kmod_config_add_alias(struct kmod_config *config, |
c35347f1 | 172 | const char *name, const char *modname) |
7c2ab358 LDM |
173 | { |
174 | struct kmod_alias *alias; | |
d13e606f | 175 | struct kmod_list *list; |
43c29d10 | 176 | size_t namelen = strlen(name) + 1, modnamelen = strlen(modname) + 1; |
7c2ab358 | 177 | |
d13e606f | 178 | DBG(config->ctx, "name=%s modname=%s\n", name, modname); |
7c2ab358 | 179 | |
43c29d10 | 180 | alias = malloc(sizeof(*alias) + namelen + modnamelen); |
d13e606f GSB |
181 | if (!alias) |
182 | goto oom_error_init; | |
28c175ed | 183 | |
43c29d10 GSB |
184 | alias->name = sizeof(*alias) + modnamelen + (char *)alias; |
185 | ||
186 | memcpy(alias->modname, modname, modnamelen); | |
187 | memcpy(alias->name, name, namelen); | |
7c2ab358 | 188 | |
d13e606f GSB |
189 | list = kmod_list_append(config->aliases, alias); |
190 | if (!list) | |
191 | goto oom_error; | |
28c175ed | 192 | |
d13e606f GSB |
193 | config->aliases = list; |
194 | return 0; | |
195 | ||
196 | oom_error: | |
d13e606f GSB |
197 | free(alias); |
198 | oom_error_init: | |
199 | ERR(config->ctx, "out-of-memory name=%s modname=%s\n", name, modname); | |
200 | return -ENOMEM; | |
7c2ab358 LDM |
201 | } |
202 | ||
c35347f1 LDM |
203 | static void kmod_config_free_alias(struct kmod_config *config, |
204 | struct kmod_list *l) | |
7c2ab358 LDM |
205 | { |
206 | struct kmod_alias *alias = l->data; | |
207 | ||
7c2ab358 LDM |
208 | free(alias); |
209 | ||
d13e606f | 210 | config->aliases = kmod_list_remove(l); |
7c2ab358 LDM |
211 | } |
212 | ||
d13e606f | 213 | static int kmod_config_add_blacklist(struct kmod_config *config, |
c35347f1 | 214 | const char *modname) |
81cf2060 | 215 | { |
81cf2060 | 216 | char *p; |
d13e606f | 217 | struct kmod_list *list; |
81cf2060 | 218 | |
d13e606f | 219 | DBG(config->ctx, "modname=%s\n", modname); |
81cf2060 LDM |
220 | |
221 | p = strdup(modname); | |
d13e606f GSB |
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; | |
81cf2060 | 230 | |
d13e606f GSB |
231 | oom_error: |
232 | free(p); | |
233 | oom_error_init: | |
234 | ERR(config->ctx, "out-of-memory modname=%s\n", modname); | |
235 | return -ENOMEM; | |
81cf2060 LDM |
236 | } |
237 | ||
d13e606f | 238 | static void kmod_config_free_blacklist(struct kmod_config *config, |
81cf2060 LDM |
239 | struct kmod_list *l) |
240 | { | |
241 | free(l->data); | |
d13e606f | 242 | config->blacklists = kmod_list_remove(l); |
81cf2060 LDM |
243 | } |
244 | ||
b7b7ac29 LDM |
245 | /* |
246 | * Take an fd and own it. It will be closed on return. filename is used only | |
247 | * for debug messages | |
248 | */ | |
249 | static int kmod_config_parse(struct kmod_config *config, int fd, | |
250 | const char *filename) | |
7c2ab358 | 251 | { |
d13e606f | 252 | struct kmod_ctx *ctx = config->ctx; |
7c2ab358 LDM |
253 | char *line; |
254 | FILE *fp; | |
255 | unsigned int linenum; | |
b7b7ac29 | 256 | int err; |
7c2ab358 | 257 | |
b7b7ac29 LDM |
258 | fp = fdopen(fd, "r"); |
259 | if (fp == NULL) { | |
260 | err = -errno; | |
261 | ERR(config->ctx, "fd %d: %m", fd); | |
262 | close(fd); | |
263 | return err; | |
264 | } | |
7c2ab358 LDM |
265 | |
266 | while ((line = getline_wrapped(fp, &linenum)) != NULL) { | |
c11e62bf | 267 | char *cmd, *saveptr; |
7c2ab358 LDM |
268 | |
269 | if (line[0] == '\0' || line[0] == '#') | |
270 | goto done_next; | |
271 | ||
c11e62bf | 272 | cmd = strtok_r(line, "\t ", &saveptr); |
7c2ab358 LDM |
273 | if (cmd == NULL) |
274 | goto done_next; | |
275 | ||
877e80cd | 276 | if (streq(cmd, "alias")) { |
c11e62bf LDM |
277 | char *alias = strtok_r(NULL, "\t ", &saveptr); |
278 | char *modname = strtok_r(NULL, "\t ", &saveptr); | |
7c2ab358 LDM |
279 | |
280 | if (alias == NULL || modname == NULL) | |
281 | goto syntax_error; | |
282 | ||
d13e606f | 283 | kmod_config_add_alias(config, |
30be7513 LDM |
284 | underscores(ctx, alias), |
285 | underscores(ctx, modname)); | |
877e80cd | 286 | } else if (streq(cmd, "blacklist")) { |
c11e62bf | 287 | char *modname = strtok_r(NULL, "\t ", &saveptr); |
81cf2060 LDM |
288 | |
289 | if (modname == NULL) | |
290 | goto syntax_error; | |
291 | ||
d13e606f | 292 | kmod_config_add_blacklist(config, |
2295acc5 | 293 | underscores(ctx, modname)); |
615c42be LDM |
294 | } else if (streq(cmd, "options")) { |
295 | char *modname = strtok_r(NULL, "\t ", &saveptr); | |
296 | ||
297 | if (modname == NULL) | |
298 | goto syntax_error; | |
299 | ||
300 | kmod_config_add_options(config, | |
301 | underscores(ctx, modname), | |
302 | strtok_r(NULL, "\0", &saveptr)); | |
a5cce6d6 LDM |
303 | } else if streq(cmd, "install") { |
304 | char *modname = strtok_r(NULL, "\t ", &saveptr); | |
305 | ||
306 | if (modname == NULL) | |
307 | goto syntax_error; | |
308 | ||
309 | kmod_config_add_command(config, | |
310 | underscores(ctx, modname), | |
311 | strtok_r(NULL, "\0", &saveptr), | |
312 | cmd, &config->install_commands); | |
313 | } else if streq(cmd, "remove") { | |
314 | char *modname = strtok_r(NULL, "\t ", &saveptr); | |
315 | ||
316 | if (modname == NULL) | |
317 | goto syntax_error; | |
318 | ||
319 | kmod_config_add_command(config, | |
320 | underscores(ctx, modname), | |
321 | strtok_r(NULL, "\0", &saveptr), | |
322 | cmd, &config->remove_commands); | |
615c42be | 323 | } else if (streq(cmd, "include") |
877e80cd LDM |
324 | || streq(cmd, "softdep") |
325 | || streq(cmd, "config")) { | |
81cf2060 LDM |
326 | INFO(ctx, "%s: command %s not implemented yet\n", |
327 | filename, cmd); | |
7c2ab358 LDM |
328 | } else { |
329 | syntax_error: | |
330 | ERR(ctx, "%s line %u: ignoring bad line starting with '%s'\n", | |
331 | filename, linenum, cmd); | |
332 | } | |
333 | ||
334 | done_next: | |
335 | free(line); | |
336 | } | |
337 | ||
338 | fclose(fp); | |
339 | ||
340 | return 0; | |
341 | } | |
342 | ||
d13e606f | 343 | void kmod_config_free(struct kmod_config *config) |
7c2ab358 LDM |
344 | { |
345 | while (config->aliases) | |
d13e606f | 346 | kmod_config_free_alias(config, config->aliases); |
81cf2060 LDM |
347 | |
348 | while (config->blacklists) | |
d13e606f GSB |
349 | kmod_config_free_blacklist(config, config->blacklists); |
350 | ||
615c42be LDM |
351 | while (config->options) |
352 | kmod_config_free_options(config, config->options); | |
353 | ||
a5cce6d6 LDM |
354 | while (config->install_commands) { |
355 | kmod_config_free_command(config, config->install_commands, | |
356 | &config->install_commands); | |
357 | } | |
358 | ||
359 | while (config->remove_commands) { | |
360 | kmod_config_free_command(config, config->remove_commands, | |
361 | &config->remove_commands); | |
362 | } | |
363 | ||
d13e606f | 364 | free(config); |
7c2ab358 LDM |
365 | } |
366 | ||
98c80f44 LDM |
367 | static bool conf_files_filter_out(struct kmod_ctx *ctx, DIR *d, |
368 | const char *path, const char *fn) | |
7c2ab358 LDM |
369 | { |
370 | size_t len = strlen(fn); | |
98c80f44 | 371 | struct stat st; |
7c2ab358 LDM |
372 | |
373 | if (fn[0] == '.') | |
8f767e2d | 374 | return true; |
7c2ab358 | 375 | |
877e80cd LDM |
376 | if (len < 6 || (!streq(&fn[len - 5], ".conf") |
377 | && !streq(&fn[len - 6], ".alias"))) { | |
7c2ab358 LDM |
378 | INFO(ctx, "All config files need .conf: %s/%s, " |
379 | "it will be ignored in a future release\n", | |
380 | path, fn); | |
8f767e2d | 381 | return true; |
7c2ab358 LDM |
382 | } |
383 | ||
98c80f44 LDM |
384 | fstatat(dirfd(d), fn, &st, 0); |
385 | ||
386 | if (S_ISDIR(st.st_mode)) { | |
387 | ERR(ctx, "Directories inside directories are not supported: " | |
388 | "%s/%s\n", path, fn); | |
8f767e2d | 389 | return true; |
98c80f44 LDM |
390 | } |
391 | ||
8f767e2d | 392 | return false; |
7c2ab358 LDM |
393 | } |
394 | ||
4782396c LDM |
395 | /* |
396 | * Iterate over a directory (given by @path) and save the list of | |
397 | * configuration files in @list. | |
398 | */ | |
b7b7ac29 LDM |
399 | static DIR *conf_files_list(struct kmod_ctx *ctx, struct kmod_list **list, |
400 | const char *path) | |
7c2ab358 | 401 | { |
7c2ab358 LDM |
402 | DIR *d; |
403 | int err; | |
404 | ||
7c2ab358 LDM |
405 | d = opendir(path); |
406 | if (d == NULL) { | |
407 | err = errno; | |
408 | ERR(ctx, "%m\n"); | |
b7b7ac29 | 409 | return NULL; |
7c2ab358 LDM |
410 | } |
411 | ||
4782396c LDM |
412 | *list = NULL; |
413 | ||
7c2ab358 LDM |
414 | for (;;) { |
415 | struct dirent ent, *entp; | |
b7b7ac29 LDM |
416 | struct kmod_list *l, *tmp; |
417 | const char *dname; | |
7c2ab358 LDM |
418 | |
419 | err = readdir_r(d, &ent, &entp); | |
420 | if (err != 0) { | |
b7b7ac29 LDM |
421 | ERR(ctx, "reading entry %s\n", strerror(-err)); |
422 | goto fail_read; | |
7c2ab358 LDM |
423 | } |
424 | ||
425 | if (entp == NULL) | |
426 | break; | |
427 | ||
8f767e2d | 428 | if (conf_files_filter_out(ctx, d, path, entp->d_name)) |
7c2ab358 LDM |
429 | continue; |
430 | ||
b7b7ac29 LDM |
431 | /* insert sorted */ |
432 | kmod_list_foreach(l, *list) { | |
433 | if (strcmp(entp->d_name, l->data) < 0) | |
434 | break; | |
7c2ab358 LDM |
435 | } |
436 | ||
b7b7ac29 LDM |
437 | dname = strdup(entp->d_name); |
438 | if (dname == NULL) | |
439 | goto fail_oom; | |
7c2ab358 | 440 | |
b7b7ac29 LDM |
441 | if (l == NULL) |
442 | tmp = kmod_list_append(*list, dname); | |
443 | else if (l == *list) | |
444 | tmp = kmod_list_prepend(*list, dname); | |
445 | else | |
446 | tmp = kmod_list_insert_before(l, dname); | |
7c2ab358 | 447 | |
b7b7ac29 LDM |
448 | if (tmp == NULL) |
449 | goto fail_oom; | |
7c2ab358 | 450 | |
b7b7ac29 LDM |
451 | if (l == NULL || l == *list) |
452 | *list = tmp; | |
453 | } | |
7c2ab358 | 454 | |
b7b7ac29 | 455 | return d; |
7c2ab358 | 456 | |
b7b7ac29 LDM |
457 | fail_oom: |
458 | ERR(ctx, "out of memory while scanning '%s'\n", path); | |
459 | fail_read: | |
460 | for (; *list != NULL; *list = kmod_list_remove(*list)) | |
461 | free((*list)->data); | |
462 | closedir(d); | |
463 | return NULL; | |
7c2ab358 LDM |
464 | } |
465 | ||
c35347f1 LDM |
466 | int kmod_config_new(struct kmod_ctx *ctx, struct kmod_config **p_config, |
467 | const char * const *config_paths) | |
7c2ab358 | 468 | { |
d13e606f | 469 | struct kmod_config *config; |
b7b7ac29 | 470 | size_t i; |
7c2ab358 | 471 | |
d13e606f | 472 | *p_config = config = calloc(1, sizeof(struct kmod_config)); |
2295acc5 | 473 | if (config == NULL) |
d13e606f | 474 | return -ENOMEM; |
2295acc5 | 475 | |
d13e606f GSB |
476 | config->ctx = ctx; |
477 | ||
cb8d4d3e GSB |
478 | for (i = 0; config_paths[i] != NULL; i++) { |
479 | const char *path = config_paths[i]; | |
4782396c | 480 | struct kmod_list *list; |
cb8d4d3e | 481 | struct stat st; |
b7b7ac29 | 482 | DIR *d; |
7c2ab358 | 483 | |
cb8d4d3e GSB |
484 | if (stat(path, &st) != 0) { |
485 | DBG(ctx, "could not load '%s': %s\n", | |
486 | path, strerror(errno)); | |
487 | continue; | |
488 | } | |
489 | ||
490 | if (S_ISREG(st.st_mode)) { | |
491 | int fd = open(path, O_RDONLY); | |
492 | DBG(ctx, "parsing file '%s': %d\n", path, fd); | |
493 | if (fd >= 0) | |
494 | kmod_config_parse(config, fd, path); | |
495 | continue; | |
496 | } else if (!S_ISDIR(st.st_mode)) { | |
497 | ERR(ctx, "unsupported file mode %s: %#x\n", | |
498 | path, st.st_mode); | |
499 | continue; | |
500 | } | |
501 | ||
502 | d = conf_files_list(ctx, &list, path); | |
7c2ab358 | 503 | |
b7b7ac29 | 504 | for (; list != NULL; list = kmod_list_remove(list)) { |
cb8d4d3e GSB |
505 | int fd = openat(dirfd(d), list->data, O_RDONLY); |
506 | DBG(ctx, "parsing file '%s/%s': %d\n", path, | |
507 | (const char *) list->data, fd); | |
b7b7ac29 LDM |
508 | if (fd >= 0) |
509 | kmod_config_parse(config, fd, list->data); | |
7c2ab358 | 510 | |
b7b7ac29 LDM |
511 | free(list->data); |
512 | } | |
7c2ab358 | 513 | |
b7b7ac29 | 514 | closedir(d); |
7c2ab358 LDM |
515 | } |
516 | ||
b7b7ac29 | 517 | return 0; |
7c2ab358 | 518 | } |