modutils/*: rewrite by Timo Teras <timo.teras AT iki.fi>
[oweals/busybox.git] / modutils / modprobe.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Modprobe written from scratch for BusyBox
4  *
5  * Copyright (c) 2008 Timo Teras <timo.teras@iki.fi>
6  * Copyright (c) 2008 Vladimir Dronnikov
7  *
8  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
9  */
10
11 #include "libbb.h"
12 #include "modutils.h"
13 #include <sys/utsname.h>
14 #include <fnmatch.h>
15
16 struct modprobe_option {
17         char *module;
18         char *option;
19 };
20
21 struct modprobe_conf {
22         char probename[MODULE_NAME_LEN];
23         llist_t *options;
24         llist_t *aliases;
25 #if ENABLE_FEATURE_MODPROBE_BLACKLIST
26 #define add_to_blacklist(conf, name) llist_add_to(&conf->blacklist, name)
27 #define check_blacklist(conf, name) (llist_find(conf->blacklist, name) == NULL)
28         llist_t *blacklist;
29 #else
30 #define add_to_blacklist(conf, name) do {} while (0)
31 #define check_blacklist(conf, name) (1)
32 #endif
33 };
34
35 #define MODPROBE_OPTS   "acdlnrt:VC:" USE_FEATURE_MODPROBE_BLACKLIST("b")
36 enum {
37         MODPROBE_OPT_INSERT_ALL = (INSMOD_OPT_UNUSED << 0), /* a */
38         MODPROBE_OPT_DUMP_ONLY  = (INSMOD_OPT_UNUSED << 1), /* c */
39         MODPROBE_OPT_D          = (INSMOD_OPT_UNUSED << 2), /* d */
40         MODPROBE_OPT_LIST_ONLY  = (INSMOD_OPT_UNUSED << 3), /* l */
41         MODPROBE_OPT_SHOW_ONLY  = (INSMOD_OPT_UNUSED << 4), /* n */
42         MODPROBE_OPT_REMOVE     = (INSMOD_OPT_UNUSED << 5), /* r */
43         MODPROBE_OPT_RESTRICT   = (INSMOD_OPT_UNUSED << 6), /* t */
44         MODPROBE_OPT_VERONLY    = (INSMOD_OPT_UNUSED << 7), /* V */
45         MODPROBE_OPT_CONFIGFILE = (INSMOD_OPT_UNUSED << 8), /* C */
46         MODPROBE_OPT_BLACKLIST  = (INSMOD_OPT_UNUSED << 9) * ENABLE_FEATURE_MODPROBE_BLACKLIST,
47 };
48
49 static llist_t *loaded;
50
51 static int read_config(struct modprobe_conf *conf, const char *path);
52
53 static void add_option(llist_t **all_opts, const char *module, const char *opts)
54 {
55         struct modprobe_option *o;
56
57         o = xzalloc(sizeof(struct modprobe_option));
58         if (module)
59                 o->module = filename2modname(module, NULL);
60         o->option = xstrdup(opts);
61         llist_add_to(all_opts, o);
62 }
63
64 static int FAST_FUNC config_file_action(const char *filename,
65                                         struct stat *statbuf UNUSED_PARAM,
66                                         void *userdata,
67                                         int depth UNUSED_PARAM)
68 {
69         struct modprobe_conf *conf = (struct modprobe_conf *) userdata;
70         RESERVE_CONFIG_BUFFER(modname, MODULE_NAME_LEN);
71         char *tokens[3];
72         parser_t *p;
73         int rc = TRUE;
74
75         if (bb_basename(filename)[0] == '.')
76                 goto error;
77
78         p = config_open2(filename, fopen_for_read);
79         if (p == NULL) {
80                 rc = FALSE;
81                 goto error;
82         }
83
84         while (config_read(p, tokens, 3, 2, "# \t", PARSE_NORMAL)) {
85                 if (strcmp(tokens[0], "alias") == 0) {
86                         filename2modname(tokens[1], modname);
87                         if (tokens[2] &&
88                             fnmatch(modname, conf->probename, 0) == 0)
89                                 llist_add_to(&conf->aliases,
90                                         filename2modname(tokens[2], NULL));
91                 } else if (strcmp(tokens[0], "options") == 0) {
92                         if (tokens[2])
93                                 add_option(&conf->options, tokens[1], tokens[2]);
94                 } else if (strcmp(tokens[0], "include") == 0) {
95                         read_config(conf, tokens[1]);
96                 } else if (ENABLE_FEATURE_MODPROBE_BLACKLIST &&
97                            strcmp(tokens[0], "blacklist") == 0) {
98                         add_to_blacklist(conf, xstrdup(tokens[1]));
99                 }
100         }
101         config_close(p);
102 error:
103         if (ENABLE_FEATURE_CLEAN_UP)
104                 RELEASE_CONFIG_BUFFER(modname);
105         return rc;
106 }
107
108 static int read_config(struct modprobe_conf *conf, const char *path)
109 {
110         return recursive_action(path, ACTION_RECURSE | ACTION_QUIET,
111                                 config_file_action, NULL, conf, 1);
112 }
113
114 static char *gather_options(llist_t *first, const char *module, int usecmdline)
115 {
116         struct modprobe_option *opt;
117         llist_t *n;
118         char *opts = xstrdup("");
119         int optlen = 0;
120
121         for (n = first; n != NULL; n = n->link) {
122                 opt = (struct modprobe_option *) n->data;
123
124                 if (opt->module == NULL && !usecmdline)
125                         continue;
126                 if (opt->module != NULL && strcmp(opt->module, module) != 0)
127                         continue;
128
129                 opts = xrealloc(opts, optlen + strlen(opt->option) + 2);
130                 optlen += sprintf(opts + optlen, "%s ", opt->option);
131         }
132         return opts;
133 }
134
135 static int do_modprobe(struct modprobe_conf *conf, const char *module)
136 {
137         RESERVE_CONFIG_BUFFER(modname, MODULE_NAME_LEN);
138         llist_t *deps = NULL;
139         char *fn, *options, *colon = NULL, *tokens[2];
140         parser_t *p;
141         int rc = -1;
142
143         p = config_open2(CONFIG_DEFAULT_DEPMOD_FILE, fopen_for_read);
144         if (p == NULL)
145                 goto error;
146
147         while (config_read(p, tokens, 2, 1, "# \t", PARSE_NORMAL)) {
148                 colon = last_char_is(tokens[0], ':');
149                 if (colon == NULL)
150                         continue;
151
152                 filename2modname(tokens[0], modname);
153                 if (strcmp(modname, module) == 0)
154                         break;
155
156                 colon = NULL;
157         }
158         if (colon == NULL)
159                 goto error_not_found;
160
161         colon[0] = '\0';
162         llist_add_to(&deps, xstrdup(tokens[0]));
163         if (tokens[1])
164                 string_to_llist(tokens[1], &deps, " ");
165
166         if (!(option_mask32 & MODPROBE_OPT_REMOVE))
167                 deps = llist_rev(deps);
168
169         rc = 0;
170         while (deps && rc == 0) {
171                 fn = llist_pop(&deps);
172                 filename2modname(fn, modname);
173                 if (option_mask32 & MODPROBE_OPT_REMOVE) {
174                         if (bb_delete_module(modname, O_EXCL) != 0)
175                                 rc = errno;
176                 } else if (llist_find(loaded, modname) == NULL) {
177                         options = gather_options(conf->options, modname,
178                                                  strcmp(modname, module) == 0);
179                         rc = bb_init_module(fn, options);
180                         if (rc == 0)
181                                 llist_add_to(&loaded, xstrdup(modname));
182                         if (ENABLE_FEATURE_CLEAN_UP)
183                                 free(options);
184                 }
185
186                 if (ENABLE_FEATURE_CLEAN_UP)
187                         free(fn);
188         }
189
190 error_not_found:
191         config_close(p);
192 error:
193         if (ENABLE_FEATURE_CLEAN_UP)
194                 RELEASE_CONFIG_BUFFER(modname);
195         if (rc > 0 && !(option_mask32 & INSMOD_OPT_SILENT))
196                 bb_error_msg("Failed to %sload module %s: %s.",
197                              (option_mask32 & MODPROBE_OPT_REMOVE) ? "un" : "",
198                              module, moderror(rc));
199         return rc;
200 }
201
202 int modprobe_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
203 int modprobe_main(int argc UNUSED_PARAM, char **argv)
204 {
205         struct utsname uts;
206         int num_modules, i, rc;
207         llist_t *options = NULL;
208         parser_t *parser;
209
210         opt_complementary = "q-v:v-q";
211         getopt32(argv, INSMOD_OPTS MODPROBE_OPTS INSMOD_ARGS,
212                  NULL, NULL);
213         argv += optind;
214         argc -= optind;
215
216         if (option_mask32 & (MODPROBE_OPT_DUMP_ONLY | MODPROBE_OPT_LIST_ONLY |
217                              MODPROBE_OPT_SHOW_ONLY))
218                 bb_error_msg_and_die("not supported");
219
220         /* goto modules location */
221         xchdir(CONFIG_DEFAULT_MODULES_DIR);
222         uname(&uts);
223         xchdir(uts.release);
224
225         if (option_mask32 & (MODPROBE_OPT_REMOVE | MODPROBE_OPT_INSERT_ALL)) {
226                 /* each parameter is a module name */
227                 num_modules = argc;
228                 if (num_modules == 0) {
229                         if (bb_delete_module(NULL, O_NONBLOCK|O_EXCL) != 0)
230                                 bb_perror_msg_and_die("rmmod");
231                         return EXIT_SUCCESS;
232                 }
233         } else {
234                 /* the only module, the rest of parameters are options */
235                 num_modules = 1;
236                 add_option(&options, NULL, parse_cmdline_module_options(argv));
237         }
238
239         /* cache modules */
240         parser = config_open2("/proc/modules", fopen_for_read);
241         if (parser) {
242                 char *s;
243                 while (config_read(parser, &s, 1, 1, "# \t", PARSE_NORMAL & ~PARSE_GREEDY))
244                         llist_add_to(&loaded, xstrdup(s));
245                 config_close(parser);
246         }
247
248         for (i = 0; i < num_modules; i++) {
249                 struct modprobe_conf *conf;
250
251                 conf = xzalloc(sizeof(struct modprobe_conf));
252                 conf->options = options;
253                 filename2modname(argv[i], conf->probename);
254                 read_config(conf, "/etc/modprobe.conf");
255                 read_config(conf, "/etc/modprobe.d");
256                 if (ENABLE_FEATURE_MODUTILS_SYMBOLS &&
257                     conf->aliases == NULL && strncmp(argv[i], "symbol:", 7) == 0)
258                         read_config(conf, "modules.symbols");
259
260                 if (ENABLE_FEATURE_MODUTILS_ALIAS && conf->aliases == NULL)
261                         read_config(conf, "modules.alias");
262
263                 if (conf->aliases == NULL) {
264                         /* Try if module by literal name is found; literal
265                          * names are blacklist only if '-b' is given. */
266                         if (!(option_mask32 & MODPROBE_OPT_BLACKLIST) ||
267                             check_blacklist(conf, conf->probename)) {
268                                 rc = do_modprobe(conf, conf->probename);
269                                 if (rc < 0 && !(option_mask32 & INSMOD_OPT_SILENT))
270                                         bb_error_msg("Module %s not found.", argv[i]);
271                         }
272                 } else {
273                         /* Probe all aliases */
274                         while (conf->aliases != NULL) {
275                                 char *realname = llist_pop(&conf->aliases);
276                                 if (check_blacklist(conf, realname))
277                                         do_modprobe(conf, realname);
278                                 if (ENABLE_FEATURE_CLEAN_UP)
279                                         free(realname);
280                         }
281                 }
282         }
283
284         return EXIT_SUCCESS;
285 }