nms-ifcfg-rh-plugin.c 37.6 KB
Newer Older
Dan Williams's avatar
Dan Williams committed
1
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 3
/* NetworkManager system settings service
 *
4
 * Dan Williams <dcbw@redhat.com>
5 6 7 8 9 10 11 12 13 14 15 16
 * Søren Sandmann <sandmann@daimi.au.dk>
 *
 * 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.
 *
17 18 19
 * 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.
20
 *
21
 * Copyright (C) 2007 - 2011 Red Hat, Inc.
22 23
 */

24
#include "nm-default.h"
25

Thomas Haller's avatar
Thomas Haller committed
26
#include "nms-ifcfg-rh-plugin.h"
27

28
#include <unistd.h>
29 30
#include <sys/types.h>
#include <sys/stat.h>
Dan Williams's avatar
Dan Williams committed
31 32
#include <gmodule.h>

33
#include "nm-dbus-compat.h"
34
#include "nm-setting-connection.h"
35
#include "settings/nm-settings-plugin.h"
36
#include "nm-config.h"
37
#include "NetworkManagerUtils.h"
38

Thomas Haller's avatar
Thomas Haller committed
39 40 41 42 43
#include "nms-ifcfg-rh-connection.h"
#include "nms-ifcfg-rh-common.h"
#include "nms-ifcfg-rh-reader.h"
#include "nms-ifcfg-rh-writer.h"
#include "nms-ifcfg-rh-utils.h"
44
#include "shvar.h"
Dan Winship's avatar
Dan Winship committed
45

46 47 48 49
#define IFCFGRH1_BUS_NAME                               "com.redhat.ifcfgrh1"
#define IFCFGRH1_OBJECT_PATH                            "/com/redhat/ifcfgrh1"
#define IFCFGRH1_IFACE1_NAME                            "com.redhat.ifcfgrh1"
#define IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS        "GetIfcfgDetails"
50

51
/*****************************************************************************/
52 53

typedef struct {
54 55 56 57 58
	NMConfig *config;

	struct {
		GDBusConnection *connection;
		GCancellable *cancellable;
59
		gulong signal_id;
60
		guint regist_id;
61 62
	} dbus;

63 64
	GHashTable *connections;  /* uuid::connection */
	gboolean initialized;
65

66
	GFileMonitor *ifcfg_monitor;
67
	gulong ifcfg_monitor_id;
68
} SettingsPluginIfcfgPrivate;
69

70
struct _SettingsPluginIfcfg {
71
	NMSettingsPlugin parent;
72 73 74 75
	SettingsPluginIfcfgPrivate _priv;
};

struct _SettingsPluginIfcfgClass {
76
	NMSettingsPluginClass parent;
77 78
};

79
G_DEFINE_TYPE (SettingsPluginIfcfg, settings_plugin_ifcfg, NM_TYPE_SETTINGS_PLUGIN)
80 81 82 83 84

#define SETTINGS_PLUGIN_IFCFG_GET_PRIVATE(self) _NM_GET_PRIVATE (self, SettingsPluginIfcfg, SETTINGS_IS_PLUGIN_IFCFG)

/*****************************************************************************/

85
static SettingsPluginIfcfg *settings_plugin_ifcfg_get (void);
86

87
NM_DEFINE_SINGLETON_GETTER (SettingsPluginIfcfg, settings_plugin_ifcfg_get, SETTINGS_TYPE_PLUGIN_IFCFG);
88

89 90 91 92 93
/*****************************************************************************/

#define _NMLOG_DOMAIN  LOGD_SETTINGS
#define _NMLOG(level, ...) \
    G_STMT_START { \
94
        nm_log ((level), (_NMLOG_DOMAIN), NULL, NULL, \
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
                "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
                "ifcfg-rh: " \
                _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
    } G_STMT_END

/*****************************************************************************/

static NMIfcfgConnection *update_connection (SettingsPluginIfcfg *plugin,
                                             NMConnection *source,
                                             const char *full_path,
                                             NMIfcfgConnection *connection,
                                             gboolean protect_existing_connection,
                                             GHashTable *protected_connections,
                                             GError **error);

/*****************************************************************************/

112 113 114
static void
connection_ifcfg_changed (NMIfcfgConnection *connection, gpointer user_data)
{
115 116
	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (user_data);
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
117 118
	const char *path;

119
	path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection));
120 121
	g_return_if_fail (path != NULL);

122 123 124 125 126 127 128 129
	if (!priv->ifcfg_monitor) {
		_LOGD ("connection_ifcfg_changed("NM_IFCFG_CONNECTION_LOG_FMTD"): %s", NM_IFCFG_CONNECTION_LOG_ARGD (connection), "ignore event");
		return;
	}

	_LOGD ("connection_ifcfg_changed("NM_IFCFG_CONNECTION_LOG_FMTD"): %s", NM_IFCFG_CONNECTION_LOG_ARGD (connection), "reload");

	update_connection (self, NULL, path, connection, TRUE, NULL, NULL);
130 131
}

