summary refs log tree commit diff
path: root/src/devices/nm-device-factory.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2015-07-14 19:38:58 +0200
committerMichael Biebl <biebl@debian.org>2015-07-14 19:38:58 +0200
commit50a58f0fabd8a34c1b6108a107e08abe3c1ccd24 (patch)
tree6790165f39daee79e2b6c6617483320613493367 /src/devices/nm-device-factory.c
parentf408e27bccfacf347605a8d98649975a68f38a17 (diff)
Imported Upstream version 1.0.4 upstream/1.0.4
Diffstat (limited to 'src/devices/nm-device-factory.c')
-rw-r--r--src/devices/nm-device-factory.c532
1 files changed, 461 insertions, 71 deletions
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index abfb8d4a..13034808 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -19,8 +19,19 @@
  */
 
 #include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+
+#include <gmodule.h>
 
 #include "nm-device-factory.h"
+#include "nm-logging.h"
+#include "nm-platform.h"
+
+const NMLinkType _nm_device_factory_no_default_links[] = { NM_LINK_TYPE_NONE };
+const char *_nm_device_factory_no_default_settings[] = { NULL };
 
 enum {
 	DEVICE_ADDED,
@@ -29,130 +40,509 @@ enum {
 };
 static guint signals[LAST_SIGNAL] = { 0 };
 
-static GSList *internal_types = NULL;
+gboolean
+nm_device_factory_emit_component_added (NMDeviceFactory *factory, GObject *component)
+{
+	gboolean consumed = FALSE;
+
+	g_signal_emit (factory, signals[COMPONENT_ADDED], 0, component, &consumed);
+	return consumed;
+}
 
 void
-_nm_device_factory_internal_register_type (GType factory_type)
+nm_device_factory_get_supported_types (NMDeviceFactory *factory,
+                                       const NMLinkType **out_link_types,
+                                       const char ***out_setting_types)
 {
-	g_return_if_fail (g_slist_find (internal_types, GUINT_TO_POINTER (factory_type)) == NULL);
-	internal_types = g_slist_prepend (internal_types, GUINT_TO_POINTER (factory_type));
+	const NMLinkType *link_types_fallback;
+	const char **setting_types_fallback;
+
+	g_return_if_fail (factory != NULL);
+
+	if (!out_link_types)
+		out_link_types = &link_types_fallback;
+	if (!out_setting_types)
+		out_setting_types = &setting_types_fallback;
+
+	NM_DEVICE_FACTORY_GET_INTERFACE (factory)->get_supported_types (factory,
+	                                                                out_link_types,
+	                                                                out_setting_types);
 }
 
-const GSList *
-nm_device_factory_get_internal_factory_types (void)
+void
+nm_device_factory_start (NMDeviceFactory *factory)
 {
-	return internal_types;
+	g_return_if_fail (factory != NULL);
+
+	if (NM_DEVICE_FACTORY_GET_INTERFACE (factory)->start)
+		NM_DEVICE_FACTORY_GET_INTERFACE (factory)->start (factory);
 }
 
-gboolean
-nm_device_factory_emit_component_added (NMDeviceFactory *factory, GObject *component)
+NMDevice *
+nm_device_factory_new_link (NMDeviceFactory *factory,
+                            NMPlatformLink *plink,
+                            gboolean *out_ignore,
+                            GError **error)
 {
-	gboolean consumed = FALSE;
+	NMDeviceFactory *interface;
+	const NMLinkType *link_types = NULL;
+	const char **setting_types = NULL;
+	int i;
 
-	g_signal_emit (factory, signals[COMPONENT_ADDED], 0, component, &consumed);
-	return consumed;
+	g_return_val_if_fail (factory != NULL, NULL);
+	g_return_val_if_fail (plink != NULL, NULL);
+
+	/* Ensure the factory can create interfaces for this connection */
+	nm_device_factory_get_supported_types (factory, &link_types, &setting_types);
+	for (i = 0; link_types[i] > NM_LINK_TYPE_UNKNOWN; i++) {
+		if (plink->type == link_types[i])
+			break;
+	}
+
+	if (link_types[i] == NM_LINK_TYPE_UNKNOWN) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
+		             "Device factory %s does not support link type %s (%d)",
+		             G_OBJECT_TYPE_NAME (factory),
+		             plink->kind, plink->type);
+		return NULL;
+	}
+
+	interface = NM_DEVICE_FACTORY_GET_INTERFACE (factory);
+	if (!interface->new_link) {
+		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
+		             "Device factory %s cannot manage new devices",
+		             G_OBJECT_TYPE_NAME (factory));
+		return NULL;
+	}
+
+	return interface->new_link (factory, plink, out_ignore, error);
 }
 
