dispatcher: multiple fixes

https://bugzilla.gnome.org/show_bug.cgi?id=727439
This commit is contained in:
Dan Winship 2014-04-18 11:33:53 -04:00
commit 30a98f3149
8 changed files with 130 additions and 37 deletions

View file

@ -46,10 +46,14 @@
static GMainLoop *loop = NULL;
static gboolean debug = FALSE;
typedef struct Request Request;
typedef struct {
GObject parent;
/* Private data */
Request *current_request;
GQueue *pending_requests;
guint quit_id;
gboolean persist;
} Handler;
@ -79,6 +83,7 @@ impl_dispatch (Handler *h,
const char *vpn_ip_iface,
GHashTable *vpn_ip4_props,
GHashTable *vpn_ip6_props,
gboolean request_debug,
DBusGMethodInvocation *context);
#include "nm-dispatcher-glue.h"
@ -94,8 +99,6 @@ handler_class_init (HandlerClass *h_class)
{
}
typedef struct Request Request;
static void dispatch_one_script (Request *request);
typedef struct {
@ -114,6 +117,8 @@ struct Request {
char *action;
char *iface;
char **envp;
gboolean debug;
GPtrArray *scripts; /* list of ScriptInfo */
guint idx;
@ -148,25 +153,58 @@ quit_timeout_cb (gpointer user_data)
return FALSE;
}
static void
quit_timeout_cancel (Handler *h)
{
if (h->quit_id) {
g_source_remove (h->quit_id);
h->quit_id = 0;
}
}
static void
quit_timeout_reschedule (Handler *h)
{
if (h->quit_id)
g_source_remove (h->quit_id);
quit_timeout_cancel (h);
if (!h->persist)
h->quit_id = g_timeout_add_seconds (10, quit_timeout_cb, NULL);
}
static void
start_request (Request *request)
{
if (request->iface)
g_message ("Dispatching action '%s' for %s", request->action, request->iface);
else
g_message ("Dispatching action '%s'", request->action);
request->handler->current_request = request;
dispatch_one_script (request);
}
static void
next_request (Handler *h)
{
Request *request = g_queue_pop_head (h->pending_requests);
if (request) {
start_request (request);
return;
}
h->current_request = NULL;
quit_timeout_reschedule (h);
}
static gboolean
next_script (gpointer user_data)
{
Request *request = user_data;
Handler *h = request->handler;
GPtrArray *results;
GValueArray *item;
guint i;
quit_timeout_reschedule (request->handler);
request->idx++;
if (request->idx < request->scripts->len) {
dispatch_one_script (request);
@ -203,9 +241,17 @@ next_script (gpointer user_data)
}
dbus_g_method_return (request->context, results);
request_free (request);
g_ptr_array_unref (results);
if (request->debug) {
if (request->iface)
g_message ("Dispatch '%s' on %s complete", request->action, request->iface);
else
g_message ("Dispatch '%s' complete", request->action);
}
request_free (request);
next_request (h);
return FALSE;
}
@ -240,7 +286,10 @@ script_watch_cb (GPid pid, gint status, gpointer user_data)
script->script);
}
if (script->result != DISPATCH_RESULT_SUCCESS) {
if (script->result == DISPATCH_RESULT_SUCCESS) {
if (script->request->debug)
g_message ("Script '%s' complete", script->script);
} else {
script->result = DISPATCH_RESULT_FAILED;
g_warning ("%s", script->error);
}
@ -348,12 +397,12 @@ dispatch_one_script (Request *request)
argv[2] = request->action;
argv[3] = NULL;
if (debug)
g_message ("Script: %s %s %s", script->script, request->iface ? request->iface : "(none)", request->action);
if (request->debug)
g_message ("Running script '%s'", script->script);
if (g_spawn_async ("/", argv, request->envp, G_SPAWN_DO_NOT_REAP_CHILD, child_setup, request, &script->pid, &error)) {
request->script_watch_id = g_child_watch_add (script->pid, (GChildWatchFunc) script_watch_cb, script);
request->script_timeout_id = g_timeout_add_seconds (3, script_timeout_cb, script);
request->script_timeout_id = g_timeout_add_seconds (20, script_timeout_cb, script);
} else {
g_warning ("Failed to execute script '%s': (%d) %s",
script->script, error->code, error->message);
@ -420,6 +469,7 @@ impl_dispatch (Handler *h,
const char *vpn_ip_iface,
GHashTable *vpn_ip4_props,
GHashTable *vpn_ip6_props,
gboolean request_debug,
DBusGMethodInvocation *context)
{
GSList *sorted_scripts = NULL;
@ -435,10 +485,11 @@ impl_dispatch (Handler *h,
return;
}
quit_timeout_reschedule (h);
quit_timeout_cancel (h);
request = g_malloc0 (sizeof (*request));
request->handler = h;
request->debug = request_debug || debug;
request->context = context;
request->action = g_strdup (str_action);
@ -455,7 +506,7 @@ impl_dispatch (Handler *h,
vpn_ip6_props,
&iface);
if (debug) {
if (request->debug) {
g_message ("------------ Action ID %p '%s' Interface %s Environment ------------",
context, str_action, iface ? iface : "(none)");
for (p = request->envp; *p; p++)
@ -474,8 +525,10 @@ impl_dispatch (Handler *h,
}
g_slist_free (sorted_scripts);
/* start dispatching scripts */
dispatch_one_script (request);
if (h->current_request)
g_queue_push_tail (h->pending_requests, request);
else
start_request (request);
}
static void
@ -653,6 +706,7 @@ main (int argc, char **argv)
if (!handler)
return 1;
handler->persist = persist;
handler->pending_requests = g_queue_new ();
dbus_g_object_type_install_info (HANDLER_TYPE, &dbus_glib_nm_dispatcher_object_info);
dbus_g_connection_register_g_object (bus,
@ -664,7 +718,9 @@ main (int argc, char **argv)
g_main_loop_run (loop);
g_queue_free (handler->pending_requests);
g_object_unref (handler);
dbus_g_connection_unref (bus);
if (!debug)

View file

@ -345,10 +345,10 @@ nm_dispatcher_utils_construct_envp (const char *action,
char **out_iface)
{
const char *iface = NULL, *ip_iface = NULL;
const char *uuid = NULL, *id = NULL;
const char *uuid = NULL, *id = NULL, *path;
NMDeviceState dev_state = NM_DEVICE_STATE_UNKNOWN;
GValue *value;
char **envp = NULL;
char **envp = NULL, *path_item;
GSList *items = NULL, *iter;
guint i;
GHashTable *con_setting_hash;
@ -359,7 +359,7 @@ nm_dispatcher_utils_construct_envp (const char *action,
/* Hostname changes don't require a device nor contain a connection */
if (!strcmp (action, "hostname"))
return g_new0 (char *, 1);
goto done;
/* Canonicalize the VPN interface name; "" is used when passing it through
* D-Bus so make sure that's fixed up here.
@ -445,12 +445,6 @@ nm_dispatcher_utils_construct_envp (const char *action,
items = construct_ip6_items (items, vpn_ip6_props, "VPN_");
}
/* Convert the list to an environment pointer */
envp = g_new0 (char *, g_slist_length (items) + 1);
for (iter = items, i = 0; iter; iter = g_slist_next (iter), i++)
envp[i] = (char *) iter->data;
g_slist_free (items);
/* Backwards compat: 'iface' is set in this order:
* 1) VPN interface name
* 2) Device IP interface name
@ -463,6 +457,19 @@ nm_dispatcher_utils_construct_envp (const char *action,
else
*out_iface = g_strdup (iface);
done:
path = g_getenv ("PATH");
if (path) {
path_item = g_strdup_printf ("PATH=%s", path);
items = g_slist_prepend (items, path_item);
}
/* Convert the list to an environment pointer */
envp = g_new0 (char *, g_slist_length (items) + 1);
for (iter = items, i = 0; iter; iter = g_slist_next (iter), i++)
envp[i] = (char *) iter->data;
g_slist_free (items);
return envp;
}

View file

@ -75,6 +75,12 @@
</tp:docstring>
</arg>
<arg name="debug" type="b" direction="in">
<tp:docstring>
Whether to log debug output.
</tp:docstring>
</arg>
<arg name="results" type="a(sus)" direction="out">
<tp:docstring>
Results of dispatching operations. Each element of the returned

View file

@ -6,6 +6,10 @@ Type=dbus
BusName=org.freedesktop.nm_dispatcher
ExecStart=@libexecdir@/nm-dispatcher.action
# We want to allow scripts to spawn long-running daemons, so tell
# systemd to not clean up when nm-dispatcher exits
KillMode=process
[Install]
Alias=dbus-org.freedesktop.nm-dispatcher.service

View file

@ -194,6 +194,15 @@
In case of VPN, VPN_IP_IFACE is set, and IP4_*, IP6_* variables with VPN prefix are
exported too, like VPN_IP4_ADDRESS_0, VPN_IP4_NUM_ADDRESSES.
</para>
<para>
Dispatcher scripts are run one at a time, but asynchronously from the main
NetworkManager process, and will be killed if they run for too long. If your script
might take arbitrarily long to complete, you should spawn a child process and have the
parent return immediately. Also beware that once a script is queued, it will always be
run, even if a later event renders it obsolete. (Eg, if an interface goes up, and then
back down again quickly, it is possible that one or more "up" scripts will be run
after the interface has gone down.)
</para>
</refsect1>
<refsect1>

View file

@ -50,7 +50,7 @@ nm_log_handler (const gchar *log_domain,
LOGD_CORE | LOGD_DEVICE | LOGD_OLPC_MESH | LOGD_WIMAX | \
LOGD_INFINIBAND | LOGD_FIREWALL | LOGD_ADSL | LOGD_BOND | \
LOGD_VLAN | LOGD_BRIDGE | LOGD_DBUS_PROPS | LOGD_TEAM | LOGD_CONCHECK | \
LOGD_DCB)
LOGD_DCB | LOGD_DISPATCH)
#define LOGD_DEFAULT (LOGD_ALL & ~(LOGD_WIFI_SCAN | LOGD_DBUS_PROPS))
@ -108,6 +108,7 @@ static const LogDesc domain_descs[] = {
{ LOGD_TEAM, "TEAM" },
{ LOGD_CONCHECK, "CONCHECK" },
{ LOGD_DCB, "DCB" },
{ LOGD_DISPATCH, "DISPATCH" },
{ 0, NULL }
};

View file

@ -62,6 +62,7 @@ enum {
LOGD_TEAM = (1LL << 32),
LOGD_CONCHECK = (1LL << 33),
LOGD_DCB = (1LL << 34), /* Data Center Bridging */
LOGD_DISPATCH = (1LL << 35),
};
#define LOGD_DHCP (LOGD_DHCP4 | LOGD_DHCP6)

View file

@ -179,16 +179,16 @@ dispatcher_done_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
DispatchResult result;
if (item->n_values != 3) {
nm_log_dbg (LOGD_CORE, "Unexpected number of items in "
"dispatcher result (got %d, expectd 3)",
item->n_values);
nm_log_dbg (LOGD_DISPATCH, "Unexpected number of items in "
"dispatcher result (got %d, expectd 3)",
item->n_values);
goto next;
}
/* Script */
tmp = g_value_array_get_nth (item, 0);
if (G_VALUE_TYPE (tmp) != G_TYPE_STRING) {
nm_log_dbg (LOGD_CORE, "Dispatcher result %d element 0 invalid type %s",
nm_log_dbg (LOGD_DISPATCH, "Dispatcher result %d element 0 invalid type %s",
i, G_VALUE_TYPE_NAME (tmp));
goto next;
}
@ -197,7 +197,7 @@ dispatcher_done_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
/* Result */
tmp = g_value_array_get_nth (item, 1);
if (G_VALUE_TYPE (tmp) != G_TYPE_UINT) {
nm_log_dbg (LOGD_CORE, "Dispatcher result %d element 1 invalid type %s",
nm_log_dbg (LOGD_DISPATCH, "Dispatcher result %d element 1 invalid type %s",
i, G_VALUE_TYPE_NAME (tmp));
goto next;
}
@ -206,14 +206,14 @@ dispatcher_done_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
/* Error */
tmp = g_value_array_get_nth (item, 2);
if (G_VALUE_TYPE (tmp) != G_TYPE_STRING) {
nm_log_dbg (LOGD_CORE, "Dispatcher result %d element 2 invalid type %s",
nm_log_dbg (LOGD_DISPATCH, "Dispatcher result %d element 2 invalid type %s",
i, G_VALUE_TYPE_NAME (tmp));
goto next;
}
err = g_value_get_string (tmp);
if (result != DISPATCH_RESULT_SUCCESS) {
nm_log_warn (LOGD_CORE, "Dispatcher script \"%s\" failed with %s: %s",
nm_log_warn (LOGD_DISPATCH, "Dispatcher script \"%s\" failed with %s: %s",
script, dispatch_result_to_string (result), err);
}
@ -223,7 +223,7 @@ next:
g_ptr_array_free (results, TRUE);
} else {
g_assert (error);
nm_log_warn (LOGD_CORE, "Dispatcher failed: (%d) %s", error->code, error->message);
nm_log_warn (LOGD_DISPATCH, "Dispatcher failed: (%d) %s", error->code, error->message);
}
if (info->callback)
@ -285,8 +285,16 @@ _dispatcher_call (DispatcherAction action,
DispatchInfo *info;
/* All actions except 'hostname' require a device */
if (action != DISPATCHER_ACTION_HOSTNAME)
if (action == DISPATCHER_ACTION_HOSTNAME) {
nm_log_dbg (LOGD_DISPATCH, "dispatching action '%s'",
action_to_string (action));
} else {
g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
nm_log_dbg (LOGD_DISPATCH, "(%s) dispatching action '%s'",
nm_device_get_iface (device), action_to_string (action));
}
/* 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, NULL);
@ -297,7 +305,7 @@ _dispatcher_call (DispatcherAction action,
NM_DISPATCHER_DBUS_PATH,
NM_DISPATCHER_DBUS_IFACE);
if (!proxy) {
nm_log_err (LOGD_CORE, "could not get dispatcher proxy!");
nm_log_err (LOGD_DISPATCH, "could not get dispatcher proxy!");
return NULL;
}
@ -342,7 +350,7 @@ _dispatcher_call (DispatcherAction action,
dispatcher_done_cb,
info,
(GDestroyNotify) dispatcher_info_free,
15000,
30000,
G_TYPE_STRING, action_to_string (action),
DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, connection_hash,
DBUS_TYPE_G_MAP_OF_VARIANT, connection_props,
@ -354,6 +362,7 @@ _dispatcher_call (DispatcherAction action,
G_TYPE_STRING, vpn_iface ? vpn_iface : "",
DBUS_TYPE_G_MAP_OF_VARIANT, vpn_ip4_props,
DBUS_TYPE_G_MAP_OF_VARIANT, vpn_ip6_props,
G_TYPE_BOOLEAN, nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH),
G_TYPE_INVALID);
g_hash_table_destroy (connection_hash);
g_hash_table_destroy (connection_props);