#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.stop_subscribe = resolve( out.dl_handle, "bambu_network_stop_subscribe"); 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.user_logout = resolve(out.dl_handle, "bambu_network_user_logout"); out.build_login_cmd = resolve(out.dl_handle, "bambu_network_build_login_cmd"); out.build_logout_cmd = resolve(out.dl_handle, "bambu_network_build_logout_cmd"); out.build_login_info = resolve(out.dl_handle, "bambu_network_build_login_info"); out.get_user_id = resolve(out.dl_handle, "bambu_network_get_user_id"); out.connect_server = resolve(out.dl_handle, "bambu_network_connect_server"); out.is_server_connected = resolve(out.dl_handle, "bambu_network_is_server_connected"); out.refresh_connection = resolve(out.dl_handle, "bambu_network_refresh_connection"); out.bind_detect = resolve_required(out.dl_handle, "bambu_network_bind_detect", so_path); out.query_bind_status = resolve(out.dl_handle, "bambu_network_query_bind_status"); out.request_bind_ticket = resolve(out.dl_handle, "bambu_network_request_bind_ticket"); out.bind = resolve(out.dl_handle, "bambu_network_bind"); // 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"); out.update_cert = resolve(out.dl_handle, "bambu_network_update_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"); out.start_print = resolve( out.dl_handle, "bambu_network_start_print"); out.get_studio_info_url = resolve( out.dl_handle, "bambu_network_get_studio_info_url"); out.get_my_message = resolve( out.dl_handle, "bambu_network_get_my_message"); out.check_user_task_report = resolve( out.dl_handle, "bambu_network_check_user_task_report"); out.get_task_plate_index = resolve( out.dl_handle, "bambu_network_get_task_plate_index"); out.get_slice_info = resolve( out.dl_handle, "bambu_network_get_slice_info"); out.get_subtask = resolve( out.dl_handle, "bambu_network_get_subtask"); out.get_model_mall_detail_url = resolve( out.dl_handle, "bambu_network_get_model_mall_detail_url"); out.get_model_mall_rating = resolve( out.dl_handle, "bambu_network_get_model_mall_rating"); out.put_model_mall_rating = resolve( out.dl_handle, "bambu_network_put_model_mall_rating"); out.get_mw_user_preference = resolve( out.dl_handle, "bambu_network_get_mw_user_preference"); out.get_mw_user_4ulist = resolve( out.dl_handle, "bambu_network_get_mw_user_4ulist"); out.get_filament_spools = resolve( out.dl_handle, "bambu_network_get_filament_spools"); out.get_filament_config = resolve( out.dl_handle, "bambu_network_get_filament_config"); #if ABI_VERSION >= 0x020801 out.sync_ams_filaments = resolve( out.dl_handle, "bambu_network_sync_ams_filaments"); #endif #if ABI_VERSION >= 0x020802 out.sync_slot_mappings = resolve( out.dl_handle, "bambu_network_sync_slot_mappings"); #endif #if ABI_VERSION >= 0x020803 out.get_soft_match_pending = resolve( out.dl_handle, "bambu_network_get_soft_match_pending"); out.post_soft_match_pending = resolve( out.dl_handle, "bambu_network_post_soft_match_pending"); #endif 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