diff options
Diffstat (limited to 'src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c')
| -rw-r--r-- | src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c | 1253 |
1 files changed, 1253 insertions, 0 deletions
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c new file mode 100644 index 00000000..d114db0f --- /dev/null +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c @@ -0,0 +1,1253 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Dan Williams <dcbw@redhat.com> + * Søren Sandmann <sandmann@daimi.au.dk> + * Copyright (C) 2007 - 2011 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "nms-ifcfg-rh-plugin.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "nm-std-aux/c-list-util.h" +#include "nm-glib-aux/nm-c-list.h" +#include "nm-glib-aux/nm-io-utils.h" +#include "nm-std-aux/nm-dbus-compat.h" +#include "nm-utils.h" +#include "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) +{ + 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; + } + return nms_ifcfg_rh_storage_new_unhandled(self, + filename, + unmanaged_spec, + unrecognized_spec); + } + + return nms_ifcfg_rh_storage_new_connection(self, + filename, + g_steal_pointer(&connection), + &st.st_mtim); +} + +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_utils_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); +} |