132 133 134
static void
connection_removed_cb (NMSettingsConnection *obj, gpointer user_data)
{
135
	g_hash_table_remove (SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) user_data)->connections,
136
	                     nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (obj)));
137 138
}

139
static void
140
remove_connection (SettingsPluginIfcfg *self, NMIfcfgConnection *connection)
141
{
142
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
143
	gboolean unmanaged, unrecognized;
144 145 146 147

	g_return_if_fail (self != NULL);
	g_return_if_fail (connection != NULL);

148 149
	_LOGI ("remove "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection));

150 151
	unmanaged = !!nm_ifcfg_connection_get_unmanaged_spec (connection);
	unrecognized = !!nm_ifcfg_connection_get_unrecognized_spec (connection);
152

153
	g_object_ref (connection);
154
	g_hash_table_remove (priv->connections, nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection)));
155
	if (!unmanaged && !unrecognized)
156
		nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
157
	g_object_unref (connection);
158

159 160
	/* Emit changes _after_ removing the connection */
	if (unmanaged)
161
		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
162
	if (unrecognized)
163
		_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
164 165
}

166
static NMIfcfgConnection *
167
find_by_path (SettingsPluginIfcfg *self, const char *path)
168
{
169
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
170
	GHashTableIter iter;
171
	NMSettingsConnection *candidate = NULL;
172 173 174 175 176

	g_return_val_if_fail (path != NULL, NULL);

	g_hash_table_iter_init (&iter, priv->connections);
	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &candidate)) {
177 178
		if (g_strcmp0 (path, nm_settings_connection_get_filename (candidate)) == 0)
			return NM_IFCFG_CONNECTION (candidate);
179 180 181 182
	}
	return NULL;
}

183
static NMIfcfgConnection *
184
update_connection (SettingsPluginIfcfg *self,
185 186 187
                   NMConnection *source,
                   const char *full_path,
                   NMIfcfgConnection *connection,
188
                   gboolean protect_existing_connection,
189 190
                   GHashTable *protected_connections,
                   GError **error)
191
{
192
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
193
	NMIfcfgConnection *connection_new;
194
	NMIfcfgConnection *connection_by_uuid;
195
	GError *local = NULL;
196
	const char *new_unmanaged = NULL, *old_unmanaged = NULL;
197
	const char *new_unrecognized = NULL, *old_unrecognized = NULL;
198 199
	gboolean unmanaged_changed = FALSE, unrecognized_changed = FALSE;
	const char *uuid;
200
	gboolean ignore_error = FALSE;
201

202 203 204 205 206 207 208 209
	g_return_val_if_fail (!source || NM_IS_CONNECTION (source), NULL);
	g_return_val_if_fail (full_path || source, NULL);

	if (full_path)
		_LOGD ("loading from file \"%s\"...", full_path);

	/* Create a NMIfcfgConnection instance, either by reading from @full_path or
	 * based on @source. */
210
	connection_new = nm_ifcfg_connection_new (source, full_path, &local, &ignore_error);
211 212 213 214 215 216
	if (!connection_new) {
		/* Unexpected failure. Probably the file is invalid? */
		if (   connection
		    && !protect_existing_connection
		    && (!protected_connections || !g_hash_table_contains (protected_connections, connection)))
			remove_connection (self, connection);
217 218 219 220
		if (!source) {
			_NMLOG (ignore_error ? LOGL_DEBUG : LOGL_WARN,
			        "loading \"%s\" fails: %s", full_path, local ? local->message : "(unknown reason)");
		}
221
		g_propagate_error (error, local);
222
		return NULL;
223 224
	}

225
	uuid = nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_new));
226 227 228 229 230 231 232 233 234 235 236
	connection_by_uuid = g_hash_table_lookup (priv->connections, uuid);

	if (   connection
	    && connection != connection_by_uuid) {

		if (   (protect_existing_connection && connection_by_uuid != NULL)
		    || (protected_connections && g_hash_table_contains (protected_connections, connection))) {
			NMIfcfgConnection *conflicting = (protect_existing_connection && connection_by_uuid != NULL) ? connection_by_uuid : connection;

			if (source)
				_LOGW ("cannot update protected connection "NM_IFCFG_CONNECTION_LOG_FMT" due to conflicting UUID %s", NM_IFCFG_CONNECTION_LOG_ARG (conflicting), uuid);
237
			else
238 239 240 241 242
				_LOGW ("cannot load %s due to conflicting UUID for "NM_IFCFG_CONNECTION_LOG_FMT, full_path, NM_IFCFG_CONNECTION_LOG_ARG (conflicting));
			g_object_unref (connection_new);
			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
			                     "Cannot update protected connection due to conflicting UUID");
			return NULL;
243 244
		}

245 246
		/* The new connection has a different UUID then the original one that we
		 * are about to update. Remove @connection. */
247
		remove_connection (self, connection);
248 249
	}

