#include "plugin_loader.hpp" #include #ifdef _WIN32 #include #else #include #endif #include #include #include #include #include #include #include namespace dstalk { namespace json = boost::json; namespace fs = std::filesystem; PluginLoader::~PluginLoader() { shutdown_all(); } int PluginLoader::load_plugin(const char* path) { if (!path) return -1; // === Path validation (F-18.3-3) === { fs::path p = fs::absolute(fs::path(path)).lexically_normal(); // Extension check (case-insensitive) std::string ext = p.extension().string(); std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); bool valid_ext = false; #ifdef _WIN32 valid_ext = (ext == ".dll"); #elif defined(__APPLE__) valid_ext = (ext == ".dylib" || ext == ".so"); #else valid_ext = (ext == ".so"); #endif if (!valid_ext) { if (host_api_) { host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] '%s': invalid extension '%s', expected .dll/.so/.dylib", path, ext.c_str()); } return -1; } // Directory traversal check bool has_dotdot = false; bool in_plugins_dir = false; for (const auto& comp : p) { if (comp == "..") { has_dotdot = true; break; } if (comp == "plugins") { in_plugins_dir = true; } } if (has_dotdot) { if (host_api_) { host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] '%s': directory traversal rejected", path); } return -1; } // Directory constraint: must be under a 'plugins' directory or be a plain filename if (!in_plugins_dir && p.has_parent_path()) { if (host_api_) { host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] '%s': path not under a 'plugins' directory", path); } return -1; } } // 加载DLL #ifdef _WIN32 void* handle = LoadLibraryA(path); #else void* handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); #endif if (!handle) { if (host_api_) { #ifdef _WIN32 DWORD err = GetLastError(); host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] '%s': LoadLibraryA failed (error %lu)", path, (unsigned long)err); #else host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] '%s': dlopen failed: %s", path, dlerror()); #endif } return -1; } // 获取入口函数 #ifdef _WIN32 auto init_fn = (dstalk_plugin_init_fn)GetProcAddress( (HMODULE)handle, "dstalk_plugin_init"); #else auto init_fn = (dstalk_plugin_init_fn)dlsym(handle, "dstalk_plugin_init"); #endif if (!init_fn) { if (host_api_) { #ifdef _WIN32 DWORD err = GetLastError(); host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] '%s': GetProcAddress(dstalk_plugin_init) failed (error %lu)", path, (unsigned long)err); #else host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] '%s': dlsym(dstalk_plugin_init) failed: %s", path, dlerror()); #endif } #ifdef _WIN32 FreeLibrary((HMODULE)handle); #else dlclose(handle); #endif return -1; } // 调用入口函数获取插件信息 dstalk_plugin_info_t* info = nullptr; try { info = init_fn(); } catch (const std::exception& e) { if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] %s: init_fn threw: %s", path, e.what()); } catch (...) { if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] %s: init_fn threw unknown exception", path); } if (!info) { if (host_api_) { host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] '%s': dstalk_plugin_init returned null", path); } #ifdef _WIN32 FreeLibrary((HMODULE)handle); #else dlclose(handle); #endif return -1; } // 检查API版本兼容性 if (info->api_version != DSTALK_API_VERSION) { if (host_api_) { host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] '%s': API version mismatch (got %d, expected %d)", path, info->api_version, DSTALK_API_VERSION); } #ifdef _WIN32 FreeLibrary((HMODULE)handle); #else dlclose(handle); #endif return -1; } // 创建插件信息 int id = next_id_++; PluginInfo plugin; plugin.id = id; plugin.name = info->name ? info->name : ""; plugin.version = info->version ? info->version : ""; plugin.description = info->description ? info->description : ""; plugin.api_version = info->api_version; plugin.handle = handle; plugin.info = info; plugin.initialized = false; // 解析依赖 for (int i = 0; i < DSTALK_MAX_DEPS && info->dependencies[i]; i++) { plugin.dependencies.push_back(info->dependencies[i]); } plugins_[id] = std::move(plugin); return id; } int PluginLoader::unload_plugin(int plugin_id) { auto it = plugins_.find(plugin_id); if (it == plugins_.end()) return -1; PluginInfo& plugin = it->second; // 调用关闭回调 if (plugin.initialized && plugin.info->on_shutdown) { try { plugin.info->on_shutdown(); } catch (const std::exception& e) { if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' on_shutdown threw: %s", plugin.name.c_str(), e.what()); } catch (...) { if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' on_shutdown threw unknown exception", plugin.name.c_str()); } } // 卸载DLL #ifdef _WIN32 FreeLibrary((HMODULE)plugin.handle); #else dlclose(plugin.handle); #endif plugins_.erase(it); return 0; } std::string PluginLoader::list_plugins() const { json::array arr; for (const auto& [id, plugin] : plugins_) { json::object obj; obj["id"] = id; obj["name"] = plugin.name; obj["version"] = plugin.version; obj["description"] = plugin.description; obj["api_version"] = plugin.api_version; obj["initialized"] = plugin.initialized; json::array deps; for (const auto& dep : plugin.dependencies) { deps.push_back(json::value(dep)); } obj["dependencies"] = std::move(deps); arr.push_back(std::move(obj)); } return json::serialize(arr); } std::vector PluginLoader::topological_sort() const { // 构建名称到ID的映射 std::unordered_map name_to_id; for (const auto& [id, plugin] : plugins_) { name_to_id[plugin.name] = id; } // 计算入度 std::unordered_map in_degree; std::unordered_map> dependents; for (const auto& [id, plugin] : plugins_) { in_degree[id] = 0; } for (const auto& [id, plugin] : plugins_) { for (const auto& dep_name : plugin.dependencies) { auto it = name_to_id.find(dep_name); if (it != name_to_id.end()) { int dep_id = it->second; dependents[dep_id].push_back(id); in_degree[id]++; } } } // 拓扑排序(Kahn算法) std::queue queue; for (const auto& [id, degree] : in_degree) { if (degree == 0) { queue.push(id); } } std::vector sorted; while (!queue.empty()) { int id = queue.front(); queue.pop(); sorted.push_back(id); for (int dependent : dependents[id]) { if (--in_degree[dependent] == 0) { queue.push(dependent); } } } // 检查循环依赖 if (sorted.size() != plugins_.size()) { throw std::runtime_error("Circular dependency detected"); } return sorted; } int PluginLoader::validate_dependencies() const { int error_count = 0; // 构建名称到ID的映射 std::unordered_map name_to_id; for (const auto& [id, plugin] : plugins_) { name_to_id[plugin.name] = id; } // 检查1:缺失依赖(deps 引用的插件未加载) for (const auto& [id, plugin] : plugins_) { for (const auto& dep_name : plugin.dependencies) { if (name_to_id.find(dep_name) == name_to_id.end()) { if (host_api_) { host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s': dependency '%s' not found (plugin not loaded)", plugin.name.c_str(), dep_name.c_str()); } error_count++; } } } // 检查2:循环依赖(拓扑排序失败) try { topological_sort(); } catch (const std::runtime_error&) { if (host_api_) { host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] Circular dependency detected among loaded plugins"); } error_count++; } return error_count > 0 ? -1 : 0; } int PluginLoader::initialize_all(const dstalk_host_api_t* host_api) { if (!host_api) return -1; host_api_ = host_api; // 依赖合法性校验(log 错误但不 crash,继续初始化流程) if (validate_dependencies() != 0) { host_api->log(DSTALK_LOG_WARN, "[plugin_loader] Dependency validation failed; initialization may be incomplete"); } try { std::vector order = topological_sort(); std::unordered_set failed_names; int failed_count = 0; for (int id : order) { auto it = plugins_.find(id); if (it == plugins_.end()) continue; PluginInfo& plugin = it->second; if (plugin.initialized) continue; // 检查依赖是否已失败 bool dep_unavailable = false; for (const auto& dep_name : plugin.dependencies) { if (failed_names.count(dep_name)) { dep_unavailable = true; break; } } if (dep_unavailable) { host_api->log(DSTALK_LOG_WARN, "[plugin_loader] Plugin '%s' skipped: dependency unavailable", plugin.name.c_str()); failed_names.insert(plugin.name); failed_count++; continue; } if (plugin.info->on_init) { int result; try { result = plugin.info->on_init(host_api); } catch (const std::exception& e) { host_api->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' init threw: %s", plugin.name.c_str(), e.what()); failed_names.insert(plugin.name); failed_count++; continue; } catch (...) { host_api->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' init threw unknown exception", plugin.name.c_str()); failed_names.insert(plugin.name); failed_count++; continue; } if (result != 0) { host_api->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' init failed (code %d)", plugin.name.c_str(), result); failed_names.insert(plugin.name); failed_count++; continue; } } plugin.initialized = true; } return failed_count; } catch (const std::runtime_error&) { // 循环依赖 return -1; } catch (const std::exception&) { return -1; } } int PluginLoader::initialize_pending(const dstalk_host_api_t* host_api) { host_api_ = host_api; try { std::vector order = topological_sort(); int count = 0; for (int id : order) { auto it = plugins_.find(id); if (it == plugins_.end()) continue; PluginInfo& plugin = it->second; if (plugin.initialized) continue; if (plugin.info->on_init) { int result; try { result = plugin.info->on_init(host_api); } catch (const std::exception& e) { if (host_api) host_api->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' init threw: %s", plugin.name.c_str(), e.what()); return -1; } catch (...) { if (host_api) host_api->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' init threw unknown exception", plugin.name.c_str()); return -1; } if (result != 0) { return -1; } } plugin.initialized = true; count++; } return count; } catch (const std::exception&) { return -1; } } void PluginLoader::shutdown_all() { // 按逆序关闭 std::vector order; try { order = topological_sort(); std::reverse(order.begin(), order.end()); } catch (...) { // 如果排序失败,按任意顺序关闭 for (const auto& [id, _] : plugins_) { order.push_back(id); } } for (int id : order) { auto it = plugins_.find(id); if (it == plugins_.end()) continue; PluginInfo& plugin = it->second; if (plugin.initialized && plugin.info->on_shutdown) { try { plugin.info->on_shutdown(); } catch (const std::exception& e) { if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' shutdown threw: %s", plugin.name.c_str(), e.what()); } catch (...) { if (host_api_) host_api_->log(DSTALK_LOG_ERROR, "[plugin_loader] Plugin '%s' shutdown threw unknown exception", plugin.name.c_str()); } } plugin.initialized = false; } // 释放所有 DLL 句柄 for (auto& [id, plugin] : plugins_) { if (plugin.handle) { #ifdef _WIN32 FreeLibrary((HMODULE)plugin.handle); #else dlclose(plugin.handle); #endif plugin.handle = nullptr; } } plugins_.clear(); } const PluginInfo* PluginLoader::get_plugin(int plugin_id) const { auto it = plugins_.find(plugin_id); if (it == plugins_.end()) return nullptr; return &it->second; } } // namespace dstalk