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