-static void
-interface_init (gpointer g_iface)
+NMDevice *
+nm_device_factory_create_virtual_device_for_connection (NMDeviceFactory *factory,
+                                                        NMConnection *connection,
+                                                        NMDevice *parent,
+                                                        GError **error)
 {
-	GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
-	static gboolean initialized = FALSE;
+	NMDeviceFactory *interface;
+	const char **setting_types = NULL;
+	gboolean found = FALSE;
+	int i;
 
-	if (G_LIKELY (initialized))
-		return;
+	g_return_val_if_fail (factory, NULL);
+	g_return_val_if_fail (connection, NULL);
+	g_return_val_if_fail (!error || !*error, NULL);
+
+	/* Ensure the factory can create interfaces for this connection */
+	nm_device_factory_get_supported_types (factory, NULL, &setting_types);
+	for (i = 0; setting_types && setting_types[i]; i++) {
+		if (nm_connection_is_type (connection, setting_types[i])) {
+			found = TRUE;
+			break;
+		}
+	}
+
+	if (!found) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
+		             "Device factory %s does not support connection type %s",
+		             G_OBJECT_TYPE_NAME (factory),
+		             nm_connection_get_connection_type (connection));
+		return NULL;
+	}
+
+	interface = NM_DEVICE_FACTORY_GET_INTERFACE (factory);
+	if (!interface->create_virtual_device_for_connection) {
+		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
+		             "Device factory %s cannot create virtual devices",
+		             G_OBJECT_TYPE_NAME (factory));
+		return NULL;
+	}
+
+	return interface->create_virtual_device_for_connection (factory, connection, parent, error);
+}
+
+const char *
+nm_device_factory_get_connection_parent (NMDeviceFactory *factory,
+                                         NMConnection *connection)
+{
+	g_return_val_if_fail (factory != NULL, NULL);
+	g_return_val_if_fail (connection != NULL, NULL);
+
+	if (!nm_connection_is_virtual (connection))
+		return NULL;
+
+	if (NM_DEVICE_FACTORY_GET_INTERFACE (factory)->get_connection_parent)
+		return NM_DEVICE_FACTORY_GET_INTERFACE (factory)->get_connection_parent (factory, connection);
+	return NULL;
+}
+
+static char *
+get_virtual_iface_name (NMDeviceFactory *factory,
+                        NMConnection *connection,
+                        const char *parent_iface)
+{
+	const char *iface;
+
+	/* For any other virtual connection, NMSettingConnection:interface-name is
+	 * the virtual device name.
+	 */
+	iface = nm_connection_get_interface_name (connection);
+	g_return_val_if_fail (iface != NULL, NULL);
+	return g_strdup (iface);
+}
+
+char *
+nm_device_factory_get_virtual_iface_name (NMDeviceFactory *factory,
+                                          NMConnection *connection,
+                                          const char *parent_iface)
+{
+	g_return_val_if_fail (factory != NULL, NULL);
+	g_return_val_if_fail (connection != NULL, NULL);
+
+	if (!nm_connection_is_virtual (connection))
+		return NULL;
+
+	if (NM_DEVICE_FACTORY_GET_INTERFACE (factory)->get_virtual_iface_name)
+		return NM_DEVICE_FACTORY_GET_INTERFACE (factory)->get_virtual_iface_name (factory, connection, parent_iface);
+	return NULL;
+}
+
+/*******************************************************************/
+
+static void
+default_init (NMDeviceFactory *factory_iface)
+{
+	factory_iface->get_virtual_iface_name = get_virtual_iface_name;
 
 	/* Signals */
 	signals[DEVICE_ADDED] = g_signal_new (NM_DEVICE_FACTORY_DEVICE_ADDED,
-	                                      iface_type,
+	                                      NM_TYPE_DEVICE_FACTORY,
 	                                      G_SIGNAL_RUN_FIRST,
 	                                      G_STRUCT_OFFSET (NMDeviceFactory, device_added),
 	                                      NULL, NULL, NULL,
 	                                      G_TYPE_NONE, 1, NM_TYPE_DEVICE);
 
 	signals[COMPONENT_ADDED] = g_signal_new (NM_DEVICE_FACTORY_COMPONENT_ADDED,
-	                                         iface_type,
+	                                         NM_TYPE_DEVICE_FACTORY,
 	                                         G_SIGNAL_RUN_LAST,
 	                                         G_STRUCT_OFFSET (NMDeviceFactory, component_added),
 	                                         g_signal_accumulator_true_handled, NULL, NULL,
 	                                         G_TYPE_BOOLEAN, 1, G_TYPE_OBJECT);
-
-	initialized = TRUE;
 }
 
 GType
 nm_device_factory_get_type (void)
 {
-	static GType device_factory_type = 0;
+	static volatile gsize g_define_type_id__volatile = 0;
+	if (g_once_init_enter (&g_define_type_id__volatile)) {
+		GType g_define_type_id =
+			g_type_register_static_simple (G_TYPE_INTERFACE,
+			                               g_intern_static_string ("NMDeviceFactory"),
+			                               sizeof (NMDeviceFactory),
+			                               (GClassInitFunc) default_init,
+			                               0,
+			                               (GInstanceInitFunc) NULL,
+			                               (GTypeFlags) 0);
+		g_type_interface_add_prerequisite (g_define_type_id, G_TYPE_OBJECT);
+		g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+	}
+	return g_define_type_id__volatile;
+}
+
+/*******************************************************************/
+
+static GSList *internal_types = NULL;
+static GHashTable *factories_by_link = NULL;
+static GHashTable *factories_by_setting = NULL;
+
+void
+_nm_device_factory_internal_register_type (GType factory_type)
+{
+	g_return_if_fail (g_slist_find (internal_types, GUINT_TO_POINTER (factory_type)) == NULL);
+	internal_types = g_slist_prepend (internal_types, GUINT_TO_POINTER (factory_type));
+}
+
+static void __attribute__((destructor))
+_cleanup (void)
+{
+	g_clear_pointer (&internal_types, g_slist_free);
+	g_clear_pointer (&factories_by_link, g_hash_table_unref);
+	g_clear_pointer (&factories_by_setting, g_hash_table_unref);
+}
 
