summaryrefslogtreecommitdiff
path: root/src/settings/plugins/keyfile/nms-keyfile-plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/settings/plugins/keyfile/nms-keyfile-plugin.c')
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c1587
1 files changed, 1251 insertions, 336 deletions
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index 46432fd257..3b9435440b 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -15,7 +15,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2008 Novell, Inc.
- * Copyright (C) 2008 - 2013 Red Hat, Inc.
+ * Copyright (C) 2008 - 2018 Red Hat, Inc.
*/
#include "nm-default.h"
@@ -27,6 +27,10 @@
#include <sys/types.h>
#include <glib/gstdio.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-connection.h"
#include "nm-setting.h"
#include "nm-setting-connection.h"
@@ -35,20 +39,76 @@
#include "nm-core-internal.h"
#include "nm-keyfile-internal.h"
+#include "systemd/nm-sd-utils-shared.h"
+
#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
-#include "nms-keyfile-connection.h"
+#include "nms-keyfile-storage.h"
#include "nms-keyfile-writer.h"
+#include "nms-keyfile-reader.h"
#include "nms-keyfile-utils.h"
/*****************************************************************************/
typedef struct {
- GHashTable *connections; /* uuid::connection */
+ CList crld_lst;
+
+ char *full_filename;
+ const char *filename;
+
+ /* the profile loaded from the file. Note that this profile is only relevant
+ * during _do_reload_all(). The winning profile at the end of reload will
+ * be referenced as connection_exported, the connection field here will be
+ * cleared. */
+ NMConnection *connection;
+
+ /* the following fields are only required during _do_reload_all() for comparing
+ * which profile is the most relevant one (in case multple files provide a profile
+ * with the same UUID). */
+ struct timespec stat_mtime;
+ dev_t stat_dev;
+ ino_t stat_ino;
+ NMSKeyfileStorageType storage_type:3;
+ guint storage_priority:13;
+ NMTernary is_nm_generated_opt:3;
+ NMTernary is_volatile_opt:3;
+} ConnReloadData;
+
+typedef struct _NMSKeyfileConnReloadHead {
+ CList crld_lst_head;
+
+ char *loaded_path_etc;
+ char *loaded_path_run;
+} ConnReloadHead;
- gboolean initialized;
+typedef struct {
NMConfig *config;
+
+ /* there can/could be multiple read-only directories. For example, one
+ * could set dirname_libs to
+ * - /usr/lib/NetworkManager/profiles/
+ * - /etc/NetworkManager/system-connections
+ * and leave dirname_etc unset. In this case, there would be multiple
+ * read-only directories.
+ *
+ * Directories that come later have higher priority and shadow profiles
+ * from earlier directories.
+ *
+ * Currently, this is only an array with zero or one elements. It could be
+ * easily extended to support multiple read-only directories.
+ */
+ char *dirname_libs[2];
+ char *dirname_etc;
+ char *dirname_run;
+
+ struct {
+ CList lst_head;
+ GHashTable *idx;
+ GHashTable *filename_idx;
+ } storages;
+
} NMSKeyfilePluginPrivate;
struct _NMSKeyfilePlugin {
@@ -62,7 +122,7 @@ struct _NMSKeyfilePluginClass {
G_DEFINE_TYPE (NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN)
-#define NMS_KEYFILE_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSKeyfilePlugin, NMS_IS_KEYFILE_PLUGIN)
+#define NMS_KEYFILE_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSKeyfilePlugin, NMS_IS_KEYFILE_PLUGIN, NMSettingsPlugin)
/*****************************************************************************/
@@ -76,430 +136,1241 @@ G_DEFINE_TYPE (NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN)
/*****************************************************************************/
-static void
-connection_removed_cb (NMSettingsConnection *sett_conn, NMSKeyfilePlugin *self)
+static void _storage_reload_data_head_clear (NMSKeyfileStorage *storage);
+
+/*****************************************************************************/
+
+static gboolean
+_ignore_filename (NMSKeyfileStorageType storage_type,
+ const char *filename)
{
- g_hash_table_remove (NMS_KEYFILE_PLUGIN_GET_PRIVATE (self)->connections,
- nm_settings_connection_get_uuid (sett_conn));
+ /* for backward-compatibility, we don't require an extension for
+ * files under "/etc/...". */
+ return nm_keyfile_utils_ignore_filename (filename,
+ (storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC));
}
-/* Monitoring */
+static const char *
+_get_plugin_dir (NMSKeyfilePluginPrivate *priv)
+{
+ /* the plugin dir is only needed to generate connection.uuid value via
+ * nm_keyfile_read_ensure_uuid(). This is either the configured /etc
+ * directory, of the compile-time default (in case the /etc directory
+ * is disabled). */
+ return priv->dirname_etc ?: NM_KEYFILE_PATH_NAME_ETC_DEFAULT;
+}
-static void
-remove_connection (NMSKeyfilePlugin *self, NMSKeyfileConnection *connection)
+static gboolean
+_path_detect_storage_type (const char *full_filename,
+ const char *const*dirname_libs,
+ const char *dirname_etc,
+ const char *dirname_run,
+ NMSKeyfileStorageType *out_storage_type,
+ const char **out_dirname,
+ const char **out_filename,
+ GError **error)
{
- gboolean removed;
+ NMSKeyfileStorageType storage_type;
+ const char *filename = NULL;
+ const char *dirname = NULL;
+ guint i;
- g_return_if_fail (connection != NULL);
+ if (full_filename[0] != '/') {
+ nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN,
+ "filename is not an absolute path");
+ return FALSE;
+ }
- _LOGI ("removed " NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection));
+ if ( dirname_run
+ && (filename = nm_utils_file_is_in_path (full_filename, dirname_run))) {
+ storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN;
+ dirname = dirname_run;
+ } else if ( dirname_etc
+ && (filename = nm_utils_file_is_in_path (full_filename, dirname_etc))) {
+ storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC;
+ dirname = dirname_etc;
+ } else {
+ for (i = 0; dirname_libs && dirname_libs[i]; i++) {
+ if ((filename = nm_utils_file_is_in_path (full_filename, dirname_libs[i]))) {
+ storage_type = NMS_KEYFILE_STORAGE_TYPE_LIB;
+ dirname = dirname_libs[i];
+ break;
+ }
+ }
+ if (!dirname) {
+ nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN,
+ "filename is not inside a keyfile directory");
+ return FALSE;
+ }
+ }
- /* Removing from the hash table should drop the last reference */
- g_object_ref (connection);
- g_signal_handlers_disconnect_by_func (connection, connection_removed_cb, self);
- removed = g_hash_table_remove (NMS_KEYFILE_PLUGIN_GET_PRIVATE (self)->connections,
- nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection)));
- nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
- g_object_unref (connection);
+ if (_ignore_filename (storage_type, filename)) {
+ nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN,
+ "filename is not a valid keyfile");
+ return FALSE;
+ }
- g_return_if_fail (removed);
+ NM_SET_OUT (out_storage_type, storage_type);
+ NM_SET_OUT (out_dirname, dirname);
+ NM_SET_OUT (out_filename, filename);
+ return TRUE;
}
-static NMSKeyfileConnection *
-find_by_path (NMSKeyfilePlugin *self, const char *path)
+/*****************************************************************************/
+
+static NMConnection *
+_read_from_file (const char *full_filename,
+ const char *plugin_dir,
+ struct stat *out_stat,
+ NMTernary *out_is_nm_generated,
+ NMTernary *out_is_volatile,
+ GError **error)
{
- NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
- GHashTableIter iter;
- NMSettingsConnection *candidate = NULL;
+ NMConnection *connection;
+
+ nm_assert (full_filename && full_filename[0] == '/');
+
+ connection = nms_keyfile_reader_from_file (full_filename, plugin_dir, out_stat, out_is_nm_generated, out_is_volatile, error);
+
+ nm_assert (!connection || (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS));
+ nm_assert (!connection || nm_utils_is_uuid (nm_connection_get_uuid (connection)));
+
+ return connection;
+}
+
+/*****************************************************************************/
- g_return_val_if_fail (path != NULL, NULL);
+static void
+_conn_reload_data_destroy (ConnReloadData *storage_data)
+{
+ c_list_unlink_stale (&storage_data->crld_lst);
+ nm_g_object_unref (storage_data->connection);
+ g_free (storage_data->full_filename);
+ g_slice_free (ConnReloadData, storage_data);
+}
- g_hash_table_iter_init (&iter, priv->connections);
- while (g_hash_table_iter_next (&iter, NULL, (gpointer) &candidate)) {
- if (g_strcmp0 (path, nm_settings_connection_get_filename (candidate)) == 0)
- return NMS_KEYFILE_CONNECTION (candidate);
+static ConnReloadData *
+_conn_reload_data_new (guint storage_priority,
+ NMSKeyfileStorageType storage_type,
+ char *full_filename_take,
+ NMConnection *connection_take,
+ NMTernary is_nm_generated_opt,
+ NMTernary is_volatile_opt,
+ const struct stat *st)
+{
+ ConnReloadData *storage_data;
+
+ nm_assert (NM_IN_SET (is_nm_generated_opt, NM_TERNARY_DEFAULT, FALSE, TRUE));
+ nm_assert (NM_IN_SET (is_nm_generated_opt, NM_TERNARY_DEFAULT, FALSE, TRUE));
+
+ storage_data = g_slice_new (ConnReloadData);
+ *storage_data = (ConnReloadData) {
+ .storage_type = storage_type,
+ .storage_priority = storage_priority,
+ .full_filename = full_filename_take,
+ .filename = strrchr (full_filename_take, '/') + 1,
+ .connection = connection_take,
+ .is_nm_generated_opt = is_nm_generated_opt,
+ .is_volatile_opt = is_volatile_opt,
+ };
+ if (st) {
+ storage_data->stat_mtime = st->st_mtim;
+ storage_data->stat_dev = st->st_dev;
+ storage_data->stat_ino = st->st_ino;
}
- return NULL;
-}
-
-/* update_connection:
- * @self: the plugin instance
- * @source: if %NULL, this re-reads the connection from @full_path
- * and updates it. When passing @source, this adds a connection from
- * memory.
- * @full_path: the filename of the keyfile to be loaded
- * @connection: an existing connection that might be updated.
- * If given, @connection must be an existing connection that is currently
- * owned by the plugin.
- * @protect_existing_connection: if %TRUE, and !@connection, we don't allow updating
- * an existing connection with the same UUID.
- * If %TRUE and @connection, allow updating only if the reload would modify
- * @connection (without changing its UUID) or if we would create a new connection.
- * In other words, if this parameter is %TRUE, we only allow creating a
- * new connection (with an unseen UUID) or updating the passed in @connection
- * (whereas the UUID cannot change).
- * Note, that this allows for @connection to be replaced by a new connection.
- * @protected_connections: (allow-none): if given, we only update an
- * existing connection if it is not contained in this hash.
- * @error: error in case of failure
- *
- * Loads a connection from file @full_path. This can both be used to
- * load a connection initially or to update an existing connection.
- *
- * If you pass in an existing connection and the reloaded file happens
- * to have a different UUID, the connection is deleted.
- * Beware, that means that after the function, you have a dangling pointer
- * if the returned connection is different from @connection.
- *
- * Returns: the updated connection.
- * */
-static NMSKeyfileConnection *
-update_connection (NMSKeyfilePlugin *self,
- NMConnection *source,
- const char *full_path,
- NMSKeyfileConnection *connection,
- gboolean protect_existing_connection,
- GHashTable *protected_connections,
- GError **error)
+
+ nm_assert (storage_data->storage_type == storage_type);
+ nm_assert (storage_data->storage_priority == storage_priority);
+ nm_assert (storage_data->full_filename);
+ nm_assert (storage_data->full_filename[0] == '/');
+ nm_assert (storage_data->filename);
+ nm_assert (storage_data->filename[0]);
+ nm_assert (!strchr (storage_data->filename, '/'));
+
+ return storage_data;
+}
+
+static int
+_conn_reload_data_cmp_by_priority (const CList *lst_a,
+ const CList *lst_b,
+ const void *user_data)
{
- NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
- NMSKeyfileConnection *connection_new;
- NMSKeyfileConnection *connection_by_uuid;
- GError *local = NULL;
- const char *uuid;
+ const ConnReloadData *a = c_list_entry (lst_a, ConnReloadData, crld_lst);
+ const ConnReloadData *b = c_list_entry (lst_b, ConnReloadData, crld_lst);
+
+ /* we sort more important entries first. */
+
+ /* sorting by storage-priority implies sorting by storage-type too.
+ * That is, because for different storage-types, we assign different storage-priorities
+ * and their sort order corresponds (with inverted order). Assert for that. */
+ nm_assert ( a->storage_type == b->storage_type
+ || ( (a->storage_priority != b->storage_priority)
+ && (a->storage_type < b->storage_type) == (a->storage_priority > b->storage_priority)));
+
+ /* sort by storage-priority, smaller is more important. */
+ NM_CMP_FIELD_UNSAFE (a, b, storage_priority);
- g_return_val_if_fail (!source || NM_IS_CONNECTION (source), NULL);
- g_return_val_if_fail (full_path || source, NULL);
+ /* newer files are more important. */
+ NM_CMP_FIELD (b, a, stat_mtime.tv_sec);
+ NM_CMP_FIELD (b, a, stat_mtime.tv_nsec);
- if (full_path)
- _LOGD ("loading from file \"%s\"...", full_path);
+ NM_CMP_FIELD_STR (a, b, filename);
- if ( !nm_utils_file_is_in_path (full_path, nms_keyfile_utils_get_path ())
- && !nm_utils_file_is_in_path (full_path, NM_KEYFILE_PATH_NAME_RUN)) {
- g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
- "File not in recognized system-connections directory");
+ nm_assert_not_reached ();
+ return 0;
+}
+
+/* stat(@loaded_path) and if the path is the same as any of the ones from
+ * @crld_lst_head, move the found entry to the front and return TRUE.
+ * Otherwise, do nothing and return FALSE. */
+static gboolean
+_conn_reload_data_prioritize_loaded (CList *crld_lst_head,
+ const char *loaded_path)
+{
+ ConnReloadData *storage_data;
+ struct stat st_loaded;
+
+ nm_assert (loaded_path);
+ nm_assert (!nm_streq (loaded_path, NM_KEYFILE_PATH_NMLOADED_NULL));
+
+ if (loaded_path[0] != '/')
return FALSE;
- }
- connection_new = nms_keyfile_connection_new (source, full_path, nms_keyfile_utils_get_path (), &local);
- if (!connection_new) {
- /* Error; remove the connection */
- if (source)
- _LOGW ("error creating connection %s: %s", nm_connection_get_uuid (source), local->message);
- else
- _LOGW ("error loading connection from file %s: %s", full_path, local->message);
- if ( connection
- && !protect_existing_connection
- && (!protected_connections || !g_hash_table_contains (protected_connections, connection)))
- remove_connection (self, connection);
- g_propagate_error (error, local);
- return NULL;
+ /* we compare the file based on the inode, not based on the path.
+ * stat() the file. */
+ if (stat (loaded_path, &st_loaded) != 0)
+ return FALSE;
+
+ c_list_for_each_entry (storage_data, crld_lst_head, crld_lst) {
+ if ( storage_data->stat_dev == st_loaded.st_dev
+ && storage_data->stat_ino == st_loaded.st_ino) {
+ nm_c_list_move_front (crld_lst_head, &storage_data->crld_lst);
+ return TRUE;
+ }
}
- uuid = nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_new));
- connection_by_uuid = g_hash_table_lookup (priv->connections, uuid);
+ return FALSE;
+}
- if ( connection
- && connection != connection_by_uuid) {
+/*****************************************************************************/
- if ( (protect_existing_connection && connection_by_uuid != NULL)
- || (protected_connections && g_hash_table_contains (protected_connections, connection))) {
- NMSKeyfileConnection *conflicting = (protect_existing_connection && connection_by_uuid != NULL) ? connection_by_uuid : connection;
+static void
+_nm_assert_storage (gpointer plugin /* NMSKeyfilePlugin */,
+ gpointer storage /* NMSKeyfileStorage */,
+ gboolean tracked)
+{
+#if NM_MORE_ASSERTS
+ nm_assert (!plugin || NMS_IS_KEYFILE_PLUGIN (plugin));
+ nm_assert (NMS_IS_KEYFILE_STORAGE (storage));
+ nm_assert (!plugin || plugin == nm_settings_storage_get_plugin (storage));
+ nm_assert (({
+ const char *f = nms_keyfile_storage_get_filename (storage);
+ !f || f[0] == '/';
+ }));
+ nm_assert (nm_utils_is_uuid (nms_keyfile_storage_get_uuid (storage)));
+
+ nm_assert ( !tracked
+ || !plugin
+ || c_list_contains (&NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin)->storages.lst_head,
+ &NMS_KEYFILE_STORAGE (storage)->storage_lst));
+
+ nm_assert ( !tracked
+ || !plugin
+ || storage == g_hash_table_lookup (NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin)->storages.idx,
+ nms_keyfile_storage_get_uuid (storage)));
+#endif
+}
- if (source)
- _LOGW ("cannot update protected "NMS_KEYFILE_CONNECTION_LOG_FMT" connection due to conflicting UUID %s", NMS_KEYFILE_CONNECTION_LOG_ARG (conflicting), uuid);
- else
- _LOGW ("cannot load %s due to conflicting UUID for "NMS_KEYFILE_CONNECTION_LOG_FMT, full_path, NMS_KEYFILE_CONNECTION_LOG_ARG (conflicting));
- g_object_unref (connection_new);
- g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
- "Cannot update protected connection due to conflicting UUID");
- return NULL;
- }
+void
+_nms_keyfile_storage_clear (NMSKeyfileStorage *storage)
+{
+ _nm_assert_storage (NULL, storage, FALSE);
- /* The new connection has a different UUID then the original one.
- * Remove @connection. */
- remove_connection (self, connection);
- }
+ c_list_unlink (&storage->storage_lst);
- if ( connection_by_uuid
- && ( (!connection && protect_existing_connection)
- || (protected_connections && g_hash_table_contains (protected_connections, connection_by_uuid)))) {
- if (source)
- _LOGW ("cannot update connection due to conflicting UUID for "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_by_uuid));
- else
- _LOGW ("cannot load %s due to conflicting UUID for "NMS_KEYFILE_CONNECTION_LOG_FMT, full_path, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_by_uuid));
- g_object_unref (connection_new);
- g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
- "Skip updating protected connection during reload");
- return NULL;
- }
+ _storage_reload_data_head_clear (storage);
- if (connection_by_uuid) {
- const char *old_path;
-
- old_path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid));
-
- if (nm_connection_compare (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_by_uuid)),
- nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
- NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS |
- NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) {
- /* Nothing to do... except updating the path. */
- if (old_path && g_strcmp0 (old_path, full_path) != 0)
- _LOGI ("rename \"%s\" to "NMS_KEYFILE_CONNECTION_LOG_FMT" without other changes", old_path, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
- } else {
- /* An existing connection changed. */
- if (source)
- _LOGI ("update "NMS_KEYFILE_CONNECTION_LOG_FMT" from %s", NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new), NMS_KEYFILE_CONNECTION_LOG_PATH (old_path));
- else if (!g_strcmp0 (old_path, nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_new))))
- _LOGI ("update "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
- else if (old_path)
- _LOGI ("rename \"%s\" to "NMS_KEYFILE_CONNECTION_LOG_FMT, old_path, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
- else
- _LOGI ("update and persist "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-
- if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (connection_by_uuid),
- nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
- NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
- NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
- "keyfile-update",
- &local)) {
- /* Shouldn't ever get here as 'connection_new' was verified by the reader already
- * and the UUID did not change. */
- g_assert_not_reached ();
- }
- g_assert_no_error (local);
- }
- nm_settings_connection_set_filename (NM_SETTINGS_CONNECTION (connection_by_uuid), full_path);
- g_object_unref (connection_new);
- return connection_by_uuid;
- } else {
- if (source)
- _LOGI ("add connection "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
- else
- _LOGI ("new connection "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
- g_hash_table_insert (priv->connections, g_strdup (uuid), connection_new);
-
- g_signal_connect (connection_new, NM_SETTINGS_CONNECTION_REMOVED,
- G_CALLBACK (connection_removed_cb),
- self);
-
- if (!source) {
- /* Only raise the signal if we were called without source, i.e. if we read the connection from file.
- * Otherwise, we were called by add_connection() which does not expect the signal. */
- _nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
- NM_SETTINGS_CONNECTION (connection_new));
- }
+ g_clear_object (&storage->connection_exported);
+}
+
+static void
+_storage_destroy (NMSKeyfileStorage *storage)
+{
+ _nm_assert_storage (NULL, storage, TRUE);
- return connection_new;
+ _nms_keyfile_storage_clear (storage);
+ g_object_unref (storage);
+}
+
+static ConnReloadHead *
+_storage_reload_data_head_ensure (NMSKeyfileStorage *storage)
+{
+ ConnReloadHead *hd;
+
+ hd = storage->_reload_data_head;
+ if (!hd) {
+ hd = g_slice_new (ConnReloadHead);
+ *hd = (ConnReloadHead) {
+ .crld_lst_head = C_LIST_INIT (hd->crld_lst_head),
+ };
+ storage->_reload_data_head = hd;
}
+
+ return hd;
}
static void
-config_changed_cb (NMConfig *config,
- NMConfigData *config_data,
- NMConfigChangeFlags changes,
- NMConfigData *old_data,
- NMSKeyfilePlugin *self)
+_storage_reload_data_head_clear (NMSKeyfileStorage *storage)
{
- gs_free char *old_value = NULL;
- gs_free char *new_value = NULL;
+ ConnReloadHead *hd;
+ ConnReloadData *storage_data;
- old_value = nm_config_data_get_value (old_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
- new_value = nm_config_data_get_value (config_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
+ hd = g_steal_pointer (&storage->_reload_data_head);
+ if (!hd)
+ return;
- if (!nm_streq0 (old_value, new_value))
- _nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
+ while ((storage_data = c_list_first_entry (&hd->crld_lst_head, ConnReloadData, crld_lst)))
+ _conn_reload_data_destroy (storage_data);
+
+ g_free (hd->loaded_path_run);
+ g_free (hd->loaded_path_etc);
+ g_slice_free (ConnReloadHead, hd);
}
-static GHashTable *
-_paths_from_connections (GHashTable *connections)
+static gboolean
+_storage_has_equal_connection (NMSKeyfileStorage *storage,
+ NMConnection *connection)
{
- GHashTableIter iter;
- NMSKeyfileConnection *connection;
- GHashTable *paths = g_hash_table_new (nm_str_hash, g_str_equal);
+ nm_assert (NM_IS_CONNECTION (connection));
- g_hash_table_iter_init (&iter, connections);
- while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
- const char *path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection));
+ return storage->connection_exported
+ && nm_connection_compare (connection,
+ storage->connection_exported,
+ NM_SETTING_COMPARE_FLAG_EXACT);
+}
- if (path)
- g_hash_table_add (paths, (void *) path);
+static void
+_storage_set_type (NMSKeyfileStorage *storage,
+ NMSKeyfileStorageType storage_type,
+ NMTernary is_nm_generated_opt,
+ NMTernary is_volatile_opt)
+{
+ storage->storage_type_exported = storage_type;
+ if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) {
+ storage->is_nm_generated = (is_nm_generated_opt == NM_TERNARY_TRUE);
+ storage->is_volatile = (is_volatile_opt == NM_TERNARY_TRUE);
+ } else {
+ storage->is_nm_generated = FALSE;
+ storage->is_volatile = FALSE;
}
- return paths;
}
-static int
-_sort_paths (const char **f1, const char **f2, GHashTable *paths)
+/*****************************************************************************/
+
+typedef struct {
+ GHashTable *filename_idx;
+ const char *allowed_filename;
+} AllowFilenameData;
+
+#define ALLOW_FILENAME_DATA(_filename_idx, _allowed_filename) \
+ (&((AllowFilenameData) { \
+ .filename_idx = (_filename_idx), \
+ .allowed_filename = (_allowed_filename), \
+ }))
+
+static gboolean
+_allow_filename_cb (const char *filename,
+ gpointer user_data)
{
- struct stat st;
- gboolean c1, c2;
- gint64 m1, m2;
+ const AllowFilenameData *allow_filename_data = user_data;
+
+ if (!g_hash_table_contains (allow_filename_data->filename_idx, filename))
+ return TRUE;
+ if ( allow_filename_data->allowed_filename
+ && nm_streq (allow_filename_data->allowed_filename, filename))
+ return TRUE;
+ return FALSE;
+}
+
+static NMSKeyfileStorage *
+_storages_get (NMSKeyfilePlugin *self,
+ const char *uuid,
+ gboolean create)
+{
+ NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+ NMSKeyfileStorage *storage;
+
+ nm_assert (uuid && nm_utils_is_uuid (uuid));
+
+ storage = g_hash_table_lookup (priv->storages.idx, uuid);
+ if ( !storage
+ && create) {
+ storage = nms_keyfile_storage_new (self, uuid);
+ c_list_link_tail (&priv->storages.lst_head, &storage->storage_lst);
+ if (!g_hash_table_insert (priv->storages.idx, (char *) nms_keyfile_storage_get_uuid (storage), storage))
+ nm_assert_not_reached ();
+ }
+
+ return storage;
+}
+
+static void
+_storages_set_full_filename (NMSKeyfilePluginPrivate *priv,
+ NMSKeyfileStorage *storage,
+ char *full_filename_take)
+{
+ const char *filename;
+
+ _nm_assert_storage (NULL, storage, TRUE);
+ nm_assert (full_filename_take && full_filename_take[0] == '/');
+
+ filename = nms_keyfile_storage_get_filename (storage);
+ if (filename) {
+ if (!g_hash_table_remove (priv->storages.filename_idx, filename))
+ nm_assert_not_reached ();
+ }
+
+ _nm_settings_storage_set_filename_take (NM_SETTINGS_STORAGE (storage), full_filename_take);
+
+ if (!g_hash_table_insert (priv->storages.filename_idx,
+ (char *) nms_keyfile_storage_get_filename (storage),
+ storage))
+ nm_assert_not_reached ();
+}
+
+static void
+_storages_remove (NMSKeyfilePluginPrivate *priv,
+ NMSKeyfileStorage *storage,
+ gboolean destroy /* or else steal */)
+{
+ const char *filename;
+
+ nm_assert (priv);
+ nm_assert (NMS_IS_KEYFILE_STORAGE (storage));
+ nm_assert (c_list_contains (&priv->storages.lst_head, &storage->storage_lst));
+ nm_assert (g_hash_table_lookup (priv->storages.idx, nms_keyfile_storage_get_uuid (storage)) == storage);
- c1 = !!g_hash_table_contains (paths, *f1);
- c2 = !!g_hash_table_contains (paths, *f2);
- if (c1 != c2)
- return c1 ? -1 : 1;
+ c_list_unlink (&storage->storage_lst);
- m1 = stat (*f1, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
- m2 = stat (*f2, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
- if (m1 != m2)
- return m1 > m2 ? -1 : 1;
+ filename = nms_keyfile_storage_get_filename (storage);
+ if (filename) {
+ if (!g_hash_table_remove (priv->storages.filename_idx, filename))
+ nm_assert_not_reached ();
+
+ /* we don't clear the filename of the storage, although we drop it.
+ *
+ * Probably nobody cares about the earlier filename at this point, but
+ * incase someone would (e.g. for logging that the storage was removed),
+ * it makes sense to keep what was previously. */
+ }
- return strcmp (*f1, *f2);
+ if (destroy) {
+ if (!g_hash_table_remove (priv->storages.idx, nms_keyfile_storage_get_uuid (storage)))
+ nm_assert_not_reached ();
+ } else {
+ if (!g_hash_table_steal (priv->storages.idx, nms_keyfile_storage_get_uuid (storage)))
+ nm_assert_not_reached ();
+ }
}
+/*****************************************************************************/
+
static void
-_read_dir (GPtrArray *filenames,
- const char *path,
- gboolean require_extension)
+_load_dir (NMSKeyfilePlugin *self,
+ guint storage_priority,
+ NMSKeyfileStorageType storage_type,
+ const char *dirname,
+ const char *plugin_dir)
{
+ const char *filename;
GDir *dir;
- const char *item;
- GError *error = NULL;
+ gs_unref_hashtable GHashTable *dupl_filenames = NULL;
- dir = g_dir_open (path, 0, &error);
- if (!dir) {
- _LOGD ("cannot read directory '%s': %s", path, error->message);
- g_clear_error (&error);
+ if (!dirname)
return;
- }
- while ((item = g_dir_read_name (dir))) {
- if (nm_keyfile_utils_ignore_filename (item, require_extension))
+ dir = g_dir_open (dirname, 0, NULL);
+ if (!dir)
+ return;
+
+ dupl_filenames = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
+
+ while ((filename = g_dir_read_name (dir))) {
+ gs_unref_object NMConnection *connection = NULL;
+ gs_free_error GError *error = NULL;
+ NMSKeyfileStorage *storage;
+ ConnReloadData *storage_data;
+ gs_free char *full_filename = NULL;
+ struct stat st;
+ ConnReloadHead *hd;
+ NMTernary is_nm_generated_opt;
+ NMTernary is_volatile_opt;
+
+ if (!g_hash_table_add (dupl_filenames, g_strdup (filename))) {
+ /* ensure no duplicates. */
+ continue;
+ }
+
+ if (_ignore_filename (storage_type, filename)) {
+ gs_free char *loaded_uuid = NULL;
+ gs_free char *loaded_path = NULL;
+
+ if (!nms_keyfile_loaded_uuid_read (dirname,
+ filename,
+ NULL,
+ &loaded_uuid,
+ &loaded_path)) {
+ _LOGT ("load: \"%s/%s\": skip file due to invalid filename", dirname, filename);
+ continue;
+ }
+ if (!NM_IN_SET (storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN,
+ NMS_KEYFILE_STORAGE_TYPE_ETC)) {
+ _LOGT ("load: \"%s/%s\": skip loaded file from read-only directory", dirname, filename);
+ continue;
+ }
+ storage = _storages_get (self, loaded_uuid, TRUE);
+ hd = _storage_reload_data_head_ensure (storage);
+ if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) {
+ nm_assert (!hd->loaded_path_run);
+ hd->loaded_path_run = g_steal_pointer (&loaded_path);
+ } else {
+ nm_assert (!hd->loaded_path_etc);
+ hd->loaded_path_etc = g_steal_pointer (&loaded_path);
+ }
+ continue;
+ }
+
+ full_filename = g_build_filename (dirname, filename, NULL);
+
+ connection = _read_from_file (full_filename, plugin_dir, &st, &is_nm_generated_opt, &is_volatile_opt, &error);
+ if (!connection) {
+ _LOGW ("load: \"%s\": failed to load connection: %s", full_filename, error->message);
continue;
- g_ptr_array_add (filenames, g_build_filename (path, item, NULL));
+ }
+
+ storage = _storages_get (self, nm_connection_get_uuid (connection), TRUE);
+ hd = _storage_reload_data_head_ensure (storage);
+ storage_data = _conn_reload_data_new (storage_priority,
+ storage_type,
+ g_steal_pointer (&full_filename),
+ g_steal_pointer (&connection),
+ is_nm_generated_opt,
+ is_volatile_opt,
+ &st);
+ c_list_link_tail (&hd->crld_lst_head, &storage_data->crld_lst);
}
+
g_dir_close (dir);
}
+/*****************************************************************************/
static void
-read_connections (NMSettingsPlugin *config)
+reload_connections (NMSettingsPlugin *plugin,
+ NMSettingsPluginConnectionReloadCallback callback,
+ gpointer user_data)
{
- NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (config);
+ NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (plugin);
NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
- GHashTable *alive_connections;
- GHashTableIter iter;
- NMSKeyfileConnection *connection;
- GPtrArray *dead_connections = NULL;
+ CList lst_conn_info_deleted = C_LIST_INIT (lst_conn_info_deleted);
+ const char *plugin_dir = _get_plugin_dir (priv);
+ gs_unref_ptrarray GPtrArray *storages_modified = NULL;
+ NMSKeyfileStorage *storage_safe;
+ NMSKeyfileStorage *storage;
guint i;
- GPtrArray *filenames;
- GHashTable *paths;
- filenames = g_ptr_array_new_with_free_func (g_free);
+#if NM_MORE_ASSERTS
+ c_list_for_each_entry (storage, &priv->storages.lst_head, storage_lst) {
+ nm_assert (!storage->_reload_data_head);
+ nm_assert (NM_IS_CONNECTION (storage->connection_exported));
+ }
+#endif
+
+ storages_modified = g_ptr_array_new_with_free_func (g_object_unref);
+
+ _load_dir (self, 1, NMS_KEYFILE_STORAGE_TYPE_RUN, priv->dirname_run, plugin_dir);
+ _load_dir (self, 2, NMS_KEYFILE_STORAGE_TYPE_ETC, priv->dirname_etc, plugin_dir);
+ for (i = 0; priv->dirname_libs[i]; i++)
+ _load_dir (self, 3 + i, NMS_KEYFILE_STORAGE_TYPE_LIB, priv->dirname_libs[i], plugin_dir);
+
+ /* process the loaded information. */
+ c_list_for_each_entry_safe (storage, storage_safe, &priv->storages.lst_head, storage_lst) {
+ ConnReloadHead *hd;
+ ConnReloadData *rd, *rd_best;
+ gboolean connection_modified;
+ gboolean connection_renamed;
+ gboolean loaded_path_masked = FALSE;
+ const char *loaded_dirname = NULL;
+ gs_free char *loaded_path = NULL;
+
+ hd = storage->_reload_data_head;
+
+ nm_assert (!hd || ( !c_list_is_empty (&hd->crld_lst_head)
+ || hd->loaded_path_run
+ || hd->loaded_path_etc));
+ nm_assert (hd || nms_keyfile_storage_get_filename (storage));
+
+ /* find and steal the loaded-path (if any) */
+ if (hd) {
+ if (hd->loaded_path_run) {
+ if (hd->loaded_path_etc) {
+ gs_free char *f1 = NULL;
+ gs_free char *f2 = NULL;
+
+ _LOGT ("load: \"%s\": shadowed by \"%s\"",
+ (f1 = nms_keyfile_loaded_uuid_filename (priv->dirname_etc, nms_keyfile_storage_get_uuid (storage), FALSE)),
+ (f2 = nms_keyfile_loaded_uuid_filename (priv->dirname_run, nms_keyfile_storage_get_uuid (storage), FALSE)));
+ nm_clear_g_free (&hd->loaded_path_etc);
+ }
+ loaded_dirname = priv->dirname_run;
+ loaded_path = g_steal_pointer (&hd->loaded_path_run);
+ } else if (hd->loaded_path_etc) {
+ loaded_dirname = priv->dirname_etc;
+ loaded_path = g_steal_pointer (&hd->loaded_path_etc);
+ }
+ }
+ nm_assert ((!loaded_path) == (!loaded_dirname));
+
+ /* sort storage datas by priority. */
+ if (hd)
+ c_list_sort (&hd->crld_lst_head, _conn_reload_data_cmp_by_priority, NULL);
+
+ if (loaded_path) {
+ if (nm_streq (loaded_path, NM_KEYFILE_PATH_NMLOADED_NULL)) {
+ loaded_path_masked = TRUE;
+ nm_clear_g_free (&loaded_path);
+ } else if (!_conn_reload_data_prioritize_loaded (&hd->crld_lst_head, loaded_path)) {
+ gs_free char *f1 = NULL;
+
+ _LOGT ("load: \"%s\": ignore invalid target \"%s\"",
+ (f1 = nms_keyfile_loaded_uuid_filename (loaded_dirname, nms_keyfile_storage_get_uuid (storage), FALSE)),
+ loaded_path);
+ nm_clear_g_free (&loaded_path);
+ }
+ }
- _read_dir (filenames, NM_KEYFILE_PATH_NAME_RUN, TRUE);
- _read_dir (filenames, nms_keyfile_utils_get_path (), FALSE);
+ rd_best = hd
+ ? c_list_first_entry (&hd->crld_lst_head, ConnReloadData, crld_lst)
+ : NULL;
+ if ( !rd_best
+ || loaded_path_masked) {
+
+ /* after reload, no file references this profile (or the files are masked from loading
+ * via a symlink to /dev/null). */
+
+ if (_LOGT_ENABLED ()) {
+ gs_free char *f1 = NULL;
+
+ if (!hd) {
+ _LOGT ("load: \"%s\": file no longer exists for profile with UUID \"%s\" (remove profile)",
+ nms_keyfile_storage_get_filename (storage),
+ nms_keyfile_storage_get_uuid (storage));
+ } else if (rd_best) {
+ c_list_for_each_entry (rd, &hd->crld_lst_head, crld_lst) {
+ _LOGT ("load: \"%s\": profile %s masked by \"%s\" file symlinking \"%s\"%s",
+ rd->full_filename,
+ nms_keyfile_storage_get_uuid (storage),
+ f1 ?: (f1 = nms_keyfile_loaded_uuid_filename (loaded_dirname, nms_keyfile_storage_get_uuid (storage), FALSE)),
+ NM_KEYFILE_PATH_NMLOADED_NULL,
+ storage->connection_exported ? " (remove profile)" : "");
+ }
+ } else if (loaded_path_masked) {
+ _LOGT ("load: \"%s\": symlinks \"%s\" to hide UUID \"%s\"%s",
+ (f1 = nms_keyfile_loaded_uuid_filename (loaded_dirname, nms_keyfile_storage_get_uuid (storage), FALSE)),
+ NM_KEYFILE_PATH_NMLOADED_NULL,
+ nms_keyfile_storage_get_uuid (storage),
+ storage->connection_exported ? " (remove profile)" : "");
+ } else {
+ _LOGT ("load: \"%s\": symlinks \"%s\" but there are no profiles with UUID \"%s\"%s",
+ (f1 = nms_keyfile_loaded_uuid_filename (loaded_dirname, nms_keyfile_storage_get_uuid (storage), FALSE)),
+ loaded_path,
+ nms_keyfile_storage_get_uuid (storage),
+ storage->connection_exported ? " (remove profile)" : "");
+ }
+ }
- alive_connections = g_hash_table_new (nm_direct_hash, NULL);
+ if (!storage->connection_exported)
+ _storages_remove (priv, storage, TRUE);
+ else {
+ _storages_remove (priv, storage, FALSE);
+ c_list_link_tail (&lst_conn_info_deleted, &storage->storage_lst);
+ }
+ continue;
+ }
- /* While reloading, we don't replace connections that we already loaded while
- * iterating over the files.
- *
- * To have sensible, reproducible behavior, sort the paths by last modification
- * time preferring older files.
- */
- paths = _paths_from_connections (priv->connections);
- g_ptr_array_sort_with_data (filenames, (GCompareDataFunc) _sort_paths, paths);
- g_hash_table_destroy (paths);
-
- for (i = 0; i < filenames->len; i++) {
- connection = update_connection (self, NULL, filenames->pdata[i], NULL, FALSE, alive_connections, NULL);
- if (connection)
- g_hash_table_add (alive_connections, connection);
- }
- g_ptr_array_free (filenames, TRUE);
-
- g_hash_table_iter_init (&iter, priv->connections);
- while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
- if ( !g_hash_table_contains (alive_connections, connection)
- && nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection))) {
- if (!dead_connections)
- dead_connections = g_ptr_array_new ();
- g_ptr_array_add (dead_connections, connection);
+ c_list_for_each_entry (rd, &hd->crld_lst_head, crld_lst) {
+ if (rd_best != rd) {
+ _LOGT ("load: \"%s\": profile %s shadowed by \"%s\" file",
+ rd->full_filename,
+ nms_keyfile_storage_get_uuid (storage),
+ rd_best->full_filename);
+ }
+ }
+
+ connection_modified = !_storage_has_equal_connection (storage, rd_best->connection);
+ connection_renamed = !nms_keyfile_storage_get_filename (storage)
+ || !nm_streq (nms_keyfile_storage_get_filename (storage), rd_best->full_filename);
+
+ {
+ gs_free char *f1 = NULL;
+
+ _LOGT ("load: \"%s\": profile %s (%s) loaded (%s)"
+ "%s%s%s"
+ "%s%s%s",
+ rd_best->full_filename,
+ nms_keyfile_storage_get_uuid (storage),
+ nm_connection_get_id (rd_best->connection),
+ connection_modified ? (nms_keyfile_storage_get_filename (storage) ? "updated" : "added" ) : "unchanged",
+ NM_PRINT_FMT_QUOTED (loaded_path,
+ " (hinted by \"",
+ (f1 = nms_keyfile_loaded_uuid_filename (loaded_dirname, nms_keyfile_storage_get_uuid (storage), FALSE)),
+ "\")",
+ ""),
+ NM_PRINT_FMT_QUOTED (connection_renamed && nms_keyfile_storage_get_filename (storage),
+ " (renamed from \"",
+ nms_keyfile_storage_get_filename (storage),
+ "\")",
+ ""));
}
+
+ _storage_set_type (storage, rd_best->storage_type, rd_best->is_nm_generated_opt, rd_best->is_volatile_opt);
+
+ if (connection_modified)
+ nm_g_object_ref_set (&storage->connection_exported, rd_best->connection);
+
+ _storages_set_full_filename (priv,
+ storage,
+ g_steal_pointer (&rd_best->full_filename));
+
+ /* we don't need the reload data anymore. Drop it. */
+ _storage_reload_data_head_clear (storage);
+
+ g_ptr_array_add (storages_modified, g_object_ref (storage));
}
- g_hash_table_destroy (alive_connections);
- if (dead_connections) {
- for (i = 0; i < dead_connections->len; i++)
- remove_connection (self, dead_connections->pdata[i]);
- g_ptr_array_free (dead_connections, TRUE);
+ /* raise events. */
+
+ c_list_for_each_entry_safe (storage, storage_safe, &lst_conn_info_deleted, storage_lst) {
+ callback (NM_SETTINGS_PLUGIN (self),
+ NM_SETTINGS_STORAGE (storage),
+ NULL,
+ user_data);
+ _storage_destroy (storage);
}
-}
-/*****************************************************************************/
+ for (i = 0; i < storages_modified->len; i++) {
+ storage = storages_modified->pdata[i];
-static GSList *
-get_connections (NMSettingsPlugin *config)
-{
- NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) config);
+ if (storage != _storages_get (self, nms_keyfile_storage_get_uuid (storage), FALSE)) {
+ /* 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;
+ }
- if (!priv->initialized) {
- read_connections (config);
- priv->initialized = TRUE;
+ nm_assert (NM_IS_CONNECTION (storage->connection_exported));
+
+ callback (NM_SETTINGS_PLUGIN (self),
+ NM_SETTINGS_STORAGE (storage),
+ storage->connection_exported,
+ user_data);
}
- return _nm_utils_hash_values_to_slist (priv->connections);
}
static gboolean
-load_connection (NMSettingsPlugin *config,
- const char *filename)
-{
- NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN ((NMSKeyfilePlugin *) config);
- NMSKeyfileConnection *connection;
- gboolean require_extension;
-
- if (nm_utils_file_is_in_path (filename, nms_keyfile_utils_get_path ()))
- require_extension = FALSE;
- else if (nm_utils_file_is_in_path (filename, NM_KEYFILE_PATH_NAME_RUN))
- require_extension = TRUE;
- else
+load_connection (NMSettingsPlugin *plugin,
+ const char *full_filename,
+ NMSettingsStorage **out_storage,
+ NMConnection **out_connection,
+ NMSettingsStorage **out_storage_replaced,
+ GError **error)
+{
+ NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (plugin);
+ NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+ NMSKeyfileStorageType storage_type;
+ const char *f_filename;
+ const char *f_dirname;
+ const char *uuid;
+ NMSKeyfileStorage *storage_by_filename;
+ NMSKeyfileStorage *storage = NULL;
+ gs_unref_object NMConnection *connection_new = NULL;
+ gboolean connection_modified;
+ gboolean connection_renamed;
+ gs_free_error GError *local = NULL;
+ gboolean loaded_uuid_success;
+ gs_free char *loaded_uuid_filename = NULL;
+ NMTernary is_nm_generated_opt;
+ NMTernary is_volatile_opt;
+
+ nm_assert (out_storage && !*out_storage);
+ nm_assert (out_connection && !*out_connection);
+ nm_assert (out_storage_replaced && !*out_storage_replaced);
+
+ if (!_path_detect_storage_type (full_filename,
+ (const char *const*) priv->dirname_libs,
+ priv->dirname_etc,
+ priv->dirname_run,
+ &storage_type,
+ &f_dirname,
+ &f_filename,
+ error))
return FALSE;
- if (nm_keyfile_utils_ignore_filename (filename, require_extension))
+ storage_by_filename = g_hash_table_lookup (priv->storages.filename_idx, full_filename);
+
+ connection_new = _read_from_file (full_filename, _get_plugin_dir (priv), NULL, &is_nm_generated_opt, &is_volatile_opt, &local);
+
+ if (!connection_new) {
+ struct stat st;
+
+ if ( storage_by_filename
+ && stat (full_filename, &st) != 0
+ && errno == ENOENT) {
+
+ /* If the file no longer exists, we accept that as command to remove the connection.
+ *
+ * That goes in line with reloading ifcfg-file files that now have NM_CONTROLLED=no.
+ * That will unload the ifcfg file. Likewise, removing the keyfile and loading
+ * the removed path will drop the connection as well.
+ *
+ * In other cases, a loading error will not unload an existing connection. Imagine
+ * you edit the file and have a typo. Then load will simply fail and not removing
+ * the profile. */
+
+ *out_storage_replaced = g_object_ref (NM_SETTINGS_STORAGE (storage_by_filename));
+
+ _LOGT ("load: \"%s\": unload previously loaded connection %s (file no longer exists)",
+ full_filename,
+ nms_keyfile_storage_get_uuid (storage_by_filename));
+
+ _storages_remove (priv, storage_by_filename, TRUE);
+
+ /* Despite we did not *load* anything, we successfully *unloaded*. Return success. */
+ return TRUE;
+ }
+
+ _LOGT ("load: \"%s\": failed to load connection: %s", full_filename, local->message);
+ g_propagate_error (error, g_steal_pointer (&local));
return FALSE;
+ }
- connection = update_connection (self, NULL, filename, find_by_path (self, filename), TRUE, NULL, NULL);
+ uuid = nm_connection_get_uuid (connection_new);
+
+ if ( storage_by_filename
+ && nm_streq (uuid, nms_keyfile_storage_get_uuid (storage_by_filename))) {
+ nm_assert (storage_by_filename == _storages_get (self, uuid, FALSE));
+ storage = g_steal_pointer (&storage_by_filename);
+ } else
+ storage = _storages_get (self, uuid, TRUE);
+
+ connection_modified = !_storage_has_equal_connection (storage, connection_new);
+ connection_renamed = !nms_keyfile_storage_get_filename (storage)
+ || !nm_streq (nms_keyfile_storage_get_filename (storage), full_filename);
+
+ /* FIXME(settings-rework): we now always mark the loaded profile via a symlink
+ * and even for deleted profiles, we remember that they got deleted via a symlink
+ * to /dev/null.
+ * The problem is that these symlinks don't get garbage collected. It'complicated
+ * to understand when a symlink is really no longer needed and when no other profile
+ * references it.
+ *
+ * I think the only solution here is to periodically load all files (temporarily,
+ * without actually using them) to find the existing files and UUIDs.
+ * And then prune the symlinks for UUIDs that are no longer in use.
+ *
+ * This periodic cleanup should also be used to prune /var/lib/NetworkManager/timestamps
+ * and /var/lib/NetworkManager/seen-bssids. */
+ loaded_uuid_success = nms_keyfile_loaded_uuid_write (priv->dirname_run,
+ uuid,
+ full_filename,
+ TRUE,
+ &loaded_uuid_filename);
+
+ _LOGT ("load: \"%s/%s\": profile %s (%s) loaded (%s)"
+ "%s%s%s"
+ "%s%s%s"
+ " (%s%s%s)",
+ f_dirname,
+ f_filename,
+ nms_keyfile_storage_get_uuid (storage),
+ nm_connection_get_id (connection_new),
+ connection_modified ? (storage->connection_exported ? "updated" : "added" ) : "unchanged",
+ NM_PRINT_FMT_QUOTED (connection_renamed && nms_keyfile_storage_get_filename (storage),
+ " (renamed from \"",
+ nms_keyfile_storage_get_filename (storage),
+ "\")",
+ ""),
+ NM_PRINT_FMT_QUOTED (storage_by_filename,
+ " (replaces ",
+ nms_keyfile_storage_get_uuid (storage_by_filename),
+ ")",
+ ""),
+ NM_PRINT_FMT_QUOTED (loaded_uuid_success,
+ "marked as preferred by \"",
+ loaded_uuid_filename,
+ "\"",
+ "failed to write loaded file"));
+
+ _storage_set_type (storage, storage_type, is_nm_generated_opt, is_volatile_opt);
+
+ if (connection_renamed) {
+ _storages_set_full_filename (priv,
+ storage,
+ g_build_filename (f_dirname, f_filename, NULL));
+ }
- return (connection != NULL);
+ if (connection_modified)
+ nm_g_object_ref_set (&storage->connection_exported, connection_new);
+
+ *out_storage = g_object_ref (NM_SETTINGS_STORAGE (storage));
+ *out_connection = g_object_ref (storage->connection_exported);
+ *out_storage_replaced = nm_g_object_ref (NM_SETTINGS_STORAGE (storage_by_filename));
+
+ return TRUE;
}
-static void
-reload_connections (NMSettingsPlugin *config)
+gboolean
+nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
+ NMConnection *connection,
+ gboolean is_nm_generated,
+ gboolean is_volatile,
+ gboolean in_memory,
+ NMSettingsStorage **out_storage,
+ NMConnection **out_connection,
+ GError **error)
{
- read_connections (config);
+ NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+ gs_unref_object NMConnection *reread = NULL;
+ gs_free char *loaded_uuid_filename = NULL;
+ gs_free char *full_filename = NULL;
+ NMSKeyfileStorageType storage_type;
+ gboolean loaded_uuid_success;
+ NMSKeyfileStorage *storage;
+ GError *local = NULL;
+ const char *uuid;
+
+ nm_assert (NM_IS_CONNECTION (connection));
+ nm_assert (out_storage && !*out_storage);
+ nm_assert (out_connection && !*out_connection);
+
+ uuid = nm_connection_get_uuid (connection);
+
+ if (_storages_get (self, uuid, FALSE)) {
+ _LOGT ("add: %s, \"%s\": failed to add connection that already exists",
+ uuid,
+ nm_connection_get_id (connection));
+ g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+ "connection with UUID %s already exists",
+ uuid);
+ return FALSE;
+ }
+
+ storage_type = !in_memory && priv->dirname_etc
+ ? NMS_KEYFILE_STORAGE_TYPE_ETC
+ : NMS_KEYFILE_STORAGE_TYPE_RUN;
+
+ if (!nms_keyfile_writer_connection (connection,
+ is_nm_generated,
+ is_volatile,
+ storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC
+ ? priv->dirname_etc
+ : priv->dirname_run,
+ _get_plugin_dir (priv),
+ NULL,
+ FALSE,
+ FALSE,
+ _allow_filename_cb,
+ ALLOW_FILENAME_DATA (priv->storages.filename_idx, NULL),
+ &full_filename,
+ &reread,
+ NULL,
+ &local)) {
+ _LOGT ("add: %s, \"%s\": failed to add connection: %s",
+ nm_connection_get_uuid (connection),
+ nm_connection_get_id (connection),
+ local->message);
+ g_propagate_error (error, local);
+ return FALSE;
+ }
+
+ nm_assert (_nm_connection_verify (reread, NULL) == NM_SETTING_VERIFY_SUCCESS);
+ nm_assert (nm_streq0 (nm_connection_get_uuid (connection), nm_connection_get_uuid (reread)));
+
+ uuid = nm_connection_get_uuid (reread);
+
+ nm_assert (full_filename && full_filename[0] == '/');
+ nm_assert (!_storages_get (self, uuid, FALSE));
+ nm_assert (!g_hash_table_contains (priv->storages.filename_idx, full_filename));
+
+ loaded_uuid_success = nms_keyfile_loaded_uuid_write (priv->dirname_run,
+ uuid,
+ full_filename,
+ TRUE,
+ &loaded_uuid_filename);
+
+ _LOGT ("add: \"%s\": profile %s (%s) written"
+ " (%s%s%s)",
+ full_filename,
+ uuid,
+ nm_connection_get_id (connection),
+ NM_PRINT_FMT_QUOTED (loaded_uuid_success,
+ "indicated by \"",
+ loaded_uuid_filename,
+ "\"",
+ "failed to write loaded file"));
+
+ storage = _storages_get (self, uuid, TRUE);
+
+ nm_assert (!storage->connection_exported);
+
+ _storage_set_type (storage, storage_type, is_nm_generated, is_volatile);
+
+ storage->connection_exported = g_object_ref (reread);
+
+ _storages_set_full_filename (priv,
+ storage,
+ g_steal_pointer (&full_filename));
+
+ *out_storage = g_object_ref (NM_SETTINGS_STORAGE (storage));
+ *out_connection = g_steal_pointer (&reread);
+
+ return TRUE;
}
-static NMSettingsConnection *
-add_connection (NMSettingsPlugin *config,
+static gboolean
+add_connection (NMSettingsPlugin *plugin,
NMConnection *connection,
- gboolean save_to_disk,
+ NMSettingsStorage **out_storage,
+ NMConnection **out_connection,
GError **error)
{
- NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (config);
- gs_free char *path = NULL;
+ return nms_keyfile_plugin_add_connection (NMS_KEYFILE_PLUGIN (plugin),
+ connection,
+ FALSE,
+ FALSE,
+ FALSE,
+ out_storage,
+ out_connection,
+ error);
+}
+
+gboolean
+nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
+ NMSettingsStorage *storage_x,
+ NMConnection *connection,
+ gboolean is_nm_generated,
+ gboolean is_volatile,
+ gboolean force_rename,
+ NMSettingsStorage **out_storage,
+ NMConnection **out_connection,
+ GError **error)
+{
+ NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+ NMSKeyfileStorage *storage = NMS_KEYFILE_STORAGE (storage_x);
+ gs_unref_object NMConnection *connection_clone = NULL;
gs_unref_object NMConnection *reread = NULL;
+ const char *previous_filename = NULL;
+ const char *uuid;
+ gs_free char *full_filename = NULL;
+ gboolean loaded_uuid_success;
+ gs_free char *loaded_uuid_filename = NULL;
+ NMSKeyfileStorageType storage_type;
+ gboolean connection_modified;
+ gboolean connection_renamed;
+ GError *local = NULL;
+
+ _nm_assert_storage (self, storage, TRUE);
+ nm_assert (NM_IS_CONNECTION (connection));
+ nm_assert (nm_connection_verify (connection, NULL));
+ nm_assert (!error || !*error);
+ nm_assert (nm_streq (nms_keyfile_storage_get_uuid (storage), nm_connection_get_uuid (connection)));
+ nm_assert ( storage->storage_type_exported == NMS_KEYFILE_STORAGE_TYPE_RUN
+ || ( !is_nm_generated
+ && !is_volatile));
+
+ if (!priv->dirname_etc)
+ storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN;
+ else {
+ storage_type = storage->storage_type_exported;
+ if (storage_type == NMS_KEYFILE_STORAGE_TYPE_LIB)
+ storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC;
+ }
+
+ previous_filename = nms_keyfile_storage_get_filename (storage);
+ uuid = nms_keyfile_storage_get_uuid (storage);
if (!nms_keyfile_writer_connection (connection,
- save_to_disk,
- NULL,
- FALSE,
- NULL,
- NULL,
- &path,
+ is_nm_generated,
+ is_volatile,
+ storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC
+ ? priv->dirname_etc
+ : priv->dirname_run,
+ _get_plugin_dir (priv),
+ previous_filename,
+ (storage_type != storage->storage_type_exported),
+ force_rename,
+ _allow_filename_cb,
+ ALLOW_FILENAME_DATA (priv->storages.filename_idx, previous_filename),
+ &full_filename,
&reread,
NULL,
- error))
- return NULL;
+ &local)) {
+ _LOGW ("commit: failure to write %s (%s) to disk: %s",
+ uuid,
+ nm_connection_get_id (connection_clone),
+ local->message);
+ g_propagate_error (error, local);
+ return FALSE;
+ }
+
+ loaded_uuid_success = nms_keyfile_loaded_uuid_write (priv->dirname_run,
+ uuid,
+ full_filename,
+ TRUE,
+ &loaded_uuid_filename);
+
+ connection_modified = !_storage_has_equal_connection (storage, connection);
+ connection_renamed = !nm_streq (previous_filename, full_filename);
+
+ _LOGT ("commit: \"%s\": profile %s (%s) written%s"
+ "%s%s%s"
+ " (%s%s%s)",
+ full_filename,
+ uuid,
+ nm_connection_get_id (connection),
+ connection_modified ? " (modified)" : "",
+ NM_PRINT_FMT_QUOTED (connection_renamed,
+ " (renamed from \"",
+ previous_filename,
+ "\")",
+ ""),
+ NM_PRINT_FMT_QUOTED (loaded_uuid_success,
+ "indicated by \"",
+ loaded_uuid_filename,
+ "\"",
+ "failed to write loaded file"));
+
+ storage->storage_type_exported = storage_type;
+
+ if (connection_renamed) {
+ _storages_set_full_filename (priv,
+ storage,
+ g_steal_pointer (&full_filename));
+ }
+
+ if (connection_modified)
+ nm_g_object_ref_set (&storage->connection_exported, connection);
+
+ *out_storage = g_object_ref (NM_SETTINGS_STORAGE (storage));
+ *out_connection = g_object_ref (storage->connection_exported);
+ return TRUE;
+}
+
+static gboolean
+update_connection (NMSettingsPlugin *plugin,
+ NMSettingsStorage *storage,
+ NMConnection *connection,
+ NMSettingsStorage **out_storage,
+ NMConnection **out_connection,
+ GError **error)
+{
+ return nms_keyfile_plugin_update_connection (NMS_KEYFILE_PLUGIN (plugin),
+ storage,
+ connection,
+ FALSE,
+ FALSE,
+ FALSE,
+ out_storage,
+ out_connection,
+ error);
+}
+
+static gboolean
+delete_connection (NMSettingsPlugin *plugin,
+ NMSettingsStorage *storage_x,
+ gboolean remove_from_disk,
+ GError **error)
+{
+ NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (plugin);
+ NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+ gs_unref_object NMSKeyfileStorage *storage = g_object_ref (NMS_KEYFILE_STORAGE (storage_x));
+ gboolean loaded_uuid_success;
+ gs_free char *loaded_uuid_filename = NULL;
+ const char *remove_from_disk_errmsg = NULL;
+ const char *operation_message;
+ const char *previous_filename;
+ const char *uuid;
+
+ _nm_assert_storage (self, storage, TRUE);
+ nm_assert (!error || !*error);
+
+ previous_filename = nms_keyfile_storage_get_filename (storage);
+ uuid = nms_keyfile_storage_get_uuid (storage);
+
+ if (!remove_from_disk)
+ operation_message = "only dropped from memory";
+ else if (!NM_IN_SET (storage->storage_type_exported, NMS_KEYFILE_STORAGE_TYPE_ETC,
+ NMS_KEYFILE_STORAGE_TYPE_RUN))
+ operation_message = "dropped readonly file from memory";
+ else if (unlink (previous_filename) != 0) {
+ int errsv;
+
+ errsv = errno;
+ if (errsv != ENOENT) {
+ remove_from_disk_errmsg = nm_strerror_native (errsv);
+ operation_message = "failed to delete from disk";
+ } else
+ operation_message = "does not exist on disk";
+ } else
+ operation_message = "deleted from disk";
+
+ loaded_uuid_success = nms_keyfile_loaded_uuid_write (priv->dirname_run,
+ uuid,
+ NM_KEYFILE_PATH_NMLOADED_NULL,
+ FALSE,
+ &loaded_uuid_filename);
+
+ _LOGT ("delete: %s: profile %s %s"
+ "%s%s%s"
+ " (%s%s%s)",
+ previous_filename,
+ uuid,
+ operation_message,
+ NM_PRINT_FMT_QUOTED (remove_from_disk_errmsg, " (", remove_from_disk_errmsg, ")", ""),
+ NM_PRINT_FMT_QUOTED (loaded_uuid_success,
+ "indicated by \"",
+ loaded_uuid_filename,
+ "\"",
+ "failed to write loaded file"));
+
+ _storages_remove (priv, storage, TRUE);
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+config_changed_cb (NMConfig *config,
+ NMConfigData *config_data,
+ NMConfigChangeFlags changes,
+ NMConfigData *old_data,
+ NMSKeyfilePlugin *self)
+{
+ gs_free char *old_value = NULL;
+ gs_free char *new_value = NULL;
- return NM_SETTINGS_CONNECTION (update_connection (self, reread ?: connection, path, NULL, FALSE, NULL, error));
+ old_value = nm_config_data_get_value (old_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
+ new_value = nm_config_data_get_value (config_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
+
+ if (!nm_streq0 (old_value, new_value))
+ _nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
}
static GSList *
get_unmanaged_specs (NMSettingsPlugin *config)
{
- NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) config);
+ NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (config);
gs_free char *value = NULL;
value = nm_config_data_get_value (nm_config_get_data (priv->config),
@@ -517,7 +1388,46 @@ nms_keyfile_plugin_init (NMSKeyfilePlugin *plugin)
NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin);
priv->config = g_object_ref (nm_config_get ());
- priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
+
+ c_list_init (&priv->storages.lst_head);
+ priv->storages.filename_idx = g_hash_table_new (nm_str_hash, g_str_equal);
+ priv->storages.idx = g_hash_table_new_full (nm_str_hash,
+ g_str_equal,
+ NULL,
+ (GDestroyNotify) _storage_destroy);
+
+ /* dirname_libs are a set of read-only directories with lower priority than /etc or /run.
+ * There is nothing complicated about having multiple of such directories, so dirname_libs
+ * is a list (which currently only has at most one directory). */
+ priv->dirname_libs[0] = nm_sd_utils_path_simplify (g_strdup (NM_KEYFILE_PATH_NAME_LIB), FALSE);
+ priv->dirname_libs[1] = NULL;
+ priv->dirname_run = nm_sd_utils_path_simplify (g_strdup (NM_KEYFILE_PATH_NAME_RUN), FALSE);
+ priv->dirname_etc = nm_config_data_get_value (NM_CONFIG_GET_DATA_ORIG,
+ NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+ NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
+ NM_CONFIG_GET_VALUE_STRIP);
+ if (priv->dirname_etc && priv->dirname_etc[0] == '\0') {
+ /* special case: configure an empty keyfile path so that NM has no writable keyfile
+ * directory. In this case, NM will only honor dirname_libs and dirname_run, meaning
+ * it cannot persist profile to non-volatile memory. */
+ nm_clear_g_free (&priv->dirname_etc);
+ } else if (!priv->dirname_etc || priv->dirname_etc[0] != '/') {
+ /* either invalid path or unspecified. Use the default. */
+ g_free (priv->dirname_etc);
+ priv->dirname_etc = nm_sd_utils_path_simplify (g_strdup (NM_KEYFILE_PATH_NAME_ETC_DEFAULT), FALSE);
+ } else
+ nm_sd_utils_path_simplify (priv->dirname_etc, FALSE);
+
+ /* no duplicates */
+ if (NM_IN_STRSET (priv->dirname_libs[0], priv->dirname_etc,
+ priv->dirname_run))
+ nm_clear_g_free (&priv->dirname_libs[0]);
+ if (NM_IN_STRSET (priv->dirname_etc, priv->dirname_run))
+ nm_clear_g_free (&priv->dirname_etc);
+
+ nm_assert (!priv->dirname_libs[0] || priv->dirname_libs[0][0] == '/');
+ nm_assert (!priv->dirname_etc || priv->dirname_etc[0] == '/');
+ nm_assert ( priv->dirname_run && priv->dirname_run[0] == '/');
}
static void
@@ -555,17 +1465,20 @@ nms_keyfile_plugin_new (void)
static void
dispose (GObject *object)
{
- NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) object);
-
- if (priv->connections) {
- g_hash_table_destroy (priv->connections);
- priv->connections = NULL;
- }
+ NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (object);
+ NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
- if (priv->config) {
+ if (priv->config)
g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, object);
- g_clear_object (&priv->config);
- }
+
+ nm_clear_pointer (&priv->storages.filename_idx, g_hash_table_destroy);
+ nm_clear_pointer (&priv->storages.idx, g_hash_table_destroy);
+
+ nm_clear_g_free (&priv->dirname_libs[0]);
+ nm_clear_g_free (&priv->dirname_etc);
+ nm_clear_g_free (&priv->dirname_run);
+
+ g_clear_object (&priv->config);
G_OBJECT_CLASS (nms_keyfile_plugin_parent_class)->dispose (object);
}
@@ -579,9 +1492,11 @@ nms_keyfile_plugin_class_init (NMSKeyfilePluginClass *klass)
object_class->constructed = constructed;
object_class->dispose = dispose;
- plugin_class->get_connections = get_connections;
- plugin_class->load_connection = load_connection;
+ plugin_class->plugin_name = "keyfile";
+ plugin_class->get_unmanaged_specs = get_unmanaged_specs;
plugin_class->reload_connections = reload_connections;
+ plugin_class->load_connection = load_connection;
plugin_class->add_connection = add_connection;
- plugin_class->get_unmanaged_specs = get_unmanaged_specs;
+ plugin_class->update_connection = update_connection;
+ plugin_class->delete_connection = delete_connection;
}