From ee5845063d81ba5da7ed68b4caf9d0920ebb78db Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 29 Sep 2023 14:07:53 +0200 Subject: [PATCH] dispatcher: support device-handler actions "device-add" and "device-delete" actions are called for device-handlers of generic devices. They differ from other actions in the following aspects: - only one script is invoked, the one with name specified by the device-handler property; - the script is searched in the "device" subdirectory; - since there is only one script executed, the result and error string from that script are returned by NM in the callback function. --- src/core/nm-dispatcher.c | 215 +++++++++++++----- src/core/nm-dispatcher.h | 15 ++ src/libnm-core-aux-extern/nm-dispatcher-api.h | 2 + src/nm-dispatcher/nm-dispatcher.c | 108 +++++++-- 4 files changed, 263 insertions(+), 77 deletions(-) diff --git a/src/core/nm-dispatcher.c b/src/core/nm-dispatcher.c index f2016f14e5..b6c3cd6dc4 100644 --- a/src/core/nm-dispatcher.c +++ b/src/core/nm-dispatcher.c @@ -52,18 +52,22 @@ /*****************************************************************************/ +/* Type for generic callback function; must be cast to either + * NMDispatcherFunc or NMDispatcherFuncDH before using. */ +typedef void (*NMDispatcherCallback)(void); + struct NMDispatcherCallId { - NMDispatcherFunc callback; - gpointer user_data; - const char *log_ifname; - const char *log_con_uuid; - GVariant *action_params; - gint64 start_at_msec; - NMDispatcherAction action; - guint idle_id; - guint32 request_id; - bool is_action2 : 1; - char extra_strings[]; + NMDispatcherCallback callback; + gpointer user_data; + const char *log_ifname; + const char *log_con_uuid; + GVariant *action_params; + gint64 start_at_msec; + NMDispatcherAction action; + guint idle_id; + guint32 request_id; + bool is_action2 : 1; + char extra_strings[]; }; /*****************************************************************************/ @@ -98,14 +102,20 @@ action_need_device(NMDispatcherAction action) return TRUE; } +static gboolean +action_is_device_handler(NMDispatcherAction action) +{ + return NM_IN_SET(action, NM_DISPATCHER_ACTION_DEVICE_ADD, NM_DISPATCHER_ACTION_DEVICE_DELETE); +} + static NMDispatcherCallId * -dispatcher_call_id_new(guint32 request_id, - gint64 start_at_msec, - NMDispatcherAction action, - NMDispatcherFunc callback, - gpointer user_data, - const char *log_ifname, - const char *log_con_uuid) +dispatcher_call_id_new(guint32 request_id, + gint64 start_at_msec, + NMDispatcherAction action, + NMDispatcherCallback callback, + gpointer user_data, + const char *log_ifname, + const char *log_con_uuid) { NMDispatcherCallId *call_id; gsize l_log_ifname; @@ -388,19 +398,42 @@ dispatch_result_to_string(DispatchResult result) g_assert_not_reached(); } +/* + * dispatcher_results_process: + * @action: the dispatcher action + * @request_id: request id + * @start_at_msec: the timestamp at which the dispatcher call was started + * @now_msec: the current timestamp in milliseconds + * @log_ifname: the interface name for logging + * @log_con_uuid: the connection UUID for logging + * @out_success: (out): for device-handler actions, the result of the script + * @out_error_msg: (out)(transfer full): for device-handler actions, the + * error message in case of failure + * @v_results: the GVariant containing the results to parse + * @is_action2: whether the D-Bus method is "Action2()" (or "Action()") + * + * Process the results of the dispatcher call. + * + */ static void -dispatcher_results_process(guint32 request_id, - gint64 start_at_msec, - gint64 now_msec, - const char *log_ifname, - const char *log_con_uuid, - GVariant *v_results, - gboolean is_action2) +dispatcher_results_process(NMDispatcherAction action, + guint32 request_id, + gint64 start_at_msec, + gint64 now_msec, + const char *log_ifname, + const char *log_con_uuid, + gboolean *out_success, + char **out_error_msg, + GVariant *v_results, + gboolean is_action2) { nm_auto_free_variant_iter GVariantIter *results = NULL; const char *script, *err; guint32 result; gsize n_children; + gboolean action_is_dh = action_is_device_handler(action); + + nm_assert(!action_is_dh || is_action2); if (is_action2) g_variant_get(v_results, "(a(susa{sv}))", &results); @@ -417,8 +450,13 @@ dispatcher_results_process(guint32 request_id, (int) ((now_msec - start_at_msec) % 1000), n_children); - if (n_children == 0) + if (n_children == 0) { + if (action_is_dh) { + NM_SET_OUT(out_success, FALSE); + NM_SET_OUT(out_error_msg, g_strdup("no result returned from dispatcher service")); + } return; + } while (TRUE) { gs_unref_variant GVariant *options = NULL; @@ -442,6 +480,11 @@ dispatcher_results_process(guint32 request_id, dispatch_result_to_string(result), err); } + if (action_is_dh) { + NM_SET_OUT(out_success, result == DISPATCH_RESULT_SUCCESS); + NM_SET_OUT(out_error_msg, g_strdup(err)); + break; + } } } @@ -452,6 +495,9 @@ dispatcher_done_cb(GObject *source, GAsyncResult *result, gpointer user_data) gs_free_error GError *error = NULL; NMDispatcherCallId *call_id = user_data; gint64 now_msec; + gboolean action_is_dh; + gboolean success = TRUE; + gs_free char *error_msg = NULL; nm_assert((gpointer) source == gl.dbus_connection); @@ -459,7 +505,7 @@ dispatcher_done_cb(GObject *source, GAsyncResult *result, gpointer user_data) ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error); - if (!ret && call_id->is_action2 + if (!ret && call_id->is_action2 && !action_is_device_handler(call_id->action) && g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { _LOG3D(call_id, "dispatcher service does not implement Action2() method, falling back to Action()"); @@ -493,38 +539,54 @@ dispatcher_done_cb(GObject *source, GAsyncResult *result, gpointer user_data) (int) ((now_msec - call_id->start_at_msec) % 1000), error->message); } else { - dispatcher_results_process(call_id->request_id, + dispatcher_results_process(call_id->action, + call_id->request_id, call_id->start_at_msec, now_msec, call_id->log_ifname, call_id->log_con_uuid, + &success, + &error_msg, ret, call_id->is_action2); } g_hash_table_remove(gl.requests, call_id); + action_is_dh = action_is_device_handler(call_id->action); - if (call_id->callback) - call_id->callback(call_id, call_id->user_data); + if (call_id->callback) { + if (action_is_dh) { + NMDispatcherFuncDH cb = (NMDispatcherFuncDH) call_id->callback; + + cb(call_id, call_id->user_data, success, error_msg); + } else { + NMDispatcherFunc cb = (NMDispatcherFunc) call_id->callback; + + cb(call_id, call_id->user_data); + } + } dispatcher_call_id_free(call_id); } -static const char *action_table[] = {[NM_DISPATCHER_ACTION_HOSTNAME] = NMD_ACTION_HOSTNAME, - [NM_DISPATCHER_ACTION_PRE_UP] = NMD_ACTION_PRE_UP, - [NM_DISPATCHER_ACTION_UP] = NMD_ACTION_UP, - [NM_DISPATCHER_ACTION_PRE_DOWN] = NMD_ACTION_PRE_DOWN, - [NM_DISPATCHER_ACTION_DOWN] = NMD_ACTION_DOWN, - [NM_DISPATCHER_ACTION_VPN_PRE_UP] = NMD_ACTION_VPN_PRE_UP, - [NM_DISPATCHER_ACTION_VPN_UP] = NMD_ACTION_VPN_UP, - [NM_DISPATCHER_ACTION_VPN_PRE_DOWN] = NMD_ACTION_VPN_PRE_DOWN, - [NM_DISPATCHER_ACTION_VPN_DOWN] = NMD_ACTION_VPN_DOWN, - [NM_DISPATCHER_ACTION_DHCP_CHANGE_4] = NMD_ACTION_DHCP4_CHANGE, - [NM_DISPATCHER_ACTION_DHCP_CHANGE_6] = NMD_ACTION_DHCP6_CHANGE, - [NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE] = - NMD_ACTION_CONNECTIVITY_CHANGE, - [NM_DISPATCHER_ACTION_REAPPLY] = NMD_ACTION_REAPPLY, - [NM_DISPATCHER_ACTION_DNS_CHANGE] = NMD_ACTION_DNS_CHANGE}; +static const char *action_table[] = { + [NM_DISPATCHER_ACTION_HOSTNAME] = NMD_ACTION_HOSTNAME, + [NM_DISPATCHER_ACTION_PRE_UP] = NMD_ACTION_PRE_UP, + [NM_DISPATCHER_ACTION_UP] = NMD_ACTION_UP, + [NM_DISPATCHER_ACTION_PRE_DOWN] = NMD_ACTION_PRE_DOWN, + [NM_DISPATCHER_ACTION_DOWN] = NMD_ACTION_DOWN, + [NM_DISPATCHER_ACTION_VPN_PRE_UP] = NMD_ACTION_VPN_PRE_UP, + [NM_DISPATCHER_ACTION_VPN_UP] = NMD_ACTION_VPN_UP, + [NM_DISPATCHER_ACTION_VPN_PRE_DOWN] = NMD_ACTION_VPN_PRE_DOWN, + [NM_DISPATCHER_ACTION_VPN_DOWN] = NMD_ACTION_VPN_DOWN, + [NM_DISPATCHER_ACTION_DHCP_CHANGE_4] = NMD_ACTION_DHCP4_CHANGE, + [NM_DISPATCHER_ACTION_DHCP_CHANGE_6] = NMD_ACTION_DHCP6_CHANGE, + [NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE] = NMD_ACTION_CONNECTIVITY_CHANGE, + [NM_DISPATCHER_ACTION_REAPPLY] = NMD_ACTION_REAPPLY, + [NM_DISPATCHER_ACTION_DNS_CHANGE] = NMD_ACTION_DNS_CHANGE, + [NM_DISPATCHER_ACTION_DEVICE_ADD] = NMD_ACTION_DEVICE_ADD, + [NM_DISPATCHER_ACTION_DEVICE_DELETE] = NMD_ACTION_DEVICE_DELETE, +}; static const char * action_to_string(NMDispatcherAction action) @@ -664,7 +726,7 @@ _dispatcher_call(NMDispatcherAction action, NMConnectivityState connectivity_state, const char *vpn_iface, const NML3ConfigData *l3cd, - NMDispatcherFunc callback, + NMDispatcherCallback callback, gpointer user_data, NMDispatcherCallId **out_call_id) { @@ -784,11 +846,14 @@ _dispatcher_call(NMDispatcherAction action, error->message); return FALSE; } - dispatcher_results_process(request_id, + dispatcher_results_process(action, + request_id, start_at_msec, now_msec, log_ifname, log_con_uuid, + NULL, + NULL, ret, is_action2); return TRUE; @@ -856,7 +921,7 @@ nm_dispatcher_call_hostname(NMDispatcherFunc callback, NM_CONNECTIVITY_UNKNOWN, NULL, NULL, - callback, + (NMDispatcherCallback) callback, user_data, out_call_id); } @@ -866,7 +931,7 @@ _dispatcher_call_device(NMDispatcherAction action, NMDevice *device, gboolean blocking, NMActRequest *act_request, - NMDispatcherFunc callback, + NMDispatcherCallback callback, gpointer user_data, NMDispatcherCallId **out_call_id) { @@ -919,11 +984,48 @@ nm_dispatcher_call_device(NMDispatcherAction action, gpointer user_data, NMDispatcherCallId **out_call_id) { + g_return_val_if_fail(!action_is_device_handler(action), FALSE); + return _dispatcher_call_device(action, device, FALSE, act_request, - callback, + (NMDispatcherCallback) callback, + user_data, + out_call_id); +} + +/** + * nm_dispatcher_call_device_handler: + * @action: the %NMDispatcherAction, must be device-add or device-remove + * @device: the #NMDevice the action applies to + * @act_request: the #NMActRequest for the action. If %NULL, use the + * current request of the device. + * @callback: a caller-supplied device-handler callback to execute when done + * @user_data: caller-supplied pointer passed to @callback + * @out_call_id: on success, a call identifier which can be passed to + * nm_dispatcher_call_cancel() + * + * This method always invokes the device dispatcher action asynchronously. To ignore + * the result, pass %NULL to @callback. + * + * Returns: %TRUE if the action was dispatched, %FALSE on failure + */ +gboolean +nm_dispatcher_call_device_handler(NMDispatcherAction action, + NMDevice *device, + NMActRequest *act_request, + NMDispatcherFuncDH callback, + gpointer user_data, + NMDispatcherCallId **out_call_id) +{ + g_return_val_if_fail(action_is_device_handler(action), FALSE); + + return _dispatcher_call_device(action, + device, + FALSE, + act_request, + (NMDispatcherCallback) callback, user_data, out_call_id); } @@ -945,6 +1047,8 @@ nm_dispatcher_call_device_sync(NMDispatcherAction action, NMDevice *device, NMActRequest *act_request) { + g_return_val_if_fail(!action_is_device_handler(action), FALSE); + return _dispatcher_call_device(action, device, TRUE, act_request, NULL, NULL, NULL); } @@ -986,7 +1090,7 @@ nm_dispatcher_call_vpn(NMDispatcherAction action, NM_CONNECTIVITY_UNKNOWN, vpn_iface, l3cd, - callback, + (NMDispatcherCallback) callback, user_data, out_call_id); } @@ -1013,6 +1117,8 @@ nm_dispatcher_call_vpn_sync(NMDispatcherAction action, const char *vpn_iface, const NML3ConfigData *l3cd) { + g_return_val_if_fail(!action_is_device_handler(action), FALSE); + return _dispatcher_call(action, TRUE, parent_device, @@ -1054,7 +1160,7 @@ nm_dispatcher_call_connectivity(NMConnectivityState connectivity_state, connectivity_state, NULL, NULL, - callback, + (NMDispatcherCallback) callback, user_data, out_call_id); } @@ -1086,7 +1192,10 @@ nm_dispatcher_call_dns_change(void) void nm_dispatcher_call_cancel(NMDispatcherCallId *call_id) { - if (!call_id || g_hash_table_lookup(gl.requests, call_id) != call_id || !call_id->callback) + if (!call_id || g_hash_table_lookup(gl.requests, call_id) != call_id) + g_return_if_reached(); + + if (!call_id->callback) g_return_if_reached(); /* Canceling just means the callback doesn't get called, so set the diff --git a/src/core/nm-dispatcher.h b/src/core/nm-dispatcher.h index a1cb96b798..fc317ca899 100644 --- a/src/core/nm-dispatcher.h +++ b/src/core/nm-dispatcher.h @@ -24,6 +24,8 @@ typedef enum { NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE, NM_DISPATCHER_ACTION_REAPPLY, NM_DISPATCHER_ACTION_DNS_CHANGE, + NM_DISPATCHER_ACTION_DEVICE_ADD, + NM_DISPATCHER_ACTION_DEVICE_DELETE, } NMDispatcherAction; #define NM_DISPATCHER_ACTION_DHCP_CHANGE_X(IS_IPv4) \ @@ -31,7 +33,13 @@ typedef enum { typedef struct NMDispatcherCallId NMDispatcherCallId; +/* Callback function for regular dispatcher calls */ typedef void (*NMDispatcherFunc)(NMDispatcherCallId *call_id, gpointer user_data); +/* Callback function for device-handler dispatcher calls */ +typedef void (*NMDispatcherFuncDH)(NMDispatcherCallId *call_id, + gpointer user_data, + gboolean success, + const char *error_msg); gboolean nm_dispatcher_call_hostname(NMDispatcherFunc callback, gpointer user_data, @@ -44,6 +52,13 @@ gboolean nm_dispatcher_call_device(NMDispatcherAction action, gpointer user_data, NMDispatcherCallId **out_call_id); +gboolean nm_dispatcher_call_device_handler(NMDispatcherAction action, + NMDevice *device, + NMActRequest *act_request, + NMDispatcherFuncDH callback_dh, + gpointer user_data, + NMDispatcherCallId **out_call_id); + gboolean nm_dispatcher_call_device_sync(NMDispatcherAction action, NMDevice *device, NMActRequest *act_request); diff --git a/src/libnm-core-aux-extern/nm-dispatcher-api.h b/src/libnm-core-aux-extern/nm-dispatcher-api.h index 7cb370a92e..635b4fb3a8 100644 --- a/src/libnm-core-aux-extern/nm-dispatcher-api.h +++ b/src/libnm-core-aux-extern/nm-dispatcher-api.h @@ -35,6 +35,8 @@ #define NMD_ACTION_CONNECTIVITY_CHANGE "connectivity-change" #define NMD_ACTION_REAPPLY "reapply" #define NMD_ACTION_DNS_CHANGE "dns-change" +#define NMD_ACTION_DEVICE_ADD "device-add" +#define NMD_ACTION_DEVICE_DELETE "device-delete" typedef enum { DISPATCH_RESULT_UNKNOWN = 0, diff --git a/src/nm-dispatcher/nm-dispatcher.c b/src/nm-dispatcher/nm-dispatcher.c index 722d6e290d..a28b895a64 100644 --- a/src/nm-dispatcher/nm-dispatcher.c +++ b/src/nm-dispatcher/nm-dispatcher.c @@ -86,6 +86,7 @@ struct Request { char **envp; gboolean debug; gboolean is_action2; + gboolean is_device_handler; GPtrArray *scripts; /* list of ScriptInfo */ guint idx; @@ -615,6 +616,31 @@ _compare_basenames(gconstpointer a, gconstpointer b) return 0; } +static gboolean +check_file(Request *request, const char *path) +{ + gs_free char *link_target = NULL; + const char *err_msg = NULL; + struct stat st; + int err; + + link_target = g_file_read_link(path, NULL); + if (nm_streq0(link_target, "/dev/null")) + return FALSE; + + err = stat(path, &st); + if (err) { + return FALSE; + } else if (!S_ISREG(st.st_mode) || st.st_size == 0) { + /* silently skip. */ + return FALSE; + } else if (!check_permissions(&st, &err_msg)) { + _LOG_R_W(request, "find-scripts: Cannot execute '%s': %s", path, err_msg); + return FALSE; + } + return TRUE; +} + static void _find_scripts(Request *request, GHashTable *scripts, const char *base, const char *subdir) { @@ -647,7 +673,7 @@ _find_scripts(Request *request, GHashTable *scripts, const char *base, const cha } static GSList * -find_scripts(Request *request) +find_scripts(Request *request, const char *device_handler) { gs_unref_hashtable GHashTable *scripts = NULL; GSList *script_list = NULL; @@ -656,6 +682,33 @@ find_scripts(Request *request) char *path; char *filename; + if (request->is_device_handler) { + const char *const dirs[] = {NMCONFDIR, NMLIBDIR}; + guint i; + + nm_assert(device_handler); + + for (i = 0; i < G_N_ELEMENTS(dirs); i++) { + gs_free char *full_name = NULL; + + full_name = g_build_filename(dirs[i], "dispatcher.d", "device", device_handler, NULL); + if (check_file(request, full_name)) { + script_list = g_slist_prepend(script_list, g_steal_pointer(&full_name)); + return script_list; + } + } + + _LOG_R_W(request, + "find-scripts: no device-handler script found with name \"%s\"", + device_handler); + return NULL; + } + + nm_assert(!device_handler); + + /* Use a hash-table to deduplicate scripts with same name from /etc and /usr */ + scripts = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + if (NM_IN_STRSET(request->action, NMD_ACTION_PRE_UP, NMD_ACTION_VPN_PRE_UP)) subdir = "pre-up.d"; else if (NM_IN_STRSET(request->action, NMD_ACTION_PRE_DOWN, NMD_ACTION_VPN_PRE_DOWN)) @@ -663,33 +716,13 @@ find_scripts(Request *request) else subdir = NULL; - scripts = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); - _find_scripts(request, scripts, NMLIBDIR, subdir); _find_scripts(request, scripts, NMCONFDIR, subdir); g_hash_table_iter_init(&iter, scripts); while (g_hash_table_iter_next(&iter, (gpointer *) &filename, (gpointer *) &path)) { - gs_free char *link_target = NULL; - const char *err_msg = NULL; - struct stat st; - int err; - - link_target = g_file_read_link(path, NULL); - if (nm_streq0(link_target, "/dev/null")) - continue; - - err = stat(path, &st); - if (err) - _LOG_R_W(request, "find-scripts: Failed to stat '%s': %d", path, err); - else if (!S_ISREG(st.st_mode) || st.st_size == 0) { - /* silently skip. */ - } else if (!check_permissions(&st, &err_msg)) - _LOG_R_W(request, "find-scripts: Cannot execute '%s': %s", path, err_msg); - else { - /* success */ + if (check_file(request, path)) { script_list = g_slist_prepend(script_list, g_strdup(path)); - continue; } } @@ -725,6 +758,27 @@ script_must_wait(const char *path) return TRUE; } +static char * +get_device_handler(GVariant *connection) +{ + gs_unref_variant GVariant *generic_setting = NULL; + const char *device_handler = NULL; + + generic_setting = g_variant_lookup_value(connection, + NM_SETTING_GENERIC_SETTING_NAME, + NM_VARIANT_TYPE_SETTING); + if (generic_setting) { + if (g_variant_lookup(generic_setting, + NM_SETTING_GENERIC_DEVICE_HANDLER, + "&s", + &device_handler)) { + return g_strdup(device_handler); + } + } + + return NULL; +} + static void _handle_action(GDBusMethodInvocation *invocation, GVariant *parameters, gboolean is_action2) { @@ -739,6 +793,7 @@ _handle_action(GDBusMethodInvocation *invocation, GVariant *parameters, gboolean gs_unref_variant GVariant *device_dhcp6_config = NULL; const char *connectivity_state; const char *vpn_ip_iface; + gs_free char *device_handler = NULL; gs_unref_variant GVariant *vpn_proxy_properties = NULL; gs_unref_variant GVariant *vpn_ip4_config = NULL; gs_unref_variant GVariant *vpn_ip6_config = NULL; @@ -829,6 +884,8 @@ _handle_action(GDBusMethodInvocation *invocation, GVariant *parameters, gboolean request->context = invocation; request->action = g_strdup(action); request->is_action2 = is_action2; + request->is_device_handler = + NM_IN_STRSET(action, NMD_ACTION_DEVICE_ADD, NMD_ACTION_DEVICE_DELETE); request->envp = nm_dispatcher_utils_construct_envp(action, connection, @@ -846,11 +903,14 @@ _handle_action(GDBusMethodInvocation *invocation, GVariant *parameters, gboolean vpn_ip6_config, &request->iface, &error_message); - if (!error_message) { + if (request->is_device_handler) { + device_handler = get_device_handler(connection); + } + request->scripts = g_ptr_array_new_full(5, script_info_free); - sorted_scripts = find_scripts(request); + sorted_scripts = find_scripts(request, device_handler); for (iter = sorted_scripts; iter; iter = g_slist_next(iter)) { ScriptInfo *s;