-	if (!device_factory_type) {
-		const GTypeInfo device_factory_info = {
-			sizeof (NMDeviceFactory), /* class_size */
-			interface_init,           /* base_init */
-			NULL,                     /* base_finalize */
-			NULL,
-			NULL,                     /* class_finalize */
-			NULL,                     /* class_data */
-			0,
-			0,                        /* n_preallocs */
-			NULL
-		};
+static NMDeviceFactory *
+find_factory (const NMLinkType *needle_link_types,
+              const char **needle_setting_types)
+{
+	NMDeviceFactory *found;
+	guint i;
 
-		device_factory_type = g_type_register_static (G_TYPE_INTERFACE,
-		                                              "NMDeviceFactory",
-		                                              &device_factory_info,
-		                                              0);
-		g_type_interface_add_prerequisite (device_factory_type, G_TYPE_OBJECT);
+	g_return_val_if_fail (factories_by_link, NULL);
+	g_return_val_if_fail (factories_by_setting, NULL);
+
+	/* NMLinkType search */
+	for (i = 0; needle_link_types && needle_link_types[i] > NM_LINK_TYPE_UNKNOWN; i++) {
+		found = g_hash_table_lookup (factories_by_link, GUINT_TO_POINTER (needle_link_types[i]));
+		if (found)
+			return found;
+	}
+
+	/* NMSetting name search */
+	for (i = 0; needle_setting_types && needle_setting_types[i]; i++) {
+		found = g_hash_table_lookup (factories_by_setting, needle_setting_types[i]);
+		if (found)
+			return found;
 	}
 
-	return device_factory_type;
+	return NULL;
 }
 
-NMDeviceType
-nm_device_factory_get_device_type (NMDeviceFactory *factory)
+NMDeviceFactory *
+nm_device_factory_manager_find_factory_for_link_type (NMLinkType link_type)
 {
-	g_return_val_if_fail (factory != NULL, NM_DEVICE_TYPE_UNKNOWN);
+	const NMLinkType ltypes[2] = { link_type, NM_LINK_TYPE_NONE };
 
-	return NM_DEVICE_FACTORY_GET_INTERFACE (factory)->get_device_type (factory);
+	if (link_type == NM_LINK_TYPE_UNKNOWN)
+		return NULL;
+	g_return_val_if_fail (link_type > NM_LINK_TYPE_UNKNOWN, NULL);
+	return find_factory (ltypes, NULL);
+}
+
+NMDeviceFactory *
+nm_device_factory_manager_find_factory_for_connection (NMConnection *connection)
+{
+	const char *stypes[2] = { nm_connection_get_connection_type (connection), NULL };
+
+	g_assert (stypes[0]);
+	return find_factory (NULL, stypes);
 }
 
 void
-nm_device_factory_start (NMDeviceFactory *factory)
+nm_device_factory_manager_for_each_factory (NMDeviceFactoryManagerFactoryFunc callback,
+                                            gpointer user_data)
 {
-	g_return_if_fail (factory != NULL);
+	GHashTableIter iter;
+	NMDeviceFactory *factory;
+	GSList *list_iter, *list = NULL;
 
-	if (NM_DEVICE_FACTORY_GET_INTERFACE (factory)->start)
-		NM_DEVICE_FACTORY_GET_INTERFACE (factory)->start (factory);
+	g_return_if_fail (factories_by_link);
+	g_return_if_fail (factories_by_setting);
+
+	g_hash_table_iter_init (&iter, factories_by_link);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &factory)) {
+		if (!g_slist_find (list, factory))
+			list = g_slist_prepend (list, factory);
+	}
+
+	g_hash_table_iter_init (&iter, factories_by_setting);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &factory)) {
+		if (!g_slist_find (list, factory))
+			list = g_slist_prepend (list, factory);
+	}
+
+	for (list_iter = list; list_iter; list_iter = list_iter->next)
+		callback (list_iter->data, user_data);
+
+	g_slist_free (list);
 }
 