250 251 252 253 254 255 256 257 258 259 260 261 262
	/* Check if the found connection with the same UUID is not protected from updating. */
	if (   connection_by_uuid
	    && (   (!connection && protect_existing_connection)
	        || (protected_connections && g_hash_table_contains (protected_connections, connection_by_uuid)))) {
		if (source)
			_LOGW ("cannot update connection due to conflicting UUID for "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_by_uuid));
		else
			_LOGW ("cannot load %s due to conflicting UUID for "NM_IFCFG_CONNECTION_LOG_FMT, full_path, NM_IFCFG_CONNECTION_LOG_ARG (connection_by_uuid));
		g_object_unref (connection_new);
		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
		                      "Skip updating protected connection during reload");
		return NULL;
	}
263

264 265 266 267
	/* Evaluate unmanaged/unrecognized flags. */
	if (connection_by_uuid)
		old_unmanaged = nm_ifcfg_connection_get_unmanaged_spec (connection_by_uuid);
	new_unmanaged = nm_ifcfg_connection_get_unmanaged_spec (connection_new);
268 269
	unmanaged_changed = g_strcmp0 (old_unmanaged, new_unmanaged);

270 271 272
	if (connection_by_uuid)
		old_unrecognized = nm_ifcfg_connection_get_unrecognized_spec (connection_by_uuid);
	new_unrecognized = nm_ifcfg_connection_get_unrecognized_spec (connection_new);
273 274
	unrecognized_changed = g_strcmp0 (old_unrecognized, new_unrecognized);

275 276 277 278 279 280 281
	if (connection_by_uuid) {
		const char *old_path;

		old_path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid));

		if (   !unmanaged_changed
		    && !unrecognized_changed
282 283
		    && nm_connection_compare (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_by_uuid)),
		                              nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
284 285
		                              NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS |
		                              NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) {
286 287 288 289 290 291
			if (   old_path
			    && !nm_streq0 (old_path, full_path)) {
				_LOGI ("rename \"%s\" to "NM_IFCFG_CONNECTION_LOG_FMT" without other changes",
				       nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid)),
				       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
			}
292 293 294 295 296 297 298 299
		} else {

			/*******************************************************
			 * UPDATE
			 *******************************************************/

			if (source)
				_LOGI ("update "NM_IFCFG_CONNECTION_LOG_FMT" from %s", NM_IFCFG_CONNECTION_LOG_ARG (connection_new), NM_IFCFG_CONNECTION_LOG_PATH (old_path));
300
			else if (nm_streq0 (old_path, nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_new))))
301 302 303 304 305 306 307 308 309 310 311
				_LOGI ("update "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
			else if (old_path)
				_LOGI ("rename \"%s\" to "NM_IFCFG_CONNECTION_LOG_FMT, old_path, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
			else
				_LOGI ("update and persist "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));

			g_object_set (connection_by_uuid,
			              NM_IFCFG_CONNECTION_UNMANAGED_SPEC, new_unmanaged,
			              NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC, new_unrecognized,
			              NULL);

312
			if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (connection_by_uuid),
313
			                                    nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
314
			                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
315 316 317
			                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
			                                    "ifcfg-update",
			                                    &local)) {
318 319 320 321 322 323 324 325
				/* Shouldn't ever get here as 'connection_new' was verified by the reader already
				 * and the UUID did not change. */
				g_assert_not_reached ();
			}
			g_assert_no_error (local);

			if (new_unmanaged || new_unrecognized) {
				if (!old_unmanaged && !old_unrecognized) {
326 327
					/* ref connection first, because we put it into priv->connections below.
					 * Emitting signal-removed might otherwise delete it. */
328
					g_object_ref (connection_by_uuid);
329

330 331 332
					/* Unexport the connection by telling the settings service it's
					 * been removed.
					 */
333
					nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection_by_uuid));
334 335 336 337 338

					/* signal_remove() will end up removing the connection from our hash,
					 * so add it back now.
					 */
					g_hash_table_insert (priv->connections,
339
					                     g_strdup (nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_by_uuid))),
340
					                     connection_by_uuid /* we took reference above and pass it on */);
341 342 343 344 345
				}
			} else {
				if (old_unmanaged /* && !new_unmanaged */) {
					_LOGI ("Managing connection "NM_IFCFG_CONNECTION_LOG_FMT" and its device because NM_CONTROLLED was true.",
					       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
346 347
					_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
					                                                  NM_SETTINGS_CONNECTION (connection_by_uuid));
348 349 350
				} else if (old_unrecognized /* && !new_unrecognized */) {
					_LOGI ("Managing connection "NM_IFCFG_CONNECTION_LOG_FMT" because it is now a recognized type.",
					       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
351 352
					_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
					                                                  NM_SETTINGS_CONNECTION (connection_by_uuid));
353 354
				}
			}
355

356
			if (unmanaged_changed)
357
				_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
358
			if (unrecognized_changed)
359
				_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
360
		}
361 362 363
		nm_settings_connection_set_filename (NM_SETTINGS_CONNECTION (connection_by_uuid), full_path);
		g_object_unref (connection_new);
		return connection_by_uuid;
