#include "plugin_loader.hpp" #include #include #include #include #include namespace obn::plugin_runner { namespace { template T resolve(void* handle, const char* name) { dlerror(); void* sym = dlsym(handle, name); const char* err = dlerror(); if (err) return nullptr; return reinterpret_cast(sym); } template T resolve_required(void* handle, const char* name, const std::string& so_path) { T fn = resolve(handle, name); if (!fn) { throw std::runtime_error("plugin '" + so_path + "' is missing required symbol '" + name + "'"); } return fn; } } // namespace PluginExports load(const std::string& so_path) { PluginExports out; out.so_path = so_path; // RTLD_NOW: surface unresolved-symbol errors right at dlopen rather // than at the first call to a stock function pointer (where the // resulting SIGSEGV would be much less informative). out.dl_handle = dlopen(so_path.c_str(), RTLD_NOW | RTLD_LOCAL); if (!out.dl_handle) { const char* err = dlerror(); throw std::runtime_error("dlopen(" + so_path + ") failed: " + (err ? err : "")); } out.create_agent = resolve_required(out.dl_handle, "bambu_network_create_agent", so_path); out.destroy_agent = resolve_required(out.dl_handle, "bambu_network_destroy_agent", so_path); out.init_log = resolve_required(out.dl_handle, "bambu_network_init_log", so_path); out.set_config_dir = resolve_required(out.dl_handle, "bambu_network_set_config_dir", so_path); out.set_cert_file = resolve_required(out.dl_handle, "bambu_network_set_cert_file", so_path); out.set_country_code = resolve_required(out.dl_handle, "bambu_network_set_country_code", so_path); out.start = resolve_required(out.dl_handle, "bambu_network_start", so_path); out.get_version = resolve_required(out.dl_handle, "bambu_network_get_version", so_path); out.connect_printer = resolve_required(out.dl_handle, "bambu_network_connect_printer", so_path); out.disconnect_printer = resolve_required(out.dl_handle, "bambu_network_disconnect_printer", so_path); out.set_on_printer_connected_fn = resolve_required( out.dl_handle, "bambu_network_set_on_printer_connected_fn", so_path); out.set_on_server_connected_fn = resolve_required( out.dl_handle, "bambu_network_set_on_server_connected_fn", so_path); out.set_on_http_error_fn = resolve_required( out.dl_handle, "bambu_network_set_on_http_error_fn", so_path); out.set_get_country_code_fn = resolve_required( out.dl_handle, "bambu_network_set_get_country_code_fn", so_path); out.set_on_subscribe_failure_fn = resolve_required( out.dl_handle, "bambu_network_set_on_subscribe_failure_fn", so_path); out.set_on_message_fn = resolve_required( out.dl_handle, "bambu_network_set_on_message_fn", so_path); out.set_on_user_message_fn = resolve_required( out.dl_handle, "bambu_network_set_on_user_message_fn", so_path); out.set_on_local_connect_fn = resolve_required( out.dl_handle, "bambu_network_set_on_local_connect_fn", so_path); out.set_on_local_message_fn = resolve_required( out.dl_handle, "bambu_network_set_on_local_message_fn", so_path); // queue_on_main is optional: some early plugins didn't have it. main.cpp // tolerates a null pointer by not installing the callback (default // upstream behaviour: dispatch on the network thread directly). out.set_queue_on_main_fn = resolve( out.dl_handle, "bambu_network_set_queue_on_main_fn"); out.start_subscribe = resolve_required( out.dl_handle, "bambu_network_start_subscribe", so_path); out.send_message_to_printer = resolve_required( out.dl_handle, "bambu_network_send_message_to_printer", so_path); out.set_on_ssdp_msg_fn = resolve_required( out.dl_handle, "bambu_network_set_on_ssdp_msg_fn", so_path); out.set_server_callback = resolve_required( out.dl_handle, "bambu_network_set_server_callback", so_path); out.set_extra_http_header = resolve_required( out.dl_handle, "bambu_network_set_extra_http_header", so_path); out.enable_multi_machine = resolve_required( out.dl_handle, "bambu_network_enable_multi_machine", so_path); out.start_discovery = resolve_required( out.dl_handle, "bambu_network_start_discovery", so_path); out.change_user = resolve_required(out.dl_handle, "bambu_network_change_user", so_path); out.is_user_login = resolve_required(out.dl_handle, "bambu_network_is_user_login", so_path); out.bind_detect = resolve_required(out.dl_handle, "bambu_network_bind_detect", so_path); // Optional in older builds. out.check_debug_consistent = resolve(out.dl_handle, "bambu_network_check_debug_consistent"); // install_device_cert is optional only on plugins old enough to predate // the per-printer cert provisioning (pre-02.04 era). Modern builds gate // privileged MQTT publish on it having run at least once. out.install_device_cert = resolve(out.dl_handle, "bambu_network_install_device_cert"); // Print-action entry points are all optional at load time; per-action // dispatch in main.cpp re-validates that the user-requested one was // actually resolved. out.start_send_gcode_to_sdcard = resolve( out.dl_handle, "bambu_network_start_send_gcode_to_sdcard"); out.start_local_print = resolve( out.dl_handle, "bambu_network_start_local_print"); out.start_sdcard_print = resolve( out.dl_handle, "bambu_network_start_sdcard_print"); out.start_local_print_with_record = resolve( out.dl_handle, "bambu_network_start_local_print_with_record"); if (out.get_version) { try { out.version = out.get_version(); } catch (...) { out.version.clear(); } } return out; } void unload(PluginExports& exports) { // Intentionally no `dlclose` here. Stock plugins keep singleton thread // pools alive past `destroy_agent` (boost::asio io_context, mqtt-cpp // worker, libcurl share, …) and rely on libc's atexit chain to drain // them at process exit. Forcing dlclose mid-process triggers their // global destructors against still-running threads and reliably // SIGABRTs. Process exit will reclaim the mapping anyway. exports = PluginExports{}; } } // namespace obn::plugin_runner