diff --git a/callouts/nm-dispatcher-api.h b/callouts/nm-dispatcher-api.h index eb6b33fe0c..03e40f7752 100644 --- a/callouts/nm-dispatcher-api.h +++ b/callouts/nm-dispatcher-api.h @@ -20,9 +20,9 @@ #include -#define NMD_SCRIPT_DIR NMCONFDIR "/dispatcher.d" -#define NMD_PRE_UP_DIR NMD_SCRIPT_DIR "/pre-up.d" -#define NMD_PRE_DOWN_DIR NMD_SCRIPT_DIR "/pre-down.d" +#define NMD_SCRIPT_DIR_DEFAULT NMCONFDIR "/dispatcher.d" +#define NMD_SCRIPT_DIR_PRE_UP NMD_SCRIPT_DIR_DEFAULT "/pre-up.d" +#define NMD_SCRIPT_DIR_PRE_DOWN NMD_SCRIPT_DIR_DEFAULT "/pre-down.d" /* dbus-glib types for dispatcher call return value */ #define DISPATCHER_TYPE_RESULT (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_INVALID)) diff --git a/callouts/nm-dispatcher.c b/callouts/nm-dispatcher.c index 93998225d1..680715d1cd 100644 --- a/callouts/nm-dispatcher.c +++ b/callouts/nm-dispatcher.c @@ -426,12 +426,12 @@ find_scripts (const char *str_action) if ( strcmp (str_action, NMD_ACTION_PRE_UP) == 0 || strcmp (str_action, NMD_ACTION_VPN_PRE_UP) == 0) - dirname = NMD_PRE_UP_DIR; + dirname = NMD_SCRIPT_DIR_PRE_UP; else if ( strcmp (str_action, NMD_ACTION_PRE_DOWN) == 0 || strcmp (str_action, NMD_ACTION_VPN_PRE_DOWN) == 0) - dirname = NMD_PRE_DOWN_DIR; + dirname = NMD_SCRIPT_DIR_PRE_DOWN; else - dirname = NMD_SCRIPT_DIR; + dirname = NMD_SCRIPT_DIR_DEFAULT; if (!(dir = g_dir_open (dirname, 0, &error))) { g_message ("Failed to open dispatcher directory '%s': (%d) %s", diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index b1bf887ebf..4d70f24595 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -216,7 +216,11 @@ typedef struct { guint act_source6_id; gpointer act_source6_func; guint recheck_assume_id; - guint dispatcher_id; + struct { + guint call_id; + NMDeviceState post_state; + NMDeviceStateReason post_state_reason; + } dispatcher; /* Link stuff */ guint link_connected_id; @@ -831,8 +835,12 @@ nm_device_release_one_slave (NMDevice *dev, NMDevice *slave, gboolean configure) reason = NM_DEVICE_STATE_REASON_NONE; else if (priv->state == NM_DEVICE_STATE_FAILED) reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED; - else + else if (priv->state_reason != NM_DEVICE_STATE_REASON_NONE) reason = priv->state_reason; + else { + g_warn_if_reached (); + reason = NM_DEVICE_STATE_REASON_UNKNOWN; + } nm_device_slave_notify_release (info->slave, reason); free_slave_info (info); @@ -5264,32 +5272,55 @@ nm_device_get_ip6_config (NMDevice *self) /****************************************************************/ static void -ip_check_pre_up_done (guint call_id, gpointer user_data) +dispatcher_cleanup (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + if (priv->dispatcher.call_id) { + nm_dispatcher_call_cancel (priv->dispatcher.call_id); + priv->dispatcher.call_id = 0; + priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; + } +} + +static void +dispatcher_complete_proceed_state (guint call_id, gpointer user_data) { NMDevice *self = NM_DEVICE (user_data); NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - g_return_if_fail (call_id == priv->dispatcher_id); + g_return_if_fail (call_id == priv->dispatcher.call_id); - priv->dispatcher_id = 0; - nm_device_queue_state (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE); + priv->dispatcher.call_id = 0; + nm_device_queue_state (self, priv->dispatcher.post_state, + priv->dispatcher.post_state_reason); + priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; } +/****************************************************************/ + static void ip_check_pre_up (NMDevice *self) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - g_warn_if_fail (priv->dispatcher_id == 0); + if (priv->dispatcher.call_id != 0) { + g_warn_if_reached (); + dispatcher_cleanup (self); + } + priv->dispatcher.post_state = NM_DEVICE_STATE_SECONDARIES; + priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE; if (!nm_dispatcher_call (DISPATCHER_ACTION_PRE_UP, nm_device_get_connection (self), self, - ip_check_pre_up_done, + dispatcher_complete_proceed_state, self, - &priv->dispatcher_id)) { + &priv->dispatcher.call_id)) { /* Just proceed on errors */ - ip_check_pre_up_done (0, self); + dispatcher_complete_proceed_state (0, self); } } @@ -6521,29 +6552,6 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason) /***********************************************************/ -static void -dispatcher_pre_down_done (guint call_id, gpointer user_data) -{ - NMDevice *self = NM_DEVICE (user_data); - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - - g_return_if_fail (call_id == priv->dispatcher_id); - - priv->dispatcher_id = 0; - nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE); -} - -static void -dispatcher_cleanup (NMDevice *self) -{ - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); - - if (priv->dispatcher_id) { - nm_dispatcher_call_cancel (priv->dispatcher_id); - priv->dispatcher_id = 0; - } -} - static gboolean ip_config_valid (NMDeviceState state) { @@ -6709,14 +6717,16 @@ _set_state_full (NMDevice *device, nm_act_request_get_connection (req), device); } else { + priv->dispatcher.post_state = NM_DEVICE_STATE_DISCONNECTED; + priv->dispatcher.post_state_reason = reason; if (!nm_dispatcher_call (DISPATCHER_ACTION_PRE_DOWN, nm_act_request_get_connection (req), device, - dispatcher_pre_down_done, + dispatcher_complete_proceed_state, device, - &priv->dispatcher_id)) { + &priv->dispatcher.call_id)) { /* Just proceed on errors */ - dispatcher_pre_down_done (0, device); + dispatcher_complete_proceed_state (0, device); } } break; diff --git a/src/nm-dispatcher.c b/src/nm-dispatcher.c index 336b91f7ed..c51a6cba00 100644 --- a/src/nm-dispatcher.c +++ b/src/nm-dispatcher.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "nm-dispatcher.h" #include "nm-dispatcher-api.h" @@ -32,11 +33,46 @@ #include "nm-dbus-glib-types.h" #include "nm-glib-compat.h" -#define CALL_TIMEOUT (1000 * 60 * 10) /* 10 mintues for all scripts */ +#define CALL_TIMEOUT (1000 * 60 * 10) /* 10 minutes for all scripts */ -static gboolean do_dispatch = TRUE; static GHashTable *requests = NULL; +typedef struct { + GFileMonitor *monitor; + const char *const description; + const char *const dir; + const guint16 dir_len; + char has_scripts; +} Monitor; + +enum { + MONITOR_INDEX_DEFAULT, + MONITOR_INDEX_PRE_UP, + MONITOR_INDEX_PRE_DOWN, +}; + +static Monitor monitors[3] = { +#define MONITORS_INIT_SET(INDEX, USE, SCRIPT_DIR) [INDEX] = { .dir_len = STRLEN (SCRIPT_DIR), .dir = SCRIPT_DIR, .description = ("" USE), .has_scripts = TRUE } + MONITORS_INIT_SET (MONITOR_INDEX_DEFAULT, "default", NMD_SCRIPT_DIR_DEFAULT), + MONITORS_INIT_SET (MONITOR_INDEX_PRE_UP, "pre-up", NMD_SCRIPT_DIR_PRE_UP), + MONITORS_INIT_SET (MONITOR_INDEX_PRE_DOWN, "pre-down", NMD_SCRIPT_DIR_PRE_DOWN), +}; + +static const Monitor* +_get_monitor_by_action (DispatcherAction action) +{ + switch (action) { + case DISPATCHER_ACTION_PRE_UP: + case DISPATCHER_ACTION_VPN_PRE_UP: + return &monitors[MONITOR_INDEX_PRE_UP]; + case DISPATCHER_ACTION_PRE_DOWN: + case DISPATCHER_ACTION_VPN_PRE_DOWN: + return &monitors[MONITOR_INDEX_PRE_DOWN]; + default: + return &monitors[MONITOR_INDEX_DEFAULT]; + } +} + static void dump_object_to_props (GObject *object, GHashTable *hash) { @@ -136,6 +172,7 @@ fill_vpn_props (NMIP4Config *ip4_config, } typedef struct { + DispatcherAction action; guint request_id; DispatcherFunc callback; gpointer user_data; @@ -197,17 +234,25 @@ validate_element (guint request_id, GValue *val, GType expected_type, guint idx, } static void -dispatcher_results_process (guint request_id, GPtrArray *results) +dispatcher_results_process (guint request_id, DispatcherAction action, GPtrArray *results) { guint i; + const Monitor *monitor = _get_monitor_by_action (action); g_return_if_fail (results != NULL); + if (results->len == 0) { + nm_log_dbg (LOGD_DISPATCH, "(%u) succeeded but no scripts invoked", + request_id); + return; + } + for (i = 0; i < results->len; i++) { GValueArray *item = g_ptr_array_index (results, i); GValue *tmp; const char *script, *err; DispatchResult result; + const char *script_validation_msg = ""; if (item->n_values != 3) { nm_log_dbg (LOGD_DISPATCH, "(%u) unexpected number of items in " @@ -221,8 +266,18 @@ dispatcher_results_process (guint request_id, GPtrArray *results) if (!validate_element (request_id, tmp, G_TYPE_STRING, i, 0)) continue; script = g_value_get_string (tmp); - if (!script || strncmp (script, NMD_SCRIPT_DIR "/", STRLEN (NMD_SCRIPT_DIR "/"))) - continue; + if (!script) { + script_validation_msg = " (path is NULL)"; + script = "(unknown)"; + } else if (!strncmp (script, monitor->dir, monitor->dir_len) /* check: prefixed by script directory */ + && script[monitor->dir_len] == '/' && script[monitor->dir_len+1] /* check: with additional "/?" */ + && !strchr (&script[monitor->dir_len+1], '/')) { /* check: and no further '/' */ + /* we expect the script to lie inside monitor->dir. If it does, + * strip the directory name. Otherwise show the full path and a warning. */ + script += monitor->dir_len + 1; + } else + script_validation_msg = " (unexpected path)"; + /* Result */ tmp = g_value_array_get_nth (item, 1); @@ -238,15 +293,15 @@ dispatcher_results_process (guint request_id, GPtrArray *results) if (result == DISPATCH_RESULT_SUCCESS) { - nm_log_dbg (LOGD_DISPATCH, "(%u) %s succeeded", + nm_log_dbg (LOGD_DISPATCH, "(%u) %s succeeded%s", request_id, - script + STRLEN (NMD_SCRIPT_DIR "/")); + script, script_validation_msg); } else { - nm_log_warn (LOGD_DISPATCH, "(%u) %s failed (%s): %s", + nm_log_warn (LOGD_DISPATCH, "(%u) %s failed (%s): %s%s", request_id, - script + STRLEN (NMD_SCRIPT_DIR "/"), + script, dispatch_result_to_string (result), - err ? err : ""); + err ? err : "", script_validation_msg); } } } @@ -269,7 +324,7 @@ dispatcher_done_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data) if (dbus_g_proxy_end_call (proxy, call, &error, DISPATCHER_TYPE_RESULT_ARRAY, &results, G_TYPE_INVALID)) { - dispatcher_results_process (info->request_id, results); + dispatcher_results_process (info->request_id, info->action, results); free_results (results); } else { g_assert (error); @@ -356,30 +411,38 @@ _dispatcher_call (DispatcherAction action, /* All actions except 'hostname' require a device */ if (action == DISPATCHER_ACTION_HOSTNAME) { - nm_log_dbg (LOGD_DISPATCH, "(%u) dispatching action '%s'", - reqid, action_to_string (action)); + nm_log_dbg (LOGD_DISPATCH, "(%u) dispatching action '%s'%s", + reqid, action_to_string (action), + blocking + ? " (blocking)" + : (callback ? " (with callback)" : "")); } else { g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); - nm_log_dbg (LOGD_DISPATCH, "(%u) (%s) dispatching action '%s'", + nm_log_dbg (LOGD_DISPATCH, "(%u) (%s) dispatching action '%s'%s", reqid, vpn_iface ? vpn_iface : nm_device_get_iface (device), - action_to_string (action)); + action_to_string (action), + blocking + ? " (blocking)" + : (callback ? " (with callback)" : "")); } /* VPN actions require at least an IPv4 config (for now) */ if (action == DISPATCHER_ACTION_VPN_UP) g_return_val_if_fail (vpn_ip4_config != NULL, FALSE); - if (do_dispatch == FALSE) { + if (!_get_monitor_by_action(action)->has_scripts) { if (blocking == FALSE && (out_call_id || callback)) { info = g_malloc0 (sizeof (*info)); + info->action = action; info->request_id = reqid; info->callback = callback; info->user_data = user_data; info->idle_id = g_idle_add (dispatcher_idle_cb, info); - } - nm_log_dbg (LOGD_DISPATCH, "(%u) ignoring request; no scripts in " NMD_SCRIPT_DIR, reqid); + nm_log_dbg (LOGD_DISPATCH, "(%u) simulate request; no scripts in %s", reqid, _get_monitor_by_action(action)->dir); + } else + nm_log_dbg (LOGD_DISPATCH, "(%u) ignoring request; no scripts in %s", reqid, _get_monitor_by_action(action)->dir); success = TRUE; goto done; } @@ -449,7 +512,7 @@ _dispatcher_call (DispatcherAction action, DISPATCHER_TYPE_RESULT_ARRAY, &results, G_TYPE_INVALID); if (success) { - dispatcher_results_process (reqid, results); + dispatcher_results_process (reqid, action, results); free_results (results); } else { nm_log_warn (LOGD_DISPATCH, "(%u) failed: (%d) %s", reqid, error->code, error->message); @@ -457,6 +520,7 @@ _dispatcher_call (DispatcherAction action, } } else { info = g_malloc0 (sizeof (*info)); + info->action = action; info->request_id = reqid; info->callback = callback; info->user_data = user_data; @@ -620,24 +684,15 @@ nm_dispatcher_call_cancel (guint call_id) * DispatcherInfo's callback to NULL. */ info = g_hash_table_lookup (requests, GUINT_TO_POINTER (call_id)); - if (info) + g_return_if_fail (info); + + if (info && info->callback) { + nm_log_dbg (LOGD_DISPATCH, "(%u) cancelling dispatcher callback action", + call_id); info->callback = NULL; - else - g_return_if_reached (); + } } -typedef struct { - const char *dir; - GFileMonitor *monitor; - gboolean has_scripts; -} Monitor; - -static Monitor monitors[3] = { - { NMD_SCRIPT_DIR, NULL, TRUE }, - { NMD_PRE_UP_DIR, NULL, TRUE }, - { NMD_PRE_DOWN_DIR, NULL, TRUE } -}; - static void dispatcher_dir_changed (GFileMonitor *monitor, GFile *file, @@ -645,22 +700,44 @@ dispatcher_dir_changed (GFileMonitor *monitor, GFileMonitorEvent event_type, Monitor *item) { + const char *name; + char *full_name; GDir *dir; - guint i; + GError *error = NULL; - /* Default to dispatching on any errors */ - item->has_scripts = TRUE; - - dir = g_dir_open (item->dir, 0, NULL); + dir = g_dir_open (item->dir, 0, &error); if (dir) { - item->has_scripts = !!g_dir_read_name (dir); + int errsv = 0; + + item->has_scripts = FALSE; + errno = 0; + while (!item->has_scripts + && (name = g_dir_read_name (dir))) { + full_name = g_build_filename (item->dir, name, NULL); + item->has_scripts = g_file_test (full_name, G_FILE_TEST_IS_EXECUTABLE); + g_free (full_name); + } + errsv = errno; g_dir_close (dir); + if (item->has_scripts) + nm_log_dbg (LOGD_DISPATCH, "dispatcher: %s script directory '%s' has scripts", item->description, item->dir); + else if (errsv == 0) + nm_log_dbg (LOGD_DISPATCH, "dispatcher: %s script directory '%s' has no scripts", item->description, item->dir); + else { + nm_log_dbg (LOGD_DISPATCH, "dispatcher: %s script directory '%s' error reading (%s)", item->description, item->dir, strerror (errsv)); + item->has_scripts = TRUE; + } + } else { + if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { + nm_log_dbg (LOGD_DISPATCH, "dispatcher: %s script directory '%s' does not exist", item->description, item->dir); + item->has_scripts = FALSE; + } else { + nm_log_dbg (LOGD_DISPATCH, "dispatcher: %s script directory '%s' error (%s)", item->description, item->dir, error->message); + item->has_scripts = TRUE; + } + g_error_free (error); } - /* Recheck all dirs for scripts and update global variable */ - do_dispatch = FALSE; - for (i = 0; i < G_N_ELEMENTS (monitors); i++) - do_dispatch |= monitors[i].has_scripts; } void diff --git a/src/nm-manager.c b/src/nm-manager.c index 1b7c87e25d..00799ffd67 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -2390,9 +2390,8 @@ ensure_master_active_connection (NMManager *self, /* If we're passed a connection and a device, we require that connection * be already activated on the device, eg returned from find_master(). */ - if (master_connection) - g_assert (device_connection == master_connection); - else if (!is_compatible_with_slave (device_connection, connection)) { + g_assert (!master_connection || master_connection == device_connection); + if (device_connection && !is_compatible_with_slave (device_connection, connection)) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, "The active connection on %s is not a valid master for '%s'", nm_device_get_iface (master_device), @@ -2404,6 +2403,7 @@ ensure_master_active_connection (NMManager *self, if ( (master_state == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (master_device)) { /* Device already using master_connection */ + g_assert (device_connection); return NM_ACTIVE_CONNECTION (nm_device_get_act_request (master_device)); }