NetworkManager/src/nm-dispatcher.c
Lubomir Rintel e69d386975 all: use the elvis operator wherever possible
Coccinelle:

  @@
  expression a, b;
  @@
  -a ? a : b
  +a ?: b

Applied with:

  spatch --sp-file ternary.cocci --in-place --smpl-spacing --dir .

With some manual adjustments on spots that Cocci didn't catch for
reasons unknown.

Thanks to the marvelous effort of the GNU compiler developer we can now
spare a couple of bits that could be used for more important things,
like this commit message. Standards commitees yet have to catch up.
2018-05-10 14:36:58 +02:00

1014 lines
36 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 - 2017 Red Hat, Inc.
* Copyright (C) 2005 - 2008 Novell, Inc.
*/
#include "nm-default.h"
#include "nm-dispatcher.h"
#include <string.h>
#include <errno.h>
#include "nm-dispatcher-api.h"
#include "NetworkManagerUtils.h"
#include "nm-utils.h"
#include "nm-connectivity.h"
#include "nm-act-request.h"
#include "devices/nm-device.h"
#include "nm-dhcp4-config.h"
#include "nm-dhcp6-config.h"
#include "nm-proxy-config.h"
#include "nm-ip4-config.h"
#include "nm-ip6-config.h"
#include "nm-manager.h"
#include "settings/nm-settings-connection.h"
#include "platform/nm-platform.h"
#include "nm-core-internal.h"
#define CALL_TIMEOUT (1000 * 60 * 10) /* 10 minutes for all scripts */
#define _NMLOG_DOMAIN LOGD_DISPATCH
#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "dispatcher", __VA_ARGS__)
static GDBusProxy *dispatcher_proxy;
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 = NM_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 (NMDispatcherAction action)
{
switch (action) {
case NM_DISPATCHER_ACTION_PRE_UP:
case NM_DISPATCHER_ACTION_VPN_PRE_UP:
return &monitors[MONITOR_INDEX_PRE_UP];
case NM_DISPATCHER_ACTION_PRE_DOWN:
case NM_DISPATCHER_ACTION_VPN_PRE_DOWN:
return &monitors[MONITOR_INDEX_PRE_DOWN];
default:
return &monitors[MONITOR_INDEX_DEFAULT];
}
}
static void
dump_proxy_to_props (NMProxyConfig *proxy, GVariantBuilder *builder)
{
const char *pac_url = NULL, *pac_script = NULL;
if (nm_proxy_config_get_method (proxy) == NM_PROXY_CONFIG_METHOD_NONE)
return;
pac_url = nm_proxy_config_get_pac_url (proxy);
if (pac_url) {
g_variant_builder_add (builder, "{sv}",
"pac-url",
g_variant_new_string (pac_url));
}
pac_script = nm_proxy_config_get_pac_script (proxy);
if (pac_script) {
g_variant_builder_add (builder, "{sv}",
"pac-script",
g_variant_new_string (pac_script));
}
}
static void
dump_ip4_to_props (NMIP4Config *ip4, GVariantBuilder *builder)
{
GVariantBuilder int_builder;
NMDedupMultiIter ipconf_iter;
gboolean first;
guint n, i;
const NMPlatformIP4Address *addr;
const NMPlatformIP4Route *route;
guint32 array[4];
/* Addresses */
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aau"));
first = TRUE;
nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &addr) {
const NMPObject *default_route;
array[0] = addr->address;
array[1] = addr->plen;
array[2] = ( first
&& (default_route = nm_ip4_config_best_default_route_get (ip4)))
? NMP_OBJECT_CAST_IP4_ROUTE (default_route)->gateway
: (guint32) 0;
g_variant_builder_add (&int_builder, "@au",
g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
array, 3, sizeof (guint32)));
first = FALSE;
}
g_variant_builder_add (builder, "{sv}",
"addresses",
g_variant_builder_end (&int_builder));
/* DNS servers */
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("au"));
n = nm_ip4_config_get_num_nameservers (ip4);
for (i = 0; i < n; i++)
g_variant_builder_add (&int_builder, "u", nm_ip4_config_get_nameserver (ip4, i));
g_variant_builder_add (builder, "{sv}",
"nameservers",
g_variant_builder_end (&int_builder));
/* Search domains */
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("as"));
n = nm_ip4_config_get_num_domains (ip4);
for (i = 0; i < n; i++)
g_variant_builder_add (&int_builder, "s", nm_ip4_config_get_domain (ip4, i));
g_variant_builder_add (builder, "{sv}",
"domains",
g_variant_builder_end (&int_builder));
/* WINS servers */
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("au"));
n = nm_ip4_config_get_num_wins (ip4);
for (i = 0; i < n; i++)
g_variant_builder_add (&int_builder, "u", nm_ip4_config_get_wins (ip4, i));
g_variant_builder_add (builder, "{sv}",
"wins-servers",
g_variant_builder_end (&int_builder));
/* Static routes */
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aau"));
nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route) {
if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
continue;
array[0] = route->network;
array[1] = route->plen;
array[2] = route->gateway;
array[3] = route->metric;
g_variant_builder_add (&int_builder, "@au",
g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
array, 4, sizeof (guint32)));
}
g_variant_builder_add (builder, "{sv}",
"routes",
g_variant_builder_end (&int_builder));
}
static void
dump_ip6_to_props (NMIP6Config *ip6, GVariantBuilder *builder)
{
GVariantBuilder int_builder;
NMDedupMultiIter ipconf_iter;
guint n, i;
gboolean first;
const NMPlatformIP6Address *addr;
const NMPlatformIP6Route *route;
GVariant *ip, *gw;
/* Addresses */
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("a(ayuay)"));
first = TRUE;
nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &addr) {
const NMPObject *default_route;
ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
&addr->address,
sizeof (struct in6_addr), 1);
gw = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
( first
&& (default_route = nm_ip6_config_best_default_route_get (ip6)))
? &NMP_OBJECT_CAST_IP6_ROUTE (default_route)->gateway
: &in6addr_any,
sizeof (struct in6_addr), 1);
g_variant_builder_add (&int_builder, "(@ayu@ay)", ip, addr->plen, gw);
first = FALSE;
}
g_variant_builder_add (builder, "{sv}",
"addresses",
g_variant_builder_end (&int_builder));
/* DNS servers */
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aay"));
n = nm_ip6_config_get_num_nameservers (ip6);
for (i = 0; i < n; i++) {
ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
nm_ip6_config_get_nameserver (ip6, i),
sizeof (struct in6_addr), 1);
g_variant_builder_add (&int_builder, "@ay", ip);
}
g_variant_builder_add (builder, "{sv}",
"nameservers",
g_variant_builder_end (&int_builder));
/* Search domains */
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("as"));
n = nm_ip6_config_get_num_domains (ip6);
for (i = 0; i < n; i++)
g_variant_builder_add (&int_builder, "s", nm_ip6_config_get_domain (ip6, i));
g_variant_builder_add (builder, "{sv}",
"domains",
g_variant_builder_end (&int_builder));
/* Static routes */
g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("a(ayuayu)"));
nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route) {
if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
continue;
ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
&route->network,
sizeof (struct in6_addr), 1);
gw = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
&route->gateway,
sizeof (struct in6_addr), 1);
g_variant_builder_add (&int_builder, "(@ayu@ayu)", ip, route->plen, gw, route->metric);
}
g_variant_builder_add (builder, "{sv}",
"routes",
g_variant_builder_end (&int_builder));
}
static void
fill_device_props (NMDevice *device,
GVariantBuilder *dev_builder,
GVariantBuilder *proxy_builder,
GVariantBuilder *ip4_builder,
GVariantBuilder *ip6_builder,
GVariant **dhcp4_props,
GVariant **dhcp6_props)
{
NMProxyConfig *proxy_config;
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 */
g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_IP_INTERFACE,
g_variant_new_string (nm_device_get_ip_iface (device)));
g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_INTERFACE,
g_variant_new_string (nm_device_get_iface (device)));
g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_TYPE,
g_variant_new_uint32 (nm_device_get_device_type (device)));
g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_STATE,
g_variant_new_uint32 (nm_device_get_state (device)));
if (nm_dbus_object_is_exported (NM_DBUS_OBJECT (device))) {
g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_PATH,
g_variant_new_object_path (nm_dbus_object_get_path (NM_DBUS_OBJECT (device))));
}
proxy_config = nm_device_get_proxy_config (device);
if (proxy_config)
dump_proxy_to_props (proxy_config, proxy_builder);
ip4_config = nm_device_get_ip4_config (device);
if (ip4_config)
dump_ip4_to_props (ip4_config, ip4_builder);
ip6_config = nm_device_get_ip6_config (device);
if (ip6_config)
dump_ip6_to_props (ip6_config, ip6_builder);
dhcp4_config = nm_device_get_dhcp4_config (device);
if (dhcp4_config)
*dhcp4_props = nm_dhcp4_config_get_options (dhcp4_config);
dhcp6_config = nm_device_get_dhcp6_config (device);
if (dhcp6_config)
*dhcp6_props = nm_dhcp6_config_get_options (dhcp6_config);
}
static void
fill_vpn_props (NMProxyConfig *proxy_config,
NMIP4Config *ip4_config,
NMIP6Config *ip6_config,
GVariantBuilder *proxy_builder,
GVariantBuilder *ip4_builder,
GVariantBuilder *ip6_builder)
{
if (proxy_config)
dump_proxy_to_props (proxy_config, proxy_builder);
if (ip4_config)
dump_ip4_to_props (ip4_config, ip4_builder);
if (ip6_config)
dump_ip6_to_props (ip6_config, ip6_builder);
}
typedef struct {
NMDispatcherAction action;
guint request_id;
NMDispatcherFunc 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 (nm_direct_hash,
NULL,
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 void
dispatcher_results_process (guint request_id, NMDispatcherAction action, GVariantIter *results)
{
const char *script, *err;
guint32 result;
const Monitor *monitor = _get_monitor_by_action (action);
g_return_if_fail (results != NULL);
if (g_variant_iter_n_children (results) == 0) {
_LOGD ("(%u) succeeded but no scripts invoked", request_id);
return;
}
while (g_variant_iter_next (results, "(&su&s)", &script, &result, &err)) {
const char *script_validation_msg = "";
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)";
if (result == DISPATCH_RESULT_SUCCESS) {
_LOGD ("(%u) %s succeeded%s",
request_id,
script, script_validation_msg);
} else {
_LOGW ("(%u) %s failed (%s): %s%s",
request_id,
script,
dispatch_result_to_string (result),
err,
script_validation_msg);
}
}
}
static void
dispatcher_done_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
{
DispatchInfo *info = user_data;
GVariant *ret;
GVariantIter *results;
GError *error = NULL;
ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
G_VARIANT_TYPE ("(a(sus))"),
&error);
if (ret) {
g_variant_get (ret, "(a(sus))", &results);
dispatcher_results_process (info->request_id, info->action, results);
g_variant_iter_free (results);
g_variant_unref (ret);
} else {
if (_nm_dbus_error_has_name (error, "org.freedesktop.systemd1.LoadFailed")) {
g_dbus_error_strip_remote_error (error);
_LOGW ("(%u) failed to call dispatcher scripts: %s",
info->request_id, error->message);
} else {
_LOGD ("(%u) failed to call dispatcher scripts: %s",
info->request_id, error->message);
}
g_clear_error (&error);
}
if (info->callback)
info->callback (info->request_id, info->user_data);
dispatcher_info_cleanup (info);
}
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_DHCP4_CHANGE] = NMD_ACTION_DHCP4_CHANGE,
[NM_DISPATCHER_ACTION_DHCP6_CHANGE] = NMD_ACTION_DHCP6_CHANGE,
[NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE] = NMD_ACTION_CONNECTIVITY_CHANGE
};
static const char *
action_to_string (NMDispatcherAction 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 (NMDispatcherAction action,
gboolean blocking,
NMDevice *device,
NMSettingsConnection *settings_connection,
NMConnection *applied_connection,
gboolean activation_type_external,
NMConnectivityState connectivity_state,
const char *vpn_iface,
NMProxyConfig *vpn_proxy_config,
NMIP4Config *vpn_ip4_config,
NMIP6Config *vpn_ip6_config,
NMDispatcherFunc callback,
gpointer user_data,
guint *out_call_id)
{
GVariant *connection_dict;
GVariantBuilder connection_props;
GVariantBuilder device_props;
GVariantBuilder device_proxy_props;
GVariantBuilder device_ip4_props;
GVariantBuilder device_ip6_props;
GVariant *device_dhcp4_props = NULL;
GVariant *device_dhcp6_props = NULL;
GVariantBuilder vpn_proxy_props;
GVariantBuilder vpn_ip4_props;
GVariantBuilder vpn_ip6_props;
DispatchInfo *info = NULL;
gboolean success = FALSE;
GError *error = NULL;
static guint request_counter = 0;
guint reqid = ++request_counter;
const char *connectivity_state_string = "UNKNOWN";
if (!dispatcher_proxy)
return FALSE;
/* Wrapping protection */
if (G_UNLIKELY (!reqid))
reqid = ++request_counter;
g_assert (!blocking || (!callback && !user_data));
_ensure_requests ();
/* All actions except 'hostname' and 'connectivity-change' require a device */
if ( action == NM_DISPATCHER_ACTION_HOSTNAME
|| action == NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE) {
_LOGD ("(%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);
_LOGD ("(%u) (%s) dispatching action '%s'%s",
reqid,
vpn_iface ?: nm_device_get_iface(device),
action_to_string (action),
blocking
? " (blocking)"
: (callback ? " (with callback)" : ""));
}
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);
_LOGD ("(%u) simulate request; no scripts in %s", reqid, _get_monitor_by_action(action)->dir);
} else
_LOGD ("(%u) ignoring request; no scripts in %s", reqid, _get_monitor_by_action(action)->dir);
success = TRUE;
goto done;
}
if (applied_connection)
connection_dict = nm_connection_to_dbus (applied_connection, NM_CONNECTION_SERIALIZE_NO_SECRETS);
else
connection_dict = g_variant_new_array (G_VARIANT_TYPE ("{sa{sv}}"), NULL, 0);
g_variant_builder_init (&connection_props, G_VARIANT_TYPE_VARDICT);
if (settings_connection) {
const char *connection_path;
const char *filename;
connection_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (settings_connection));
if (connection_path) {
g_variant_builder_add (&connection_props, "{sv}",
NMD_CONNECTION_PROPS_PATH,
g_variant_new_object_path (connection_path));
}
filename = nm_settings_connection_get_filename (settings_connection);
if (filename) {
g_variant_builder_add (&connection_props, "{sv}",
NMD_CONNECTION_PROPS_FILENAME,
g_variant_new_string (filename));
}
if (activation_type_external) {
g_variant_builder_add (&connection_props, "{sv}",
NMD_CONNECTION_PROPS_EXTERNAL,
g_variant_new_boolean (TRUE));
}
}
g_variant_builder_init (&device_props, G_VARIANT_TYPE_VARDICT);
g_variant_builder_init (&device_proxy_props, G_VARIANT_TYPE_VARDICT);
g_variant_builder_init (&device_ip4_props, G_VARIANT_TYPE_VARDICT);
g_variant_builder_init (&device_ip6_props, G_VARIANT_TYPE_VARDICT);
g_variant_builder_init (&vpn_proxy_props, G_VARIANT_TYPE_VARDICT);
g_variant_builder_init (&vpn_ip4_props, G_VARIANT_TYPE_VARDICT);
g_variant_builder_init (&vpn_ip6_props, G_VARIANT_TYPE_VARDICT);
/* hostname and connectivity-change actions don't send device data */
if ( action != NM_DISPATCHER_ACTION_HOSTNAME
&& action != NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE) {
fill_device_props (device,
&device_props,
&device_proxy_props,
&device_ip4_props,
&device_ip6_props,
&device_dhcp4_props,
&device_dhcp6_props);
if (vpn_ip4_config || vpn_ip6_config) {
fill_vpn_props (vpn_proxy_config,
vpn_ip4_config,
vpn_ip6_config,
&vpn_proxy_props,
&vpn_ip4_props,
&vpn_ip6_props);
}
}
if (!device_dhcp4_props)
device_dhcp4_props = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
if (!device_dhcp6_props)
device_dhcp6_props = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
connectivity_state_string = nm_connectivity_state_to_string (connectivity_state);
/* Send the action to the dispatcher */
if (blocking) {
GVariant *ret;
GVariantIter *results;
ret = _nm_dbus_proxy_call_sync (dispatcher_proxy, "Action",
g_variant_new ("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}ssa{sv}a{sv}a{sv}b)",
action_to_string (action),
connection_dict,
&connection_props,
&device_props,
&device_proxy_props,
&device_ip4_props,
&device_ip6_props,
device_dhcp4_props,
device_dhcp6_props,
connectivity_state_string,
vpn_iface ?: "",
&vpn_proxy_props,
&vpn_ip4_props,
&vpn_ip6_props,
nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH)),
G_VARIANT_TYPE ("(a(sus))"),
G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT,
NULL, &error);
if (ret) {
g_variant_get (ret, "(a(sus))", &results);
dispatcher_results_process (reqid, action, results);
g_variant_iter_free (results);
g_variant_unref (ret);
success = TRUE;
} else {
g_dbus_error_strip_remote_error (error);
_LOGW ("(%u) failed: %s", reqid, error->message);
g_clear_error (&error);
success = FALSE;
}
} else {
info = g_malloc0 (sizeof (*info));
info->action = action;
info->request_id = reqid;
info->callback = callback;
info->user_data = user_data;
g_dbus_proxy_call (dispatcher_proxy, "Action",
g_variant_new ("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}ssa{sv}a{sv}a{sv}b)",
action_to_string (action),
connection_dict,
&connection_props,
&device_props,
&device_proxy_props,
&device_ip4_props,
&device_ip6_props,
device_dhcp4_props,
device_dhcp6_props,
connectivity_state_string,
vpn_iface ?: "",
&vpn_proxy_props,
&vpn_ip4_props,
&vpn_ip6_props,
nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH)),
G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT,
NULL, dispatcher_done_cb, info);
success = TRUE;
}
g_variant_unref (device_dhcp4_props);
g_variant_unref (device_dhcp6_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_hostname:
* @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.
*
* Returns: %TRUE if the action was dispatched, %FALSE on failure
*/
gboolean
nm_dispatcher_call_hostname (NMDispatcherFunc callback,
gpointer user_data,
guint *out_call_id)
{
return _dispatcher_call (NM_DISPATCHER_ACTION_HOSTNAME, FALSE,
NULL, NULL, NULL, FALSE,
NM_CONNECTIVITY_UNKNOWN,
NULL, NULL, NULL, NULL,
callback, user_data, out_call_id);
}
/**
* nm_dispatcher_call_device:
* @action: the %NMDispatcherAction
* @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 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 (NMDispatcherAction action,
NMDevice *device,
NMActRequest *act_request,
NMDispatcherFunc callback,
gpointer user_data,
guint *out_call_id)
{
nm_assert (NM_IS_DEVICE (device));
if (!act_request) {
act_request = nm_device_get_act_request (device);
if (!act_request)
return FALSE;
}
nm_assert (NM_IN_SET (nm_active_connection_get_device (NM_ACTIVE_CONNECTION (act_request)), NULL, device));
return _dispatcher_call (action, FALSE,
device,
nm_act_request_get_settings_connection (act_request),
nm_act_request_get_applied_connection (act_request),
nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (act_request)) == NM_ACTIVATION_TYPE_EXTERNAL,
NM_CONNECTIVITY_UNKNOWN,
NULL, NULL, NULL, NULL,
callback, user_data, out_call_id);
}
/**
* nm_dispatcher_call_device_sync():
* @action: the %NMDispatcherAction
* @device: the #NMDevice the action applies to
* @act_request: the #NMActRequest for the action. If %NULL, use the
* current request of the device.
*
* 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_device_sync (NMDispatcherAction action,
NMDevice *device,
NMActRequest *act_request)
{
nm_assert (NM_IS_DEVICE (device));
if (!act_request) {
act_request = nm_device_get_act_request (device);
if (!act_request)
return FALSE;
}
nm_assert (NM_IN_SET (nm_active_connection_get_device (NM_ACTIVE_CONNECTION (act_request)), NULL, device));
return _dispatcher_call (action, TRUE,
device,
nm_act_request_get_settings_connection (act_request),
nm_act_request_get_applied_connection (act_request),
nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (act_request)) == NM_ACTIVATION_TYPE_EXTERNAL,
NM_CONNECTIVITY_UNKNOWN,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL);
}
/**
* nm_dispatcher_call_vpn():
* @action: the %NMDispatcherAction
* @settings_connection: the #NMSettingsConnection the action applies to
* @applied_connection: the currently applied connection
* @parent_device: the parent #NMDevice of the VPN connection
* @vpn_iface: the IP interface of the VPN tunnel, if any
* @vpn_proxy_config: the #NMProxyConfig of the VPN connection
* @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 (NMDispatcherAction action,
NMSettingsConnection *settings_connection,
NMConnection *applied_connection,
NMDevice *parent_device,
const char *vpn_iface,
NMProxyConfig *vpn_proxy_config,
NMIP4Config *vpn_ip4_config,
NMIP6Config *vpn_ip6_config,
NMDispatcherFunc callback,
gpointer user_data,
guint *out_call_id)
{
return _dispatcher_call (action, FALSE,
parent_device,
settings_connection,
applied_connection,
FALSE,
NM_CONNECTIVITY_UNKNOWN,
vpn_iface, vpn_proxy_config, vpn_ip4_config, vpn_ip6_config,
callback, user_data, out_call_id);
}
/**
* nm_dispatcher_call_vpn_sync():
* @action: the %NMDispatcherAction
* @settings_connection: the #NMSettingsConnection the action applies to
* @applied_connection: the currently applied connection
* @parent_device: the parent #NMDevice of the VPN connection
* @vpn_iface: the IP interface of the VPN tunnel, if any
* @vpn_proxy_config: the #NMProxyConfig of the VPN connection
* @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 (NMDispatcherAction action,
NMSettingsConnection *settings_connection,
NMConnection *applied_connection,
NMDevice *parent_device,
const char *vpn_iface,
NMProxyConfig *vpn_proxy_config,
NMIP4Config *vpn_ip4_config,
NMIP6Config *vpn_ip6_config)
{
return _dispatcher_call (action, TRUE,
parent_device,
settings_connection,
applied_connection,
FALSE,
NM_CONNECTIVITY_UNKNOWN,
vpn_iface, vpn_proxy_config, vpn_ip4_config, vpn_ip6_config,
NULL, NULL, NULL);
}
/**
* nm_dispatcher_call_connectivity():
* @connectivity_state: the #NMConnectivityState value
* @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 does not block the caller.
*
* Returns: %TRUE if the action was dispatched, %FALSE on failure
*/
gboolean
nm_dispatcher_call_connectivity (NMConnectivityState connectivity_state,
NMDispatcherFunc callback,
gpointer user_data,
guint *out_call_id)
{
return _dispatcher_call (NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE, FALSE,
NULL, NULL, NULL, FALSE,
connectivity_state,
NULL, NULL, NULL, NULL,
callback, user_data, out_call_id);
}
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) {
_LOGD ("(%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)
_LOGD ("%s script directory '%s' has scripts", item->description, item->dir);
else if (errsv == 0)
_LOGD ("%s script directory '%s' has no scripts", item->description, item->dir);
else {
_LOGD ("%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)) {
_LOGD ("%s script directory '%s' does not exist", item->description, item->dir);
item->has_scripts = FALSE;
} else {
_LOGD ("%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;
GError *error = NULL;
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);
}
dispatcher_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
NULL,
NM_DISPATCHER_DBUS_SERVICE,
NM_DISPATCHER_DBUS_PATH,
NM_DISPATCHER_DBUS_INTERFACE,
NULL, &error);
if (!dispatcher_proxy) {
_LOGE ("could not get dispatcher proxy! %s", error->message);
g_clear_error (&error);
}
}