core: convert dispatcher to asynchronous operation and return its results

In preparation for making NM wait on the dispatcher, make the dispatcher
call scripts asynchronously, and report the script results back to NM.
This commit is contained in:
Dan Williams 2012-06-05 11:09:05 -05:00
parent cf255aa83b
commit 0201d6da87
4 changed files with 509 additions and 313 deletions

View file

@ -15,7 +15,7 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2008 - 2011 Red Hat, Inc.
* Copyright (C) 2008 - 2012 Red Hat, Inc.
*/
#include <syslog.h>
@ -44,53 +44,40 @@
static GMainLoop *loop = NULL;
static gboolean debug = FALSE;
static gboolean quit_timeout_cb (gpointer user_data);
typedef struct {
GObject parent;
typedef struct Handler Handler;
typedef struct HandlerClass HandlerClass;
/* Private data */
guint quit_id;
gboolean persist;
} Handler;
typedef struct {
GObjectClass parent;
} HandlerClass;
GType handler_get_type (void);
struct Handler {
GObject parent;
};
struct HandlerClass {
GObjectClass parent;
};
#define HANDLER_TYPE (handler_get_type ())
#define HANDLER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), HANDLER_TYPE, Handler))
#define HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), HANDLER_TYPE, HandlerClass))
#define IS_HANDLER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), HANDLER_TYPE))
#define IS_HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), HANDLER_TYPE))
#define HANDLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), HANDLER_TYPE, HandlerClass))
#define HANDLER_TYPE (handler_get_type ())
#define HANDLER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), HANDLER_TYPE, Handler))
#define HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), HANDLER_TYPE, HandlerClass))
G_DEFINE_TYPE(Handler, handler, G_TYPE_OBJECT)
typedef struct {
DBusGConnection *g_connection;
DBusGProxy *bus_proxy;
guint quit_timeout;
gboolean persist;
Handler *handler;
} Dispatcher;
static gboolean
nm_dispatcher_action (Handler *h,
const char *action,
GHashTable *connection_hash,
GHashTable *connection_props,
GHashTable *device_props,
GHashTable *device_ip4_props,
GHashTable *device_ip6_props,
GHashTable *device_dhcp4_props,
GHashTable *device_dhcp6_props,
const char *vpn_ip_iface,
GHashTable *vpn_ip4_props,
GHashTable *vpn_ip6_props,
GError **error);
static void
impl_dispatch (Handler *h,
const char *action,
GHashTable *connection_hash,
GHashTable *connection_props,
GHashTable *device_props,
GHashTable *device_ip4_props,
GHashTable *device_ip6_props,
GHashTable *device_dhcp4_props,
GHashTable *device_dhcp6_props,
const char *vpn_ip_iface,
GHashTable *vpn_ip4_props,
GHashTable *vpn_ip6_props,
DBusGMethodInvocation *context);
#include "nm-dispatcher-glue.h"
@ -101,33 +88,188 @@ handler_init (Handler *h)
}
static void
handler_finalize (GObject *object)
handler_class_init (HandlerClass *h_class)
{
G_OBJECT_CLASS (handler_parent_class)->finalize (object);
}
typedef struct Request Request;
static void dispatch_one_script (Request *request);
typedef struct {
Request *request;
char *script;
GPid pid;
DispatchResult result;
char *error;
} ScriptInfo;
struct Request {
Handler *handler;
DBusGMethodInvocation *context;
char *action;
char *iface;
char **envp;
GPtrArray *scripts; /* list of ScriptInfo */
guint idx;
guint script_watch_id;
guint script_timeout_id;
};
static void
script_info_free (ScriptInfo *info)
{
g_free (info->script);
g_free (info->error);
g_free (info);
}
static void
handler_class_init (HandlerClass *h_class)
request_free (Request *request)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (h_class);
gobject_class->finalize = handler_finalize;
g_free (request->action);
g_free (request->iface);
g_strfreev (request->envp);
if (request->scripts)
g_ptr_array_foreach (request->scripts, (GFunc) script_info_free, NULL);
g_ptr_array_free (request->scripts, TRUE);
}
static gboolean
quit_timeout_cb (gpointer user_data)
{
g_main_loop_quit (loop);
return FALSE;
}
static void
quit_timeout_reschedule (Handler *h)
{
if (h->quit_id)
g_source_remove (h->quit_id);
if (!h->persist)
h->quit_id = g_timeout_add_seconds (10, quit_timeout_cb, NULL);
}
static gboolean
next_script (gpointer user_data)
{
Request *request = user_data;
GPtrArray *results;
GValueArray *item;
guint i;
quit_timeout_reschedule (request->handler);
request->idx++;
if (request->idx < request->scripts->len) {
dispatch_one_script (request);
return FALSE;
}
/* All done */
results = g_ptr_array_sized_new (request->scripts->len);
for (i = 0; i < request->scripts->len; i++) {
ScriptInfo *script = g_ptr_array_index (request->scripts, i);
GValue elt = {0, };
item = g_value_array_new (3);
/* Script path */
g_value_init (&elt, G_TYPE_STRING);
g_value_set_string (&elt, script->script);
g_value_array_append (item, &elt);
g_value_unset (&elt);
/* Result */
g_value_init (&elt, G_TYPE_UINT);
g_value_set_uint (&elt, script->result);
g_value_array_append (item, &elt);
g_value_unset (&elt);
/* Error */
g_value_init (&elt, G_TYPE_STRING);
g_value_set_string (&elt, script->error ? script->error : "");
g_value_array_append (item, &elt);
g_value_unset (&elt);
g_ptr_array_add (results, item);
}
dbus_g_method_return (request->context, results);
request_free (request);
return FALSE;
}
static void
script_watch_cb (GPid pid, gint status, gpointer user_data)
{
ScriptInfo *script = user_data;
guint err;
g_assert (pid == script->pid);
script->request->script_watch_id = 0;
g_source_remove (script->request->script_timeout_id);
script->request->script_timeout_id = 0;
if (WIFEXITED (status)) {
err = WEXITSTATUS (status);
if (err == 0)
script->result = DISPATCH_RESULT_SUCCESS;
else {
script->error = g_strdup_printf ("Script '%s' exited with error status %d.",
script->script, err);
}
} else if (WIFSTOPPED (status)) {
script->error = g_strdup_printf ("Script '%s' stopped unexpectedly with signal %d.",
script->script, WSTOPSIG (status));
} else if (WIFSIGNALED (status)) {
script->error = g_strdup_printf ("Script '%s' died with signal %d",
script->script, WTERMSIG (status));
} else {
script->error = g_strdup_printf ("Script '%s' died from an unknown cause",
script->script);
}
if (script->result != DISPATCH_RESULT_SUCCESS) {
script->result = DISPATCH_RESULT_FAILED;
g_warning ("%s", script->error);
}
g_spawn_close_pid (script->pid);
next_script (script->request);
}
static gboolean
script_timeout_cb (gpointer user_data)
{
ScriptInfo *script = user_data;
g_source_remove (script->request->script_watch_id);
script->request->script_watch_id = 0;
script->request->script_timeout_id = 0;
g_warning ("Script '%s' took too long; killing it.", script->script);
if (kill (script->pid, 0) == 0)
kill (script->pid, SIGKILL);
waitpid (script->pid, NULL, 0);
script->error = g_strdup_printf ("Script '%s' timed out.", script->script);
script->result = DISPATCH_RESULT_TIMEOUT;
g_spawn_close_pid (script->pid);
g_idle_add (next_script, script->request);
return FALSE;
}
/*
* nmd_permission_check
*
* Verify that the given script has the permissions we want. Specifically,
* ensure that the file is
* - A regular file.
* - Owned by root.
* - Not writable by the group or by other.
* - Not setuid.
* - Executable by the owner.
*
*/
static inline gboolean
nmd_permission_check (struct stat *s, GError **error)
check_permissions (struct stat *s, GError **error)
{
g_return_val_if_fail (s != NULL, FALSE);
g_return_val_if_fail (error != NULL, FALSE);
@ -160,223 +302,170 @@ nmd_permission_check (struct stat *s, GError **error)
return TRUE;
}
/*
* nmd_is_valid_filename
*
* Verify that the given script is a valid file name. Specifically,
* ensure that the file:
* - is not a editor backup file
* - is not a package management file
* - does not start with '.'
*/
static inline gboolean
nmd_is_valid_filename (const char *file_name)
static gboolean
check_filename (const char *file_name)
{
char *bad_suffixes[] = { "~", ".rpmsave", ".rpmorig", ".rpmnew", NULL };
char *tmp;
int i;
guint i;
/* File must not be a backup file, package management file, or start with '.' */
if (file_name[0] == '.')
return FALSE;
for (i = 0; bad_suffixes[i]; i++) {
if (g_str_has_suffix(file_name, bad_suffixes[i]))
if (g_str_has_suffix (file_name, bad_suffixes[i]))
return FALSE;
}
tmp = g_strrstr(file_name, ".dpkg-");
if (tmp && (tmp == strrchr(file_name,'.')))
tmp = g_strrstr (file_name, ".dpkg-");
if (tmp && (tmp == strrchr (file_name, '.')))
return FALSE;
return TRUE;
}
static gint
sort_files (gconstpointer a, gconstpointer b)
{
char *a_base = NULL, *b_base = NULL;
int ret = 0;
if (a && !b)
return 1;
if (!a && !b)
return 0;
if (!a && b)
return -1;
a_base = g_path_get_basename (a);
b_base = g_path_get_basename (b);
ret = strcmp (a_base, b_base);
g_free (a_base);
g_free (b_base);
return ret;
}
static void
child_setup (gpointer user_data G_GNUC_UNUSED)
{
/* We are in the child process at this point */
/* Give child a different process group to ensure signal separation. */
pid_t pid = getpid ();
setpgid (pid, pid);
/* We are in the child process at this point */
/* Give child a different process group to ensure signal separation. */
pid_t pid = getpid ();
setpgid (pid, pid);
}
static void
dispatch_scripts (const char *action, const char *iface, char **envp)
dispatch_one_script (Request *request)
{
GError *error = NULL;
gchar *argv[4];
ScriptInfo *script = g_ptr_array_index (request->scripts, request->idx);
argv[0] = script->script;
argv[1] = request->iface ? request->iface : "none";
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 (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);
} else {
g_warning ("Failed to execute script '%s': (%d) %s",
script->script, error->code, error->message);
script->result = DISPATCH_RESULT_EXEC_FAILED;
script->error = g_strdup (error->message);
g_clear_error (&error);
/* Try the next script */
g_idle_add (next_script, request);
}
}
static GPtrArray *
find_scripts (Request *request)
{
GPtrArray *scripts;
ScriptInfo *s;
GDir *dir;
const char *filename;
GSList *scripts = NULL, *iter;
GSList *sorted = NULL, *iter;
GError *error = NULL;
if (!(dir = g_dir_open (NMD_SCRIPT_DIR, 0, &error))) {
g_warning ("g_dir_open() could not open '" NMD_SCRIPT_DIR "'. '%s'",
error->message);
g_warning ("Failed to open dispatcher directory '%s': (%d) %s",
NMD_SCRIPT_DIR, error->code, error->message);
g_error_free (error);
return;
return NULL;
}
while ((filename = g_dir_read_name (dir))) {
char *file_path;
struct stat s;
GError *pc_error = NULL;
char *path;
struct stat st;
int err;
if (!nmd_is_valid_filename (filename))
if (!check_filename (filename))
continue;
file_path = g_build_filename (NMD_SCRIPT_DIR, filename, NULL);
path = g_build_filename (NMD_SCRIPT_DIR, filename, NULL);
err = stat (file_path, &s);
if (err) {
g_warning ("Script '%s' could not be stated: %d", file_path, err);
g_free (file_path);
continue;
}
if (!nmd_permission_check (&s, &pc_error)) {
g_warning ("Script '%s' could not be executed: %s", file_path, pc_error->message);
g_error_free (pc_error);
g_free (file_path);
err = stat (path, &st);
if (err)
g_warning ("Failed to stat '%s': %d", path, err);
else if (!check_permissions (&st, &error)) {
g_warning ("Cannot execute '%s': %s", path, error->message);
g_clear_error (&error);
} else {
/* success */
scripts = g_slist_insert_sorted (scripts, file_path, sort_files);
sorted = g_slist_insert_sorted (sorted, path, (GCompareFunc) g_strcmp0);
}
}
g_dir_close (dir);
for (iter = scripts; iter; iter = g_slist_next (iter)) {
gchar *argv[4];
gint status = -1;
argv[0] = (char *) iter->data;
argv[1] = iface ? (char *) iface : "none";
argv[2] = (char *) action;
argv[3] = NULL;
if (debug)
g_message ("Script: %s %s %s", (char *) iter->data, iface ? (char *) iface : "(none)", (char *) action);
error = NULL;
if (g_spawn_sync ("/", argv, envp, 0, child_setup, NULL, NULL, NULL, &status, &error)) {
if (WIFEXITED (status)) {
if (WEXITSTATUS (status) != 0)
g_warning ("Script '%s' exited with error status %d.",
(char *) iter->data, WEXITSTATUS (status));
} else
g_warning ("Script '%s' exited abnormally.", (char *) iter->data);
} else {
g_warning ("Could not run script '%s': (%d) %s",
(char *) iter->data, error->code, error->message);
g_error_free (error);
}
scripts = g_ptr_array_sized_new (5);
for (iter = sorted; iter; iter = g_slist_next (iter)) {
s = g_malloc0 (sizeof (*s));
s->request = request;
s->script = iter->data;
g_ptr_array_add (scripts, s);
}
g_slist_free (sorted);
g_slist_foreach (scripts, (GFunc) g_free, NULL);
g_slist_free (scripts);
return scripts;
}
static gboolean
nm_dispatcher_action (Handler *h,
const char *action,
GHashTable *connection_hash,
GHashTable *connection_props,
GHashTable *device_props,
GHashTable *device_ip4_props,
GHashTable *device_ip6_props,
GHashTable *device_dhcp4_props,
GHashTable *device_dhcp6_props,
const char *vpn_ip_iface,
GHashTable *vpn_ip4_props,
GHashTable *vpn_ip6_props,
GError **error)
static void
impl_dispatch (Handler *h,
const char *str_action,
GHashTable *connection_hash,
GHashTable *connection_props,
GHashTable *device_props,
GHashTable *device_ip4_props,
GHashTable *device_ip6_props,
GHashTable *device_dhcp4_props,
GHashTable *device_dhcp6_props,
const char *vpn_ip_iface,
GHashTable *vpn_ip4_props,
GHashTable *vpn_ip6_props,
DBusGMethodInvocation *context)
{
Dispatcher *d = g_object_get_data (G_OBJECT (h), "dispatcher");
char **envp, **p;
Request *request;
char **p;
char *iface = NULL;
/* Back off the quit timeout */
if (d->quit_timeout)
g_source_remove (d->quit_timeout);
if (!d->persist)
d->quit_timeout = g_timeout_add_seconds (10, quit_timeout_cb, NULL);
quit_timeout_reschedule (h);
envp = nm_dispatcher_utils_construct_envp (action,
connection_hash,
connection_props,
device_props,
device_ip4_props,
device_ip6_props,
device_dhcp4_props,
device_dhcp6_props,
vpn_ip_iface,
vpn_ip4_props,
vpn_ip6_props,
&iface);
request = g_malloc0 (sizeof (*request));
request->handler = h;
request->context = context;
request->action = g_strdup (str_action);
request->envp = nm_dispatcher_utils_construct_envp (str_action,
connection_hash,
connection_props,
device_props,
device_ip4_props,
device_ip6_props,
device_dhcp4_props,
device_dhcp6_props,
vpn_ip_iface,
vpn_ip4_props,
vpn_ip6_props,
&iface);
if (debug) {
g_message ("------------ Script Environment ------------");
for (p = envp; *p; p++)
g_message ("------------ Action ID %p '%s' Interface %s Environment ------------",
context, str_action, iface ? iface : "(none)");
for (p = request->envp; *p; p++)
g_message (" %s", *p);
g_message ("\n");
}
dispatch_scripts (action, iface, envp);
g_strfreev (envp);
g_free (iface);
request->iface = g_strdup (iface);
request->scripts = find_scripts (request);
return TRUE;
}
static gboolean
start_dbus_service (Dispatcher *d)
{
int request_name_result;
GError *err = NULL;
gboolean success = FALSE;
if (!dbus_g_proxy_call (d->bus_proxy, "RequestName", &err,
G_TYPE_STRING, NM_DISPATCHER_DBUS_SERVICE,
G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE,
G_TYPE_INVALID,
G_TYPE_UINT, &request_name_result,
G_TYPE_INVALID)) {
g_warning ("Could not acquire the " NM_DISPATCHER_DBUS_SERVICE " service.\n"
" Message: '%s'", err->message);
g_error_free (err);
goto out;
}
if (request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
g_warning ("Could not acquire the " NM_DISPATCHER_DBUS_SERVICE " service "
"as it is already taken. Return: %d",
request_name_result);
goto out;
}
success = TRUE;
out:
return success;
/* start dispatching scripts */
dispatch_one_script (request);
}
static void
@ -386,42 +475,67 @@ destroy_cb (DBusGProxy *proxy, gpointer user_data)
g_main_loop_quit (loop);
}
static gboolean
dbus_init (Dispatcher *d)
static DBusGConnection *
dbus_init (void)
{
GError *err = NULL;
GError *error = NULL;
DBusGConnection *bus;
DBusConnection *connection;
DBusGProxy *proxy;
int result;
dbus_connection_set_change_sigpipe (TRUE);
d->g_connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &err);
if (!d->g_connection) {
bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
if (!bus) {
g_warning ("Could not get the system bus. Make sure "
"the message bus daemon is running! Message: %s",
err->message);
g_error_free (err);
return FALSE;
error->message);
g_error_free (error);
return NULL;
}
/* Clean up nicely if we get kicked off the bus */
connection = dbus_g_connection_get_connection (d->g_connection);
connection = dbus_g_connection_get_connection (bus);
dbus_connection_set_exit_on_disconnect (connection, FALSE);
d->bus_proxy = dbus_g_proxy_new_for_name (d->g_connection,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus");
if (!d->bus_proxy) {
g_warning ("Could not get the DBus object!");
proxy = dbus_g_proxy_new_for_name (bus,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus");
if (!proxy) {
g_warning ("Could not create the DBus proxy!");
goto error;
}
g_signal_connect (d->bus_proxy, "destroy", G_CALLBACK (destroy_cb), NULL);
g_signal_connect (proxy, "destroy", G_CALLBACK (destroy_cb), NULL);
return TRUE;
if (!dbus_g_proxy_call (proxy, "RequestName", &error,
G_TYPE_STRING, NM_DISPATCHER_DBUS_SERVICE,
G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE,
G_TYPE_INVALID,
G_TYPE_UINT, &result,
G_TYPE_INVALID)) {
g_warning ("Could not acquire the " NM_DISPATCHER_DBUS_SERVICE " service.\n"
" Message: '%s'", error->message);
g_error_free (error);
goto error;
}
error:
return FALSE;
if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
g_warning ("Could not acquire the " NM_DISPATCHER_DBUS_SERVICE " service "
"as it is already taken. Result: %d",
result);
goto error;
}
return bus;
error:
if (proxy)
g_object_unref (proxy);
dbus_g_connection_unref (bus);
return NULL;
}
static void
@ -433,30 +547,25 @@ log_handler (const gchar *log_domain,
int syslog_priority;
switch (log_level) {
case G_LOG_LEVEL_ERROR:
syslog_priority = LOG_CRIT;
break;
case G_LOG_LEVEL_CRITICAL:
syslog_priority = LOG_ERR;
break;
case G_LOG_LEVEL_WARNING:
syslog_priority = LOG_WARNING;
break;
case G_LOG_LEVEL_MESSAGE:
syslog_priority = LOG_NOTICE;
break;
case G_LOG_LEVEL_DEBUG:
syslog_priority = LOG_DEBUG;
break;
case G_LOG_LEVEL_INFO:
default:
syslog_priority = LOG_INFO;
break;
case G_LOG_LEVEL_ERROR:
syslog_priority = LOG_CRIT;
break;
case G_LOG_LEVEL_CRITICAL:
syslog_priority = LOG_ERR;
break;
case G_LOG_LEVEL_WARNING:
syslog_priority = LOG_WARNING;
break;
case G_LOG_LEVEL_MESSAGE:
syslog_priority = LOG_NOTICE;
break;
case G_LOG_LEVEL_DEBUG:
syslog_priority = LOG_DEBUG;
break;
case G_LOG_LEVEL_INFO:
default:
syslog_priority = LOG_INFO;
break;
}
syslog (syslog_priority, "%s", message);
@ -502,20 +611,14 @@ setup_signals (void)
sigaction (SIGINT, &action, NULL);
}
static gboolean
quit_timeout_cb (gpointer user_data)
{
g_main_loop_quit (loop);
return FALSE;
}
int
main (int argc, char **argv)
{
Dispatcher *d = g_malloc0 (sizeof (Dispatcher));
GOptionContext *opt_ctx;
GError *error = NULL;
gboolean persist = FALSE;
DBusGConnection *bus;
Handler *handler;
GOptionEntry entries[] = {
{ "debug", 0, 0, G_OPTION_ARG_NONE, &debug, "Output to console rather than syslog", NULL },
@ -530,7 +633,6 @@ main (int argc, char **argv)
if (!g_option_context_parse (opt_ctx, &argc, &argv, &error)) {
g_warning ("%s\n", error->message);
g_error_free (error);
g_free (d);
return 1;
}
@ -544,30 +646,27 @@ main (int argc, char **argv)
loop = g_main_loop_new (NULL, FALSE);
if (!dbus_init (d))
return 1;
if (!start_dbus_service (d))
bus = dbus_init ();
if (!bus)
return 1;
d->persist = persist;
d->handler = g_object_new (HANDLER_TYPE, NULL);
if (!d->handler)
handler = g_object_new (HANDLER_TYPE, NULL);
if (!handler)
return 1;
g_object_set_data (G_OBJECT (d->handler), "dispatcher", d);
handler->persist = persist;
dbus_g_object_type_install_info (HANDLER_TYPE, &dbus_glib_nm_dispatcher_object_info);
dbus_g_connection_register_g_object (d->g_connection,
dbus_g_connection_register_g_object (bus,
NM_DISPATCHER_DBUS_PATH,
G_OBJECT (d->handler));
G_OBJECT (handler));
if (!persist)
d->quit_timeout = g_timeout_add_seconds (10, quit_timeout_cb, NULL);
handler->quit_id = g_timeout_add_seconds (10, quit_timeout_cb, NULL);
g_main_loop_run (loop);
g_object_unref (d->handler);
dbus_g_connection_unref (d->g_connection);
g_free (d);
g_object_unref (handler);
dbus_g_connection_unref (bus);
if (!debug)
logging_shutdown ();

View file

@ -15,9 +15,15 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2008 Red Hat, Inc.
* Copyright (C) 2008 - 2012 Red Hat, Inc.
*/
#include <dbus/dbus-glib.h>
/* 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))
#define DISPATCHER_TYPE_RESULT_ARRAY (dbus_g_type_get_collection ("GPtrArray", DISPATCHER_TYPE_RESULT))
#define NM_DISPATCHER_DBUS_SERVICE "org.freedesktop.nm_dispatcher"
#define NM_DISPATCHER_DBUS_IFACE "org.freedesktop.nm_dispatcher"
#define NM_DISPATCHER_DBUS_PATH "/org/freedesktop/nm_dispatcher"
@ -30,3 +36,11 @@
#define NMD_DEVICE_PROPS_STATE "state"
#define NMD_DEVICE_PROPS_PATH "path"
typedef enum {
DISPATCH_RESULT_UNKNOWN = 0,
DISPATCH_RESULT_SUCCESS = 1,
DISPATCH_RESULT_EXEC_FAILED = 2,
DISPATCH_RESULT_FAILED = 3,
DISPATCH_RESULT_TIMEOUT = 4,
} DispatchResult;

View file

@ -8,6 +8,9 @@
INTERNAL; not public API. Perform an action.
</tp:docstring>
<annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_dispatch"/>
<annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
<arg name="action" type="s" direction="in">
<tp:docstring>
The action being performed.
@ -72,6 +75,15 @@
</tp:docstring>
</arg>
<arg name="results" type="a(sus)" direction="out">
<tp:docstring>
Results of dispatching operations. Each element of the returned
array is a struct containing the path of an executed script (s),
the result of running that script (u), and a description of the
result (s).
</tp:docstring>
</arg>
</method>
</interface>
</node>

View file

@ -128,10 +128,81 @@ fill_vpn_props (NMIP4Config *ip4_config,
dump_object_to_props (G_OBJECT (ip6_config), ip6_hash);
}
typedef struct {
NMDBusManager *dbus_mgr;
} DispatchInfo;
static void
dispatcher_info_free (DispatchInfo *info)
{
g_object_unref (info->dbus_mgr);
g_free (info);
}
static const char *
dispatch_result_to_string (DispatchResult result)
{
switch (result) {
case DISPATCH_RESULT_UNKNOWN:
return "unknown";
case DISPATCH_RESULT_SUCCESS:
return "success";
case DISPATCH_RESULT_EXEC_FAILED:
return "exec failed";
case DISPATCH_RESULT_FAILED:
return "failed";
case DISPATCH_RESULT_TIMEOUT:
return "timed out";
}
g_assert_not_reached ();
}
static void
dispatcher_done_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
{
dbus_g_proxy_end_call (proxy, call, NULL, G_TYPE_INVALID);
GError *error = NULL;
GPtrArray *results = NULL;
guint i;
if (dbus_g_proxy_end_call (proxy, call, &error,
DISPATCHER_TYPE_RESULT_ARRAY, &results,
G_TYPE_INVALID)) {
for (i = 0; results && (i < results->len); i++) {
GValueArray *item = g_ptr_array_index (results, i);
GValue *tmp;
const char *script, *err;
DispatchResult result;
if ( (G_VALUE_TYPE (g_value_array_get_nth (item, 0)) == G_TYPE_STRING)
&& (G_VALUE_TYPE (g_value_array_get_nth (item, 1)) == G_TYPE_UINT)
&& (G_VALUE_TYPE (g_value_array_get_nth (item, 2)) == G_TYPE_STRING)) {
/* result */
tmp = g_value_array_get_nth (item, 1);
result = g_value_get_uint (tmp);
if (result != DISPATCH_RESULT_SUCCESS) {
/* script */
tmp = g_value_array_get_nth (item, 0);
script = g_value_get_string (tmp);
/* error */
tmp = g_value_array_get_nth (item, 2);
err = g_value_get_string (tmp);
nm_log_warn (LOGD_CORE, "Dispatcher script %s: %s",
dispatch_result_to_string (result), err);
}
} else
nm_log_dbg (LOGD_CORE, "Dispatcher result element %d invalid type", i);
g_array_unref ((GArray *) item);
}
g_ptr_array_free (results, TRUE);
} else {
g_assert (error);
nm_log_warn (LOGD_CORE, "Dispatcher failed: (%d) %s", error->code, error->message);
}
g_clear_error (&error);
g_object_unref (proxy);
}
@ -155,6 +226,8 @@ nm_utils_call_dispatcher (const char *action,
GHashTable *device_dhcp6_props;
GHashTable *vpn_ip4_props;
GHashTable *vpn_ip6_props;
DBusGProxyCall *call;
DispatchInfo *info;
g_return_if_fail (action != NULL);
@ -209,29 +282,27 @@ nm_utils_call_dispatcher (const char *action,
fill_vpn_props (vpn_ip4_config, NULL, vpn_ip4_props, vpn_ip6_props);
}
/* Do a non-blocking call, but wait for the reply, because dbus-glib
* sometimes needs time to complete internal housekeeping. If we use
* dbus_g_proxy_call_no_reply(), that housekeeping (specifically the
* GetNameOwner response) doesn't complete and we run into an assert
* on unreffing the proxy.
*/
dbus_g_proxy_begin_call_with_timeout (proxy, "Action",
dispatcher_done_cb,
dbus_mgr, /* automatically unref the dbus mgr when call is done */
g_object_unref,
5000,
G_TYPE_STRING, action,
DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, connection_hash,
DBUS_TYPE_G_MAP_OF_VARIANT, connection_props,
DBUS_TYPE_G_MAP_OF_VARIANT, device_props,
DBUS_TYPE_G_MAP_OF_VARIANT, device_ip4_props,
DBUS_TYPE_G_MAP_OF_VARIANT, device_ip6_props,
DBUS_TYPE_G_MAP_OF_VARIANT, device_dhcp4_props,
DBUS_TYPE_G_MAP_OF_VARIANT, device_dhcp6_props,
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_INVALID);
info = g_malloc0 (sizeof (*info));
info->dbus_mgr = dbus_mgr;
/* Send the action to the dispatcher */
call = dbus_g_proxy_begin_call_with_timeout (proxy, "Action",
dispatcher_done_cb,
info,
(GDestroyNotify) dispatcher_info_free,
15000,
G_TYPE_STRING, action,
DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, connection_hash,
DBUS_TYPE_G_MAP_OF_VARIANT, connection_props,
DBUS_TYPE_G_MAP_OF_VARIANT, device_props,
DBUS_TYPE_G_MAP_OF_VARIANT, device_ip4_props,
DBUS_TYPE_G_MAP_OF_VARIANT, device_ip6_props,
DBUS_TYPE_G_MAP_OF_VARIANT, device_dhcp4_props,
DBUS_TYPE_G_MAP_OF_VARIANT, device_dhcp6_props,
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_INVALID);
g_hash_table_destroy (connection_hash);
g_hash_table_destroy (connection_props);
g_hash_table_destroy (device_props);