364
	} else {
365

366 367 368 369 370 371 372 373
		/*******************************************************
		 * ADD
		 *******************************************************/

		if (source)
			_LOGI ("add connection "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
		else
			_LOGI ("new connection "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
374 375 376
		g_hash_table_insert (priv->connections,
		                     g_strdup (uuid),
		                     connection_new /* take reference */);
377 378 379 380 381 382

		g_signal_connect (connection_new, NM_SETTINGS_CONNECTION_REMOVED,
		                  G_CALLBACK (connection_removed_cb),
		                  self);

		if (nm_ifcfg_connection_get_unmanaged_spec (connection_new)) {
383 384 385
			_LOGI ("Ignoring connection "NM_IFCFG_CONNECTION_LOG_FMT" due to NM_CONTROLLED=no. Unmanaged: %s.",
			       NM_IFCFG_CONNECTION_LOG_ARG (connection_new),
			       nm_ifcfg_connection_get_unmanaged_spec (connection_new));
386 387 388 389 390 391 392 393 394 395 396
		} else if (nm_ifcfg_connection_get_unrecognized_spec (connection_new))
			_LOGW ("Ignoring connection "NM_IFCFG_CONNECTION_LOG_FMT" of unrecognized type.", NM_IFCFG_CONNECTION_LOG_ARG (connection_new));

		/* watch changes of ifcfg hardlinks */
		g_signal_connect (G_OBJECT (connection_new), "ifcfg-changed",
		                  G_CALLBACK (connection_ifcfg_changed), self);

		if (!source) {
			/* Only raise the signal if we were called without source, i.e. if we read the connection from file.
			 * Otherwise, we were called by add_connection() which does not expect the signal. */
			if (nm_ifcfg_connection_get_unmanaged_spec (connection_new))
397
				_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
398
			else if (nm_ifcfg_connection_get_unrecognized_spec (connection_new))
399 400 401 402 403
				_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
			else {
				_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
				                                                  NM_SETTINGS_CONNECTION (connection_new));
			}
404
		}
405
		return connection_new;
406
	}
407 408 409
}

static void
410 411 412 413 414
ifcfg_dir_changed (GFileMonitor *monitor,
                   GFile *file,
                   GFile *other_file,
                   GFileMonitorEvent event_type,
                   gpointer user_data)
415
{
416
	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (user_data);
417
	char *path, *ifcfg_path;
418 419
	NMIfcfgConnection *connection;

420
	path = g_file_get_path (file);
421

422
	ifcfg_path = utils_detect_ifcfg_path (path, FALSE);
423
	_LOGD ("ifcfg_dir_changed(%s) = %d // %s", path, event_type, ifcfg_path ?: "(none)");
424 425
	if (ifcfg_path) {
		connection = find_by_path (plugin, ifcfg_path);
426 427 428 429 430 431 432 433
		switch (event_type) {
		case G_FILE_MONITOR_EVENT_DELETED:
			if (connection)
				remove_connection (plugin, connection);
			break;
		case G_FILE_MONITOR_EVENT_CREATED:
		case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
			/* Update or new */
434
			update_connection (plugin, NULL, ifcfg_path, connection, TRUE, NULL, NULL);
435 436 437 438
			break;
		default:
			break;
		}
439
		g_free (ifcfg_path);
440
	}
441
	g_free (path);
442 443 444
}

static void
445
setup_ifcfg_monitoring (SettingsPluginIfcfg *plugin)
446
{
447
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (plugin);
448 449
	GFile *file;
	GFileMonitor *monitor;
450

451
	file = g_file_new_for_path (IFCFG_DIR "/");
452
	monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
453
	g_object_unref (file);
454

455
	if (monitor) {
456 457 458
		priv->ifcfg_monitor_id = g_signal_connect (monitor, "changed",
		                                           G_CALLBACK (ifcfg_dir_changed), plugin);
		priv->ifcfg_monitor = monitor;
459
	}
460 461
}

462 463 464 465 466
static GHashTable *
_paths_from_connections (GHashTable *connections)
{
	GHashTableIter iter;
	NMIfcfgConnection *connection;
467
	GHashTable *paths = g_hash_table_new (nm_str_hash, g_str_equal);
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498

	g_hash_table_iter_init (&iter, connections);
	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
		const char *path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection));

		if (path)
			g_hash_table_add (paths, (void *) path);
	}
	return paths;
}

