diff options
Diffstat (limited to 'src/devices/nm-device-factory.c')
| -rw-r--r-- | src/devices/nm-device-factory.c | 532 |
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); } |