]>
Commit | Line | Data |
---|---|---|
c3d0a5f2 | 1 | /* |
8b013767 | 2 | * kmod-modprobe - manage linux kernel modules using libkmod. |
c3d0a5f2 | 3 | * |
e6b0e49b | 4 | * Copyright (C) 2011-2013 ProFUSION embedded systems |
c3d0a5f2 | 5 | * |
cb451f35 LDM |
6 | * This program is free software: you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation, either version 2 of the License, or | |
9 | * (at your option) any later version. | |
c3d0a5f2 GSB |
10 | * |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
cb451f35 LDM |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. | |
c3d0a5f2 | 15 | * |
cb451f35 LDM |
16 | * You should have received a copy of the GNU General Public License |
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
c3d0a5f2 | 18 | */ |
cb451f35 | 19 | |
4744ebce | 20 | #include <assert.h> |
c2e4286b LDM |
21 | #include <errno.h> |
22 | #include <getopt.h> | |
23 | #include <limits.h> | |
24 | #include <stdbool.h> | |
c3d0a5f2 GSB |
25 | #include <stdio.h> |
26 | #include <stdlib.h> | |
c3d0a5f2 | 27 | #include <string.h> |
c2e4286b | 28 | #include <unistd.h> |
c3d0a5f2 | 29 | #include <sys/stat.h> |
c2e4286b | 30 | #include <sys/types.h> |
c3d0a5f2 | 31 | #include <sys/utsname.h> |
eff917c0 | 32 | #include <sys/wait.h> |
74d1df66 LDM |
33 | |
34 | #include <shared/array.h> | |
576dd439 | 35 | #include <shared/macro.h> |
c3d0a5f2 | 36 | |
f357866d | 37 | #include <libkmod/libkmod.h> |
c2e4286b | 38 | |
4a2e20df LDM |
39 | #include "kmod.h" |
40 | ||
c3d0a5f2 GSB |
41 | static int log_priority = LOG_CRIT; |
42 | static int use_syslog = 0; | |
fcb0ce94 | 43 | #define LOG(...) log_printf(log_priority, __VA_ARGS__) |
c3d0a5f2 GSB |
44 | |
45 | #define DEFAULT_VERBOSE LOG_WARNING | |
46 | static int verbose = DEFAULT_VERBOSE; | |
525fa07b | 47 | static int do_show = 0; |
c3d0a5f2 GSB |
48 | static int dry_run = 0; |
49 | static int ignore_loaded = 0; | |
8122985d | 50 | static int lookup_only = 0; |
c3d0a5f2 GSB |
51 | static int first_time = 0; |
52 | static int ignore_commands = 0; | |
53 | static int use_blacklist = 0; | |
54 | static int force = 0; | |
55 | static int strip_modversion = 0; | |
56 | static int strip_vermagic = 0; | |
42b32d30 | 57 | static int remove_holders = 0; |
d8a6c0cc | 58 | static int quiet_inuse = 0; |
c3d0a5f2 | 59 | |
cc98830c | 60 | static const char cmdopts_s[] = "arRibfDcnC:d:S:sqvVh"; |
c3d0a5f2 GSB |
61 | static const struct option cmdopts[] = { |
62 | {"all", no_argument, 0, 'a'}, | |
63 | {"remove", no_argument, 0, 'r'}, | |
64 | {"remove-dependencies", no_argument, 0, 5}, | |
42b32d30 | 65 | {"remove-holders", no_argument, 0, 5}, |
c3d0a5f2 GSB |
66 | {"resolve-alias", no_argument, 0, 'R'}, |
67 | {"first-time", no_argument, 0, 3}, | |
68 | {"ignore-install", no_argument, 0, 'i'}, | |
69 | {"ignore-remove", no_argument, 0, 'i'}, | |
70 | {"use-blacklist", no_argument, 0, 'b'}, | |
71 | {"force", no_argument, 0, 'f'}, | |
72 | {"force-modversion", no_argument, 0, 2}, | |
73 | {"force-vermagic", no_argument, 0, 1}, | |
74 | ||
c3d0a5f2 GSB |
75 | {"show-depends", no_argument, 0, 'D'}, |
76 | {"showconfig", no_argument, 0, 'c'}, | |
77 | {"show-config", no_argument, 0, 'c'}, | |
78 | {"show-modversions", no_argument, 0, 4}, | |
79 | {"dump-modversions", no_argument, 0, 4}, | |
3ada8df8 | 80 | {"show-exports", no_argument, 0, 6}, |
c3d0a5f2 GSB |
81 | |
82 | {"dry-run", no_argument, 0, 'n'}, | |
83 | {"show", no_argument, 0, 'n'}, | |
84 | ||
85 | {"config", required_argument, 0, 'C'}, | |
86 | {"dirname", required_argument, 0, 'd'}, | |
87 | {"set-version", required_argument, 0, 'S'}, | |
88 | ||
89 | {"syslog", no_argument, 0, 's'}, | |
90 | {"quiet", no_argument, 0, 'q'}, | |
91 | {"verbose", no_argument, 0, 'v'}, | |
92 | {"version", no_argument, 0, 'V'}, | |
93 | {"help", no_argument, 0, 'h'}, | |
94 | {NULL, 0, 0, 0} | |
95 | }; | |
96 | ||
4a2e20df | 97 | static void help(void) |
c3d0a5f2 | 98 | { |
34e06bfb | 99 | printf("Usage:\n" |
c3d0a5f2 GSB |
100 | "\t%s [options] [-i] [-b] modulename\n" |
101 | "\t%s [options] -a [-i] [-b] modulename [modulename...]\n" | |
102 | "\t%s [options] -r [-i] modulename\n" | |
103 | "\t%s [options] -r -a [-i] modulename [modulename...]\n" | |
c3d0a5f2 GSB |
104 | "\t%s [options] -c\n" |
105 | "\t%s [options] --dump-modversions filename\n" | |
106 | "Management Options:\n" | |
ab70dce1 GSB |
107 | "\t-a, --all Consider every non-argument to\n" |
108 | "\t be a module name to be inserted\n" | |
109 | "\t or removed (-r)\n" | |
110 | "\t-r, --remove Remove modules instead of inserting\n" | |
42b32d30 LDM |
111 | "\t --remove-dependencies Deprecated: use --remove-holders\n" |
112 | "\t --remove-holders Also remove module holders (use together with -r)\n" | |
ab70dce1 GSB |
113 | "\t-R, --resolve-alias Only lookup and print alias and exit\n" |
114 | "\t --first-time Fail if module already inserted or removed\n" | |
115 | "\t-i, --ignore-install Ignore install commands\n" | |
116 | "\t-i, --ignore-remove Ignore remove commands\n" | |
117 | "\t-b, --use-blacklist Apply blacklist to resolved alias.\n" | |
118 | "\t-f, --force Force module insertion or removal.\n" | |
119 | "\t implies --force-modversions and\n" | |
120 | "\t --force-vermagic\n" | |
121 | "\t --force-modversion Ignore module's version\n" | |
122 | "\t --force-vermagic Ignore module's version magic\n" | |
c3d0a5f2 GSB |
123 | "\n" |
124 | "Query Options:\n" | |
ab70dce1 GSB |
125 | "\t-D, --show-depends Only print module dependencies and exit\n" |
126 | "\t-c, --showconfig Print out known configuration and exit\n" | |
127 | "\t-c, --show-config Same as --showconfig\n" | |
128 | "\t --show-modversions Dump module symbol version and exit\n" | |
129 | "\t --dump-modversions Same as --show-modversions\n" | |
3ada8df8 | 130 | "\t --show-exports Only print module exported symbol versions and exit\n" |
c3d0a5f2 GSB |
131 | "\n" |
132 | "General Options:\n" | |
ab70dce1 GSB |
133 | "\t-n, --dry-run Do not execute operations, just print out\n" |
134 | "\t-n, --show Same as --dry-run\n" | |
c3d0a5f2 | 135 | |
2c96693e | 136 | "\t-C, --config=FILE Use FILE instead of default search paths\n" |
c5b37dba | 137 | "\t-d, --dirname=DIR Use DIR as filesystem root for /lib/modules\n" |
ab70dce1 | 138 | "\t-S, --set-version=VERSION Use VERSION instead of `uname -r`\n" |
c3d0a5f2 GSB |
139 | |
140 | "\t-s, --syslog print to syslog, not stderr\n" | |
141 | "\t-q, --quiet disable messages\n" | |
142 | "\t-v, --verbose enables more messages\n" | |
143 | "\t-V, --version show version\n" | |
144 | "\t-h, --help show this help\n", | |
7c04aeee LDM |
145 | program_invocation_short_name, program_invocation_short_name, |
146 | program_invocation_short_name, program_invocation_short_name, | |
147 | program_invocation_short_name, program_invocation_short_name); | |
c3d0a5f2 GSB |
148 | } |
149 | ||
1958af88 | 150 | _printf_format_(1, 2) |
c3d0a5f2 GSB |
151 | static inline void _show(const char *fmt, ...) |
152 | { | |
153 | va_list args; | |
154 | ||
525fa07b | 155 | if (!do_show && verbose <= DEFAULT_VERBOSE) |
c3d0a5f2 GSB |
156 | return; |
157 | ||
158 | va_start(args, fmt); | |
159 | vfprintf(stdout, fmt, args); | |
160 | fflush(stdout); | |
161 | va_end(args); | |
162 | } | |
c3d0a5f2 GSB |
163 | #define SHOW(...) _show(__VA_ARGS__) |
164 | ||
c3d0a5f2 GSB |
165 | static int show_config(struct kmod_ctx *ctx) |
166 | { | |
bc43496a LDM |
167 | struct config_iterators { |
168 | const char *name; | |
169 | struct kmod_config_iter *(*get_iter)(const struct kmod_ctx *ctx); | |
170 | } ci[] = { | |
171 | { "blacklist", kmod_config_get_blacklists }, | |
172 | { "install", kmod_config_get_install_commands }, | |
173 | { "remove", kmod_config_get_remove_commands }, | |
174 | { "alias", kmod_config_get_aliases }, | |
02629fa0 | 175 | { "options", kmod_config_get_options }, |
bc43496a LDM |
176 | { "softdep", kmod_config_get_softdeps }, |
177 | }; | |
178 | size_t i; | |
179 | ||
180 | for (i = 0; i < ARRAY_SIZE(ci); i++) { | |
181 | struct kmod_config_iter *iter = ci[i].get_iter(ctx); | |
182 | ||
183 | if (iter == NULL) | |
184 | continue; | |
185 | ||
186 | while (kmod_config_iter_next(iter)) { | |
187 | const char *val; | |
188 | ||
189 | printf("%s %s", ci[i].name, | |
190 | kmod_config_iter_get_key(iter)); | |
191 | val = kmod_config_iter_get_value(iter); | |
192 | if (val != NULL) { | |
193 | putchar(' '); | |
194 | puts(val); | |
195 | } else | |
196 | putchar('\n'); | |
197 | } | |
198 | ||
199 | kmod_config_iter_free_iter(iter); | |
200 | } | |
201 | ||
28f32c65 | 202 | puts("\n# End of configuration files. Dumping indexes now:\n"); |
09e9ae58 LDM |
203 | fflush(stdout); |
204 | ||
49a16375 LDM |
205 | kmod_dump_index(ctx, KMOD_INDEX_MODULES_ALIAS, STDOUT_FILENO); |
206 | kmod_dump_index(ctx, KMOD_INDEX_MODULES_SYMBOL, STDOUT_FILENO); | |
207 | ||
bc43496a | 208 | return 0; |
c3d0a5f2 GSB |
209 | } |
210 | ||
211 | static int show_modversions(struct kmod_ctx *ctx, const char *filename) | |
212 | { | |
0e3e2f43 GSB |
213 | struct kmod_list *l, *list = NULL; |
214 | struct kmod_module *mod; | |
215 | int err = kmod_module_new_from_path(ctx, filename, &mod); | |
216 | if (err < 0) { | |
217 | LOG("Module %s not found.\n", filename); | |
218 | return err; | |
219 | } | |
220 | ||
221 | err = kmod_module_get_versions(mod, &list); | |
222 | if (err < 0) { | |
63698377 | 223 | LOG("could not get modversions of %s: %s\n", |
0e3e2f43 GSB |
224 | filename, strerror(-err)); |
225 | kmod_module_unref(mod); | |
226 | return err; | |
227 | } | |
228 | ||
229 | kmod_list_foreach(l, list) { | |
230 | const char *symbol = kmod_module_version_get_symbol(l); | |
231 | uint64_t crc = kmod_module_version_get_crc(l); | |
232 | printf("0x%08"PRIx64"\t%s\n", crc, symbol); | |
233 | } | |
234 | kmod_module_versions_free_list(list); | |
235 | kmod_module_unref(mod); | |
236 | return 0; | |
c3d0a5f2 GSB |
237 | } |
238 | ||
3ada8df8 YK |
239 | static int show_exports(struct kmod_ctx *ctx, const char *filename) |
240 | { | |
241 | struct kmod_list *l, *list = NULL; | |
242 | struct kmod_module *mod; | |
243 | int err = kmod_module_new_from_path(ctx, filename, &mod); | |
244 | if (err < 0) { | |
245 | LOG("Module %s not found.\n", filename); | |
246 | return err; | |
247 | } | |
248 | ||
249 | err = kmod_module_get_symbols(mod, &list); | |
250 | if (err < 0) { | |
251 | LOG("could not get symbols of %s: %s\n", | |
252 | filename, strerror(-err)); | |
253 | kmod_module_unref(mod); | |
254 | return err; | |
255 | } | |
256 | ||
257 | kmod_list_foreach(l, list) { | |
258 | const char *symbol = kmod_module_symbol_get_symbol(l); | |
259 | uint64_t crc = kmod_module_symbol_get_crc(l); | |
260 | printf("0x%08"PRIx64"\t%s\n", crc, symbol); | |
261 | } | |
262 | kmod_module_symbols_free_list(list); | |
263 | kmod_module_unref(mod); | |
264 | return 0; | |
265 | } | |
266 | ||
8f192210 LDM |
267 | static int command_do(struct kmod_module *module, const char *type, |
268 | const char *command, const char *cmdline_opts) | |
c3d0a5f2 GSB |
269 | { |
270 | const char *modname = kmod_module_get_name(module); | |
271 | char *p, *cmd = NULL; | |
272 | size_t cmdlen, cmdline_opts_len, varlen; | |
273 | int ret = 0; | |
274 | ||
275 | if (cmdline_opts == NULL) | |
276 | cmdline_opts = ""; | |
277 | cmdline_opts_len = strlen(cmdline_opts); | |
278 | ||
279 | cmd = strdup(command); | |
280 | if (cmd == NULL) | |
281 | return -ENOMEM; | |
282 | cmdlen = strlen(cmd); | |
283 | varlen = sizeof("$CMDLINE_OPTS") - 1; | |
284 | while ((p = strstr(cmd, "$CMDLINE_OPTS")) != NULL) { | |
285 | size_t prefixlen = p - cmd; | |
286 | size_t suffixlen = cmdlen - prefixlen - varlen; | |
287 | size_t slen = cmdlen - varlen + cmdline_opts_len; | |
288 | char *suffix = p + varlen; | |
289 | char *s = malloc(slen + 1); | |
290 | if (s == NULL) { | |
291 | free(cmd); | |
292 | return -ENOMEM; | |
293 | } | |
294 | memcpy(s, cmd, p - cmd); | |
295 | memcpy(s + prefixlen, cmdline_opts, cmdline_opts_len); | |
296 | memcpy(s + prefixlen + cmdline_opts_len, suffix, suffixlen); | |
297 | s[slen] = '\0'; | |
298 | ||
299 | free(cmd); | |
300 | cmd = s; | |
301 | cmdlen = slen; | |
302 | } | |
303 | ||
304 | SHOW("%s %s\n", type, cmd); | |
305 | if (dry_run) | |
306 | goto end; | |
307 | ||
308 | setenv("MODPROBE_MODULE", modname, 1); | |
309 | ret = system(cmd); | |
310 | unsetenv("MODPROBE_MODULE"); | |
311 | if (ret == -1 || WEXITSTATUS(ret)) { | |
312 | LOG("Error running %s command for %s\n", type, modname); | |
313 | if (ret != -1) | |
314 | ret = -WEXITSTATUS(ret); | |
315 | } | |
316 | ||
317 | end: | |
318 | free(cmd); | |
319 | return ret; | |
320 | } | |
321 | ||
a872bba2 | 322 | static int rmmod_do_remove_module(struct kmod_module *mod) |
c3d0a5f2 | 323 | { |
a872bba2 LDM |
324 | const char *modname = kmod_module_get_name(mod); |
325 | int flags = 0, err; | |
e793f1ea | 326 | |
6f7ab216 | 327 | SHOW("rmmod %s\n", modname); |
c3d0a5f2 | 328 | |
a872bba2 LDM |
329 | if (dry_run) |
330 | return 0; | |
c3d0a5f2 | 331 | |
a872bba2 LDM |
332 | if (force) |
333 | flags |= KMOD_REMOVE_FORCE; | |
c3d0a5f2 | 334 | |
a872bba2 LDM |
335 | err = kmod_module_remove_module(mod, flags); |
336 | if (err == -EEXIST) { | |
337 | if (!first_time) | |
338 | err = 0; | |
e793f1ea | 339 | else |
a872bba2 | 340 | LOG("Module %s is not in kernel.\n", modname); |
c3d0a5f2 | 341 | } |
e793f1ea GSB |
342 | |
343 | return err; | |
344 | } | |
345 | ||
42b32d30 | 346 | #define RMMOD_FLAG_REMOVE_HOLDERS 0x1 |
a6b540de LDM |
347 | #define RMMOD_FLAG_IGNORE_BUILTIN 0x2 |
348 | static int rmmod_do_module(struct kmod_module *mod, int flags); | |
e793f1ea | 349 | |
7089386e LDM |
350 | /* Remove modules in reverse order */ |
351 | static int rmmod_do_modlist(struct kmod_list *list, bool stop_on_errors) | |
e793f1ea | 352 | { |
a872bba2 LDM |
353 | struct kmod_list *l; |
354 | ||
355 | kmod_list_foreach_reverse(l, list) { | |
356 | struct kmod_module *m = kmod_module_get_module(l); | |
a6b540de | 357 | int r = rmmod_do_module(m, RMMOD_FLAG_IGNORE_BUILTIN); |
a872bba2 LDM |
358 | kmod_module_unref(m); |
359 | ||
360 | if (r < 0 && stop_on_errors) | |
361 | return r; | |
362 | } | |
363 | ||
364 | return 0; | |
c3d0a5f2 GSB |
365 | } |
366 | ||
a6b540de | 367 | static int rmmod_do_module(struct kmod_module *mod, int flags) |
c3d0a5f2 GSB |
368 | { |
369 | const char *modname = kmod_module_get_name(mod); | |
a872bba2 LDM |
370 | struct kmod_list *pre = NULL, *post = NULL; |
371 | const char *cmd = NULL; | |
c3d0a5f2 GSB |
372 | int err; |
373 | ||
c3d0a5f2 | 374 | if (!ignore_commands) { |
e793f1ea GSB |
375 | err = kmod_module_get_softdeps(mod, &pre, &post); |
376 | if (err < 0) { | |
377 | WRN("could not get softdeps of '%s': %s\n", | |
8f192210 | 378 | modname, strerror(-err)); |
c3d0a5f2 | 379 | return err; |
e793f1ea GSB |
380 | } |
381 | ||
c3d0a5f2 | 382 | cmd = kmod_module_get_remove_commands(mod); |
c3d0a5f2 GSB |
383 | } |
384 | ||
ea3c8adf LDM |
385 | /* Quick check if module is loaded, otherwise there's nothing to do */ |
386 | if (!cmd && !ignore_loaded) { | |
9bf60d21 LDM |
387 | int state = kmod_module_get_initstate(mod); |
388 | ||
e5e2a683 | 389 | if (state < 0) { |
9bf60d21 LDM |
390 | if (first_time) { |
391 | LOG("Module %s is not in kernel.\n", modname); | |
e4e1e64a | 392 | err = -ENOENT; |
e4e1e64a LDM |
393 | } else { |
394 | err = 0; | |
e4e1e64a | 395 | } |
f758caf5 DR |
396 | goto error; |
397 | } else if (state == KMOD_MODULE_BUILTIN) { | |
a6b540de | 398 | if (flags & RMMOD_FLAG_IGNORE_BUILTIN) { |
52a0ba82 YK |
399 | err = 0; |
400 | } else { | |
401 | LOG("Module %s is builtin.\n", modname); | |
402 | err = -ENOENT; | |
403 | } | |
f758caf5 | 404 | goto error; |
9bf60d21 LDM |
405 | } |
406 | } | |
407 | ||
ea3c8adf | 408 | /* 1. @mod's post-softdeps in reverse order */ |
7089386e | 409 | rmmod_do_modlist(post, false); |
a872bba2 | 410 | |
ea3c8adf | 411 | /* 2. Other modules holding @mod */ |
d29ed6ef | 412 | if (flags & RMMOD_FLAG_REMOVE_HOLDERS) { |
42b32d30 | 413 | struct kmod_list *holders = kmod_module_get_holders(mod); |
a872bba2 | 414 | |
42b32d30 | 415 | err = rmmod_do_modlist(holders, true); |
9bf60d21 | 416 | if (err < 0) |
a872bba2 | 417 | goto error; |
9bf60d21 LDM |
418 | } |
419 | ||
ea3c8adf LDM |
420 | /* 3. @mod itself, but check for refcnt first */ |
421 | if (!cmd && !ignore_loaded) { | |
c3d0a5f2 | 422 | int usage = kmod_module_get_refcnt(mod); |
d8a6c0cc | 423 | |
c3d0a5f2 | 424 | if (usage > 0) { |
d8a6c0cc LDM |
425 | if (!quiet_inuse) |
426 | LOG("Module %s is in use.\n", modname); | |
427 | ||
e793f1ea | 428 | err = -EBUSY; |
e793f1ea GSB |
429 | goto error; |
430 | } | |
431 | } | |
432 | ||
ea3c8adf | 433 | if (!cmd) |
a872bba2 LDM |
434 | err = rmmod_do_remove_module(mod); |
435 | else | |
436 | err = command_do(mod, "remove", cmd, NULL); | |
d8a6c0cc | 437 | |
a872bba2 LDM |
438 | if (err < 0) |
439 | goto error; | |
c9a14448 | 440 | |
ea3c8adf LDM |
441 | /* 4. Other modules that became unused: errors are non-fatal */ |
442 | if (!cmd) { | |
443 | struct kmod_list *deps, *itr; | |
444 | ||
445 | deps = kmod_module_get_dependencies(mod); | |
446 | kmod_list_foreach(itr, deps) { | |
447 | struct kmod_module *dep = kmod_module_get_module(itr); | |
448 | if (kmod_module_get_refcnt(dep) == 0) | |
449 | rmmod_do_remove_module(dep); | |
450 | kmod_module_unref(dep); | |
451 | } | |
452 | kmod_module_unref_list(deps); | |
453 | } | |
454 | ||
455 | /* 5. @mod's pre-softdeps in reverse order: errors are non-fatal */ | |
7089386e | 456 | rmmod_do_modlist(pre, false); |
0e9bd2d1 | 457 | |
e793f1ea GSB |
458 | error: |
459 | kmod_module_unref_list(pre); | |
460 | kmod_module_unref_list(post); | |
a872bba2 | 461 | |
c3d0a5f2 GSB |
462 | return err; |
463 | } | |
464 | ||
569f1609 | 465 | static int rmmod(struct kmod_ctx *ctx, const char *alias) |
c3d0a5f2 GSB |
466 | { |
467 | struct kmod_list *l, *list = NULL; | |
468 | int err; | |
469 | ||
470 | err = kmod_module_new_from_lookup(ctx, alias, &list); | |
e5e2a683 | 471 | if (err < 0) |
c3d0a5f2 | 472 | return err; |
e5e2a683 | 473 | |
f758caf5 | 474 | if (list == NULL) { |
e5e2a683 | 475 | LOG("Module %s not found.\n", alias); |
f758caf5 DR |
476 | err = -ENOENT; |
477 | } | |
c3d0a5f2 GSB |
478 | |
479 | kmod_list_foreach(l, list) { | |
480 | struct kmod_module *mod = kmod_module_get_module(l); | |
d29ed6ef LDM |
481 | int flags = remove_holders ? RMMOD_FLAG_REMOVE_HOLDERS : 0; |
482 | ||
483 | err = rmmod_do_module(mod, flags); | |
c3d0a5f2 GSB |
484 | kmod_module_unref(mod); |
485 | if (err < 0) | |
486 | break; | |
487 | } | |
488 | ||
489 | kmod_module_unref_list(list); | |
490 | return err; | |
491 | } | |
492 | ||
c3d0a5f2 GSB |
493 | static int rmmod_all(struct kmod_ctx *ctx, char **args, int nargs) |
494 | { | |
495 | int i, err = 0; | |
496 | ||
497 | for (i = 0; i < nargs; i++) { | |
498 | int r = rmmod(ctx, args[i]); | |
499 | if (r < 0) | |
500 | err = r; | |
501 | } | |
502 | ||
503 | return err; | |
504 | } | |
505 | ||
2e9dcd74 LDM |
506 | static void print_action(struct kmod_module *m, bool install, |
507 | const char *options) | |
508 | { | |
4744ebce LDM |
509 | const char *path; |
510 | ||
511 | if (install) { | |
2e9dcd74 LDM |
512 | printf("install %s %s\n", kmod_module_get_install_commands(m), |
513 | options); | |
4744ebce LDM |
514 | return; |
515 | } | |
516 | ||
517 | path = kmod_module_get_path(m); | |
518 | ||
519 | if (path == NULL) { | |
ace742fa LDM |
520 | /* |
521 | * Either a builtin module, or an alias, print only for | |
522 | * builtin | |
523 | */ | |
524 | if (kmod_module_get_initstate(m) == KMOD_MODULE_BUILTIN) | |
525 | printf("builtin %s\n", kmod_module_get_name(m)); | |
4744ebce LDM |
526 | } else |
527 | printf("insmod %s %s\n", kmod_module_get_path(m), options); | |
2e9dcd74 LDM |
528 | } |
529 | ||
569f1609 | 530 | static int insmod(struct kmod_ctx *ctx, const char *alias, |
8f192210 | 531 | const char *extra_options) |
c3d0a5f2 GSB |
532 | { |
533 | struct kmod_list *l, *list = NULL; | |
2e9dcd74 LDM |
534 | int err, flags = 0; |
535 | ||
536 | void (*show)(struct kmod_module *m, bool install, | |
537 | const char *options) = NULL; | |
c3d0a5f2 GSB |
538 | |
539 | err = kmod_module_new_from_lookup(ctx, alias, &list); | |
e5e2a683 | 540 | |
b87d01d6 LA |
541 | if (list == NULL || err < 0) { |
542 | LOG("Module %s not found in directory %s\n", alias, | |
543 | ctx ? kmod_get_dirname(ctx) : "(missing)"); | |
e2719b32 LDM |
544 | return -ENOENT; |
545 | } | |
c3d0a5f2 | 546 | |
2e9dcd74 LDM |
547 | if (strip_modversion || force) |
548 | flags |= KMOD_PROBE_FORCE_MODVERSION; | |
549 | if (strip_vermagic || force) | |
550 | flags |= KMOD_PROBE_FORCE_VERMAGIC; | |
551 | if (ignore_commands) | |
552 | flags |= KMOD_PROBE_IGNORE_COMMAND; | |
553 | if (ignore_loaded) | |
554 | flags |= KMOD_PROBE_IGNORE_LOADED; | |
555 | if (dry_run) | |
556 | flags |= KMOD_PROBE_DRY_RUN; | |
557 | if (do_show || verbose > DEFAULT_VERBOSE) | |
558 | show = &print_action; | |
559 | ||
36ddee65 | 560 | flags |= KMOD_PROBE_APPLY_BLACKLIST_ALIAS_ONLY; |
2e9dcd74 LDM |
561 | |
562 | if (use_blacklist) | |
563 | flags |= KMOD_PROBE_APPLY_BLACKLIST; | |
564 | if (first_time) | |
814a57ba | 565 | flags |= KMOD_PROBE_FAIL_ON_LOADED; |
c3d0a5f2 GSB |
566 | |
567 | kmod_list_foreach(l, list) { | |
568 | struct kmod_module *mod = kmod_module_get_module(l); | |
8b013767 | 569 | |
8122985d LDM |
570 | if (lookup_only) |
571 | printf("%s\n", kmod_module_get_name(mod)); | |
8b013767 | 572 | else { |
2e9dcd74 LDM |
573 | err = kmod_module_probe_insert_module(mod, flags, |
574 | extra_options, NULL, NULL, show); | |
575 | } | |
8b013767 | 576 | |
297a3182 DR |
577 | if (err >= 0) |
578 | /* ignore flag return values such as a mod being blacklisted */ | |
579 | err = 0; | |
580 | else { | |
581 | switch (err) { | |
582 | case -EEXIST: | |
583 | ERR("could not insert '%s': Module already in kernel\n", | |
584 | kmod_module_get_name(mod)); | |
585 | break; | |
ccb64d1e DR |
586 | case -ENOENT: |
587 | ERR("could not insert '%s': Unknown symbol in module, " | |
588 | "or unknown parameter (see dmesg)\n", | |
589 | kmod_module_get_name(mod)); | |
590 | break; | |
297a3182 DR |
591 | default: |
592 | ERR("could not insert '%s': %s\n", | |
593 | kmod_module_get_name(mod), | |
594 | strerror(-err)); | |
595 | break; | |
596 | } | |
8b013767 | 597 | } |
2e9dcd74 | 598 | |
c3d0a5f2 | 599 | kmod_module_unref(mod); |
c3d0a5f2 GSB |
600 | } |
601 | ||
602 | kmod_module_unref_list(list); | |
603 | return err; | |
604 | } | |
605 | ||
c3d0a5f2 GSB |
606 | static int insmod_all(struct kmod_ctx *ctx, char **args, int nargs) |
607 | { | |
608 | int i, err = 0; | |
609 | ||
610 | for (i = 0; i < nargs; i++) { | |
611 | int r = insmod(ctx, args[i], NULL); | |
612 | if (r < 0) | |
613 | err = r; | |
614 | } | |
615 | ||
616 | return err; | |
617 | } | |
618 | ||
619 | static void env_modprobe_options_append(const char *value) | |
620 | { | |
621 | const char *old = getenv("MODPROBE_OPTIONS"); | |
622 | char *env; | |
623 | ||
624 | if (old == NULL) { | |
625 | setenv("MODPROBE_OPTIONS", value, 1); | |
626 | return; | |
627 | } | |
628 | ||
629 | if (asprintf(&env, "%s %s", old, value) < 0) { | |
630 | ERR("could not append value to $MODPROBE_OPTIONS\n"); | |
631 | return; | |
632 | } | |
633 | ||
634 | if (setenv("MODPROBE_OPTIONS", env, 1) < 0) | |
635 | ERR("could not setenv(MODPROBE_OPTIONS, \"%s\")\n", env); | |
636 | free(env); | |
637 | } | |
638 | ||
639 | static int options_from_array(char **args, int nargs, char **output) | |
640 | { | |
641 | char *opts = NULL; | |
642 | size_t optslen = 0; | |
643 | int i, err = 0; | |
644 | ||
645 | for (i = 1; i < nargs; i++) { | |
646 | size_t len = strlen(args[i]); | |
647 | size_t qlen = 0; | |
648 | const char *value; | |
649 | void *tmp; | |
650 | ||
651 | value = strchr(args[i], '='); | |
652 | if (value) { | |
653 | value++; | |
654 | if (*value != '"' && *value != '\'') { | |
655 | if (strchr(value, ' ')) | |
656 | qlen = 2; | |
657 | } | |
658 | } | |
659 | ||
660 | tmp = realloc(opts, optslen + len + qlen + 2); | |
661 | if (!tmp) { | |
662 | err = -errno; | |
663 | free(opts); | |
664 | opts = NULL; | |
665 | ERR("could not gather module options: out-of-memory\n"); | |
666 | break; | |
667 | } | |
668 | opts = tmp; | |
669 | if (optslen > 0) { | |
670 | opts[optslen] = ' '; | |
671 | optslen++; | |
672 | } | |
673 | if (qlen == 0) { | |
674 | memcpy(opts + optslen, args[i], len + 1); | |
675 | optslen += len; | |
676 | } else { | |
677 | size_t keylen = value - args[i]; | |
678 | size_t valuelen = len - keylen; | |
679 | memcpy(opts + optslen, args[i], keylen); | |
680 | optslen += keylen; | |
681 | opts[optslen] = '"'; | |
682 | optslen++; | |
683 | memcpy(opts + optslen, value, valuelen); | |
684 | optslen += valuelen; | |
685 | opts[optslen] = '"'; | |
686 | optslen++; | |
687 | opts[optslen] = '\0'; | |
688 | } | |
689 | } | |
690 | ||
691 | *output = opts; | |
692 | return err; | |
693 | } | |
694 | ||
695 | static char **prepend_options_from_env(int *p_argc, char **orig_argv) | |
696 | { | |
697 | const char *p, *env = getenv("MODPROBE_OPTIONS"); | |
d890179b | 698 | char **new_argv, *str_end, *str, *s, *quote; |
c3d0a5f2 GSB |
699 | int i, argc = *p_argc; |
700 | size_t envlen, space_count = 0; | |
701 | ||
702 | if (env == NULL) | |
703 | return orig_argv; | |
704 | ||
705 | for (p = env; *p != '\0'; p++) { | |
706 | if (*p == ' ') | |
707 | space_count++; | |
708 | } | |
709 | ||
710 | envlen = p - env; | |
711 | new_argv = malloc(sizeof(char *) * (argc + space_count + 3 + envlen)); | |
712 | if (new_argv == NULL) | |
713 | return NULL; | |
714 | ||
715 | new_argv[0] = orig_argv[0]; | |
d890179b | 716 | str = (char *) (new_argv + argc + space_count + 3); |
c3d0a5f2 GSB |
717 | memcpy(str, env, envlen + 1); |
718 | ||
d890179b | 719 | str_end = str + envlen; |
c3d0a5f2 GSB |
720 | |
721 | quote = NULL; | |
722 | for (i = 1, s = str; *s != '\0'; s++) { | |
723 | if (quote == NULL) { | |
724 | if (*s == ' ') { | |
725 | new_argv[i] = str; | |
726 | i++; | |
727 | *s = '\0'; | |
728 | str = s + 1; | |
729 | } else if (*s == '"' || *s == '\'') | |
730 | quote = s; | |
731 | } else { | |
732 | if (*s == *quote) { | |
733 | if (quote == str) { | |
734 | new_argv[i] = str + 1; | |
735 | i++; | |
736 | *s = '\0'; | |
737 | str = s + 1; | |
738 | } else { | |
739 | char *it; | |
740 | for (it = quote; it < s - 1; it++) | |
741 | it[0] = it[1]; | |
742 | for (it = s - 1; it < str_end - 2; it++) | |
743 | it[0] = it[2]; | |
744 | str_end -= 2; | |
745 | *str_end = '\0'; | |
746 | s -= 2; | |
747 | } | |
748 | quote = NULL; | |
749 | } | |
750 | } | |
751 | } | |
752 | if (str < s) { | |
753 | new_argv[i] = str; | |
754 | i++; | |
755 | } | |
756 | ||
757 | memcpy(new_argv + i, orig_argv + 1, sizeof(char *) * (argc - 1)); | |
757b3599 | 758 | new_argv[i + argc - 1] = NULL; |
c3d0a5f2 GSB |
759 | *p_argc = i + argc - 1; |
760 | ||
761 | return new_argv; | |
762 | } | |
763 | ||
fa29c0ee | 764 | static int do_modprobe(int argc, char **orig_argv) |
c3d0a5f2 GSB |
765 | { |
766 | struct kmod_ctx *ctx; | |
767 | char **args = NULL, **argv; | |
cb8d4d3e GSB |
768 | const char **config_paths = NULL; |
769 | int nargs = 0, n_config_paths = 0; | |
c3d0a5f2 GSB |
770 | char dirname_buf[PATH_MAX]; |
771 | const char *dirname = NULL; | |
772 | const char *root = NULL; | |
c3d0a5f2 | 773 | const char *kversion = NULL; |
c3d0a5f2 GSB |
774 | int use_all = 0; |
775 | int do_remove = 0; | |
776 | int do_show_config = 0; | |
777 | int do_show_modversions = 0; | |
3ada8df8 | 778 | int do_show_exports = 0; |
c3d0a5f2 GSB |
779 | int err; |
780 | ||
781 | argv = prepend_options_from_env(&argc, orig_argv); | |
782 | if (argv == NULL) { | |
d9a2e155 | 783 | ERR("Could not prepend options from command line\n"); |
c3d0a5f2 GSB |
784 | return EXIT_FAILURE; |
785 | } | |
786 | ||
787 | for (;;) { | |
788 | int c, idx = 0; | |
789 | c = getopt_long(argc, argv, cmdopts_s, cmdopts, &idx); | |
790 | if (c == -1) | |
791 | break; | |
792 | switch (c) { | |
793 | case 'a': | |
794 | log_priority = LOG_WARNING; | |
795 | use_all = 1; | |
796 | break; | |
797 | case 'r': | |
798 | do_remove = 1; | |
799 | break; | |
800 | case 5: | |
42b32d30 | 801 | remove_holders = 1; |
c3d0a5f2 GSB |
802 | break; |
803 | case 'R': | |
8122985d | 804 | lookup_only = 1; |
c3d0a5f2 GSB |
805 | break; |
806 | case 3: | |
807 | first_time = 1; | |
808 | break; | |
809 | case 'i': | |
810 | ignore_commands = 1; | |
811 | break; | |
812 | case 'b': | |
813 | use_blacklist = 1; | |
814 | break; | |
815 | case 'f': | |
816 | force = 1; | |
817 | break; | |
818 | case 2: | |
819 | strip_modversion = 1; | |
820 | break; | |
821 | case 1: | |
822 | strip_vermagic = 1; | |
823 | break; | |
c3d0a5f2 GSB |
824 | case 'D': |
825 | ignore_loaded = 1; | |
826 | dry_run = 1; | |
525fa07b | 827 | do_show = 1; |
c3d0a5f2 GSB |
828 | break; |
829 | case 'c': | |
830 | do_show_config = 1; | |
831 | break; | |
832 | case 4: | |
833 | do_show_modversions = 1; | |
834 | break; | |
3ada8df8 YK |
835 | case 6: |
836 | do_show_exports = 1; | |
837 | break; | |
c3d0a5f2 GSB |
838 | case 'n': |
839 | dry_run = 1; | |
840 | break; | |
cb8d4d3e GSB |
841 | case 'C': { |
842 | size_t bytes = sizeof(char *) * (n_config_paths + 2); | |
843 | void *tmp = realloc(config_paths, bytes); | |
844 | if (!tmp) { | |
d9a2e155 LDM |
845 | ERR("out-of-memory\n"); |
846 | err = -1; | |
847 | goto done; | |
cb8d4d3e GSB |
848 | } |
849 | config_paths = tmp; | |
850 | config_paths[n_config_paths] = optarg; | |
851 | n_config_paths++; | |
852 | config_paths[n_config_paths] = NULL; | |
853 | ||
c3d0a5f2 GSB |
854 | env_modprobe_options_append("-C"); |
855 | env_modprobe_options_append(optarg); | |
c3d0a5f2 | 856 | break; |
cb8d4d3e | 857 | } |
c3d0a5f2 GSB |
858 | case 'd': |
859 | root = optarg; | |
860 | break; | |
861 | case 'S': | |
862 | kversion = optarg; | |
863 | break; | |
864 | case 's': | |
865 | env_modprobe_options_append("-s"); | |
866 | use_syslog = 1; | |
867 | break; | |
868 | case 'q': | |
869 | env_modprobe_options_append("-q"); | |
ae7ebe87 | 870 | verbose = LOG_EMERG; |
c3d0a5f2 GSB |
871 | break; |
872 | case 'v': | |
873 | env_modprobe_options_append("-v"); | |
874 | verbose++; | |
875 | break; | |
876 | case 'V': | |
877 | puts(PACKAGE " version " VERSION); | |
655de275 | 878 | puts(KMOD_FEATURES); |
d9a2e155 | 879 | err = 0; |
4434d8ba | 880 | goto done; |
c3d0a5f2 | 881 | case 'h': |
4a2e20df | 882 | help(); |
d9a2e155 | 883 | err = 0; |
4434d8ba | 884 | goto done; |
c3d0a5f2 | 885 | case '?': |
d9a2e155 LDM |
886 | err = -1; |
887 | goto done; | |
c3d0a5f2 | 888 | default: |
d9a2e155 LDM |
889 | ERR("unexpected getopt_long() value '%c'.\n", c); |
890 | err = -1; | |
891 | goto done; | |
c3d0a5f2 GSB |
892 | } |
893 | } | |
894 | ||
895 | args = argv + optind; | |
896 | nargs = argc - optind; | |
897 | ||
92aad749 | 898 | log_open(use_syslog); |
d9a2e155 | 899 | |
b09668cf | 900 | if (!do_show_config) { |
c3d0a5f2 | 901 | if (nargs == 0) { |
d9a2e155 LDM |
902 | ERR("missing parameters. See -h.\n"); |
903 | err = -1; | |
904 | goto done; | |
c3d0a5f2 GSB |
905 | } |
906 | } | |
907 | ||
c3d0a5f2 GSB |
908 | if (root != NULL || kversion != NULL) { |
909 | struct utsname u; | |
910 | if (root == NULL) | |
911 | root = ""; | |
912 | if (kversion == NULL) { | |
913 | if (uname(&u) < 0) { | |
d9a2e155 LDM |
914 | ERR("uname() failed: %m\n"); |
915 | err = -1; | |
916 | goto done; | |
c3d0a5f2 GSB |
917 | } |
918 | kversion = u.release; | |
919 | } | |
8f192210 | 920 | snprintf(dirname_buf, sizeof(dirname_buf), |
c5b37dba | 921 | "%s/lib/modules/%s", root, |
8f192210 | 922 | kversion); |
c3d0a5f2 GSB |
923 | dirname = dirname_buf; |
924 | } | |
925 | ||
cb8d4d3e | 926 | ctx = kmod_new(dirname, config_paths); |
c3d0a5f2 | 927 | if (!ctx) { |
d9a2e155 LDM |
928 | ERR("kmod_new() failed!\n"); |
929 | err = -1; | |
930 | goto done; | |
c3d0a5f2 | 931 | } |
c3d0a5f2 | 932 | |
52a50fe2 | 933 | log_setup_kmod_log(ctx, verbose); |
d9a2e155 LDM |
934 | |
935 | kmod_load_resources(ctx); | |
c3d0a5f2 | 936 | |
b09668cf | 937 | if (do_show_config) |
c3d0a5f2 GSB |
938 | err = show_config(ctx); |
939 | else if (do_show_modversions) | |
940 | err = show_modversions(ctx, args[0]); | |
3ada8df8 YK |
941 | else if (do_show_exports) |
942 | err = show_exports(ctx, args[0]); | |
c3d0a5f2 | 943 | else if (do_remove) |
c1b84540 | 944 | err = rmmod_all(ctx, args, nargs); |
c3d0a5f2 GSB |
945 | else if (use_all) |
946 | err = insmod_all(ctx, args, nargs); | |
947 | else { | |
948 | char *opts; | |
949 | err = options_from_array(args, nargs, &opts); | |
950 | if (err == 0) { | |
951 | err = insmod(ctx, args[0], opts); | |
952 | free(opts); | |
953 | } | |
954 | } | |
955 | ||
956 | kmod_unref(ctx); | |
957 | ||
d9a2e155 | 958 | done: |
92aad749 | 959 | log_close(); |
c3d0a5f2 GSB |
960 | |
961 | if (argv != orig_argv) | |
962 | free(argv); | |
8f192210 | 963 | |
cb8d4d3e | 964 | free(config_paths); |
c3d0a5f2 | 965 | |
d9a2e155 | 966 | return err >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; |
c3d0a5f2 | 967 | } |
fa29c0ee | 968 | |
fa29c0ee LDM |
969 | const struct kmod_cmd kmod_cmd_compat_modprobe = { |
970 | .name = "modprobe", | |
971 | .cmd = do_modprobe, | |
972 | .help = "compat modprobe command", | |
973 | }; |