/* -*- 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 "nm-default.h" #include #include #include #include #include #include "nm-device-factory.h" #include "nm-platform.h" #include "nm-utils.h" const NMLinkType _nm_device_factory_no_default_links[] = { NM_LINK_TYPE_NONE }; const char *_nm_device_factory_no_default_settings[] = { NULL }; G_DEFINE_INTERFACE (NMDeviceFactory, nm_device_factory, G_TYPE_OBJECT) 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_create_device (NMDeviceFactory *factory, const char *iface, const NMPlatformLink *plink, NMConnection *connection, gboolean *out_ignore, GError **error) { NMDeviceFactoryInterface *interface; const NMLinkType *link_types = NULL; const char **setting_types = NULL; int i; NMDevice *device; gboolean ignore = FALSE; g_return_val_if_fail (factory, NULL); g_return_val_if_fail (iface && *iface, NULL); g_return_val_if_fail (plink || connection, NULL); g_return_val_if_fail (!plink || !connection, NULL); nm_device_factory_get_supported_types (factory, &link_types, &setting_types); NM_SET_OUT (out_ignore, FALSE); if (plink) { g_return_val_if_fail (strcmp (iface, plink->name) == 0, NULL); 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; } } else if (connection) { for (i = 0; setting_types && setting_types[i]; i++) { if (nm_connection_is_type (connection, setting_types[i])) break; } if (!setting_types[i]) { 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_device) { 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; } device = interface->create_device (factory, iface, plink, connection, &ignore); NM_SET_OUT (out_ignore, ignore); if (!device) { if (ignore) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, "Device factory %s ignores device %s", G_OBJECT_TYPE_NAME (factory), iface); } else { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, "Device factory %s failed to create device %s", G_OBJECT_TYPE_NAME (factory), iface); } } return device; } 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; } char * nm_device_factory_get_connection_iface (NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface, GError **error) { NMDeviceFactoryInterface *klass; char *ifname; g_return_val_if_fail (factory != NULL, NULL); g_return_val_if_fail (connection != NULL, NULL); g_return_val_if_fail (!error || !*error, NULL); klass = NM_DEVICE_FACTORY_GET_INTERFACE (factory); ifname = g_strdup (nm_connection_get_interface_name (connection)); if (!ifname && klass->get_connection_iface) ifname = klass->get_connection_iface (factory, connection, parent_iface); if (!ifname) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, "failed to determine interface name: error determine name for %s", nm_connection_get_connection_type (connection)); return NULL; } if (!nm_utils_iface_valid_name (ifname)) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, "failed to determine interface name: name \"%s\" is invalid", ifname); g_free (ifname); return NULL; } return ifname; } /*****************************************************************************/ static void nm_device_factory_default_init (NMDeviceFactoryInterface *factory_iface) { /* Signals */ signals[DEVICE_ADDED] = g_signal_new (NM_DEVICE_FACTORY_DEVICE_ADDED, NM_TYPE_DEVICE_FACTORY, G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (NMDeviceFactoryInterface, 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 (NMDeviceFactoryInterface, component_added), g_signal_accumulator_true_handled, NULL, NULL, G_TYPE_BOOLEAN, 1, G_TYPE_OBJECT); } /*****************************************************************************/ 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; if (factories_by_link) { 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); } } if (factories_by_setting) { 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->message); 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; } /* after loading glib types from the plugin, we cannot unload the library anymore. * Make it resident. */ g_module_make_resident (plugin); factory = create_func (&error); if (!factory) { nm_log_warn (LOGD_HW, "(%s): failed to initialize device factory: %s", item, NM_G_ERROR_MSG (error)); g_clear_error (&error); continue; } g_clear_error (&error); _add_factory (factory, TRUE, g_module_name (plugin), callback, user_data); g_object_unref (factory); } g_strfreev (paths); }