-NMDevice *
-nm_device_factory_new_link (NMDeviceFactory *factory,
-                            NMPlatformLink *plink,
-                            GError **error)
+#define PLUGIN_PREFIX "libnm-device-plugin-"
+#define PLUGIN_PATH_TAG "NMManager-plugin-path"
+
+struct read_device_factory_paths_data {
+	char *path;
+	struct stat st;
+};
+
+static gint
+read_device_factory_paths_sort_fcn (gconstpointer a, gconstpointer b)
 {
-	g_return_val_if_fail (factory != NULL, NULL);
-	g_return_val_if_fail (plink != NULL, NULL);
+	const struct read_device_factory_paths_data *da = a;
+	const struct read_device_factory_paths_data *db = b;
+	time_t ta, tb;
 
-	if (NM_DEVICE_FACTORY_GET_INTERFACE (factory)->new_link)
-		return NM_DEVICE_FACTORY_GET_INTERFACE (factory)->new_link (factory, plink, error);
-	return NULL;
+	ta = MAX (da->st.st_mtime, da->st.st_ctime);
+	tb = MAX (db->st.st_mtime, db->st.st_ctime);
+
+	if (ta < tb)
+		return 1;
+	if (ta > tb)
+		return -1;
+	return 0;
 }
 
-NMDevice *
-nm_device_factory_create_virtual_device_for_connection (NMDeviceFactory *factory,
-                                                        NMConnection *connection,
-                                                        NMDevice *parent,
-                                                        GError **error)
+static char**
+read_device_factory_paths (void)
 {
-	NMDeviceFactory *interface;
+	GDir *dir;
+	GError *error = NULL;
+	const char *item;
+	GArray *paths;
+	char **result;
+	guint i;
 
-	g_return_val_if_fail (factory, NULL);
-	g_return_val_if_fail (connection, NULL);
-	g_return_val_if_fail (!error || !*error, NULL);
+	dir = g_dir_open (NMPLUGINDIR, 0, &error);
+	if (!dir) {
+		nm_log_warn (LOGD_HW, "device plugin: failed to open directory %s: %s",
+		             NMPLUGINDIR,
+		             (error && error->message) ? error->message : "(unknown)");
+		g_clear_error (&error);
+		return NULL;
+	}
 
-	interface = NM_DEVICE_FACTORY_GET_INTERFACE (factory);
-	if (interface->create_virtual_device_for_connection)
-		return interface->create_virtual_device_for_connection (factory, connection, parent, error);
-	return NULL;
+	paths = g_array_new (FALSE, FALSE, sizeof (struct read_device_factory_paths_data));
+
+	while ((item = g_dir_read_name (dir))) {
+		int errsv;
+		struct read_device_factory_paths_data data;
+
+		if (!g_str_has_prefix (item, PLUGIN_PREFIX))
+			continue;
+		if (g_str_has_suffix (item, ".la"))
+			continue;
+
+		data.path = g_build_filename (NMPLUGINDIR, item, NULL);
+
+		if (stat (data.path, &data.st) != 0) {
+			errsv = errno;
+			nm_log_warn (LOGD_HW, "device plugin: skip invalid file %s (error during stat: %s)", data.path, strerror (errsv));
+			goto NEXT;
+		}
+		if (!S_ISREG (data.st.st_mode))
+			goto NEXT;
+		if (data.st.st_uid != 0) {
+			nm_log_warn (LOGD_HW, "device plugin: skip invalid file %s (file must be owned by root)", data.path);
+			goto NEXT;
+		}
+		if (data.st.st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) {
+			nm_log_warn (LOGD_HW, "device plugin: skip invalid file %s (invalid file permissions)", data.path);
+			goto NEXT;
+		}
+
+		g_array_append_val (paths, data);
+		continue;
+NEXT:
+		g_free (data.path);
+	}
+	g_dir_close (dir);
+
+	/* sort filenames by modification time. */
+	g_array_sort (paths, read_device_factory_paths_sort_fcn);
+
+	result = g_new (char *, paths->len + 1);
+	for (i = 0; i < paths->len; i++)
+		result[i] = g_array_index (paths, struct read_device_factory_paths_data, i).path;
+	result[i] = NULL;
+
+	g_array_free (paths, TRUE);
+	return result;
+}
+
+static gboolean
+_add_factory (NMDeviceFactory *factory,
+              gboolean check_duplicates,
+              const char *path,
+              NMDeviceFactoryManagerFactoryFunc callback,
+              gpointer user_data)
+{
+	NMDeviceFactory *found = NULL;
+	const NMLinkType *link_types = NULL;
+	const char **setting_types = NULL;
+	int i;
+
+	g_return_val_if_fail (factories_by_link, FALSE);
+	g_return_val_if_fail (factories_by_setting, FALSE);
+
+	nm_device_factory_get_supported_types (factory, &link_types, &setting_types);
+	if (check_duplicates) {
+		found = find_factory (link_types, setting_types);
+		if (found) {
+			nm_log_warn (LOGD_HW, "Loading device plugin failed: multiple plugins "
+			             "for same type (using '%s' instead of '%s')",
+			             (char *) g_object_get_data (G_OBJECT (found), PLUGIN_PATH_TAG),
+			             path);
+			return FALSE;
+		}
+	}
+
+	g_object_set_data_full (G_OBJECT (factory), PLUGIN_PATH_TAG, g_strdup (path), g_free);
+	for (i = 0; link_types && link_types[i] > NM_LINK_TYPE_UNKNOWN; i++)
+		g_hash_table_insert (factories_by_link, GUINT_TO_POINTER (link_types[i]), g_object_ref (factory));
+	for (i = 0; setting_types && setting_types[i]; i++)
+		g_hash_table_insert (factories_by_setting, (char *) setting_types[i], g_object_ref (factory));
+
+	callback (factory, user_data);
+
+	nm_log_info (LOGD_HW, "Loaded device plugin: %s (%s)", G_OBJECT_TYPE_NAME (factory), path);
+	return TRUE;
+}
+
+void
+nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc callback,
+                                          gpointer user_data)
+{
+	NMDeviceFactory *factory;
+	const GSList *iter;
+	GError *error = NULL;
+	char **path, **paths;
+
+	g_return_if_fail (factories_by_link == NULL);
+	g_return_if_fail (factories_by_setting == NULL);
+
+	factories_by_link = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
+	factories_by_setting = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+
+	/* Register internal factories first */
+	for (iter = internal_types; iter; iter = iter->next) {
+		GType ftype = (GType) GPOINTER_TO_SIZE (iter->data);
+
+		factory = (NMDeviceFactory *) g_object_new (ftype, NULL);
+		g_assert (factory);
+		_add_factory (factory, FALSE, "internal", callback, user_data);
+	}
+
+	paths = read_device_factory_paths ();
+	if (!paths)
+		return;
+
+	for (path = paths; *path; path++) {
+		GModule *plugin;
+		NMDeviceFactoryCreateFunc create_func;
+		const char *item;
+
+		item = strrchr (*path, '/');
+		g_assert (item);
+
+		plugin = g_module_open (*path, G_MODULE_BIND_LOCAL);
+
+		if (!plugin) {
+			nm_log_warn (LOGD_HW, "(%s): failed to load plugin: %s", item, g_module_error ());
+			continue;
+		}
+
+		if (!g_module_symbol (plugin, "nm_device_factory_create", (gpointer) &create_func)) {
+			nm_log_warn (LOGD_HW, "(%s): failed to find device factory creator: %s", item, g_module_error ());
+			g_module_close (plugin);
+			continue;
+		}
+
+		factory = create_func (&error);
+		if (!factory) {
+			nm_log_warn (LOGD_HW, "(%s): failed to initialize device factory: %s",
+			             item, error ? error->message : "unknown");
+			g_clear_error (&error);
+			g_module_close (plugin);
+			continue;
+		}
+		g_clear_error (&error);
+
+		if (_add_factory (factory, TRUE, g_module_name (plugin), callback, user_data))
+			g_module_make_resident (plugin);
+		else
+			g_module_close (plugin);
+
+		g_object_unref (factory);
+	}
+	g_strfreev (paths);
 }