diff --git a/init/Android.bp b/init/Android.bp index fa0a35c48..9c1ed1522 100644 --- a/init/Android.bp +++ b/init/Android.bp @@ -63,6 +63,7 @@ cc_defaults { "libavb", "libc++fs", "libcgrouprc_format", + "libmodprobe", "libprotobuf-cpp-lite", "libpropertyinfoserializer", "libpropertyinfoparser", diff --git a/init/Android.mk b/init/Android.mk index 0a3e8c7c7..b24f7577b 100644 --- a/init/Android.mk +++ b/init/Android.mk @@ -110,6 +110,7 @@ LOCAL_STATIC_LIBRARIES := \ libdexfile_support \ libunwindstack \ libbacktrace \ + libmodprobe \ LOCAL_SANITIZE := signed-integer-overflow # First stage init is weird: it may start without stdout/stderr, and no /proc. diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp index 5d64f415d..17387e200 100644 --- a/init/first_stage_init.cpp +++ b/init/first_stage_init.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include "debug_ramdisk.h" @@ -192,6 +193,11 @@ int FirstStageMain(int argc, char** argv) { old_root_dir.reset(); } + Modprobe m({"/lib/modules"}); + if (!m.LoadListedModules()) { + LOG(FATAL) << "Failed to load kernel modules"; + } + if (ForceNormalBoot()) { mkdir("/first_stage_ramdisk", 0755); // SwitchRoot() must be called with a mount point as the target, so we bind mount the diff --git a/init/modalias_handler.cpp b/init/modalias_handler.cpp index a51115628..07b05d8b4 100644 --- a/init/modalias_handler.cpp +++ b/init/modalias_handler.cpp @@ -16,147 +16,20 @@ #include "modalias_handler.h" -#include -#include - -#include -#include #include #include -#include -#include -#include - -#include "parser.h" +#include namespace android { namespace init { -Result ModaliasHandler::ParseDepCallback(std::vector&& args) { - std::vector deps; - - // Set first item as our modules path - std::string::size_type pos = args[0].find(':'); - if (pos != std::string::npos) { - deps.emplace_back(args[0].substr(0, pos)); - } else { - return Error() << "dependency lines must start with name followed by ':'"; - } - - // Remaining items are dependencies of our module - for (auto arg = args.begin() + 1; arg != args.end(); ++arg) { - deps.push_back(*arg); - } - - // Key is striped module name to match names in alias file - std::size_t start = args[0].find_last_of('/'); - std::size_t end = args[0].find(".ko:"); - if ((end - start) <= 1) return Error() << "malformed dependency line"; - auto mod_name = args[0].substr(start + 1, (end - start) - 1); - // module names can have '-', but their file names will have '_' - std::replace(mod_name.begin(), mod_name.end(), '-', '_'); - this->module_deps_[mod_name] = deps; - - return {}; -} - -Result ModaliasHandler::ParseAliasCallback(std::vector&& args) { - auto it = args.begin(); - const std::string& type = *it++; - - if (type != "alias") { - return Error() << "we only handle alias lines, got: " << type; - } - - if (args.size() != 3) { - return Error() << "alias lines must have 3 entries"; - } - - std::string& alias = *it++; - std::string& module_name = *it++; - this->module_aliases_.emplace_back(alias, module_name); - - return {}; -} - -ModaliasHandler::ModaliasHandler() { - using namespace std::placeholders; - - static const std::string base_paths[] = { - "/vendor/lib/modules/", - "/lib/modules/", - "/odm/lib/modules/", - }; - - Parser alias_parser; - auto alias_callback = std::bind(&ModaliasHandler::ParseAliasCallback, this, _1); - alias_parser.AddSingleLineParser("alias", alias_callback); - for (const auto& base_path : base_paths) alias_parser.ParseConfig(base_path + "modules.alias"); - - Parser dep_parser; - auto dep_callback = std::bind(&ModaliasHandler::ParseDepCallback, this, _1); - dep_parser.AddSingleLineParser("", dep_callback); - for (const auto& base_path : base_paths) dep_parser.ParseConfig(base_path + "modules.dep"); -} - -Result ModaliasHandler::Insmod(const std::string& path_name, const std::string& args) { - base::unique_fd fd( - TEMP_FAILURE_RETRY(open(path_name.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC))); - if (fd == -1) return ErrnoError() << "Could not open module '" << path_name << "'"; - - int ret = syscall(__NR_finit_module, fd.get(), args.c_str(), 0); - if (ret != 0) { - if (errno == EEXIST) { - // Module already loaded - return {}; - } - return ErrnoError() << "Failed to insmod '" << path_name << "' with args '" << args << "'"; - } - - LOG(INFO) << "Loaded kernel module " << path_name; - return {}; -} - -Result ModaliasHandler::InsmodWithDeps(const std::string& module_name, - const std::string& args) { - if (module_name.empty()) { - return Error() << "Need valid module name"; - } - - auto it = module_deps_.find(module_name); - if (it == module_deps_.end()) { - return Error() << "Module '" << module_name << "' not in dependency file"; - } - auto& dependencies = it->second; - - // load module dependencies in reverse order - for (auto dep = dependencies.rbegin(); dep != dependencies.rend() - 1; ++dep) { - if (auto result = Insmod(*dep, ""); !result) return result; - } - - // load target module itself with args - return Insmod(dependencies[0], args); -} +ModaliasHandler::ModaliasHandler(const std::vector& base_paths) + : modprobe_(base_paths) {} void ModaliasHandler::HandleUevent(const Uevent& uevent) { if (uevent.modalias.empty()) return; - - for (const auto& [alias, module] : module_aliases_) { - if (fnmatch(alias.c_str(), uevent.modalias.c_str(), 0) != 0) continue; // Keep looking - - LOG(DEBUG) << "Loading kernel module '" << module << "' for alias '" << uevent.modalias - << "'"; - - if (auto result = InsmodWithDeps(module, ""); !result) { - LOG(ERROR) << "Cannot load module: " << result.error(); - // try another one since there may be another match - continue; - } - - // loading was successful - return; - } + modprobe_.LoadWithAliases(uevent.modalias, true); } } // namespace init diff --git a/init/modalias_handler.h b/init/modalias_handler.h index 7d0afded5..ce89a059f 100644 --- a/init/modalias_handler.h +++ b/init/modalias_handler.h @@ -17,10 +17,10 @@ #pragma once #include -#include #include -#include "result.h" +#include + #include "uevent.h" #include "uevent_handler.h" @@ -29,20 +29,13 @@ namespace init { class ModaliasHandler : public UeventHandler { public: - ModaliasHandler(); + ModaliasHandler(const std::vector&); virtual ~ModaliasHandler() = default; void HandleUevent(const Uevent& uevent) override; private: - Result InsmodWithDeps(const std::string& module_name, const std::string& args); - Result Insmod(const std::string& path_name, const std::string& args); - - Result ParseDepCallback(std::vector&& args); - Result ParseAliasCallback(std::vector&& args); - - std::vector> module_aliases_; - std::unordered_map> module_deps_; + Modprobe modprobe_; }; } // namespace init diff --git a/init/ueventd.cpp b/init/ueventd.cpp index d700c461c..f550bc260 100644 --- a/init/ueventd.cpp +++ b/init/ueventd.cpp @@ -251,7 +251,8 @@ int ueventd_main(int argc, char** argv) { std::move(ueventd_configuration.firmware_directories))); if (ueventd_configuration.enable_modalias_handling) { - uevent_handlers.emplace_back(std::make_unique()); + std::vector base_paths = {"/odm/lib/modules", "/vendor/lib/modules"}; + uevent_handlers.emplace_back(std::make_unique(base_paths)); } UeventListener uevent_listener(ueventd_configuration.uevent_socket_rcvbuf_size); diff --git a/libmodprobe/Android.bp b/libmodprobe/Android.bp new file mode 100644 index 000000000..a2824d124 --- /dev/null +++ b/libmodprobe/Android.bp @@ -0,0 +1,30 @@ +cc_library_static { + name: "libmodprobe", + cflags: [ + "-Werror", + ], + recovery_available: true, + srcs: [ + "libmodprobe.cpp", + "libmodprobe_ext.cpp", + ], + shared_libs: [ + "libbase", + ], + export_include_dirs: ["include/"], +} + +cc_test { + name: "libmodprobe_tests", + cflags: ["-Werror"], + shared_libs: [ + "libbase", + ], + local_include_dirs: ["include/"], + srcs: [ + "libmodprobe_test.cpp", + "libmodprobe.cpp", + "libmodprobe_ext_test.cpp", + ], + test_suites: ["device-tests"], +} diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h new file mode 100644 index 000000000..0ec766aaa --- /dev/null +++ b/libmodprobe/include/modprobe/modprobe.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 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. + */ + +#pragma once + +#include +#include +#include + +class Modprobe { + public: + Modprobe(const std::vector&); + + bool LoadListedModules(); + bool LoadWithAliases(const std::string& module_name, bool strict); + + private: + std::string MakeCanonical(const std::string& module_path); + bool InsmodWithDeps(const std::string& module_name); + bool Insmod(const std::string& path_name); + std::vector GetDependencies(const std::string& module); + bool ModuleExists(const std::string& module_name); + + bool ParseDepCallback(const std::string& base_path, const std::vector& args); + bool ParseAliasCallback(const std::vector& args); + bool ParseSoftdepCallback(const std::vector& args); + bool ParseLoadCallback(const std::vector& args); + bool ParseOptionsCallback(const std::vector& args); + void ParseCfg(const std::string& cfg, std::function&)> f); + + std::vector> module_aliases_; + std::unordered_map> module_deps_; + std::vector> module_pre_softdep_; + std::vector> module_post_softdep_; + std::vector module_load_; + std::unordered_map module_options_; +}; diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp new file mode 100644 index 000000000..01cf2e354 --- /dev/null +++ b/libmodprobe/libmodprobe.cpp @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2018 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 + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +std::string Modprobe::MakeCanonical(const std::string& module_path) { + auto start = module_path.find_last_of('/'); + if (start == std::string::npos) { + start = 0; + } else { + start += 1; + } + auto end = module_path.size(); + if (android::base::EndsWith(module_path, ".ko")) { + end -= 3; + } + if ((end - start) <= 1) { + LOG(ERROR) << "malformed module name: " << module_path; + return ""; + } + std::string module_name = module_path.substr(start, end - start); + // module names can have '-', but their file names will have '_' + std::replace(module_name.begin(), module_name.end(), '-', '_'); + return module_name; +} + +bool Modprobe::ParseDepCallback(const std::string& base_path, + const std::vector& args) { + std::vector deps; + std::string prefix = ""; + + // Set first item as our modules path + std::string::size_type pos = args[0].find(':'); + if (args[0][0] != '/') { + prefix = base_path + "/"; + } + if (pos != std::string::npos) { + deps.emplace_back(prefix + args[0].substr(0, pos)); + } else { + LOG(ERROR) << "dependency lines must start with name followed by ':'"; + } + + // Remaining items are dependencies of our module + for (auto arg = args.begin() + 1; arg != args.end(); ++arg) { + if ((*arg)[0] != '/') { + prefix = base_path + "/"; + } else { + prefix = ""; + } + deps.push_back(prefix + *arg); + } + + std::string canonical_name = MakeCanonical(args[0].substr(0, pos)); + if (canonical_name.empty()) { + return false; + } + this->module_deps_[canonical_name] = deps; + + return true; +} + +bool Modprobe::ParseAliasCallback(const std::vector& args) { + auto it = args.begin(); + const std::string& type = *it++; + + if (type != "alias") { + LOG(ERROR) << "non-alias line encountered in modules.alias, found " << type; + return false; + } + + if (args.size() != 3) { + LOG(ERROR) << "alias lines in modules.alias must have 3 entries, not " << args.size(); + return false; + } + + const std::string& alias = *it++; + const std::string& module_name = *it++; + this->module_aliases_.emplace_back(alias, module_name); + + return true; +} + +bool Modprobe::ParseSoftdepCallback(const std::vector& args) { + auto it = args.begin(); + const std::string& type = *it++; + std::string state = ""; + + if (type != "softdep") { + LOG(ERROR) << "non-softdep line encountered in modules.softdep, found " << type; + return false; + } + + if (args.size() < 4) { + LOG(ERROR) << "softdep lines in modules.softdep must have at least 4 entries"; + return false; + } + + const std::string& module = *it++; + while (it != args.end()) { + const std::string& token = *it++; + if (token == "pre:" || token == "post:") { + state = token; + continue; + } + if (state == "") { + LOG(ERROR) << "malformed modules.softdep at token " << token; + return false; + } + if (state == "pre:") { + this->module_pre_softdep_.emplace_back(module, token); + } else { + this->module_post_softdep_.emplace_back(module, token); + } + } + + return true; +} + +bool Modprobe::ParseLoadCallback(const std::vector& args) { + auto it = args.begin(); + const std::string& module = *it++; + + const std::string& canonical_name = MakeCanonical(module); + if (canonical_name.empty()) { + return false; + } + this->module_load_.emplace_back(canonical_name); + + return true; +} + +bool Modprobe::ParseOptionsCallback(const std::vector& args) { + auto it = args.begin(); + const std::string& type = *it++; + + if (type != "options") { + LOG(ERROR) << "non-options line encountered in modules.options"; + return false; + } + + if (args.size() < 2) { + LOG(ERROR) << "lines in modules.options must have at least 2 entries, not " << args.size(); + return false; + } + + const std::string& module = *it++; + std::string options = ""; + + const std::string& canonical_name = MakeCanonical(module); + if (canonical_name.empty()) { + return false; + } + + while (it != args.end()) { + options += *it++; + if (it != args.end()) { + options += " "; + } + } + + auto [unused, inserted] = this->module_options_.emplace(canonical_name, options); + if (!inserted) { + LOG(ERROR) << "multiple options lines present for module " << module; + return false; + } + return true; +} + +void Modprobe::ParseCfg(const std::string& cfg, + std::function&)> f) { + std::string cfg_contents; + if (!android::base::ReadFileToString(cfg, &cfg_contents, false)) { + return; + } + + std::vector lines = android::base::Split(cfg_contents, "\n"); + for (const std::string line : lines) { + if (line.empty() || line[0] == '#') { + continue; + } + const std::vector args = android::base::Split(line, " "); + if (args.empty()) continue; + f(args); + } + return; +} + +Modprobe::Modprobe(const std::vector& base_paths) { + using namespace std::placeholders; + + for (const auto& base_path : base_paths) { + auto alias_callback = std::bind(&Modprobe::ParseAliasCallback, this, _1); + ParseCfg(base_path + "/modules.alias", alias_callback); + + auto dep_callback = std::bind(&Modprobe::ParseDepCallback, this, base_path, _1); + ParseCfg(base_path + "/modules.dep", dep_callback); + + auto softdep_callback = std::bind(&Modprobe::ParseSoftdepCallback, this, _1); + ParseCfg(base_path + "/modules.softdep", softdep_callback); + + auto load_callback = std::bind(&Modprobe::ParseLoadCallback, this, _1); + ParseCfg(base_path + "/modules.load", load_callback); + + auto options_callback = std::bind(&Modprobe::ParseOptionsCallback, this, _1); + ParseCfg(base_path + "/modules.options", options_callback); + } +} + +std::vector Modprobe::GetDependencies(const std::string& module) { + auto it = module_deps_.find(module); + if (it == module_deps_.end()) { + return {}; + } + return it->second; +} + +bool Modprobe::InsmodWithDeps(const std::string& module_name) { + if (module_name.empty()) { + LOG(ERROR) << "Need valid module name, given: " << module_name; + return false; + } + + auto dependencies = GetDependencies(module_name); + if (dependencies.empty()) { + LOG(ERROR) << "Module " << module_name << " not in dependency file"; + return false; + } + + // load module dependencies in reverse order + for (auto dep = dependencies.rbegin(); dep != dependencies.rend() - 1; ++dep) { + const std::string& canonical_name = MakeCanonical(*dep); + if (canonical_name.empty()) { + return false; + } + if (!LoadWithAliases(canonical_name, true)) { + return false; + } + } + + // try to load soft pre-dependencies + for (const auto& [module, softdep] : module_pre_softdep_) { + if (module_name == module) { + LoadWithAliases(softdep, false); + } + } + + // load target module itself with args + if (!Insmod(dependencies[0])) { + return false; + } + + // try to load soft post-dependencies + for (const auto& [module, softdep] : module_post_softdep_) { + if (module_name == module) { + LoadWithAliases(softdep, false); + } + } + + return true; +} + +bool Modprobe::LoadWithAliases(const std::string& module_name, bool strict) { + std::set modules_to_load = {module_name}; + bool module_loaded = false; + + // use aliases to expand list of modules to load (multiple modules + // may alias themselves to the requested name) + for (const auto& [alias, aliased_module] : module_aliases_) { + if (fnmatch(alias.c_str(), module_name.c_str(), 0) != 0) continue; + modules_to_load.emplace(aliased_module); + } + + // attempt to load all modules aliased to this name + for (const auto& module : modules_to_load) { + if (!ModuleExists(module)) continue; + if (InsmodWithDeps(module)) module_loaded = true; + } + + if (strict && !module_loaded) { + LOG(ERROR) << "LoadWithAliases did not find a module for " << module_name; + return false; + } + return true; +} + +bool Modprobe::LoadListedModules() { + for (const auto& module : module_load_) { + if (!LoadWithAliases(module, true)) { + return false; + } + } + return true; +} diff --git a/libmodprobe/libmodprobe_ext.cpp b/libmodprobe/libmodprobe_ext.cpp new file mode 100644 index 000000000..5f3a04da8 --- /dev/null +++ b/libmodprobe/libmodprobe_ext.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 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 +#include + +#include +#include + +#include + +bool Modprobe::Insmod(const std::string& path_name) { + android::base::unique_fd fd( + TEMP_FAILURE_RETRY(open(path_name.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC))); + if (fd == -1) { + LOG(ERROR) << "Could not open module '" << path_name << "'"; + return false; + } + + std::string options = ""; + auto options_iter = module_options_.find(MakeCanonical(path_name)); + if (options_iter != module_options_.end()) { + options = options_iter->second; + } + + LOG(INFO) << "Loading module " << path_name << " with args \"" << options << "\""; + int ret = syscall(__NR_finit_module, fd.get(), options.c_str(), 0); + if (ret != 0) { + if (errno == EEXIST) { + // Module already loaded + return true; + } + LOG(ERROR) << "Failed to insmod '" << path_name << "' with args '" << options << "'"; + return false; + } + + LOG(INFO) << "Loaded kernel module " << path_name; + return true; +} + +bool Modprobe::ModuleExists(const std::string& module_name) { + struct stat fileStat; + auto deps = GetDependencies(module_name); + if (deps.empty()) { + // missing deps can happen in the case of an alias + return false; + } + if (stat(deps.front().c_str(), &fileStat)) { + return false; + } + if (!S_ISREG(fileStat.st_mode)) { + return false; + } + return true; +} diff --git a/libmodprobe/libmodprobe_ext_test.cpp b/libmodprobe/libmodprobe_ext_test.cpp new file mode 100644 index 000000000..0f073cb5c --- /dev/null +++ b/libmodprobe/libmodprobe_ext_test.cpp @@ -0,0 +1,61 @@ +/* + * 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 +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include "libmodprobe_test.h" + +bool Modprobe::Insmod(const std::string& path_name) { + auto deps = GetDependencies(MakeCanonical(path_name)); + if (deps.empty()) { + return false; + } + if (std::find(test_modules.begin(), test_modules.end(), deps.front()) == test_modules.end()) { + return false; + } + for (auto it = modules_loaded.begin(); it != modules_loaded.end(); ++it) { + if (android::base::StartsWith(*it, path_name)) { + return true; + } + } + std::string options; + auto options_iter = module_options_.find(MakeCanonical(path_name)); + if (options_iter != module_options_.end()) { + options = " " + options_iter->second; + } + modules_loaded.emplace_back(path_name + options); + return true; +} + +bool Modprobe::ModuleExists(const std::string& module_name) { + auto deps = GetDependencies(module_name); + if (deps.empty()) { + // missing deps can happen in the case of an alias + return false; + } + return std::find(test_modules.begin(), test_modules.end(), deps.front()) != test_modules.end(); +} diff --git a/libmodprobe/libmodprobe_test.cpp b/libmodprobe/libmodprobe_test.cpp new file mode 100644 index 000000000..481658d14 --- /dev/null +++ b/libmodprobe/libmodprobe_test.cpp @@ -0,0 +1,134 @@ +/* + * 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 + +#include +#include +#include +#include + +#include + +#include "libmodprobe_test.h" + +// Used by libmodprobe_ext_test to check if requested modules are present. +std::vector test_modules; + +// Used by libmodprobe_ext_test to report which modules would have been loaded. +std::vector modules_loaded; + +TEST(libmodprobe, Test) { + test_modules = { + "/test1.ko", "/test2.ko", "/test3.ko", "/test4.ko", "/test5.ko", + "/test6.ko", "/test7.ko", "/test8.ko", "/test9.ko", "/test10.ko", + "/test11.ko", "/test12.ko", "/test13.ko", "/test14.ko", "/test15.ko", + }; + + std::vector expected_modules_loaded = { + "/test14.ko", + "/test15.ko", + "/test3.ko", + "/test4.ko", + "/test1.ko", + "/test6.ko", + "/test2.ko", + "/test5.ko", + "/test8.ko", + "/test7.ko param1=4", + "/test9.ko param_x=1 param_y=2 param_z=3", + "/test10.ko", + "/test12.ko", + "/test11.ko", + "/test13.ko", + }; + + const std::string modules_dep = + "test1.ko:\n" + "test2.ko:\n" + "test3.ko:\n" + "test4.ko: test3.ko\n" + "test5.ko: test2.ko test6.ko\n" + "test6.ko:\n" + "test7.ko:\n" + "test8.ko:\n" + "test9.ko:\n" + "test10.ko:\n" + "test11.ko:\n" + "test12.ko:\n" + "test13.ko:\n" + "test14.ko:\n" + "test15.ko:\n"; + + const std::string modules_softdep = + "softdep test7 pre: test8\n" + "softdep test9 post: test10\n" + "softdep test11 pre: test12 post: test13\n" + "softdep test3 pre: test141516\n"; + + const std::string modules_alias = + "# Aliases extracted from modules themselves.\n" + "\n" + "alias test141516 test14\n" + "alias test141516 test15\n" + "alias test141516 test16\n"; + + const std::string modules_options = + "options test7.ko param1=4\n" + "options test9.ko param_x=1 param_y=2 param_z=3\n" + "options test100.ko param_1=1\n"; + + const std::string modules_load = + "test4.ko\n" + "test1.ko\n" + "test3.ko\n" + "test5.ko\n" + "test7.ko\n" + "test9.ko\n" + "test11.ko\n"; + + TemporaryDir dir; + ASSERT_TRUE(android::base::WriteStringToFile( + modules_alias, std::string(dir.path) + "/modules.alias", 0600, getuid(), getgid())); + + ASSERT_TRUE(android::base::WriteStringToFile( + modules_dep, std::string(dir.path) + "/modules.dep", 0600, getuid(), getgid())); + ASSERT_TRUE(android::base::WriteStringToFile( + modules_softdep, std::string(dir.path) + "/modules.softdep", 0600, getuid(), getgid())); + ASSERT_TRUE(android::base::WriteStringToFile( + modules_options, std::string(dir.path) + "/modules.options", 0600, getuid(), getgid())); + ASSERT_TRUE(android::base::WriteStringToFile( + modules_load, std::string(dir.path) + "/modules.load", 0600, getuid(), getgid())); + + for (auto i = test_modules.begin(); i != test_modules.end(); ++i) { + *i = dir.path + *i; + } + + Modprobe m({dir.path}); + EXPECT_TRUE(m.LoadListedModules()); + + GTEST_LOG_(INFO) << "Expected modules loaded (in order):"; + for (auto i = expected_modules_loaded.begin(); i != expected_modules_loaded.end(); ++i) { + *i = dir.path + *i; + GTEST_LOG_(INFO) << "\"" << *i << "\""; + } + GTEST_LOG_(INFO) << "Actual modules loaded (in order):"; + for (auto i = modules_loaded.begin(); i != modules_loaded.end(); ++i) { + GTEST_LOG_(INFO) << "\"" << *i << "\""; + } + + EXPECT_TRUE(modules_loaded == expected_modules_loaded); +} diff --git a/libmodprobe/libmodprobe_test.h b/libmodprobe/libmodprobe_test.h new file mode 100644 index 000000000..a001b69a7 --- /dev/null +++ b/libmodprobe/libmodprobe_test.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +extern std::vector test_modules; +extern std::vector modules_loaded;