static int
_sort_paths (const char **f1, const char **f2, GHashTable *paths)
{
	struct stat st;
	gboolean c1, c2;
	gint64 m1, m2;

	c1 = !!g_hash_table_contains (paths, *f1);
	c2 = !!g_hash_table_contains (paths, *f2);
	if (c1 != c2)
		return c1 ? -1 : 1;

	m1 = stat (*f1, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
	m2 = stat (*f2, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
	if (m1 != m2)
		return m1 > m2 ? -1 : 1;

	return strcmp (*f1, *f2);
}

499
static void
500
read_connections (SettingsPluginIfcfg *plugin)
501
{
502
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (plugin);
503 504 505
	GDir *dir;
	GError *err = NULL;
	const char *item;
506
	GHashTable *alive_connections;
507 508
	GHashTableIter iter;
	NMIfcfgConnection *connection;
509
	GPtrArray *dead_connections = NULL;
510 511 512
	guint i;
	GPtrArray *filenames;
	GHashTable *paths;
513 514 515

	dir = g_dir_open (IFCFG_DIR, 0, &err);
	if (!dir) {
516
		_LOGW ("Could not read directory '%s': %s", IFCFG_DIR, err->message);
517 518 519 520
		g_error_free (err);
		return;
	}

521
	alive_connections = g_hash_table_new (nm_direct_hash, NULL);
522

523
	filenames = g_ptr_array_new_with_free_func (g_free);
524
	while ((item = g_dir_read_name (dir))) {
525
		char *full_path, *real_path;
526 527

		full_path = g_build_filename (IFCFG_DIR, item, NULL);
528 529 530 531 532
		real_path = utils_detect_ifcfg_path (full_path, TRUE);

		if (real_path)
			g_ptr_array_add (filenames, real_path);
		g_free (full_path);
533 534
	}
	g_dir_close (dir);
535

536 537 538 539
	/* While reloading, we don't replace connections that we already loaded while
	 * iterating over the files.
	 *
	 * To have sensible, reproducible behavior, sort the paths by last modification
luz.paz's avatar
luz.paz committed
540
	 * time preferring older files.
541 542 543 544 545 546
	 */
	paths = _paths_from_connections (priv->connections);
	g_ptr_array_sort_with_data (filenames, (GCompareDataFunc) _sort_paths, paths);
	g_hash_table_destroy (paths);

	for (i = 0; i < filenames->len; i++) {
547 548 549
		connection = update_connection (plugin, NULL, filenames->pdata[i], NULL, FALSE, alive_connections, NULL);
		if (connection)
			g_hash_table_add (alive_connections, connection);
550
	}
551
	g_ptr_array_free (filenames, TRUE);
552

553 554 555 556 557 558 559 560
	g_hash_table_iter_init (&iter, priv->connections);
	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
		if (   !g_hash_table_contains (alive_connections, connection)
		    && nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection))) {
			if (!dead_connections)
				dead_connections = g_ptr_array_new ();
			g_ptr_array_add (dead_connections, connection);
		}
561
	}
562
	g_hash_table_destroy (alive_connections);
563

564 565 566 567 568
	if (dead_connections) {
		for (i = 0; i < dead_connections->len; i++)
			remove_connection (plugin, dead_connections->pdata[i]);
		g_ptr_array_free (dead_connections, TRUE);
	}
569 570
}

571
static GSList *
572
get_connections (NMSettingsPlugin *config)
573
{
574 575
	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (config);
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (plugin);
576
	GSList *list = NULL;
577
	GHashTableIter iter;
578
	NMIfcfgConnection *connection;
579

580
	if (!priv->initialized) {
581 582
		if (nm_config_get_monitor_connection_files (nm_config_get ()))
			setup_ifcfg_monitoring (plugin);
583
		read_connections (plugin);
584
		priv->initialized = TRUE;
585
	}
586

587
	g_hash_table_iter_init (&iter, priv->connections);
588
	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &connection)) {
589 590
		if (   !nm_ifcfg_connection_get_unmanaged_spec (connection)
		    && !nm_ifcfg_connection_get_unrecognized_spec (connection))
591
			list = g_slist_prepend (list, connection);
592
	}
593

594
	return list;
595 596
}

597
static gboolean
598
load_connection (NMSettingsPlugin *config,
599 600
                 const char *filename)
{
601
	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (config);
602
	NMIfcfgConnection *connection;
603
	char *ifcfg_path;
604

605
	if (!nm_utils_file_is_in_path (filename, IFCFG_DIR))
606 607
		return FALSE;

608 609 610 611
	/* get the real ifcfg-path. This allows us to properly
	 * handle load command using a route-* file etc. */
	ifcfg_path = utils_detect_ifcfg_path (filename, FALSE);
	if (!ifcfg_path)
612 613
		return FALSE;

614 615
	connection = find_by_path (plugin, ifcfg_path);
	update_connection (plugin, NULL, ifcfg_path, connection, TRUE, NULL, NULL);
616
	if (!connection)
617
		connection = find_by_path (plugin, ifcfg_path);
618

619
	g_free (ifcfg_path);
620 621 622
	return (connection != NULL);
}

623
static void
624
reload_connections (NMSettingsPlugin *config)
625
{
626
	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (config);
627 628 629 630

	read_connections (plugin);
}

631
static GSList *
632
get_unhandled_specs (NMSettingsPlugin *config,
633
                     const char *property)
634
{
635
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) config);
636 637
	GSList *list = NULL, *list_iter;
	GHashTableIter iter;
638 639
	gpointer connection;
	char *spec;
640
	gboolean found;
641

642
	g_hash_table_iter_init (&iter, priv->connections);
643 644
	while (g_hash_table_iter_next (&iter, NULL, &connection)) {
		g_object_get (connection, property, &spec, NULL);
645 646 647 648 649 650 651 652
		if (spec) {
			/* Ignore duplicates */
			for (list_iter = list, found = FALSE; list_iter; list_iter = g_slist_next (list_iter)) {
				if (g_str_equal (list_iter->data, spec)) {
					found = TRUE;
					break;
				}
			}
653 654 655 656
			if (found)
				g_free (spec);
			else
				list = g_slist_prepend (list, spec);
657 658
		}
	}
