| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <ctype.h> |
| #include <getopt.h> |
| #include <stdlib.h> |
| |
| #include <string> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/strings.h> |
| #include <android-base/stringprintf.h> |
| #include <modprobe/modprobe.h> |
| |
| #include <sys/utsname.h> |
| |
| namespace { |
| |
| enum modprobe_mode { |
| AddModulesMode, |
| RemoveModulesMode, |
| ListModulesMode, |
| ShowDependenciesMode, |
| }; |
| |
| void print_usage(void) { |
| LOG(INFO) << "Usage:"; |
| LOG(INFO); |
| LOG(INFO) << " modprobe [options] [-d DIR] [--all=FILE|MODULE]..."; |
| LOG(INFO) << " modprobe [options] [-d DIR] MODULE [symbol=value]..."; |
| LOG(INFO); |
| LOG(INFO) << "Options:"; |
| LOG(INFO) << " --all=FILE: FILE to acquire module names from"; |
| LOG(INFO) << " -b, --use-blocklist: Apply blocklist to module names too"; |
| LOG(INFO) << " -d, --dirname=DIR: Load modules from DIR, option may be used multiple times"; |
| LOG(INFO) << " -D, --show-depends: Print dependencies for modules only, do not load"; |
| LOG(INFO) << " -h, --help: Print this help"; |
| LOG(INFO) << " -l, --list: List modules matching pattern"; |
| LOG(INFO) << " -r, --remove: Remove MODULE (multiple modules may be specified)"; |
| LOG(INFO) << " -s, --syslog: print to syslog also"; |
| LOG(INFO) << " -q, --quiet: disable messages"; |
| LOG(INFO) << " -v, --verbose: enable more messages, even more with a second -v"; |
| LOG(INFO); |
| } |
| |
| #define check_mode() \ |
| if (mode != AddModulesMode) { \ |
| LOG(ERROR) << "multiple mode flags specified"; \ |
| print_usage(); \ |
| return EXIT_FAILURE; \ |
| } |
| |
| std::string stripComments(const std::string& str) { |
| for (std::string rv = str;;) { |
| auto comment = rv.find('#'); |
| if (comment == std::string::npos) return rv; |
| auto end = rv.find('\n', comment); |
| if (end != std::string::npos) end = end - comment; |
| rv.erase(comment, end); |
| } |
| /* NOTREACHED */ |
| } |
| |
| auto syslog = false; |
| |
| void MyLogger(android::base::LogId id, android::base::LogSeverity severity, const char* tag, |
| const char* file, unsigned int line, const char* message) { |
| android::base::StdioLogger(id, severity, tag, file, line, message); |
| if (syslog && message[0]) { |
| android::base::KernelLogger(id, severity, tag, file, line, message); |
| } |
| } |
| |
| // Find directories in format of "/lib/modules/x.y.z-*". |
| static int KernelVersionNameFilter(const dirent* de) { |
| unsigned int major, minor; |
| static std::string kernel_version; |
| utsname uts; |
| |
| if (kernel_version.empty()) { |
| if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) { |
| LOG(ERROR) << "Could not parse the kernel version from uname"; |
| return 0; |
| } |
| kernel_version = android::base::StringPrintf("%u.%u", major, minor); |
| } |
| |
| if (android::base::StartsWith(de->d_name, kernel_version)) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| } // anonymous namespace |
| |
| extern "C" int modprobe_main(int argc, char** argv) { |
| android::base::InitLogging(argv, MyLogger); |
| android::base::SetMinimumLogSeverity(android::base::INFO); |
| |
| std::vector<std::string> modules; |
| std::string module_parameters; |
| std::string mods; |
| std::vector<std::string> mod_dirs; |
| modprobe_mode mode = AddModulesMode; |
| bool blocklist = false; |
| int rv = EXIT_SUCCESS; |
| |
| int opt; |
| int option_index = 0; |
| // NB: We have non-standard short options -l and -D to make it easier for |
| // OEMs to transition from toybox. |
| // clang-format off |
| static struct option long_options[] = { |
| { "all", optional_argument, 0, 'a' }, |
| { "use-blocklist", no_argument, 0, 'b' }, |
| { "dirname", required_argument, 0, 'd' }, |
| { "show-depends", no_argument, 0, 'D' }, |
| { "help", no_argument, 0, 'h' }, |
| { "list", no_argument, 0, 'l' }, |
| { "quiet", no_argument, 0, 'q' }, |
| { "remove", no_argument, 0, 'r' }, |
| { "syslog", no_argument, 0, 's' }, |
| { "verbose", no_argument, 0, 'v' }, |
| }; |
| // clang-format on |
| while ((opt = getopt_long(argc, argv, "a::bd:Dhlqrsv", long_options, &option_index)) != -1) { |
| switch (opt) { |
| case 'a': |
| // toybox modprobe supported -a to load multiple modules, this |
| // is supported here by default, ignore flag if no argument. |
| check_mode(); |
| if (optarg == NULL) break; |
| if (!android::base::ReadFileToString(optarg, &mods)) { |
| PLOG(ERROR) << "Failed to open " << optarg; |
| rv = EXIT_FAILURE; |
| } |
| for (auto mod : android::base::Split(stripComments(mods), "\n")) { |
| mod = android::base::Trim(mod); |
| if (mod == "") continue; |
| if (std::find(modules.begin(), modules.end(), mod) != modules.end()) continue; |
| modules.emplace_back(mod); |
| } |
| break; |
| case 'b': |
| blocklist = true; |
| break; |
| case 'd': |
| mod_dirs.emplace_back(optarg); |
| break; |
| case 'D': |
| check_mode(); |
| mode = ShowDependenciesMode; |
| break; |
| case 'h': |
| android::base::SetMinimumLogSeverity(android::base::INFO); |
| print_usage(); |
| return rv; |
| case 'l': |
| check_mode(); |
| mode = ListModulesMode; |
| break; |
| case 'q': |
| android::base::SetMinimumLogSeverity(android::base::WARNING); |
| break; |
| case 'r': |
| check_mode(); |
| mode = RemoveModulesMode; |
| break; |
| case 's': |
| syslog = true; |
| break; |
| case 'v': |
| if (android::base::GetMinimumLogSeverity() <= android::base::DEBUG) { |
| android::base::SetMinimumLogSeverity(android::base::VERBOSE); |
| } else { |
| android::base::SetMinimumLogSeverity(android::base::DEBUG); |
| } |
| break; |
| default: |
| LOG(ERROR) << "Unrecognized option: " << opt; |
| print_usage(); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| int parameter_count = 0; |
| for (opt = optind; opt < argc; opt++) { |
| if (!strchr(argv[opt], '=')) { |
| modules.emplace_back(argv[opt]); |
| } else { |
| parameter_count++; |
| if (module_parameters.empty()) { |
| module_parameters = argv[opt]; |
| } else { |
| module_parameters = module_parameters + " " + argv[opt]; |
| } |
| } |
| } |
| |
| if (mod_dirs.empty()) { |
| static constexpr auto LIB_MODULES_PREFIX = "/lib/modules/"; |
| dirent** kernel_dirs = NULL; |
| |
| int n = scandir(LIB_MODULES_PREFIX, &kernel_dirs, KernelVersionNameFilter, NULL); |
| if (n == -1) { |
| PLOG(ERROR) << "Failed to scan dir " << LIB_MODULES_PREFIX; |
| return EXIT_FAILURE; |
| } else if (n > 0) { |
| while (n--) { |
| mod_dirs.emplace_back(LIB_MODULES_PREFIX + std::string(kernel_dirs[n]->d_name)); |
| } |
| } |
| free(kernel_dirs); |
| |
| // Allow modules to be directly inside /lib/modules |
| mod_dirs.emplace_back(LIB_MODULES_PREFIX); |
| } |
| |
| LOG(DEBUG) << "mode is " << mode; |
| LOG(DEBUG) << "mod_dirs is: " << android::base::Join(mod_dirs, " "); |
| LOG(DEBUG) << "modules is: " << android::base::Join(modules, " "); |
| LOG(DEBUG) << "module parameters is: " << android::base::Join(module_parameters, " "); |
| |
| if (modules.empty()) { |
| if (mode == ListModulesMode) { |
| // emulate toybox modprobe list with no pattern (list all) |
| modules.emplace_back("*"); |
| } else { |
| LOG(ERROR) << "No modules given."; |
| print_usage(); |
| return EXIT_FAILURE; |
| } |
| } |
| if (parameter_count && modules.size() > 1) { |
| LOG(ERROR) << "Only one module may be loaded when specifying module parameters."; |
| print_usage(); |
| return EXIT_FAILURE; |
| } |
| |
| Modprobe m(mod_dirs, "modules.load", blocklist); |
| |
| for (const auto& module : modules) { |
| switch (mode) { |
| case AddModulesMode: |
| if (!m.LoadWithAliases(module, true, module_parameters)) { |
| PLOG(ERROR) << "Failed to load module " << module; |
| rv = EXIT_FAILURE; |
| } |
| break; |
| case RemoveModulesMode: |
| if (!m.Remove(module)) { |
| PLOG(ERROR) << "Failed to remove module " << module; |
| rv = EXIT_FAILURE; |
| } |
| break; |
| case ListModulesMode: { |
| std::vector<std::string> list = m.ListModules(module); |
| LOG(INFO) << android::base::Join(list, "\n"); |
| break; |
| } |
| case ShowDependenciesMode: { |
| std::vector<std::string> pre_deps; |
| std::vector<std::string> deps; |
| std::vector<std::string> post_deps; |
| if (!m.GetAllDependencies(module, &pre_deps, &deps, &post_deps)) { |
| rv = EXIT_FAILURE; |
| break; |
| } |
| LOG(INFO) << "Dependencies for " << module << ":"; |
| LOG(INFO) << "Soft pre-dependencies:"; |
| LOG(INFO) << android::base::Join(pre_deps, "\n"); |
| LOG(INFO) << "Hard dependencies:"; |
| LOG(INFO) << android::base::Join(deps, "\n"); |
| LOG(INFO) << "Soft post-dependencies:"; |
| LOG(INFO) << android::base::Join(post_deps, "\n"); |
| break; |
| } |
| default: |
| LOG(ERROR) << "Bad mode"; |
| rv = EXIT_FAILURE; |
| } |
| } |
| |
| return rv; |
| } |