blob: b0e76ea81bdc300241c73f1f09e9da723780e742 [file] [log] [blame]
/*
* 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 <fcntl.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>
#include <unistd.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;
}
std::string GetPageSizeSuffix() {
static const size_t page_size = sysconf(_SC_PAGE_SIZE);
return android::base::StringPrintf("_%zuk", page_size / 1024);
}
} // 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 modules_load_file;
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, fd;
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;
// Since libmodprobe doesn't fail when the modules load file
// doesn't exist, let's check that here so that we don't
// silently fail.
fd = open(optarg, O_RDONLY | O_CLOEXEC | O_BINARY);
if (fd == -1) {
PLOG(ERROR) << "Failed to open " << optarg;
return EXIT_FAILURE;
}
close(fd);
mod_dirs.emplace_back(android::base::Dirname(optarg));
modules_load_file = android::base::Basename(optarg);
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);
}
if (getpagesize() != 4096) {
struct utsname uts {};
if (uname(&uts)) {
PLOG(FATAL) << "Failed to get kernel version";
}
const auto module_dir = android::base::StringPrintf("/lib/modules/%s%s", uts.release,
GetPageSizeSuffix().c_str());
struct stat st {};
if (stat(module_dir.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
mod_dirs.clear();
mod_dirs.emplace_back(module_dir);
}
}
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) << "modules load file is: " << modules_load_file;
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 if (modules_load_file.empty()) {
LOG(ERROR) << "No modules given.";
print_usage();
return EXIT_FAILURE;
}
}
if (parameter_count && (modules.size() > 1 || !modules_load_file.empty())) {
LOG(ERROR) << "Only one module may be loaded when specifying module parameters.";
print_usage();
return EXIT_FAILURE;
}
Modprobe m(mod_dirs, modules_load_file.empty() ? "modules.load" : modules_load_file, blocklist);
if (mode == AddModulesMode && !modules_load_file.empty()) {
if (!m.LoadListedModules(false)) {
PLOG(ERROR) << "Failed to load all the modules from " << modules_load_file;
return EXIT_FAILURE;
}
/* Fall-through to load modules provided on the command line (if any)*/
}
for (const auto& module : modules) {
switch (mode) {
case AddModulesMode:
if (!m.LoadWithAliases(module, true, module_parameters)) {
if (m.IsBlocklisted(module)) continue;
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;
}