659 660 661
	return list;
}

662
static GSList *
663
get_unmanaged_specs (NMSettingsPlugin *config)
664 665 666 667 668
{
	return get_unhandled_specs (config, NM_IFCFG_CONNECTION_UNMANAGED_SPEC);
}

static GSList *
669
get_unrecognized_specs (NMSettingsPlugin *config)
670 671 672 673
{
	return get_unhandled_specs (config, NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC);
}

674
static NMSettingsConnection *
675
add_connection (NMSettingsPlugin *config,
676
                NMConnection *connection,
677
                gboolean save_to_disk,
678 679
                GError **error)
{
680
	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (config);
681
	gs_free char *path = NULL;
682
	gs_unref_object NMConnection *reread = NULL;
683

684
	if (save_to_disk) {
685
		if (!nms_ifcfg_rh_writer_write_connection (connection, IFCFG_DIR, NULL, &path, &reread, NULL, error))
686
			return NULL;
687 688 689
	} else {
		if (!nms_ifcfg_rh_writer_can_write_connection (connection, error))
			return NULL;
690
	}
691
	return NM_SETTINGS_CONNECTION (update_connection (self, reread ?: connection, path, NULL, FALSE, NULL, error));
692 693
}

Dan Winship's avatar
Dan Winship committed
694
static void
695
impl_ifcfgrh_get_ifcfg_details (SettingsPluginIfcfg *plugin,
Dan Winship's avatar
Dan Winship committed
696 697
                                GDBusMethodInvocation *context,
                                const char *in_ifcfg)
698 699 700 701 702
{
	NMIfcfgConnection *connection;
	NMSettingConnection *s_con;
	const char *uuid;
	const char *path;
703
	gs_free char *ifcfg_path = NULL;
704 705

	if (!g_path_is_absolute (in_ifcfg)) {
Dan Winship's avatar
Dan Winship committed
706 707 708 709 710
		g_dbus_method_invocation_return_error (context,
		                                       NM_SETTINGS_ERROR,
		                                       NM_SETTINGS_ERROR_INVALID_CONNECTION,
		                                       "ifcfg path '%s' is not absolute", in_ifcfg);
		return;
711 712
	}

713
	ifcfg_path = utils_detect_ifcfg_path (in_ifcfg, TRUE);
714 715 716 717 718 719 720 721 722
	if (!ifcfg_path) {
		g_dbus_method_invocation_return_error (context,
		                                       NM_SETTINGS_ERROR,
		                                       NM_SETTINGS_ERROR_INVALID_CONNECTION,
		                                       "ifcfg path '%s' is not an ifcfg base file", in_ifcfg);
		return;
	}

	connection = find_by_path (plugin, ifcfg_path);
723 724 725
	if (   !connection
	    || nm_ifcfg_connection_get_unmanaged_spec (connection)
	    || nm_ifcfg_connection_get_unrecognized_spec (connection)) {
Dan Winship's avatar
Dan Winship committed
726 727 728 729 730
		g_dbus_method_invocation_return_error (context,
		                                       NM_SETTINGS_ERROR,
		                                       NM_SETTINGS_ERROR_INVALID_CONNECTION,
		                                       "ifcfg file '%s' unknown", in_ifcfg);
		return;
731 732
	}

733
	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection)));
734
	if (!s_con) {
Dan Winship's avatar
Dan Winship committed
735 736 737 738 739
		g_dbus_method_invocation_return_error (context,
		                                       NM_SETTINGS_ERROR,
		                                       NM_SETTINGS_ERROR_FAILED,
		                                       "unable to retrieve the connection setting");
		return;
740 741 742 743
	}

	uuid = nm_setting_connection_get_uuid (s_con);
	if (!uuid) {
Dan Winship's avatar
Dan Winship committed
744 745 746 747 748
		g_dbus_method_invocation_return_error (context,
		                                       NM_SETTINGS_ERROR,
		                                       NM_SETTINGS_ERROR_FAILED,
		                                       "unable to get the UUID");
		return;
749
	}
750

751
	path = nm_dbus_object_get_path (NM_DBUS_OBJECT (connection));
752
	if (!path) {
Dan Winship's avatar
Dan Winship committed
753 754 755 756 757
		g_dbus_method_invocation_return_error (context,
		                                       NM_SETTINGS_ERROR,
		                                       NM_SETTINGS_ERROR_FAILED,
		                                       "unable to get the connection D-Bus path");
		return;
758 759
	}

Dan Winship's avatar
Dan Winship committed
760 761
	g_dbus_method_invocation_return_value (context,
	                                       g_variant_new ("(so)", uuid, path));
762 763
}

764 765 766 767 768 769
/*****************************************************************************/

