blob: 0b0ca4cb6ef263f0166f4008c5fed45527da688b [file]
/*
* Copyright (C) 2025 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 <modprobe/module_dependency_graph.h>
#include <modprobe/utils.h>
#include <fnmatch.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
namespace android {
namespace modprobe {
bool Module::IsReady() const {
return unmet_dependencies.empty() && status == ModuleStatus::Pending;
}
void Module::MarkBlocklisted() {
status = ModuleStatus::Blocklisted;
for (const std::weak_ptr<Module>& rev_dep_weak : rev_unmet_dependencies) {
if (std::shared_ptr<Module> rev_dep = rev_dep_weak.lock()) {
rev_dep->MarkBlocklisted();
}
}
}
static std::unordered_map<std::string, std::string> BuildPathToName(
const std::unordered_map<std::string, std::vector<std::string>>& parsed_deps) {
std::unordered_map<std::string, std::string> path_to_name;
for (const auto& [mod, deps] : parsed_deps) {
CHECK(!deps.empty())
<< "The first element of a dependency list should be the module itself";
path_to_name.emplace(deps[0], mod);
}
return path_to_name;
}
std::vector<std::string> ModuleDependencyGraph::GetModuleNames(const std::string& module_name) {
std::vector<std::string> module_names = {CanonicalizeModulePath(module_name)};
for (const auto& [alias, aliased_name] : module_aliases_) {
if (fnmatch(alias.c_str(), module_name.c_str(), 0) == 0) {
module_names.push_back(CanonicalizeModulePath(aliased_name));
}
}
return module_names;
}
std::vector<std::shared_ptr<Module>> ModuleDependencyGraph::GetModules(
const std::string& module_name) {
const auto& module_names = GetModuleNames(module_name);
std::vector<std::shared_ptr<Module>> modules;
for (const auto& name : module_names) {
if (modules_.contains(name)) {
modules.push_back(modules_.at(name));
}
}
if (modules.empty()) {
LOG(ERROR) << "LMP: DependencyGraph: Unable to find a module for " << module_name
<< " in the .dep file, tried: " << base::Join(module_names, ",");
}
return modules;
}
static bool HasLoop(const std::shared_ptr<Module>& mod,
std::unordered_set<std::shared_ptr<Module>>& visited,
std::unordered_set<std::shared_ptr<Module>>& recursion_stack) {
visited.insert(mod);
recursion_stack.insert(mod);
for (const std::shared_ptr<Module>& dep : mod->unmet_dependencies) {
if (recursion_stack.count(dep)) {
return true;
}
if (!visited.count(dep)) {
if (HasLoop(dep, visited, recursion_stack)) {
return true;
}
}
}
recursion_stack.erase(mod);
return false;
}
static bool HasLoop(const std::unordered_map<std::string, std::shared_ptr<Module>>& modules) {
std::unordered_set<std::shared_ptr<Module>> visited;
std::unordered_set<std::shared_ptr<Module>> recursion_stack;
for (const auto& [_, mod] : modules) {
if (!visited.count(mod)) {
if (HasLoop(mod, visited, recursion_stack)) {
return true;
}
}
}
return false;
}
ModuleDependencyGraph::ModuleDependencyGraph(const ModuleConfig& config)
: path_to_name_(BuildPathToName(config.module_deps)), module_aliases_(config.module_aliases) {
for (const auto& [path, name] : path_to_name_) {
std::shared_ptr<Module> mod = std::make_shared<Module>();
mod->name = name;
mod->path = path;
modules_.emplace(name, mod);
}
for (const auto& [_, deps] : config.module_deps) {
CHECK(!deps.empty())
<< "The first element of a dependency list should be the module itself";
const std::string& mod_name = path_to_name_.at(deps[0]);
std::shared_ptr<Module> mod = modules_.at(mod_name);
for (size_t i = 1; i < deps.size(); i++) {
if (path_to_name_.contains(deps[i])) {
const std::string& dep_name = path_to_name_.at(deps[i]);
std::shared_ptr<Module> dep_module = modules_.at(dep_name);
AddUnmetDependency(mod, dep_module);
}
}
}
for (const auto& [mod_name, softdep] : config.module_pre_softdep) {
for (std::shared_ptr<Module> pre_softdep : GetModules(softdep)) {
std::shared_ptr<Module> mod = modules_.at(mod_name);
mod->pre_softdeps.insert(pre_softdep);
AddUnmetDependency(mod, pre_softdep);
}
}
for (const auto& [mod_name, softdep] : config.module_post_softdep) {
for (std::shared_ptr<Module> post_softdep : GetModules(softdep)) {
std::shared_ptr<Module> mod = modules_.at(mod_name);
mod->post_softdeps.insert(post_softdep);
AddUnmetDependency(post_softdep, mod);
}
}
for (const std::string& name : config.module_blocklist) {
for (std::shared_ptr<Module> mod : GetModules(name)) {
mod->MarkBlocklisted();
}
}
if (HasLoop(modules_)) {
LOG(ERROR) << "Dependency graph has a loop";
}
}
void ModuleDependencyGraph::AddUnmetDependency(std::shared_ptr<Module> mod,
std::shared_ptr<Module> dep_mod) {
mod->unmet_dependencies.insert(dep_mod);
dep_mod->rev_unmet_dependencies.insert(mod);
}
void ModuleDependencyGraph::RemoveUnmetDependency(std::shared_ptr<Module> mod,
std::shared_ptr<Module> dep_mod) {
mod->unmet_dependencies.erase(dep_mod);
dep_mod->rev_unmet_dependencies.erase(mod);
if (mod->IsReady()) {
ready_module_paths_.insert(mod->path);
}
}
void ModuleDependencyGraph::AddModule(const std::string& module_name) {
std::lock_guard<std::mutex> lock(graph_lock_);
for (std::shared_ptr<Module> mod : GetModules(module_name)) {
AddModuleLocked(mod);
}
}
void ModuleDependencyGraph::AddModuleLocked(std::shared_ptr<Module>& mod) {
for (std::weak_ptr<Module> softdep_weak : mod->post_softdeps) {
if (std::shared_ptr<Module> softdep = softdep_weak.lock()) {
AddModuleLocked(softdep);
}
}
switch (mod->status) {
case ModuleStatus::LoadFailed:
LOG(VERBOSE) << "LMP: DependencyGraph: Retrying previously failed module " << mod->name;
FALLTHROUGH_INTENDED;
case ModuleStatus::NotRequested:
mod->status = ModuleStatus::Pending;
if (mod->IsReady()) {
ready_module_paths_.insert(mod->path);
}
for (std::shared_ptr<Module> dep : mod->unmet_dependencies) {
AddModuleLocked(dep);
}
break;
case ModuleStatus::Blocklisted:
LOG(VERBOSE) << "LMP: DependencyGraph: Skipping blocklisted module " << mod->name;
break;
case ModuleStatus::Loaded:
LOG(VERBOSE) << "LMP: DependencyGraph: Module " << mod->name << " already loaded";
break;
case ModuleStatus::Pending:
break;
}
}
void ModuleDependencyGraph::MarkModuleLoaded(const std::string& module_path) {
MarkModuleLoadResult(module_path, false);
}
void ModuleDependencyGraph::MarkModuleLoadFailed(const std::string& module_path) {
MarkModuleLoadResult(module_path, true);
}
void ModuleDependencyGraph::MarkModuleLoadResult(const std::string& module_path, bool failed) {
std::lock_guard<std::mutex> lock(graph_lock_);
const std::string& mod_name = path_to_name_.at(module_path);
std::shared_ptr<Module> mod = modules_.at(mod_name);
CHECK(mod->status == ModuleStatus::Pending || mod->status == ModuleStatus::LoadFailed);
mod->status = failed ? ModuleStatus::LoadFailed : ModuleStatus::Loaded;
Module::WeakModuleSet rev_deps = mod->rev_unmet_dependencies;
for (const std::weak_ptr<Module>& rev_dep_weak : rev_deps) {
if (std::shared_ptr<Module> rev_dep = rev_dep_weak.lock()) {
// A hard-dep is satisfied only if the module is successfully loaded, but a soft-dep is
// satisfied regardless of the loading being successful or failed.
if (!failed || rev_dep->pre_softdeps.contains(mod)) {
RemoveUnmetDependency(rev_dep, mod);
}
}
}
}
std::unordered_set<std::string> ModuleDependencyGraph::PopReadyModules() {
std::lock_guard<std::mutex> lock(graph_lock_);
std::unordered_set<std::string> ready;
ready.swap(ready_module_paths_);
return ready;
}
} // namespace modprobe
} // namespace android