/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* NetworkManager * * 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) 2014 Red Hat, Inc. */ #include "config.h" #include #include #include #include #include #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, COMPONENT_ADDED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; 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_get_supported_types (NMDeviceFactory *factory, const NMLinkType **out_link_types, const char ***out_setting_types) { 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); } void nm_device_factory_start (NMDeviceFactory *factory) { g_return_if_fail (factory != NULL); if (NM_DEVICE_FACTORY_GET_INTERFACE (factory)->start) NM_DEVICE_FACTORY_GET_INTERFACE (factory)->start (factory); } NMDevice * nm_device_factory_new_link (NMDeviceFactory *factory, NMPlatformLink *plink, gboolean *out_ignore, GError **error) { NMDeviceFactory *interface; const NMLinkType *link_types = NULL; const char **setting_types = NULL; int i; 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); } NMDevice * nm_device_factory_create_virtual_device_for_connection (NMDeviceFactory *factory, NMConnection *connection, NMDevice *parent, GError **error) { NMDeviceFactory *interface; const char **setting_types = NULL; gboolean found = FALSE; int i; 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, 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, 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); } GType nm_device_factory_get_type (void) { 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); } static NMDeviceFactory * find_factory (const NMLinkType *needle_link_types, const char **needle_setting_types) { NMDeviceFactory *found; guint i; 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 NULL; } NMDeviceFactory * nm_device_factory_manager_find_factory_for_link_type (NMLinkType link_type) { const NMLinkType ltypes[2] = { link_type, NM_LINK_TYPE_NONE }; 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_manager_for_each_factory (NMDeviceFactoryManagerFactoryFunc callback, gpointer user_data) { GHashTableIter iter; NMDeviceFactory *factory; GSList *list_iter, *list = NULL; 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); } #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) { const struct read_device_factory_paths_data *da = a; const struct read_device_factory_paths_data *db = b; time_t ta, tb; 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; } static char** read_device_factory_paths (void) { GDir *dir; GError *error = NULL; const char *item; GArray *paths; char **result; guint i; 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; } 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); }