static void
_dbus_clear (SettingsPluginIfcfg *self)
{
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
770
	guint id;
771 772 773 774 775

	nm_clear_g_signal_handler (priv->dbus.connection, &priv->dbus.signal_id);

	nm_clear_g_cancellable (&priv->dbus.cancellable);

776 777 778
	if ((id = nm_steal_int (&priv->dbus.regist_id))) {
		if (!g_dbus_connection_unregister_object (priv->dbus.connection, id))
			_LOGW ("dbus: unexpected failure to unregister object");
779 780 781 782 783 784 785 786 787 788 789
	}

	g_clear_object (&priv->dbus.connection);
}

static void
_dbus_connection_closed (GDBusConnection *connection,
                         gboolean         remote_peer_vanished,
                         GError          *error,
                         gpointer         user_data)
{
790
	_LOGW ("dbus: %s bus closed", IFCFGRH1_BUS_NAME);
791 792 793 794 795
	_dbus_clear (SETTINGS_PLUGIN_IFCFG (user_data));

	/* Retry or recover? */
}

796 797 798 799 800 801 802 803 804 805 806 807 808
static void
_method_call (GDBusConnection *connection,
              const char *sender,
              const char *object_path,
              const char *interface_name,
              const char *method_name,
              GVariant *parameters,
              GDBusMethodInvocation *invocation,
              gpointer user_data)
{
	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (user_data);
	const char *ifcfg;

809 810 811 812 813 814 815 816
	if (   !nm_streq (interface_name, IFCFGRH1_IFACE1_NAME)
	    || !nm_streq (method_name, IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS)) {
		g_dbus_method_invocation_return_error (invocation,
		                                       G_DBUS_ERROR,
		                                       G_DBUS_ERROR_UNKNOWN_METHOD,
		                                       "Unknown method %s",
		                                       method_name);
		return;
817 818
	}

819 820
	g_variant_get (parameters, "(&s)", &ifcfg);
	impl_ifcfgrh_get_ifcfg_details (self, invocation, ifcfg);
821 822
}

823
static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO (
824 825 826 827 828
	IFCFGRH1_BUS_NAME,
	.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
		NM_DEFINE_GDBUS_METHOD_INFO (
			IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS,
			.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
829
				NM_DEFINE_GDBUS_ARG_INFO ("ifcfg", "s"),
830 831
			),
			.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
832 833
				NM_DEFINE_GDBUS_ARG_INFO ("uuid", "s"),
				NM_DEFINE_GDBUS_ARG_INFO ("path", "o"),
834 835 836 837 838
			),
		),
	),
);

839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
static void
_dbus_request_name_done (GObject *source_object,
                         GAsyncResult *res,
                         gpointer user_data)
{
	GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
	SettingsPluginIfcfg *self;
	SettingsPluginIfcfgPrivate *priv;
	gs_free_error GError *error = NULL;
	gs_unref_variant GVariant *ret = NULL;
	guint32 result;

	ret = g_dbus_connection_call_finish (connection, res, &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = SETTINGS_PLUGIN_IFCFG (user_data);
	priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);

	g_clear_object (&priv->dbus.cancellable);

	if (!ret) {
		_LOGW ("dbus: couldn't acquire D-Bus service: %s", error->message);
		_dbus_clear (self);
		return;
	}

	g_variant_get (ret, "(u)", &result);

	if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
		_LOGW ("dbus: couldn't acquire ifcfgrh1 D-Bus service (already taken)");
		_dbus_clear (self);
		return;
	}

	{
875 876
		static const GDBusInterfaceVTable interface_vtable = {
			.method_call = _method_call,
877 878
		};

879 880 881 882 883 884 885 886 887
		priv->dbus.regist_id = g_dbus_connection_register_object (connection,
		                                                          IFCFGRH1_OBJECT_PATH,
		                                                          interface_info,
		                                                          NM_UNCONST_PTR (GDBusInterfaceVTable, &interface_vtable),
		                                                          self,
		                                                          NULL,
		                                                          &error);
		if (!priv->dbus.regist_id) {
			_LOGW ("dbus: couldn't register D-Bus service: %s", error->message);
888 889 890 891 892
			_dbus_clear (self);
			return;
		}
	}

893
	_LOGD ("dbus: acquired D-Bus service %s and exported %s object",
894 895
	       IFCFGRH1_BUS_NAME,
	       IFCFGRH1_OBJECT_PATH);
896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
}

static void
_dbus_create_done (GObject *source_object,
                   GAsyncResult *res,
                   gpointer user_data)
{
	SettingsPluginIfcfg *self;
	SettingsPluginIfcfgPrivate *priv;
	gs_free_error GError *error = NULL;
	GDBusConnection *connection;

	connection = g_dbus_connection_new_for_address_finish (res, &error);
	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
		return;

	self = SETTINGS_PLUGIN_IFCFG (user_data);
	priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);

	g_clear_object (&priv->dbus.cancellable);

	if (!connection) {
		_LOGW ("dbus: couldn't initialize system bus: %s", error->message);
		return;
	}

	priv->dbus.connection = connection;
	priv->dbus.cancellable = g_cancellable_new ();

	priv->dbus.signal_id = g_signal_connect (priv->dbus.connection,
	                                         "closed",
	                                         G_CALLBACK (_dbus_connection_closed),
	                                         self);

	g_dbus_connection_call (priv->dbus.connection,
	                        DBUS_SERVICE_DBUS,
	                        DBUS_PATH_DBUS,
	                        DBUS_INTERFACE_DBUS,
	                        "RequestName",
	                        g_variant_new ("(su)",
936
	                                       IFCFGRH1_BUS_NAME,
937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952
	                                       DBUS_NAME_FLAG_DO_NOT_QUEUE),
	                        G_VARIANT_TYPE ("(u)"),
	                        G_DBUS_CALL_FLAGS_NONE,
	                        -1,
	                        priv->dbus.cancellable,
	                        _dbus_request_name_done,
	                        self);
}

