/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Dan Williams * Søren Sandmann * Copyright (C) 2007 - 2011 Red Hat, Inc. */ #include "src/core/nm-default-daemon.h" #include "nms-ifcfg-rh-plugin.h" #include #include #include #include "libnm-std-aux/c-list-util.h" #include "libnm-glib-aux/nm-c-list.h" #include "libnm-glib-aux/nm-io-utils.h" #include "libnm-std-aux/nm-dbus-compat.h" #include "nm-utils.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-config.h" #include "nm-dbus-manager.h" #include "settings/nm-settings-plugin.h" #include "settings/nm-settings-utils.h" #include "NetworkManagerUtils.h" #include "nms-ifcfg-rh-storage.h" #include "nms-ifcfg-rh-common.h" #include "nms-ifcfg-rh-utils.h" #include "nms-ifcfg-rh-reader.h" #include "nms-ifcfg-rh-writer.h" #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" /*****************************************************************************/ typedef struct { NMConfig *config; struct { GDBusConnection *connection; GCancellable *cancellable; gulong signal_id; guint regist_id; } dbus; NMSettUtilStorages storages; GHashTable *unmanaged_specs; GHashTable *unrecognized_specs; } NMSIfcfgRHPluginPrivate; struct _NMSIfcfgRHPlugin { NMSettingsPlugin parent; NMSIfcfgRHPluginPrivate _priv; }; struct _NMSIfcfgRHPluginClass { NMSettingsPluginClass parent; }; G_DEFINE_TYPE(NMSIfcfgRHPlugin, nms_ifcfg_rh_plugin, NM_TYPE_SETTINGS_PLUGIN) #define NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self) \ _NM_GET_PRIVATE(self, NMSIfcfgRHPlugin, NMS_IS_IFCFG_RH_PLUGIN, NMSettingsPlugin) /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_SETTINGS #define _NMLOG(level, ...) \ G_STMT_START \ { \ nm_log((level), \ (_NMLOG_DOMAIN), \ NULL, \ NULL, \ "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ "ifcfg-rh: " _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } \ G_STMT_END /*****************************************************************************/ static void _unhandled_specs_reset(NMSIfcfgRHPlugin *self); static void _unhandled_specs_merge_storages(NMSIfcfgRHPlugin *self, NMSettUtilStorages *storages); /*****************************************************************************/ static void nm_assert_self(NMSIfcfgRHPlugin *self, gboolean unhandled_specs_consistent) { nm_assert(NMS_IS_IFCFG_RH_PLUGIN(self)); #if NM_MORE_ASSERTS > 5 { NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); NMSIfcfgRHStorage *storage; gsize n_uuid; gs_unref_hashtable GHashTable *h_unmanaged = NULL; gs_unref_hashtable GHashTable *h_unrecognized = NULL; nm_assert(g_hash_table_size(priv->storages.idx_by_filename) == c_list_length(&priv->storages._storage_lst_head)); h_unmanaged = g_hash_table_new(nm_str_hash, g_str_equal); h_unrecognized = g_hash_table_new(nm_str_hash, g_str_equal); n_uuid = 0; c_list_for_each_entry (storage, &priv->storages._storage_lst_head, parent._storage_lst) { const char *uuid; const char *filename; filename = nms_ifcfg_rh_storage_get_filename(storage); nm_assert(filename && NM_STR_HAS_PREFIX(filename, IFCFG_DIR "/")); uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage); nm_assert((!!uuid) + (!!storage->unmanaged_spec) + (!!storage->unrecognized_spec) == 1); nm_assert(storage == nm_sett_util_storages_lookup_by_filename(&priv->storages, filename)); if (uuid) { NMSettUtilStorageByUuidHead *sbuh; NMSettUtilStorageByUuidHead *sbuh2; if (storage->connection) nm_assert(nm_streq0(nm_connection_get_uuid(storage->connection), uuid)); if (!g_hash_table_lookup_extended(priv->storages.idx_by_uuid, &uuid, (gpointer *) &sbuh, (gpointer *) &sbuh2)) nm_assert_not_reached(); nm_assert(sbuh); nm_assert(nm_streq(uuid, sbuh->uuid)); nm_assert(sbuh == sbuh2); nm_assert(c_list_contains(&sbuh->_storage_by_uuid_lst_head, &storage->parent._storage_by_uuid_lst)); if (c_list_first(&sbuh->_storage_by_uuid_lst_head) == &storage->parent._storage_by_uuid_lst) n_uuid++; } else if (storage->unmanaged_spec) { nm_assert(strlen(storage->unmanaged_spec) > 0); g_hash_table_add(h_unmanaged, storage->unmanaged_spec); } else if (storage->unrecognized_spec) { nm_assert(strlen(storage->unrecognized_spec) > 0); g_hash_table_add(h_unrecognized, storage->unrecognized_spec); } else nm_assert_not_reached(); nm_assert(!storage->connection); } nm_assert(g_hash_table_size(priv->storages.idx_by_uuid) == n_uuid); if (unhandled_specs_consistent) { nm_assert(nm_utils_hashtable_same_keys(h_unmanaged, priv->unmanaged_specs)); nm_assert(nm_utils_hashtable_same_keys(h_unrecognized, priv->unrecognized_specs)); } } #endif } /*****************************************************************************/ static NMSIfcfgRHStorage * _load_file(NMSIfcfgRHPlugin *self, const char *filename, GError **error) { NMSIfcfgRHStorage *ret = NULL; gs_unref_object NMConnection *connection = NULL; gs_free_error GError *load_error = NULL; gs_free char *unhandled_spec = NULL; gboolean load_error_ignore; struct stat st; if (stat(filename, &st) != 0) { int errsv = errno; if (error) { nm_utils_error_set_errno(error, errsv, "failure to stat file \%s\": %s", filename); } else _LOGT("load[%s]: failure to stat file: %s", filename, nm_strerror_native(errsv)); return NULL; } connection = connection_from_file(filename, &unhandled_spec, &load_error, &load_error_ignore); if (load_error) { if (error) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "failure to read file \"%s\": %s", filename, load_error->message); } else { _NMLOG(load_error_ignore ? LOGL_TRACE : LOGL_WARN, "load[%s]: failure to read file: %s", filename, load_error->message); } return NULL; } if (unhandled_spec) { const char *unmanaged_spec; const char *unrecognized_spec; if (!nms_ifcfg_rh_utils_parse_unhandled_spec(unhandled_spec, &unmanaged_spec, &unrecognized_spec)) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "invalid unhandled spec \"%s\"", unhandled_spec); nm_assert_not_reached(); return NULL; } ret = nms_ifcfg_rh_storage_new_unhandled(self, filename, unmanaged_spec, unrecognized_spec); } else { ret = nms_ifcfg_rh_storage_new_connection(self, filename, g_steal_pointer(&connection), &st.st_mtim); } return ret; } static void _load_dir(NMSIfcfgRHPlugin *self, NMSettUtilStorages *storages) { gs_unref_hashtable GHashTable *dupl_filenames = NULL; gs_free_error GError *local = NULL; const char *f_filename; GDir *dir; dir = g_dir_open(IFCFG_DIR, 0, &local); if (!dir) { _LOGT("Could not read directory '%s': %s", IFCFG_DIR, local->message); return; } dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free); while ((f_filename = g_dir_read_name(dir))) { gs_free char *full_path = NULL; NMSIfcfgRHStorage *storage; char *full_filename; full_path = g_build_filename(IFCFG_DIR, f_filename, NULL); full_filename = utils_detect_ifcfg_path(full_path, TRUE); if (!full_filename) continue; if (!g_hash_table_add(dupl_filenames, full_filename)) continue; nm_assert(!nm_sett_util_storages_lookup_by_filename(storages, full_filename)); storage = _load_file(self, full_filename, NULL); if (storage) nm_sett_util_storages_add_take(storages, storage); } g_dir_close(dir); } static void _storages_consolidate(NMSIfcfgRHPlugin *self, NMSettUtilStorages *storages_new, gboolean replace_all, GHashTable *storages_replaced, NMSettingsPluginConnectionLoadCallback callback, gpointer user_data) { NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); CList lst_conn_info_deleted = C_LIST_INIT(lst_conn_info_deleted); gs_unref_ptrarray GPtrArray *storages_modified = NULL; CList storages_deleted; NMSIfcfgRHStorage *storage_safe; NMSIfcfgRHStorage *storage_new; NMSIfcfgRHStorage *storage_old; NMSIfcfgRHStorage *storage; guint i; /* when we reload all files, we must signal add/update/modify of profiles one-by-one. * NMSettings then goes ahead and emits further signals and a lot of things happen. * * So, first, emit an update of the unmanaged/unrecognized specs that contains *all* * the unmanaged/unrecognized devices from before and after. Since both unmanaged/unrecognized * specs have the meaning of "not doing something", it makes sense that we temporarily * disable that action for the sum of before and after. */ _unhandled_specs_merge_storages(self, storages_new); storages_modified = g_ptr_array_new_with_free_func(g_object_unref); c_list_init(&storages_deleted); c_list_for_each_entry (storage_old, &priv->storages._storage_lst_head, parent._storage_lst) storage_old->dirty = TRUE; c_list_for_each_entry_safe (storage_new, storage_safe, &storages_new->_storage_lst_head, parent._storage_lst) { storage_old = nm_sett_util_storages_lookup_by_filename( &priv->storages, nms_ifcfg_rh_storage_get_filename(storage_new)); nm_sett_util_storages_steal(storages_new, storage_new); if (!storage_old || !nms_ifcfg_rh_storage_equal_type(storage_new, storage_old)) { if (storage_old) { nm_sett_util_storages_steal(&priv->storages, storage_old); if (nms_ifcfg_rh_storage_get_uuid_opt(storage_old)) c_list_link_tail(&storages_deleted, &storage_old->parent._storage_lst); else nms_ifcfg_rh_storage_destroy(storage_old); } storage_new->dirty = FALSE; nm_sett_util_storages_add_take(&priv->storages, storage_new); g_ptr_array_add(storages_modified, g_object_ref(storage_new)); continue; } storage_old->dirty = FALSE; nms_ifcfg_rh_storage_copy_content(storage_old, storage_new); nms_ifcfg_rh_storage_destroy(storage_new); g_ptr_array_add(storages_modified, g_object_ref(storage_old)); } c_list_for_each_entry_safe (storage_old, storage_safe, &priv->storages._storage_lst_head, parent._storage_lst) { if (!storage_old->dirty) continue; if (replace_all || (storages_replaced && g_hash_table_contains(storages_replaced, storage_old))) { nm_sett_util_storages_steal(&priv->storages, storage_old); if (nms_ifcfg_rh_storage_get_uuid_opt(storage_old)) c_list_link_tail(&storages_deleted, &storage_old->parent._storage_lst); else nms_ifcfg_rh_storage_destroy(storage_old); } } /* raise events. */ for (i = 0; i < storages_modified->len; i++) { storage = storages_modified->pdata[i]; storage->dirty = TRUE; } for (i = 0; i < storages_modified->len; i++) { gs_unref_object NMConnection *connection = NULL; storage = storages_modified->pdata[i]; if (!storage->dirty) { /* the entry is no longer dirty. In the meantime we already emitted * another signal for it. */ continue; } storage->dirty = FALSE; if (storage != nm_sett_util_storages_lookup_by_filename( &priv->storages, nms_ifcfg_rh_storage_get_filename(storage))) { /* hm? The profile was deleted in the meantime? That is only possible * if the signal handler called again into the plugin. In any case, the event * was already emitted. Skip. */ continue; } connection = nms_ifcfg_rh_storage_steal_connection(storage); if (!connection) { nm_assert(!nms_ifcfg_rh_storage_get_uuid_opt(storage)); continue; } nm_assert(NM_IS_CONNECTION(connection)); nm_assert(nms_ifcfg_rh_storage_get_uuid_opt(storage)); callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), connection, user_data); } while ( (storage = c_list_first_entry(&storages_deleted, NMSIfcfgRHStorage, parent._storage_lst))) { c_list_unlink(&storage->parent._storage_lst); callback(NM_SETTINGS_PLUGIN(self), NM_SETTINGS_STORAGE(storage), NULL, user_data); nms_ifcfg_rh_storage_destroy(storage); } } /*****************************************************************************/ static void load_connections(NMSettingsPlugin *plugin, NMSettingsPluginConnectionLoadEntry *entries, gsize n_entries, NMSettingsPluginConnectionLoadCallback callback, gpointer user_data) { NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(plugin); NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_ifcfg_rh_storage_destroy); gs_unref_hashtable GHashTable *dupl_filenames = NULL; gs_unref_hashtable GHashTable *storages_replaced = NULL; gs_unref_hashtable GHashTable *loaded_uuids = NULL; const char *loaded_uuid; GHashTableIter h_iter; gsize i; if (n_entries == 0) return; dupl_filenames = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); loaded_uuids = g_hash_table_new(nm_str_hash, g_str_equal); storages_replaced = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL); for (i = 0; i < n_entries; i++) { NMSettingsPluginConnectionLoadEntry *const entry = &entries[i]; gs_free_error GError *local = NULL; const char *full_filename; const char *uuid; gs_free char *full_filename_keep = NULL; NMSettingsPluginConnectionLoadEntry *dupl_content_entry; gs_unref_object NMSIfcfgRHStorage *storage = NULL; if (entry->handled) continue; if (entry->filename[0] != '/') continue; full_filename_keep = utils_detect_ifcfg_path(entry->filename, FALSE); if (!full_filename_keep) { if (nm_utils_file_is_in_path(entry->filename, IFCFG_DIR)) { nm_utils_error_set(&entry->error, NM_UTILS_ERROR_UNKNOWN, ("path is not a valid name for an ifcfg-rh file")); entry->handled = TRUE; } continue; } if ((dupl_content_entry = g_hash_table_lookup(dupl_filenames, full_filename_keep))) { /* we already visited this file. */ entry->handled = dupl_content_entry->handled; if (dupl_content_entry->error) { g_set_error_literal(&entry->error, dupl_content_entry->error->domain, dupl_content_entry->error->code, dupl_content_entry->error->message); } continue; } entry->handled = TRUE; full_filename = full_filename_keep; if (!g_hash_table_insert(dupl_filenames, g_steal_pointer(&full_filename_keep), entry)) nm_assert_not_reached(); storage = _load_file(self, full_filename, &local); if (!storage) { if (nm_utils_file_stat(full_filename, NULL) == -ENOENT) { NMSIfcfgRHStorage *storage2; /* the file does not exist. We take that as indication to unload the file * that was previously loaded... */ storage2 = nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename); if (storage2) g_hash_table_add(storages_replaced, g_object_ref(storage2)); continue; } g_propagate_error(&entry->error, g_steal_pointer(&local)); continue; } uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage); if (uuid) g_hash_table_add(loaded_uuids, (char *) uuid); nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage)); } /* now we visit all UUIDs that are about to change... */ g_hash_table_iter_init(&h_iter, loaded_uuids); while (g_hash_table_iter_next(&h_iter, (gpointer *) &loaded_uuid, NULL)) { NMSIfcfgRHStorage *storage; NMSettUtilStorageByUuidHead *sbuh; sbuh = nm_sett_util_storages_lookup_by_uuid(&priv->storages, loaded_uuid); if (!sbuh) continue; c_list_for_each_entry (storage, &sbuh->_storage_by_uuid_lst_head, parent._storage_by_uuid_lst) { const char *full_filename = nms_ifcfg_rh_storage_get_filename(storage); gs_unref_object NMSIfcfgRHStorage *storage_new = NULL; gs_free_error GError *local = NULL; if (g_hash_table_contains(dupl_filenames, full_filename)) { /* already re-loaded. */ continue; } /* @storage has a UUID that was just loaded from disk, but we have an entry in cache. * Reload that file too despite not being told to do so. The reason is to get * the latest file timestamp so that we get the priorities right. */ storage_new = _load_file(self, full_filename, &local); if (storage_new && !nm_streq0(loaded_uuid, nms_ifcfg_rh_storage_get_uuid_opt(storage_new))) { /* the file now references a different UUID. We are not told to reload * that file, so this means the existing storage (with the previous * filename and UUID tuple) is no longer valid. */ g_clear_object(&storage_new); } g_hash_table_add(storages_replaced, g_object_ref(storage)); if (storage_new) nm_sett_util_storages_add_take(&storages_new, g_steal_pointer(&storage_new)); } } nm_clear_pointer(&loaded_uuids, g_hash_table_destroy); nm_clear_pointer(&dupl_filenames, g_hash_table_destroy); _storages_consolidate(self, &storages_new, FALSE, storages_replaced, callback, user_data); } static void reload_connections(NMSettingsPlugin *plugin, NMSettingsPluginConnectionLoadCallback callback, gpointer user_data) { NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(plugin); nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = NM_SETT_UTIL_STORAGES_INIT(storages_new, nms_ifcfg_rh_storage_destroy); nm_assert_self(self, TRUE); _load_dir(self, &storages_new); _storages_consolidate(self, &storages_new, TRUE, NULL, callback, user_data); nm_assert_self(self, FALSE); } static void load_connections_done(NMSettingsPlugin *plugin) { NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(plugin); /* at the beginning of a load, we emit a change signal for unmanaged/unrecognized * specs that contain the sum of before and after (_unhandled_specs_merge_storages()). * * The idea is that while we emit signals about changes to connection, we have * the sum of all unmanaged/unrecognized devices from before and after. * * This if triggered at the end, to reset the specs. */ _unhandled_specs_reset(self); nm_assert_self(self, TRUE); } /*****************************************************************************/ static gboolean add_connection(NMSettingsPlugin *plugin, NMConnection *connection, NMSettingsStorage **out_storage, NMConnection **out_connection, GError **error) { NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(plugin); NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); gs_unref_object NMSIfcfgRHStorage *storage = NULL; gs_unref_object NMConnection *reread = NULL; gs_free char *full_filename = NULL; GError *local = NULL; gboolean reread_same; struct timespec mtime; nm_assert_self(self, TRUE); nm_assert(NM_IS_CONNECTION(connection)); nm_assert(out_storage && !*out_storage); nm_assert(out_connection && !*out_connection); if (!nms_ifcfg_rh_writer_write_connection( connection, IFCFG_DIR, NULL, nm_sett_util_allow_filename_cb, NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, NULL), &full_filename, &reread, &reread_same, &local)) { _LOGT("commit: %s (%s): failed to add: %s", nm_connection_get_uuid(connection), nm_connection_get_id(connection), local->message); g_propagate_error(error, local); return FALSE; } if (!reread || reread_same) nm_g_object_ref_set(&reread, connection); nm_assert(full_filename && full_filename[0] == '/'); _LOGT("commit: %s (%s) added as \"%s\"", nm_connection_get_uuid(reread), nm_connection_get_id(reread), full_filename); storage = nms_ifcfg_rh_storage_new_connection(self, full_filename, g_steal_pointer(&reread), nm_sett_util_stat_mtime(full_filename, FALSE, &mtime)); nm_sett_util_storages_add_take(&priv->storages, g_object_ref(storage)); *out_connection = nms_ifcfg_rh_storage_steal_connection(storage); *out_storage = NM_SETTINGS_STORAGE(g_steal_pointer(&storage)); nm_assert_self(self, TRUE); return TRUE; } static gboolean update_connection(NMSettingsPlugin *plugin, NMSettingsStorage *storage_x, NMConnection *connection, NMSettingsStorage **out_storage, NMConnection **out_connection, GError **error) { NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(plugin); NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); NMSIfcfgRHStorage *storage = NMS_IFCFG_RH_STORAGE(storage_x); const char *full_filename; const char *uuid; GError *local = NULL; gs_unref_object NMConnection *reread = NULL; gboolean reread_same; struct timespec mtime; nm_assert_self(self, TRUE); nm_assert(NM_IS_CONNECTION(connection)); nm_assert(NMS_IS_IFCFG_RH_STORAGE(storage)); nm_assert(_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS); nm_assert(!error || !*error); uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage); nm_assert(uuid && nm_streq0(uuid, nm_connection_get_uuid(connection))); full_filename = nms_ifcfg_rh_storage_get_filename(storage); nm_assert(full_filename); nm_assert(storage == nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename)); if (!nms_ifcfg_rh_writer_write_connection( connection, IFCFG_DIR, full_filename, nm_sett_util_allow_filename_cb, NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, full_filename), NULL, &reread, &reread_same, &local)) { _LOGT("commit: failure to write %s (%s) to \"%s\": %s", nm_connection_get_uuid(connection), nm_connection_get_id(connection), full_filename, local->message); g_propagate_error(error, local); return FALSE; } if (!reread || reread_same) nm_g_object_ref_set(&reread, connection); _LOGT("commit: \"%s\": profile %s (%s) written", full_filename, uuid, nm_connection_get_id(connection)); storage->stat_mtime = *nm_sett_util_stat_mtime(full_filename, FALSE, &mtime); *out_storage = NM_SETTINGS_STORAGE(g_object_ref(storage)); *out_connection = g_steal_pointer(&reread); nm_assert_self(self, TRUE); return TRUE; } static gboolean delete_connection(NMSettingsPlugin *plugin, NMSettingsStorage *storage_x, GError **error) { NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(plugin); NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); NMSIfcfgRHStorage *storage = NMS_IFCFG_RH_STORAGE(storage_x); const char *operation_message; const char *full_filename; nm_assert_self(self, TRUE); nm_assert(!error || !*error); nm_assert(NMS_IS_IFCFG_RH_STORAGE(storage)); full_filename = nms_ifcfg_rh_storage_get_filename(storage); nm_assert(full_filename); nm_assert(nms_ifcfg_rh_storage_get_uuid_opt(storage)); nm_assert(storage == nm_sett_util_storages_lookup_by_filename(&priv->storages, full_filename)); { gs_free char *keyfile = utils_get_keys_path(full_filename); gs_free char *routefile = utils_get_route_path(full_filename); gs_free char *route6file = utils_get_route6_path(full_filename); const char *const files[] = {full_filename, keyfile, routefile, route6file}; gboolean any_deleted = FALSE; gboolean any_failure = FALSE; int i; for (i = 0; i < G_N_ELEMENTS(files); i++) { int errsv; if (unlink(files[i]) == 0) { any_deleted = TRUE; continue; } errsv = errno; if (errsv == ENOENT) continue; _LOGW("commit: failure to delete file \"%s\": %s", files[i], nm_strerror_native(errsv)); any_failure = TRUE; } if (any_failure) operation_message = "failed to delete files from disk"; else if (any_deleted) operation_message = "deleted from disk"; else operation_message = "does not exist on disk"; } _LOGT("commit: deleted \"%s\", profile %s (%s)", full_filename, nms_ifcfg_rh_storage_get_uuid_opt(storage), operation_message); nm_sett_util_storages_steal(&priv->storages, storage); nms_ifcfg_rh_storage_destroy(storage); nm_assert_self(self, TRUE); return TRUE; } /*****************************************************************************/ static void _unhandled_specs_reset(NMSIfcfgRHPlugin *self) { NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); gs_unref_hashtable GHashTable *unmanaged_specs = NULL; gs_unref_hashtable GHashTable *unrecognized_specs = NULL; NMSIfcfgRHStorage *storage; unmanaged_specs = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); unrecognized_specs = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); c_list_for_each_entry (storage, &priv->storages._storage_lst_head, parent._storage_lst) { if (storage->unmanaged_spec) g_hash_table_add(unmanaged_specs, g_strdup(storage->unmanaged_spec)); if (storage->unrecognized_spec) g_hash_table_add(unrecognized_specs, g_strdup(storage->unrecognized_spec)); } if (!nm_utils_hashtable_same_keys(unmanaged_specs, priv->unmanaged_specs)) { g_hash_table_unref(priv->unmanaged_specs); priv->unmanaged_specs = g_steal_pointer(&unmanaged_specs); } if (!nm_utils_hashtable_same_keys(unrecognized_specs, priv->unrecognized_specs)) { g_hash_table_unref(priv->unrecognized_specs); priv->unrecognized_specs = g_steal_pointer(&unrecognized_specs); } if (!unmanaged_specs) _nm_settings_plugin_emit_signal_unmanaged_specs_changed(NM_SETTINGS_PLUGIN(self)); if (!unrecognized_specs) _nm_settings_plugin_emit_signal_unrecognized_specs_changed(NM_SETTINGS_PLUGIN(self)); } static void _unhandled_specs_merge_storages(NMSIfcfgRHPlugin *self, NMSettUtilStorages *storages) { NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); gboolean unmanaged_changed = FALSE; gboolean unrecognized_changed = FALSE; NMSIfcfgRHStorage *storage; c_list_for_each_entry (storage, &storages->_storage_lst_head, parent._storage_lst) { if (storage->unmanaged_spec && !g_hash_table_contains(priv->unmanaged_specs, storage->unmanaged_spec)) { unmanaged_changed = TRUE; g_hash_table_add(priv->unmanaged_specs, g_strdup(storage->unmanaged_spec)); } if (storage->unrecognized_spec && !g_hash_table_contains(priv->unrecognized_specs, storage->unrecognized_spec)) { unrecognized_changed = TRUE; g_hash_table_add(priv->unrecognized_specs, g_strdup(storage->unrecognized_spec)); } } if (unmanaged_changed) _nm_settings_plugin_emit_signal_unmanaged_specs_changed(NM_SETTINGS_PLUGIN(self)); if (unrecognized_changed) _nm_settings_plugin_emit_signal_unrecognized_specs_changed(NM_SETTINGS_PLUGIN(self)); } static GSList * _unhandled_specs_from_hashtable(GHashTable *hash) { gs_free const char **keys = NULL; GSList *list = NULL; guint i, l; keys = nm_strdict_get_keys(hash, TRUE, &l); for (i = l; i > 0;) { i--; list = g_slist_prepend(list, g_strdup(keys[i])); } return list; } static GSList * get_unmanaged_specs(NMSettingsPlugin *plugin) { return _unhandled_specs_from_hashtable( NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(plugin)->unmanaged_specs); } static GSList * get_unrecognized_specs(NMSettingsPlugin *plugin) { return _unhandled_specs_from_hashtable( NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(plugin)->unrecognized_specs); } /*****************************************************************************/ static void impl_ifcfgrh_get_ifcfg_details(NMSIfcfgRHPlugin *self, GDBusMethodInvocation *context, const char *in_ifcfg) { NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); gs_free char *ifcfg_path = NULL; NMSIfcfgRHStorage *storage; const char *uuid; const char *path; if (in_ifcfg[0] != '/') { g_dbus_method_invocation_return_error(context, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "ifcfg path '%s' is not absolute", in_ifcfg); return; } ifcfg_path = utils_detect_ifcfg_path(in_ifcfg, TRUE); 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; } storage = nm_sett_util_storages_lookup_by_filename(&priv->storages, ifcfg_path); if (!storage) { g_dbus_method_invocation_return_error(context, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "ifcfg file '%s' unknown", in_ifcfg); return; } uuid = nms_ifcfg_rh_storage_get_uuid_opt(storage); if (!uuid) { g_dbus_method_invocation_return_error(context, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "ifcfg file '%s' not managed by NetworkManager", in_ifcfg); return; } /* It is ugly that the ifcfg-rh plugin needs to call back into NMSettings this * way. * There are alternatives (like invoking a signal), but they are all significant * extra code (and performance overhead). So the quick and dirty solution here * is likely to be simpler than getting this right (also from point of readability!). */ path = nm_settings_get_dbus_path_for_uuid(nm_settings_get(), uuid); if (!path) { g_dbus_method_invocation_return_error(context, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED, "unable to get the connection D-Bus path"); return; } g_dbus_method_invocation_return_value(context, g_variant_new("(so)", uuid, path)); } /*****************************************************************************/ static void _dbus_clear(NMSIfcfgRHPlugin *self) { NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); guint id; nm_clear_g_signal_handler(priv->dbus.connection, &priv->dbus.signal_id); nm_clear_g_cancellable(&priv->dbus.cancellable); 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"); } g_clear_object(&priv->dbus.connection); } static void _dbus_connection_closed(GDBusConnection *connection, gboolean remote_peer_vanished, GError *error, gpointer user_data) { _LOGW("dbus: %s bus closed", IFCFGRH1_BUS_NAME); _dbus_clear(NMS_IFCFG_RH_PLUGIN(user_data)); /* Retry or recover? */ } 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) { NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(user_data); if (nm_streq(interface_name, IFCFGRH1_IFACE1_NAME)) { if (nm_streq(method_name, IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS)) { const char *ifcfg; g_variant_get(parameters, "(&s)", &ifcfg); impl_ifcfgrh_get_ifcfg_details(self, invocation, ifcfg); return; } } g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "Unknown method %s", method_name); } static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO( IFCFGRH1_IFACE1_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(NM_DEFINE_GDBUS_ARG_INFO("ifcfg", "s"), ), .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("uuid", "s"), NM_DEFINE_GDBUS_ARG_INFO("path", "o"), ), ), ), ); static void _dbus_request_name_done(GObject *source_object, GAsyncResult *res, gpointer user_data) { GDBusConnection *connection = G_DBUS_CONNECTION(source_object); NMSIfcfgRHPlugin *self; NMSIfcfgRHPluginPrivate *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 = NMS_IFCFG_RH_PLUGIN(user_data); priv = NMS_IFCFG_RH_PLUGIN_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; } { static const GDBusInterfaceVTable interface_vtable = { .method_call = _method_call, }; 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); _dbus_clear(self); return; } } _LOGD("dbus: acquired D-Bus service %s and exported %s object", IFCFGRH1_BUS_NAME, IFCFGRH1_OBJECT_PATH); } static void _dbus_create_done(GObject *source_object, GAsyncResult *res, gpointer user_data) { NMSIfcfgRHPlugin *self; NMSIfcfgRHPluginPrivate *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 = NMS_IFCFG_RH_PLUGIN(user_data); priv = NMS_IFCFG_RH_PLUGIN_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)", IFCFGRH1_BUS_NAME, 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(NMSIfcfgRHPlugin *self) { NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); gs_free char *address = NULL; gs_free_error GError *error = NULL; _dbus_clear(self); if (!NM_MAIN_DBUS_CONNECTION_GET) { _LOGW("dbus: don't use D-Bus for %s service", IFCFGRH1_BUS_NAME); return; } /* We use a separate D-Bus connection so that org.freedesktop.NetworkManager and com.redhat.ifcfgrh1 * are exported by different connections. */ 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, NMSIfcfgRHPlugin *self) { NMSIfcfgRHPluginPrivate *priv; /* 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 * event that the D-Bus connection is broken, that allows for recovery * without need for restarting NetworkManager. */ if (!NM_FLAGS_ANY(changes, NM_CONFIG_CHANGE_CAUSE_SIGHUP | NM_CONFIG_CHANGE_CAUSE_SIGUSR1)) return; priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); if (!priv->dbus.connection && !priv->dbus.cancellable) _dbus_setup(self); } /*****************************************************************************/ static void nms_ifcfg_rh_plugin_init(NMSIfcfgRHPlugin *self) { NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); priv->config = g_object_ref(nm_config_get()); priv->unmanaged_specs = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); priv->unrecognized_specs = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); priv->storages = (NMSettUtilStorages) NM_SETT_UTIL_STORAGES_INIT(priv->storages, nms_ifcfg_rh_storage_destroy); } static void constructed(GObject *object) { NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(object); NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); G_OBJECT_CLASS(nms_ifcfg_rh_plugin_parent_class)->constructed(object); g_signal_connect(priv->config, NM_CONFIG_SIGNAL_CONFIG_CHANGED, G_CALLBACK(config_changed_cb), self); _dbus_setup(self); } static void dispose(GObject *object) { NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN(object); NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self); if (priv->config) g_signal_handlers_disconnect_by_func(priv->config, config_changed_cb, self); /* FIXME(shutdown) we need a stop method so that we can unregistering the D-Bus service * when NMSettings is shutting down, and not when the instance gets destroyed. */ _dbus_clear(self); nm_sett_util_storages_clear(&priv->storages); g_clear_object(&priv->config); G_OBJECT_CLASS(nms_ifcfg_rh_plugin_parent_class)->dispose(object); nm_clear_pointer(&priv->unmanaged_specs, g_hash_table_destroy); nm_clear_pointer(&priv->unrecognized_specs, g_hash_table_destroy); } static void nms_ifcfg_rh_plugin_class_init(NMSIfcfgRHPluginClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS(klass); object_class->constructed = constructed; object_class->dispose = dispose; plugin_class->plugin_name = "ifcfg-rh"; plugin_class->get_unmanaged_specs = get_unmanaged_specs; plugin_class->get_unrecognized_specs = get_unrecognized_specs; plugin_class->reload_connections = reload_connections; plugin_class->load_connections = load_connections; plugin_class->load_connections_done = load_connections_done; plugin_class->add_connection = add_connection; plugin_class->update_connection = update_connection; plugin_class->delete_connection = delete_connection; } /*****************************************************************************/ G_MODULE_EXPORT NMSettingsPlugin * nm_settings_plugin_factory(void) { return g_object_new(NMS_TYPE_IFCFG_RH_PLUGIN, NULL); }