mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-29 08:00:18 +01:00
786 lines
26 KiB
C
786 lines
26 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
|
|
/* NetworkManager -- Network link manager
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Copyright (C) 2004 - 2012 Red Hat, Inc.
|
|
* Copyright (C) 2005 - 2008 Novell, Inc.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <glib.h>
|
|
#include <gio/gio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "nm-dispatcher.h"
|
|
#include "nm-dispatcher-api.h"
|
|
#include "NetworkManagerUtils.h"
|
|
#include "nm-utils.h"
|
|
#include "nm-logging.h"
|
|
#include "nm-dbus-manager.h"
|
|
#include "nm-device.h"
|
|
#include "nm-dhcp4-config.h"
|
|
#include "nm-dhcp6-config.h"
|
|
#include "nm-dbus-glib-types.h"
|
|
#include "nm-glib-compat.h"
|
|
#include "nm-settings-connection.h"
|
|
|
|
#define CALL_TIMEOUT (1000 * 60 * 10) /* 10 minutes for all scripts */
|
|
|
|
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)
|
|
{
|
|
GParamSpec **pspecs;
|
|
guint len = 0, i;
|
|
|
|
pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (object), &len);
|
|
for (i = 0; i < len; i++) {
|
|
value_hash_add_object_property (hash,
|
|
pspecs[i]->name,
|
|
object,
|
|
pspecs[i]->name,
|
|
pspecs[i]->value_type);
|
|
}
|
|
g_free (pspecs);
|
|
}
|
|
|
|
static void
|
|
dump_dhcp4_to_props (NMDhcp4Config *config, GHashTable *hash)
|
|
{
|
|
GSList *options, *iter;
|
|
|
|
options = nm_dhcp4_config_list_options (config);
|
|
for (iter = options; iter; iter = g_slist_next (iter)) {
|
|
const char *option = (const char *) iter->data;
|
|
const char *val;
|
|
|
|
val = nm_dhcp4_config_get_option (config, option);
|
|
value_hash_add_str (hash, option, val);
|
|
}
|
|
g_slist_free (options);
|
|
}
|
|
|
|
static void
|
|
dump_dhcp6_to_props (NMDhcp6Config *config, GHashTable *hash)
|
|
{
|
|
GSList *options, *iter;
|
|
|
|
options = nm_dhcp6_config_list_options (config);
|
|
for (iter = options; iter; iter = g_slist_next (iter)) {
|
|
const char *option = (const char *) iter->data;
|
|
const char *val;
|
|
|
|
val = nm_dhcp6_config_get_option (config, option);
|
|
value_hash_add_str (hash, option, val);
|
|
}
|
|
g_slist_free (options);
|
|
}
|
|
|
|
static void
|
|
fill_device_props (NMDevice *device,
|
|
GHashTable *dev_hash,
|
|
GHashTable *ip4_hash,
|
|
GHashTable *ip6_hash,
|
|
GHashTable *dhcp4_hash,
|
|
GHashTable *dhcp6_hash)
|
|
{
|
|
NMIP4Config *ip4_config;
|
|
NMIP6Config *ip6_config;
|
|
NMDhcp4Config *dhcp4_config;
|
|
NMDhcp6Config *dhcp6_config;
|
|
|
|
/* If the action is for a VPN, send the VPN's IP interface instead of the device's */
|
|
value_hash_add_str (dev_hash, NMD_DEVICE_PROPS_IP_INTERFACE, nm_device_get_ip_iface (device));
|
|
value_hash_add_str (dev_hash, NMD_DEVICE_PROPS_INTERFACE, nm_device_get_iface (device));
|
|
value_hash_add_uint (dev_hash, NMD_DEVICE_PROPS_TYPE, nm_device_get_device_type (device));
|
|
value_hash_add_uint (dev_hash, NMD_DEVICE_PROPS_STATE, nm_device_get_state (device));
|
|
value_hash_add_object_path (dev_hash, NMD_DEVICE_PROPS_PATH, nm_device_get_path (device));
|
|
|
|
ip4_config = nm_device_get_ip4_config (device);
|
|
if (ip4_config)
|
|
dump_object_to_props (G_OBJECT (ip4_config), ip4_hash);
|
|
|
|
ip6_config = nm_device_get_ip6_config (device);
|
|
if (ip6_config)
|
|
dump_object_to_props (G_OBJECT (ip6_config), ip6_hash);
|
|
|
|
dhcp4_config = nm_device_get_dhcp4_config (device);
|
|
if (dhcp4_config)
|
|
dump_dhcp4_to_props (dhcp4_config, dhcp4_hash);
|
|
|
|
dhcp6_config = nm_device_get_dhcp6_config (device);
|
|
if (dhcp6_config)
|
|
dump_dhcp6_to_props (dhcp6_config, dhcp6_hash);
|
|
}
|
|
|
|
static void
|
|
fill_vpn_props (NMIP4Config *ip4_config,
|
|
NMIP6Config *ip6_config,
|
|
GHashTable *ip4_hash,
|
|
GHashTable *ip6_hash)
|
|
{
|
|
if (ip4_config)
|
|
dump_object_to_props (G_OBJECT (ip4_config), ip4_hash);
|
|
if (ip6_config)
|
|
dump_object_to_props (G_OBJECT (ip6_config), ip6_hash);
|
|
}
|
|
|
|
typedef struct {
|
|
DispatcherAction action;
|
|
guint request_id;
|
|
DispatcherFunc callback;
|
|
gpointer user_data;
|
|
guint idle_id;
|
|
} DispatchInfo;
|
|
|
|
static void
|
|
dispatcher_info_free (DispatchInfo *info)
|
|
{
|
|
if (info->idle_id)
|
|
g_source_remove (info->idle_id);
|
|
g_free (info);
|
|
}
|
|
|
|
static void
|
|
_ensure_requests (void)
|
|
{
|
|
if (G_UNLIKELY (requests == NULL)) {
|
|
requests = g_hash_table_new_full (g_direct_hash,
|
|
g_direct_equal,
|
|
NULL,
|
|
(GDestroyNotify) dispatcher_info_free);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dispatcher_info_cleanup (DispatchInfo *info)
|
|
{
|
|
g_hash_table_remove (requests, GUINT_TO_POINTER (info->request_id));
|
|
}
|
|
|
|
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 gboolean
|
|
validate_element (guint request_id, GValue *val, GType expected_type, guint idx, guint eltnum)
|
|
{
|
|
if (G_VALUE_TYPE (val) != expected_type) {
|
|
nm_log_dbg (LOGD_DISPATCH, "(%u) result %d element %d invalid type %s",
|
|
request_id, idx, eltnum, G_VALUE_TYPE_NAME (val));
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
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 "
|
|
"dispatcher result (got %d, expected 3)",
|
|
request_id, item->n_values);
|
|
continue;
|
|
}
|
|
|
|
/* Script */
|
|
tmp = g_value_array_get_nth (item, 0);
|
|
if (!validate_element (request_id, tmp, G_TYPE_STRING, i, 0))
|
|
continue;
|
|
script = g_value_get_string (tmp);
|
|
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);
|
|
if (!validate_element (request_id, tmp, G_TYPE_UINT, i, 1))
|
|
continue;
|
|
result = g_value_get_uint (tmp);
|
|
|
|
/* Error */
|
|
tmp = g_value_array_get_nth (item, 2);
|
|
if (!validate_element (request_id, tmp, G_TYPE_STRING, i, 2))
|
|
continue;
|
|
err = g_value_get_string (tmp);
|
|
|
|
|
|
if (result == DISPATCH_RESULT_SUCCESS) {
|
|
nm_log_dbg (LOGD_DISPATCH, "(%u) %s succeeded%s",
|
|
request_id,
|
|
script, script_validation_msg);
|
|
} else {
|
|
nm_log_warn (LOGD_DISPATCH, "(%u) %s failed (%s): %s%s",
|
|
request_id,
|
|
script,
|
|
dispatch_result_to_string (result),
|
|
err ? err : "", script_validation_msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_results (GPtrArray *results)
|
|
{
|
|
g_return_if_fail (results != NULL);
|
|
g_ptr_array_foreach (results, (GFunc) g_value_array_free, NULL);
|
|
g_ptr_array_free (results, TRUE);
|
|
}
|
|
|
|
static void
|
|
dispatcher_done_cb (DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
|
|
{
|
|
DispatchInfo *info = user_data;
|
|
GError *error = NULL;
|
|
GPtrArray *results = NULL;
|
|
|
|
if (dbus_g_proxy_end_call (proxy, call, &error,
|
|
DISPATCHER_TYPE_RESULT_ARRAY, &results,
|
|
G_TYPE_INVALID)) {
|
|
dispatcher_results_process (info->request_id, info->action, results);
|
|
free_results (results);
|
|
} else {
|
|
g_assert (error);
|
|
|
|
if (!g_error_matches (error, DBUS_GERROR, DBUS_GERROR_REMOTE_EXCEPTION)) {
|
|
nm_log_warn (LOGD_DISPATCH, "(%u) failed to call dispatcher scripts: (%s:%d) %s",
|
|
info->request_id, g_quark_to_string (error->domain),
|
|
error->code, error->message);
|
|
} else if (!dbus_g_error_has_name (error, "org.freedesktop.systemd1.LoadFailed")) {
|
|
nm_log_warn (LOGD_DISPATCH, "(%u) failed to call dispatcher scripts: (%s) %s",
|
|
info->request_id, dbus_g_error_get_name (error), error->message);
|
|
} else {
|
|
nm_log_dbg (LOGD_DISPATCH, "(%u) failed to call dispatcher scripts: (%s) %s",
|
|
info->request_id, dbus_g_error_get_name (error), error->message);
|
|
}
|
|
}
|
|
|
|
if (info->callback)
|
|
info->callback (info->request_id, info->user_data);
|
|
|
|
g_clear_error (&error);
|
|
g_object_unref (proxy);
|
|
}
|
|
|
|
static const char *action_table[] = {
|
|
[DISPATCHER_ACTION_HOSTNAME] = NMD_ACTION_HOSTNAME,
|
|
[DISPATCHER_ACTION_PRE_UP] = NMD_ACTION_PRE_UP,
|
|
[DISPATCHER_ACTION_UP] = NMD_ACTION_UP,
|
|
[DISPATCHER_ACTION_PRE_DOWN] = NMD_ACTION_PRE_DOWN,
|
|
[DISPATCHER_ACTION_DOWN] = NMD_ACTION_DOWN,
|
|
[DISPATCHER_ACTION_VPN_PRE_UP] = NMD_ACTION_VPN_PRE_UP,
|
|
[DISPATCHER_ACTION_VPN_UP] = NMD_ACTION_VPN_UP,
|
|
[DISPATCHER_ACTION_VPN_PRE_DOWN] = NMD_ACTION_VPN_PRE_DOWN,
|
|
[DISPATCHER_ACTION_VPN_DOWN] = NMD_ACTION_VPN_DOWN,
|
|
[DISPATCHER_ACTION_DHCP4_CHANGE] = NMD_ACTION_DHCP4_CHANGE,
|
|
[DISPATCHER_ACTION_DHCP6_CHANGE] = NMD_ACTION_DHCP6_CHANGE,
|
|
};
|
|
|
|
static const char *
|
|
action_to_string (DispatcherAction action)
|
|
{
|
|
g_assert ((gsize) action < G_N_ELEMENTS (action_table));
|
|
return action_table[action];
|
|
}
|
|
|
|
static gboolean
|
|
dispatcher_idle_cb (gpointer user_data)
|
|
{
|
|
DispatchInfo *info = user_data;
|
|
|
|
info->idle_id = 0;
|
|
if (info->callback)
|
|
info->callback (info->request_id, info->user_data);
|
|
dispatcher_info_cleanup (info);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
_dispatcher_call (DispatcherAction action,
|
|
gboolean blocking,
|
|
NMConnection *connection,
|
|
NMDevice *device,
|
|
const char *vpn_iface,
|
|
NMIP4Config *vpn_ip4_config,
|
|
NMIP6Config *vpn_ip6_config,
|
|
DispatcherFunc callback,
|
|
gpointer user_data,
|
|
guint *out_call_id)
|
|
{
|
|
DBusGProxy *proxy;
|
|
DBusGConnection *g_connection;
|
|
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;
|
|
GHashTable *vpn_ip4_props;
|
|
GHashTable *vpn_ip6_props;
|
|
DispatchInfo *info = NULL;
|
|
gboolean success = FALSE;
|
|
GError *error = NULL;
|
|
static guint request_counter = 0;
|
|
guint reqid = ++request_counter;
|
|
|
|
/* Wrapping protection */
|
|
if (G_UNLIKELY (!reqid))
|
|
reqid = ++request_counter;
|
|
|
|
g_assert (!blocking || (!callback && !user_data));
|
|
|
|
_ensure_requests ();
|
|
|
|
/* All actions except 'hostname' require a device */
|
|
if (action == DISPATCHER_ACTION_HOSTNAME) {
|
|
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'%s",
|
|
reqid,
|
|
vpn_iface ? vpn_iface : nm_device_get_iface (device),
|
|
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 (!_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) 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;
|
|
}
|
|
|
|
g_connection = nm_dbus_manager_get_connection (nm_dbus_manager_get ());
|
|
proxy = dbus_g_proxy_new_for_name (g_connection,
|
|
NM_DISPATCHER_DBUS_SERVICE,
|
|
NM_DISPATCHER_DBUS_PATH,
|
|
NM_DISPATCHER_DBUS_INTERFACE);
|
|
if (!proxy) {
|
|
nm_log_err (LOGD_DISPATCH, "(%u) could not get dispatcher proxy!", reqid);
|
|
return FALSE;
|
|
}
|
|
|
|
if (connection) {
|
|
GVariant *connection_dict;
|
|
const char *filename;
|
|
|
|
connection_dict = nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_NO_SECRETS);
|
|
connection_hash = nm_utils_connection_dict_to_hash (connection_dict);
|
|
g_variant_unref (connection_dict);
|
|
|
|
connection_props = value_hash_create ();
|
|
value_hash_add_object_path (connection_props,
|
|
NMD_CONNECTION_PROPS_PATH,
|
|
nm_connection_get_path (connection));
|
|
filename = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection));
|
|
if (filename) {
|
|
value_hash_add_str (connection_props,
|
|
NMD_CONNECTION_PROPS_FILENAME,
|
|
filename);
|
|
}
|
|
} else {
|
|
connection_hash = value_hash_create ();
|
|
connection_props = value_hash_create ();
|
|
}
|
|
|
|
device_props = value_hash_create ();
|
|
device_ip4_props = value_hash_create ();
|
|
device_ip6_props = value_hash_create ();
|
|
device_dhcp4_props = value_hash_create ();
|
|
device_dhcp6_props = value_hash_create ();
|
|
vpn_ip4_props = value_hash_create ();
|
|
vpn_ip6_props = value_hash_create ();
|
|
|
|
/* hostname actions only send the hostname */
|
|
if (action != DISPATCHER_ACTION_HOSTNAME) {
|
|
fill_device_props (device,
|
|
device_props,
|
|
device_ip4_props,
|
|
device_ip6_props,
|
|
device_dhcp4_props,
|
|
device_dhcp6_props);
|
|
if (vpn_ip4_config || vpn_ip6_config)
|
|
fill_vpn_props (vpn_ip4_config, vpn_ip6_config, vpn_ip4_props, vpn_ip6_props);
|
|
}
|
|
|
|
/* Send the action to the dispatcher */
|
|
if (blocking) {
|
|
GPtrArray *results = NULL;
|
|
|
|
success = dbus_g_proxy_call_with_timeout (proxy, "Action",
|
|
CALL_TIMEOUT,
|
|
&error,
|
|
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,
|
|
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_BOOLEAN, nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH),
|
|
G_TYPE_INVALID,
|
|
DISPATCHER_TYPE_RESULT_ARRAY, &results,
|
|
G_TYPE_INVALID);
|
|
if (success) {
|
|
dispatcher_results_process (reqid, action, results);
|
|
free_results (results);
|
|
} else {
|
|
nm_log_warn (LOGD_DISPATCH, "(%u) failed: (%d) %s", reqid, error->code, error->message);
|
|
g_error_free (error);
|
|
}
|
|
} else {
|
|
info = g_malloc0 (sizeof (*info));
|
|
info->action = action;
|
|
info->request_id = reqid;
|
|
info->callback = callback;
|
|
info->user_data = user_data;
|
|
dbus_g_proxy_begin_call_with_timeout (proxy, "Action",
|
|
dispatcher_done_cb,
|
|
info,
|
|
(GDestroyNotify) dispatcher_info_cleanup,
|
|
CALL_TIMEOUT,
|
|
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,
|
|
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_BOOLEAN, nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH),
|
|
G_TYPE_INVALID);
|
|
success = TRUE;
|
|
}
|
|
|
|
g_hash_table_destroy (connection_hash);
|
|
g_hash_table_destroy (connection_props);
|
|
g_hash_table_destroy (device_props);
|
|
g_hash_table_destroy (device_ip4_props);
|
|
g_hash_table_destroy (device_ip6_props);
|
|
g_hash_table_destroy (device_dhcp4_props);
|
|
g_hash_table_destroy (device_dhcp6_props);
|
|
g_hash_table_destroy (vpn_ip4_props);
|
|
g_hash_table_destroy (vpn_ip6_props);
|
|
|
|
done:
|
|
if (success && info) {
|
|
/* Track the request in case of cancelation */
|
|
g_hash_table_insert (requests, GUINT_TO_POINTER (info->request_id), info);
|
|
if (out_call_id)
|
|
*out_call_id = info->request_id;
|
|
} else if (out_call_id)
|
|
*out_call_id = 0;
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* nm_dispatcher_call:
|
|
* @action: the %DispatcherAction
|
|
* @connection: the #NMConnection the action applies to
|
|
* @device: the #NMDevice the action applies to
|
|
* @callback: a caller-supplied 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 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 (DispatcherAction action,
|
|
NMConnection *connection,
|
|
NMDevice *device,
|
|
DispatcherFunc callback,
|
|
gpointer user_data,
|
|
guint *out_call_id)
|
|
{
|
|
return _dispatcher_call (action, FALSE, connection, device, NULL, NULL,
|
|
NULL, callback, user_data, out_call_id);
|
|
}
|
|
|
|
/**
|
|
* nm_dispatcher_call_sync():
|
|
* @action: the %DispatcherAction
|
|
* @connection: the #NMConnection the action applies to
|
|
* @device: the #NMDevice the action applies to
|
|
*
|
|
* This method always invokes the dispatcher action synchronously and it may
|
|
* take a long time to return.
|
|
*
|
|
* Returns: %TRUE if the action was dispatched, %FALSE on failure
|
|
*/
|
|
gboolean
|
|
nm_dispatcher_call_sync (DispatcherAction action,
|
|
NMConnection *connection,
|
|
NMDevice *device)
|
|
{
|
|
return _dispatcher_call (action, TRUE, connection, device, NULL, NULL,
|
|
NULL, NULL, NULL, NULL);
|
|
}
|
|
|
|
/**
|
|
* nm_dispatcher_call_vpn():
|
|
* @action: the %DispatcherAction
|
|
* @connection: the #NMConnection the action applies to
|
|
* @parent_device: the parent #NMDevice of the VPN connection
|
|
* @vpn_iface: the IP interface of the VPN tunnel, if any
|
|
* @vpn_ip4_config: the #NMIP4Config of the VPN connection
|
|
* @vpn_ip6_config: the #NMIP6Config of the VPN connection
|
|
* @callback: a caller-supplied 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 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_vpn (DispatcherAction action,
|
|
NMConnection *connection,
|
|
NMDevice *parent_device,
|
|
const char *vpn_iface,
|
|
NMIP4Config *vpn_ip4_config,
|
|
NMIP6Config *vpn_ip6_config,
|
|
DispatcherFunc callback,
|
|
gpointer user_data,
|
|
guint *out_call_id)
|
|
{
|
|
return _dispatcher_call (action, FALSE, connection, parent_device, vpn_iface,
|
|
vpn_ip4_config, vpn_ip6_config, callback, user_data, out_call_id);
|
|
}
|
|
|
|
/**
|
|
* nm_dispatcher_call_vpn_sync():
|
|
* @action: the %DispatcherAction
|
|
* @connection: the #NMConnection the action applies to
|
|
* @parent_device: the parent #NMDevice of the VPN connection
|
|
* @vpn_iface: the IP interface of the VPN tunnel, if any
|
|
* @vpn_ip4_config: the #NMIP4Config of the VPN connection
|
|
* @vpn_ip6_config: the #NMIP6Config of the VPN connection
|
|
*
|
|
* This method always invokes the dispatcher action synchronously and it may
|
|
* take a long time to return.
|
|
*
|
|
* Returns: %TRUE if the action was dispatched, %FALSE on failure
|
|
*/
|
|
gboolean
|
|
nm_dispatcher_call_vpn_sync (DispatcherAction action,
|
|
NMConnection *connection,
|
|
NMDevice *parent_device,
|
|
const char *vpn_iface,
|
|
NMIP4Config *vpn_ip4_config,
|
|
NMIP6Config *vpn_ip6_config)
|
|
{
|
|
return _dispatcher_call (action, TRUE, connection, parent_device, vpn_iface,
|
|
vpn_ip4_config, vpn_ip6_config, NULL, NULL, NULL);
|
|
}
|
|
|
|
void
|
|
nm_dispatcher_call_cancel (guint call_id)
|
|
{
|
|
DispatchInfo *info;
|
|
|
|
_ensure_requests ();
|
|
|
|
/* Canceling just means the callback doesn't get called, so set the
|
|
* DispatcherInfo's callback to NULL.
|
|
*/
|
|
info = g_hash_table_lookup (requests, GUINT_TO_POINTER (call_id));
|
|
g_return_if_fail (info);
|
|
|
|
if (info && info->callback) {
|
|
nm_log_dbg (LOGD_DISPATCH, "(%u) cancelling dispatcher callback action",
|
|
call_id);
|
|
info->callback = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dispatcher_dir_changed (GFileMonitor *monitor,
|
|
GFile *file,
|
|
GFile *other_file,
|
|
GFileMonitorEvent event_type,
|
|
Monitor *item)
|
|
{
|
|
const char *name;
|
|
char *full_name;
|
|
GDir *dir;
|
|
GError *error = NULL;
|
|
|
|
dir = g_dir_open (item->dir, 0, &error);
|
|
if (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);
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
nm_dispatcher_init (void)
|
|
{
|
|
GFile *file;
|
|
guint i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (monitors); i++) {
|
|
file = g_file_new_for_path (monitors[i].dir);
|
|
monitors[i].monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
|
|
if (monitors[i].monitor) {
|
|
g_signal_connect (monitors[i].monitor, "changed", G_CALLBACK (dispatcher_dir_changed), &monitors[i]);
|
|
dispatcher_dir_changed (monitors[i].monitor, file, NULL, 0, &monitors[i]);
|
|
}
|
|
g_object_unref (file);
|
|
}
|
|
}
|
|
|