static void
_dbus_setup (SettingsPluginIfcfg *self)
{
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
	gs_free char *address = NULL;
	gs_free_error GError *error = NULL;

953
	_dbus_clear (self);
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978

	address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
	if (address == NULL) {
		_LOGW ("dbus: failed getting address for system bus: %s", error->message);
		return;
	}

	priv->dbus.cancellable = g_cancellable_new ();

	g_dbus_connection_new_for_address (address,
	                                   G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT
	                                   | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
	                                   NULL,
	                                   priv->dbus.cancellable,
	                                   _dbus_create_done,
	                                   self);
}

static void
config_changed_cb (NMConfig *config,
                   NMConfigData *config_data,
                   NMConfigChangeFlags changes,
                   NMConfigData *old_data,
                   SettingsPluginIfcfg *self)
{
979 980
	SettingsPluginIfcfgPrivate *priv;

981 982 983 984
	/* If the dbus connection for some reason is borked the D-Bus service
	 * won't be offered.
	 *
	 * On SIGHUP and SIGUSR1 try to re-connect to D-Bus. So in the unlikely
luz.paz's avatar
luz.paz committed
985
	 * event that the D-Bus connection is broken, that allows for recovery
986
	 * without need for restarting NetworkManager. */
987 988 989 990 991 992 993 994
	if (!NM_FLAGS_ANY (changes,   NM_CONFIG_CHANGE_CAUSE_SIGHUP
	                            | NM_CONFIG_CHANGE_CAUSE_SIGUSR1))
		return;

	priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
	if (   !priv->dbus.connection
	    && !priv->dbus.cancellable)
		_dbus_setup (self);
995 996 997 998
}

/*****************************************************************************/

999
static void
1000
settings_plugin_ifcfg_init (SettingsPluginIfcfg *plugin)
1001
{
1002
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) plugin);
1003

1004
	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
1005 1006 1007 1008 1009
}

static void
constructed (GObject *object)
{
1010
	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (object);
1011
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
1012

1013 1014
	G_OBJECT_CLASS (settings_plugin_ifcfg_parent_class)->constructed (object);

1015 1016 1017 1018 1019 1020
	priv->config = nm_config_get ();
	g_object_add_weak_pointer ((GObject *) priv->config, (gpointer *) &priv->config);
	g_signal_connect (priv->config,
	                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
	                  G_CALLBACK (config_changed_cb),
	                  self);
1021

1022
	_dbus_setup (self);
1023 1024 1025 1026 1027
}

static void
dispose (GObject *object)
{
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (object);
	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);

	if (priv->config) {
		g_object_remove_weak_pointer ((GObject *) priv->config, (gpointer *) &priv->config);
		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
		priv->config = NULL;
	}

	_dbus_clear (self);
1038

1039
	if (priv->connections) {
1040
		g_hash_table_destroy (priv->connections);
1041 1042
		priv->connections = NULL;
	}
1043

1044 1045 1046
	if (priv->ifcfg_monitor) {
		if (priv->ifcfg_monitor_id)
			g_signal_handler_disconnect (priv->ifcfg_monitor, priv->ifcfg_monitor_id);
1047

1048 1049
		g_file_monitor_cancel (priv->ifcfg_monitor);
		g_object_unref (priv->ifcfg_monitor);
1050 1051
	}

1052
	G_OBJECT_CLASS (settings_plugin_ifcfg_parent_class)->dispose (object);
1053 1054 1055
}

static void
1056
settings_plugin_ifcfg_class_init (SettingsPluginIfcfgClass *klass)
1057
{
1058 1059
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS (klass);
1060

1061
	object_class->constructed = constructed;
1062 1063
	object_class->dispose = dispose;

1064 1065 1066 1067 1068 1069
	plugin_class->get_connections = get_connections;
	plugin_class->add_connection = add_connection;
	plugin_class->load_connection = load_connection;
	plugin_class->reload_connections = reload_connections;
	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
	plugin_class->get_unrecognized_specs = get_unrecognized_specs;
1070 1071
}

1072 1073
/*****************************************************************************/

1074
G_MODULE_EXPORT NMSettingsPlugin *
1075
nm_settings_plugin_factory (void)
1076
{
1077
	return NM_SETTINGS_PLUGIN (g_object_ref (settings_plugin_ifcfg_get ()));
1078
}