diff options
author | Thomas Haller <thaller@redhat.com> | 2019-07-26 09:03:02 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2019-07-26 09:03:02 +0200 |
commit | 7bd16e85e3b9e0e5f827272e5ed6e3769445e7ea (patch) | |
tree | b77303cc9da9435b4348c5725b562753f5e70a6f | |
parent | 40355d03a49920b4c87119d306d7216ee0990b41 (diff) | |
parent | df252a620d570ac85dec40c68aac36fc380762a7 (diff) | |
download | NetworkManager-7bd16e85e3b9e0e5f827272e5ed6e3769445e7ea.tar.gz |
settings: merge branch 'th/settings-shadowed-storage'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/merge_requests/211
23 files changed, 1384 insertions, 459 deletions
diff --git a/Makefile.examples b/Makefile.examples index dcd1ac9d18..385a8353ed 100644 --- a/Makefile.examples +++ b/Makefile.examples @@ -178,6 +178,7 @@ EXTRA_DIST += \ examples/python/gi/list-connections.py \ examples/python/gi/nm-add-connection2.py \ examples/python/gi/nm-connection-update-stable-id.py \ + examples/python/gi/nm-update2.py \ examples/python/gi/nm-wg-set \ examples/python/gi/setting-user-data.py \ examples/python/gi/show-wifi-networks.py \ diff --git a/examples/python/gi/nm-update2.py b/examples/python/gi/nm-update2.py new file mode 100755 index 0000000000..36449fd893 --- /dev/null +++ b/examples/python/gi/nm-update2.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright 2019 Red Hat, Inc. +# + +import sys +import re + +import gi +gi.require_version('NM', '1.0') +from gi.repository import GLib, NM + +def find_connections(nm_client, arg_type, arg_id): + for c in nm_client.get_connections(): + if arg_type in [None, 'id'] and c.get_id() == arg_id: + yield c + if arg_type in [None, 'uuid'] and c.get_uuid() == arg_id: + yield c + +def find_connection_first(nm_client, arg_type, arg_id): + for f in find_connections(nm_client, arg_type, arg_id): + return f + +def con_to_str(con): + s_con = con.get_setting_connection() + return '"%s" (%s)' % (s_con.get_id(), s_con.get_uuid()) + +def usage(): + print('Usage: %s [[id] <id>]' % (sys.argv[0])) + print(' %s [[uuid] <uuid>]' % (sys.argv[0])) + return 1 + +def die(msg, print_usage=False): + print(msg) + if print_usage: + usage() + sys.exit(1) + +def main(): + + main_loop = GLib.MainLoop() + + nm_client = NM.Client.new(None) + + arg_mode = None + arg_block_autoconnect = NM.SettingsUpdate2Flags.NONE + arg_volatile = NM.SettingsUpdate2Flags.NONE + arg_no_reapply = NM.SettingsUpdate2Flags.NONE + + cons = [] + + argv = list(sys.argv[1:]) + while argv: + if argv[0] in ['id', 'uuid']: + if cons: + die('cannot specify multiple connections') + if len(argv) < 2: + die('missing argument for "%s" specifier' % (argv[0])) + cons.extend(find_connections(nm_client, argv[0], argv[1])) + if len(cons) == 0: + die('could not find connection for "%s %s"' % (argv[0], argv[1])) + if len(cons) != 1: + die('could not find unique connection for "%s %s"' % (argv[0], argv[1])) + argv = argv[2:] + continue + if argv[0] in ['--block-autoconnect']: + arg_block_autoconnect = NM.SettingsUpdate2Flags.BLOCK_AUTOCONNECT + argv = argv[1:] + continue + if argv[0] in ['--volatile']: + arg_volatile = NM.SettingsUpdate2Flags.VOLATILE + argv = argv[1:] + continue + if argv[0] in ['--no-reapply']: + arg_no_reapply = NM.SettingsUpdate2Flags.NO_REAPPLY + argv = argv[1:] + continue + if argv[0] in ['--to-disk', '--in-memory', '--in-memory-detached', '--in-memory-only']: + if argv[0] == '--to-disk': + v = NM.SettingsUpdate2Flags.TO_DISK + elif argv[0] == '--in-memory': + v = NM.SettingsUpdate2Flags.IN_MEMORY + elif argv[0] == '--in-memory-detached': + v = NM.SettingsUpdate2Flags.IN_MEMORY_DETACHED + elif argv[0] == '--in-memory-only': + v = NM.SettingsUpdate2Flags.IN_MEMORY_ONLY + elif argv[0] == '--keep': + v = NM.SettingsUpdate2Flags.NONE + else: + assert(False) + if arg_mode is not None: + die('duplicate storage modes ("%s")' % (argv[0])) + arg_mode = v + argv = argv[1:] + continue + if cons: + die('unknown argument "%s"' % (argv[0])) + cons.extend(find_connections(nm_client, None, argv[0])) + if len(cons) == 0: + die('could not find connection for "%s"' % (argv[0])) + if len(cons) != 1: + die('could not find unique connection for "%s"' % (argv[0])) + argv = argv[1:] + continue + + if len(cons) != 1: + die('missing connection argument', True) + + con = cons[0] + + con2 = NM.SimpleConnection.new_clone(con) + + result = {} + def _update2_cb(con, async_result, user_data): + try: + r = con.update2_finish(async_result) + except Exception as e: + result['error'] = e + else: + result['result'] = r + main_loop.quit() + + con.update2(con2.to_dbus(NM.ConnectionSerializationFlags.ALL), + (arg_mode if arg_mode is not None else NM.SettingsUpdate2Flags.NONE) + | arg_block_autoconnect + | arg_volatile + | arg_no_reapply, + None, + None, + _update2_cb, + None) + + main_loop.run() + + if 'error' in result: + die('update connection %s failed [%s]: %s' % (con_to_str(con2), ' '.join(sys.argv), result['error'])) + + print('update connection %s succeeded [%s]: %s' % (con_to_str(con2), ' '.join(sys.argv), result['result'])) + +if __name__ == '__main__': + main() diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h index b2569af1ef..35496e82c2 100644 --- a/libnm-core/nm-dbus-interface.h +++ b/libnm-core/nm-dbus-interface.h @@ -1030,26 +1030,31 @@ typedef enum { /*< flags >*/ * NMSettingsUpdate2Flags: * @NM_SETTINGS_UPDATE2_FLAG_NONE: an alias for numeric zero, no flags set. * @NM_SETTINGS_UPDATE2_FLAG_TO_DISK: to persist the connection to disk. - * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY: to make the connection in-memory only. - * If the connection was previously persistent, the corresponding file on disk - * is not deleted but merely the connection is decoupled from the file - * on disk. If you later delete an in-memory connection, the connection - * on disk will be deleted as well. - * Note: with 1.20, this flag is no longer implemented because in-memory connections - * are also persisted under /run. For the moment, this behaves the same as - * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED. - * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED: this is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, - * but if the connection has a corresponding file on disk, the association between - * the connection and the file is forgotten but the file is not modified. - * The difference to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY is if you later - * save the connection again to disk, a new file name will be chosen without - * overwriting the remaining file on disk. Also, if you delete the connection - * later, the file on disk will not be deleted. + * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY: makes the profile in-memory. + * Note that such profiles are stored in keyfile format under /run. + * If the file is already in-memory, the file in /run is updated in-place. + * Otherwise, the previous storage for the profile is left unchanged + * on disk, and the in-memory copy shadows it. + * Note that the original filename of the previous persistent storage (if any) + * is remembered. That means, when later persisting the profile again to disk, + * the file on disk will be overwritten again. + * Likewise, when finally deleting the profile, both the storage from /run + * and persistent storage are deleted (or if the persistent storage does not + * allow deletion, and nmmeta file is written to mark the UUID as deleted). + * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED: this is almost the same as + * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, with one difference: when later deleting + * the profile, the original profile will not be deleted. Instead a nmmeta + * file is written to /run to indicate that the profile is gone. + * Note that if such a nmmeta tombstone file exists and hides a file in persistant + * storage, then when re-adding the profile with the same UUID, then the original + * storage is taken over again. * @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY: this is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, - * but if the connection has a corresponding file on disk, the file on - * disk will be deleted. + * but if the connection has a corresponding file on persistent storage, the file + * will be deleted right away. If the profile is later again persisted to disk, + * a new, unused filename will be chosen. * @NM_SETTINGS_UPDATE2_FLAG_VOLATILE: This can be specified with either - * %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED or %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY. + * %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY, %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED + * or %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY. * After making the connection in-memory only, the connection is marked * as volatile. That means, if the connection is currently not active * it will be deleted right away. Otherwise, it is marked to for deletion diff --git a/libnm-core/nm-keyfile-internal.h b/libnm-core/nm-keyfile-internal.h index e1fb10c2b4..ced4ed975f 100644 --- a/libnm-core/nm-keyfile-internal.h +++ b/libnm-core/nm-keyfile-internal.h @@ -174,6 +174,8 @@ gboolean _nm_keyfile_has_values (GKeyFile *keyfile); #define NM_KEYFILE_GROUP_NMMETA ".nmmeta" #define NM_KEYFILE_KEY_NMMETA_NM_GENERATED "nm-generated" #define NM_KEYFILE_KEY_NMMETA_VOLATILE "volatile" +#define NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE "shadowed-storage" +#define NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED "shadowed-owned" #define NM_KEYFILE_PATH_NAME_LIB NMLIBDIR "/system-connections" #define NM_KEYFILE_PATH_NAME_ETC_DEFAULT NMCONFDIR "/system-connections" diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 04a94db8a1..da34602427 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -12420,7 +12420,7 @@ nm_device_set_ip_config (NMDevice *self, nm_settings_connection_update (settings_connection, new_connection, - NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, NM_SETTINGS_CONNECTION_INT_FLAGS_NONE, NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE, diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c index b696f128a7..5b550ea46d 100644 --- a/src/devices/wifi/nm-iwd-manager.c +++ b/src/devices/wifi/nm-iwd-manager.c @@ -436,7 +436,6 @@ mirror_8021x_connection (NMIwdManager *self, NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_CONNECTION_ID, name, NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_buf (uuid), - NM_SETTING_CONNECTION_READ_ONLY, TRUE, NULL)); nm_connection_add_setting (connection, setting); diff --git a/src/nm-checkpoint.c b/src/nm-checkpoint.c index 1f7723c539..9bc979bb94 100644 --- a/src/nm-checkpoint.c +++ b/src/nm-checkpoint.c @@ -254,7 +254,7 @@ restore_and_activate_connection (NMCheckpoint *self, _LOGD ("rollback: adding connection %s again", nm_connection_get_uuid (dev_checkpoint->settings_connection)); - persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK; + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; if (!nm_settings_add_connection (NM_SETTINGS_GET, dev_checkpoint->settings_connection, persist_mode, diff --git a/src/nm-manager.c b/src/nm-manager.c index 4f114e4d13..6ea535021a 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -5454,7 +5454,7 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj, const char *device_path; const char *specific_object_path; gs_free NMConnection **conns = NULL; - NMSettingsConnectionPersistMode persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK; + NMSettingsConnectionPersistMode persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; gboolean is_volatile = FALSE; gboolean bind_dbus_client = FALSE; AsyncOpType async_op_type; @@ -5485,7 +5485,7 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj, s = g_variant_get_string (option_value, NULL); is_volatile = FALSE; - persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK; + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; if (nm_streq (s, "volatile")) { persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY; diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c index 2093342c9c..25a27e68cc 100644 --- a/src/settings/nm-settings-connection.c +++ b/src/settings/nm-settings-connection.c @@ -44,6 +44,11 @@ #define AUTOCONNECT_RETRIES_FOREVER -1 #define AUTOCONNECT_RESET_RETRIES_TIMER 300 +#define _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES ((NMSettingsUpdate2Flags) ( NM_SETTINGS_UPDATE2_FLAG_TO_DISK \ + | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY \ + | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED \ + | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY)) + /*****************************************************************************/ NMConnection ** @@ -1319,37 +1324,6 @@ auth_start (NMSettingsConnection *self, /**** DBus method handlers ************************************/ -static gboolean -check_writable (NMConnection *self, GError **error) -{ - NMSettingConnection *s_con; - - g_return_val_if_fail (NM_IS_CONNECTION (self), FALSE); - - s_con = nm_connection_get_setting_connection (self); - if (!s_con) { - g_set_error_literal (error, - NM_SETTINGS_ERROR, - NM_SETTINGS_ERROR_INVALID_CONNECTION, - "Connection did not have required 'connection' setting"); - return FALSE; - } - - /* If the connection is read-only, that has to be changed at the source of - * the problem (ex a system settings plugin that can't write connections out) - * instead of over D-Bus. - */ - if (nm_setting_connection_get_read_only (s_con)) { - g_set_error_literal (error, - NM_SETTINGS_ERROR, - NM_SETTINGS_ERROR_READ_ONLY_CONNECTION, - "Connection is read-only"); - return FALSE; - } - - return TRUE; -} - static void get_settings_auth_cb (NMSettingsConnection *self, GDBusMethodInvocation *context, @@ -1504,10 +1478,14 @@ update_auth_cb (NMSettingsConnection *self, } } + nm_assert ( !NM_FLAGS_ANY (info->flags, _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES) + || nm_utils_is_power_of_two (info->flags & _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES)); + if (NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_TO_DISK)) - persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK; - else if (NM_FLAGS_ANY (info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY - | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED)) + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; + else if (NM_FLAGS_ANY (info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY)) + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY; + else if (NM_FLAGS_ANY (info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED)) persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED; else if (NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY)) { persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY; @@ -1594,13 +1572,6 @@ settings_connection_update (NMSettingsConnection *self, UpdateInfo *info; const char *permission; - /* If the connection is read-only, that has to be changed at the source of - * the problem (ex a system settings plugin that can't write connections out) - * instead of over D-Bus. - */ - if (!check_writable (nm_settings_connection_get_connection (self), &error)) - goto error; - /* Check if the settings are valid first */ if (new_settings) { if (!g_variant_is_of_type (new_settings, NM_VARIANT_TYPE_CONNECTION)) { @@ -1725,14 +1696,10 @@ impl_settings_connection_update2 (NMDBusObject *obj, GVariantIter iter; const char *args_name; NMSettingsUpdate2Flags flags; - const NMSettingsUpdate2Flags ALL_PERSIST_MODES = NM_SETTINGS_UPDATE2_FLAG_TO_DISK - | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY - | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED - | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY; g_variant_get (parameters, "(@a{sa{sv}}u@a{sv})", &settings, &flags_u, &args); - if (NM_FLAGS_ANY (flags_u, ~((guint32) ( ALL_PERSIST_MODES + if (NM_FLAGS_ANY (flags_u, ~((guint32) ( _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES | NM_SETTINGS_UPDATE2_FLAG_VOLATILE | NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT | NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY)))) { @@ -1745,11 +1712,12 @@ impl_settings_connection_update2 (NMDBusObject *obj, flags = (NMSettingsUpdate2Flags) flags_u; - if ( ( NM_FLAGS_ANY (flags, ALL_PERSIST_MODES) - && !nm_utils_is_power_of_two (flags & ALL_PERSIST_MODES)) + if ( ( NM_FLAGS_ANY (flags, _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES) + && !nm_utils_is_power_of_two (flags & _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES)) || ( NM_FLAGS_HAS (flags, NM_SETTINGS_UPDATE2_FLAG_VOLATILE) - && !NM_FLAGS_ANY (flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED | - NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY))) { + && !NM_FLAGS_ANY (flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY + | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED + | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY))) { error = g_error_new_literal (NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_ARGUMENTS, "Conflicting flags"); @@ -1831,9 +1799,6 @@ impl_settings_connection_delete (NMDBusObject *obj, nm_assert (nm_settings_connection_still_valid (self)); - if (!check_writable (nm_settings_connection_get_connection (self), &error)) - goto err; - subject = _new_auth_subject (invocation, &error); if (!subject) goto err; diff --git a/src/settings/nm-settings-connection.h b/src/settings/nm-settings-connection.h index ab23dc1ff5..61d5c246c3 100644 --- a/src/settings/nm-settings-connection.h +++ b/src/settings/nm-settings-connection.h @@ -85,21 +85,46 @@ typedef enum { * if the profile is on-disk, update it on-disk, and keep it. */ NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP, - /* persist to disk. If the profile is currenly in-memory, remove - * it from /run. */ - NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK, + /* persist to disk. If the profile is currently in-memory, remove + * it from /run. Depending on the shadowed-storage, the pre-existing + * file is reused when moving the storage. + * + * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_TO_DISK. */ + NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK, + + /* Update in-memory (i.e. persist to /run). If the profile is currently on disk, + * then a reference to the profile is remembered as "shadowed-storage". + * Later, when storing again to persistant storage, the shawowed-storage is + * updated. When deleting the profile, the shadowed-storage is also deleted + * from disk. + * + * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY. */ + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, + + /* Update in-memory (i.e. persist to /run). This is almost like + * %NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, except the in-memory profile + * remembers not to own the shadowed-storage ("shadowed-owned"). + * The diffrence is that when deleting the in-memory profile, the original + * profile is not deleted but instead the nmmeta tombstone remembers the + * shadowed-storage and re-used it when re-adding the profile. + * + * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED. */ + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED, - /* persist to /run (in-memory). If the profile is currently on disk, - * delete it from disk. */ + /* Update in-memory (i.e. persist to /run). If the profile is currently on disk, + * delete it from disk. + * + * If the profile is in-memory and has a shadowed-storage, the original profile + * will be deleted from disk. + * + * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY. */ NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, - /* persist to /run (in-memory). If the profile is currently on disk, - * forget about it, but don't delete it from disk. */ - NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED, - - /* this only updates the connection in-memory. Note that "in-memory" above - * means to write to keyfile in /run. This really means to not notify the - * settings plugin about the change. */ + /* This only updates the connection in-memory. Note that "in-memory" above + * means to write to keyfile in /run. This mode really means to not notify the + * settings plugin about the change. This should be only used for updating + * secrets. + */ NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST, } NMSettingsConnectionPersistMode; diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c index 6961134c6c..430d277693 100644 --- a/src/settings/nm-settings.c +++ b/src/settings/nm-settings.c @@ -168,16 +168,12 @@ nm_assert_storage_data_lst (CList *head) static gboolean _storage_data_is_alive (StorageData *sd) { - if (sd->connection) - return TRUE; - - if (nm_settings_storage_is_keyfile_tombstone (sd->storage)) { - /* entry does not have a profile, but it's here as a tombstone to - * hide/shadow other connections. That's also relevant. */ - return TRUE; - } - - return FALSE; + /* If the storage tracks a connection, it is considered alive. + * + * Meta-data storages are special: they never track a connection. + * We need to check them specially to know when to drop them. */ + return sd->connection + || nm_settings_storage_is_meta_data_alive (sd->storage); } /*****************************************************************************/ @@ -232,6 +228,110 @@ _sett_conn_entry_get_conn (SettConnEntry *sett_conn_entry) return sett_conn_entry ? sett_conn_entry->sett_conn : NULL; } +/** + * _sett_conn_entry_storage_find_conflicting_storage: + * @sett_conn_entry: the list of settings-storages for the given UUID. + * @target_plugin: the settings plugin to check + * @storage_check_including: (allow-none): optionally compare against this storage. + * @plugins: the list of plugins sorted in descending priority. This determines + * the priority and whether a storage conflicts. + * + * If we were to add the a storage to @target_plugin, then this function checks + * whether there are already other storages that would hide the storage after we + * add it. Those conflicting/hiding storages are a problem, because they have higher + * priority, so we cannot add the storage. + * + * @storage_check_including is optional, and if given then it checks whether updating + * the profile in this storage would result in confict. This is the check before + * update-connection. If this parameter is omitted, then it's about what happens + * when adding a new profile (add-connection). + * + * Returns: the conflicting storage or %NULL if there is none. + */ +static NMSettingsStorage * +_sett_conn_entry_storage_find_conflicting_storage (SettConnEntry *sett_conn_entry, + NMSettingsPlugin *target_plugin, + NMSettingsStorage *storage_check_including, + const GSList *plugins) +{ + StorageData *sd; + + if (!sett_conn_entry) + return NULL; + + if ( storage_check_including + && nm_settings_storage_is_keyfile_run (storage_check_including)) { + /* the storage we check against is in-memory. It always has highest + * priority, so there can be no other conflicting storages. */ + return NULL; + } + + /* Finds the first (highest priority) storage that has a connection. + * Note that due to tombstones (that have a high priority), the connection + * may not actually be exposed. This is to find hidden/shadowed storages + * that provide a connection. */ + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + nm_assert (NM_IS_SETTINGS_STORAGE (sd->storage)); + + if (!sd->connection) { + /* We only consider storages with connection. In particular, + * tombstones are not relevant, because we can delete them to + * resolve the conflict. */ + continue; + } + + if (sd->storage == storage_check_including) { + /* ok, the storage is the one we are about to check. All other + * storages are lower priority, so there is no storage that hides + * our storage_check_including. */ + return NULL; + } + + if (nm_settings_plugin_cmp_by_priority (nm_settings_storage_get_plugin (sd->storage), + target_plugin, + plugins) <= 0) { + /* the plugin of the existing storage is less important than @target_plugin. + * We have no conflicting/hiding storage. */ + return NULL; + } + + /* Found. If we would add the profile to @target_plugin, then it would be hidden + * by existing_storage. */ + return sd->storage; + } + + return NULL; +} + +static NMSettingsStorage * +_sett_conn_entry_find_shadowed_storage (SettConnEntry *sett_conn_entry, + const char *shadowed_storage_filename, + NMSettingsStorage *blacklisted_storage) +{ + StorageData *sd; + + if (!shadowed_storage_filename) + return NULL; + + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + + nm_assert (NM_IS_SETTINGS_STORAGE (sd->storage)); + + if (!sd->connection) + continue; + + if (blacklisted_storage == sd->storage) + continue; + + if (!nm_streq0 (nm_settings_storage_get_filename_for_shadowed_storage (sd->storage), shadowed_storage_filename)) + continue; + + return sd->storage; + } + + return NULL; +} + /*****************************************************************************/ NM_GOBJECT_PROPERTIES_DEFINE (NMSettings, @@ -725,23 +825,82 @@ _sett_conn_entries_remove_and_destroy (NMSettings *self, /*****************************************************************************/ static int +_sett_conn_entry_sds_update_cmp_ascending (const StorageData *sd_a, + const StorageData *sd_b, + const GSList *plugins) +{ + const NMSettingsMetaData *meta_data_a; + const NMSettingsMetaData *meta_data_b; + bool is_keyfile_run_a; + bool is_keyfile_run_b; + + /* Sort storages by priority. More important storages are sorted + * higher (ascending sort). For example, if "sd_a" is more important than + * "sd_b" (sd_a>sd_b), a positive integer is returned. */ + + meta_data_a = nm_settings_storage_is_meta_data (sd_a->storage); + meta_data_b = nm_settings_storage_is_meta_data (sd_b->storage); + + /* runtime storages (both connections and meta-data) are always more + * important. */ + is_keyfile_run_a = nm_settings_storage_is_keyfile_run (sd_a->storage); + is_keyfile_run_b = nm_settings_storage_is_keyfile_run (sd_b->storage); + if (is_keyfile_run_a != is_keyfile_run_b) { + + if ( !meta_data_a + && !meta_data_b) { + /* Ok, both are non-meta-data providing actual profiles. But one is in /run and one is in + * another storage. In this case we first honor whether one of the storages is explicitly + * prioritized. The prioritize flag is an in-memory hack to overwrite relative priorities + * contrary to what exists on-disk. + * + * This is done because when we use explicit D-Bus API (like update-connection) + * to update a profile, then we really want to prioritize the candidate + * despite having multiple other profiles. + * + * The example is if you have the same UUID twice in /run (one of them shadowed). + * If you move it to disk, then one of the profiles gets deleted and re-created + * on disk, but that on-disk profile must win against the remainging profile in + * /run. At least until the next reload/restart. */ + NM_CMP_FIELD_UNSAFE (sd_a, sd_b, prioritize); + } + + /* in-memory has higher priority. That is regardless of whether any of + * them is meta-data/tombstone or a profile. + * + * That works, because if any of them are tombstones/metadata, then we are in full + * control. There can by only one meta-data file, which is fully owned (and accordingly + * created/deleted) by NetworkManager. + * + * The only case where this might not be right is if we have profiles + * in /run that are shadowed. When we move such a profile to disk, then + * a conflict might arise. That is handled by "prioritize" above! */ + NM_CMP_DIRECT (is_keyfile_run_a, is_keyfile_run_b); + } + + /* After we determined that both profiles are either in /run or not, + * tombstones are always more important than non-tombstones. */ + NM_CMP_DIRECT (meta_data_a && meta_data_a->is_tombstone, + meta_data_b && meta_data_b->is_tombstone); + + /* Again, prioritized entries are sorted first (higher priority). */ + NM_CMP_FIELD_UNSAFE (sd_a, sd_b, prioritize); + + /* finally, compare the storages. This basically honors the timestamp + * of the profile and the relative order of the source plugin (via the + * @plugins list). */ + return nm_settings_storage_cmp (sd_a->storage, sd_b->storage, plugins); +} + +static int _sett_conn_entry_sds_update_cmp (const CList *ls_a, const CList *ls_b, gconstpointer user_data) { - const GSList *plugins = user_data; - StorageData *sd_a = c_list_entry (ls_a, StorageData, sd_lst); - StorageData *sd_b = c_list_entry (ls_b, StorageData, sd_lst); - - /* prioritized entries are sorted first (higher priority). */ - NM_CMP_FIELD_UNSAFE (sd_b, sd_a, prioritize); - - /* nm_settings_storage_cmp() compares in ascending order. Meaning, - * if the storage has higher priority, it gives a positive number (as one - * would expect). - * - * We want to sort the list in reverse though, with highest priority first. */ - return nm_settings_storage_cmp (sd_b->storage, sd_a->storage, plugins); + /* we sort highest priority storages first (descending). Hence, the order is swapped. */ + return _sett_conn_entry_sds_update_cmp_ascending (c_list_entry (ls_b, StorageData, sd_lst), + c_list_entry (ls_a, StorageData, sd_lst), + user_data); } static void @@ -1192,19 +1351,28 @@ _connection_changed_track (NMSettings *self, if (_LOGT_ENABLED ()) { const char *filename; + const NMSettingsMetaData *meta_data; + const char *shadowed_storage; + gboolean shadowed_owned; filename = nm_settings_storage_get_filename (storage); if (connection) { - _LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event with connection \"%s\"%s%s%s", + shadowed_storage = nm_settings_storage_get_shadowed_storage (storage, &shadowed_owned); + _LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event with connection \"%s\"%s%s%s%s%s%s", sett_conn_entry->uuid, NM_SETTINGS_STORAGE_PRINT_ARG (storage), nm_connection_get_id (connection), - NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", "")); - } else if (nm_settings_storage_is_keyfile_tombstone (storage)) { - _LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event for hiding profile%s%s%s", + NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""), + NM_PRINT_FMT_QUOTED (shadowed_storage, shadowed_owned ? " (owns \"" : " (shadows \"", shadowed_storage, "\")", "")); + } else if ((meta_data = nm_settings_storage_is_meta_data (storage))) { + nm_assert (meta_data->is_tombstone); + shadowed_storage = nm_settings_storage_get_shadowed_storage (storage, &shadowed_owned); + _LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event for %shiding profile%s%s%s%s%s%s", sett_conn_entry->uuid, NM_SETTINGS_STORAGE_PRINT_ARG (storage), - NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", "")); + nm_settings_storage_is_meta_data_alive (storage) ? "" : "dropping ", + NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""), + NM_PRINT_FMT_QUOTED (shadowed_storage, shadowed_owned ? " (owns \"" : " (shadows \"", shadowed_storage, "\")", "")); } else { _LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event for dropping profile%s%s%s", sett_conn_entry->uuid, @@ -1276,10 +1444,12 @@ _plugin_connections_reload (NMSettings *self) static gboolean _add_connection_to_first_plugin (NMSettings *self, + SettConnEntry *sett_conn_entry, NMConnection *new_connection, gboolean in_memory, - gboolean is_nm_generated, - gboolean is_volatile, + NMSettingsConnectionIntFlags sett_flags, + const char *shadowed_storage, + gboolean shadowed_owned, NMSettingsStorage **out_new_storage, NMConnection **out_new_connection, GError **error) @@ -1304,20 +1474,44 @@ _add_connection_to_first_plugin (NMSettings *self, gboolean success; const char *filename; + if (!in_memory) { + NMSettingsStorage *conflicting_storage; + + conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage (sett_conn_entry, plugin, NULL, priv->plugins); + if (conflicting_storage) { + /* we have a connection provided by a plugin with higher priority than the one + * we would want to add the connection. We cannot do that, because doing so + * would result in adding a connection that gets hidden by the existing profile. + * Also, since we test the plugins in order of priority, all following plugins + * are unsuitable. + * + * Multiple connection plugins are so cumbersome, especially if they are unable + * to add the connection. I suggest to disable all plugins except keyfile. */ + _LOGT ("add-connection: failed to add %s/'%s': there is an existing storage "NM_SETTINGS_STORAGE_PRINT_FMT" with higher priority", + nm_connection_get_uuid (new_connection), + nm_connection_get_id (new_connection), + NM_SETTINGS_STORAGE_PRINT_ARG (conflicting_storage)); + nm_assert (first_error); + break; + } + } + if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) { success = nms_keyfile_plugin_add_connection (priv->keyfile_plugin, new_connection, - is_nm_generated, - is_volatile, in_memory, + NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED), + NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE), + shadowed_storage, + shadowed_owned, &storage, &connection_to_add, &add_error); } else { if (in_memory) continue; - nm_assert (!is_nm_generated); - nm_assert (!is_volatile); + nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)); + nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)); success = nm_settings_plugin_add_connection (plugin, new_connection, &storage, @@ -1371,6 +1565,97 @@ _add_connection_to_first_plugin (NMSettings *self, return FALSE; } +static gboolean +_update_connection_to_plugin (NMSettings *self, + NMSettingsStorage *storage, + NMConnection *connection, + NMSettingsConnectionIntFlags sett_flags, + gboolean force_rename, + const char *shadowed_storage, + gboolean shadowed_owned, + NMSettingsStorage **out_new_storage, + NMConnection **out_new_connection, + GError **error) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self); + NMSettingsPlugin *plugin; + gboolean success; + + plugin = nm_settings_storage_get_plugin (storage); + + if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) { + success = nms_keyfile_plugin_update_connection (priv->keyfile_plugin, + storage, + connection, + NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED), + NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE), + shadowed_storage, + shadowed_owned, + force_rename, + out_new_storage, + out_new_connection, + error); + } else { + nm_assert (!shadowed_storage); + nm_assert (!shadowed_owned); + success = nm_settings_plugin_update_connection (plugin, + storage, + connection, + out_new_storage, + out_new_connection, + error); + } + + return success; +} + +static void +_set_nmmeta_tombstone (NMSettings *self, + const char *uuid, + gboolean tombstone_on_disk, + gboolean tombstone_in_memory, + const char *shadowed_storage) +{ + NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self); + gs_unref_object NMSettingsStorage *tombstone_1_storage = NULL; + gs_unref_object NMSettingsStorage *tombstone_2_storage = NULL; + + if (tombstone_on_disk) { + if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin, + FALSE, + uuid, + FALSE, + TRUE, + NULL, + &tombstone_1_storage, + NULL)) + tombstone_in_memory = TRUE; + if (tombstone_1_storage) + _connection_changed_track (self, tombstone_1_storage, NULL, FALSE); + } + + if (tombstone_in_memory) { + if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin, + FALSE, + uuid, + TRUE, + TRUE, + shadowed_storage, + &tombstone_2_storage, + NULL)) { + nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin, + TRUE, + uuid, + TRUE, + TRUE, + shadowed_storage, + &tombstone_2_storage, + NULL); + } + _connection_changed_track (self, tombstone_2_storage, NULL, FALSE); + } +} + /** * nm_settings_add_connection: * @self: the #NMSettings object @@ -1396,24 +1681,34 @@ nm_settings_add_connection (NMSettings *self, NMSettingsConnection **out_sett_conn, GError **error) { + NMSettingsPrivate *priv; gs_unref_object NMConnection *connection_cloned_1 = NULL; gs_unref_object NMConnection *new_connection = NULL; gs_unref_object NMSettingsStorage *new_storage = NULL; + gs_unref_object NMSettingsStorage *shadowed_storage = NULL; + NMSettingsStorage *update_storage = NULL; gs_free_error GError *local = NULL; SettConnEntry *sett_conn_entry; const char *uuid; StorageData *sd; + gboolean new_in_memory; + gboolean success; + const char *shadowed_storage_filename = NULL; + + priv = NM_SETTINGS_GET_PRIVATE (self); - nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK, + nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY)); - nm_assert (!NM_FLAGS_ANY (sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK)); + new_in_memory = (persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK); - nm_assert ( !NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED) - || persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY); + nm_assert (!NM_FLAGS_ANY (sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK)); - nm_assert ( !NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE) - || persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY); + if (NM_FLAGS_ANY (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE + | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) { + nm_assert (new_in_memory); + new_in_memory = TRUE; + } nm_assert (!NM_FLAGS_ANY (add_reason, ~NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT)); @@ -1421,8 +1716,8 @@ nm_settings_add_connection (NMSettings *self, uuid = nm_connection_get_uuid (connection); - /* Make sure a connection with this UUID doesn't already exist */ - if (_sett_conn_entry_get_conn (_sett_conn_entries_get (self, uuid))) { + sett_conn_entry = _sett_conn_entries_get (self, uuid); + if (_sett_conn_entry_get_conn (sett_conn_entry)) { g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_UUID_EXISTS, @@ -1446,47 +1741,152 @@ nm_settings_add_connection (NMSettings *self, if (connection_cloned_1) connection = connection_cloned_1; - if (!_add_connection_to_first_plugin (self, - connection, - ( persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK - || NM_FLAGS_ANY (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE - | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)), - NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED), - NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE), - &new_storage, - &new_connection, - &local)) { - g_set_error (error, - NM_SETTINGS_ERROR, - NM_SETTINGS_ERROR_FAILED, - "failure adding connection: %s", - local->message); + if (sett_conn_entry) { + c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { + if (!nm_settings_storage_is_meta_data (sd->storage)) + continue; + shadowed_storage = nm_g_object_ref (_sett_conn_entry_find_shadowed_storage (sett_conn_entry, + nm_settings_storage_get_shadowed_storage (sd->storage, NULL), + NULL)); + if (shadowed_storage) { + /* We have a nmmeta tombstone that indicates that a storage is shadowed. + * + * This happens when deleting a in-memory profile that was decoupled from + * the persitant storage with NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED. + * We need to take over this storage again... */ + break; + } + } + } + + if ( shadowed_storage + && !new_in_memory) { + NMSettingsStorage *conflicting_storage; + + conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage (sett_conn_entry, + nm_settings_storage_get_plugin (shadowed_storage), + shadowed_storage, + priv->plugins); + if (conflicting_storage) { + /* We cannot add the profile as @shadowed_storage, because there is another, existing storage + * that would hide it. Just add it as new storage. In general, this leads to duplication of profiles, + * but the circumstances where this happens are very exotic (you need at least one additional settings + * plugin, then going through the paths of making shadowed_storage in-memory-detached and delete it, + * and finally adding the conflicting storage outside of NM and restart/reload). */ + _LOGT ("ignore shadowed storage "NM_SETTINGS_STORAGE_PRINT_FMT" due to conflicting storage "NM_SETTINGS_STORAGE_PRINT_FMT, + NM_SETTINGS_STORAGE_PRINT_ARG (shadowed_storage), + NM_SETTINGS_STORAGE_PRINT_ARG (conflicting_storage)); + } else + update_storage = shadowed_storage; + } + + shadowed_storage_filename = ( shadowed_storage + && !update_storage) + ? nm_settings_storage_get_filename_for_shadowed_storage (shadowed_storage) + : NULL; + +again_add_connection: + + if (!update_storage) { + success = _add_connection_to_first_plugin (self, + sett_conn_entry, + connection, + new_in_memory, + sett_flags, + shadowed_storage_filename, + FALSE, + &new_storage, + &new_connection, + &local); + } else { + success = _update_connection_to_plugin (self, + update_storage, + connection, + sett_flags, + FALSE, + shadowed_storage_filename, + FALSE, + &new_storage, + &new_connection, + &local); + if (!success) { + if (!NMS_IS_KEYFILE_STORAGE (update_storage)) { + /* hm, the intended storage is not keyfile (it's ifcfg-rh). This settings + * plugin may not support the new connection. So step back and retry adding + * the profile anew. */ + _LOGT ("failure to add profile as existing storage \"%s\": %s", + nm_settings_storage_get_filename (update_storage), + local->message); + update_storage = NULL; + g_clear_object (&shadowed_storage); + shadowed_storage_filename = NULL; + g_clear_error (&local); + goto again_add_connection; + } + } + } + + if (!success) { + if (!update_storage) { + g_set_error (error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "failure adding connection: %s", + local->message); + } else { + g_set_error (error, + NM_SETTINGS_ERROR, + NM_SETTINGS_ERROR_FAILED, + "failure writing connection to existing storage \"%s\": %s", + nm_settings_storage_get_filename (update_storage), + local->message); + } return FALSE; } sett_conn_entry = _connection_changed_track (self, new_storage, new_connection, TRUE); c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { - - if (!nm_settings_storage_is_keyfile_tombstone (sd->storage)) + const NMSettingsMetaData *meta_data; + gs_unref_object NMSettingsStorage *new_tombstone_storage = NULL; + gboolean in_memory; + gboolean simulate; + + meta_data = nm_settings_storage_is_meta_data_alive (sd->storage); + if ( !meta_data + || !meta_data->is_tombstone) continue; - if (nm_settings_storage_is_keyfile_run (sd->storage)) { - /* We remove this file from /run. */ - } else { + if (nm_settings_storage_is_keyfile_run (sd->storage)) + in_memory = TRUE; + else { if (nm_settings_storage_is_keyfile_run (new_storage)) { /* Don't remove the file from /etc if we just wrote an in-memory connection */ continue; } + in_memory = FALSE; } - nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (sd->storage), - sd->storage, - NULL); - - nm_assert (!nm_settings_storage_is_keyfile_tombstone (sd->storage)); - - _connection_changed_track (self, sd->storage, NULL, FALSE); + simulate = FALSE; +again_delete_tombstone: + if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin, + simulate, + uuid, + in_memory, + FALSE, + NULL, + &new_tombstone_storage, + NULL)) { + /* Ups, something went wrong. We really need to get rid of the tombstone. At least + * forget about it in-memory. Upong next restart/reload, this might be reverted + * however :( .*/ + if (!simulate) { + simulate = TRUE; + goto again_delete_tombstone; + } + } + if (new_tombstone_storage) + _connection_changed_track (self, new_tombstone_storage, NULL, FALSE); } _connection_changed_process_all_dirty (self, @@ -1520,16 +1920,19 @@ nm_settings_update_connection (NMSettings *self, const char *log_context_name, GError **error) { - NMSettingsPrivate *priv; gs_unref_object NMConnection *connection_cloned_1 = NULL; gs_unref_object NMConnection *new_connection_cloned = NULL; gs_unref_object NMConnection *new_connection = NULL; NMConnection *new_connection_real; gs_unref_object NMSettingsStorage *cur_storage = NULL; gs_unref_object NMSettingsStorage *new_storage = NULL; + NMSettingsStorage *drop_storage = NULL; + SettConnEntry *sett_conn_entry; gboolean cur_in_memory; gboolean new_in_memory; const char *uuid; + gboolean tombstone_in_memory = FALSE; + gboolean tombstone_on_disk = FALSE; g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE); g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn), FALSE); @@ -1539,18 +1942,20 @@ nm_settings_update_connection (NMSettings *self, nm_assert (!NM_FLAGS_ANY (sett_flags, ~sett_mask)); nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP, NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST, - NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK, + NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY)); - priv = NM_SETTINGS_GET_PRIVATE (self); - cur_storage = g_object_ref (nm_settings_connection_get_storage (sett_conn)); uuid = nm_settings_storage_get_uuid (cur_storage); nm_assert (NM_IS_SETTINGS_STORAGE (cur_storage)); - nm_assert (_sett_conn_entry_get_conn (_sett_conn_entries_get (self, uuid)) == sett_conn); + + sett_conn_entry = _sett_conn_entries_get (self, uuid); + + nm_assert (_sett_conn_entry_get_conn (sett_conn_entry) == sett_conn); if (connection) { gs_free_error GError *local = NULL; @@ -1581,8 +1986,8 @@ nm_settings_update_connection (NMSettings *self, if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP) { persist_mode = cur_in_memory - ? NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED - : NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK; + ? NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY + : NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; } if ( NM_FLAGS_HAS (sett_mask, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED) @@ -1602,11 +2007,15 @@ nm_settings_update_connection (NMSettings *self, default_wired_clear_tag (self, device, sett_conn, FALSE); - if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP, - NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST)) { - /* making a default-wired-connection a regulard connection implies persisting - * it to disk (unless specified differently). */ - persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK; + if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST)) { + /* making a default-wired-connection a regular connection implies persisting + * it to disk (unless specified differently). + * + * Actually, this line is probably unreached, because we should not use + * NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST to toggle the nm-generated + * flag. */ + nm_assert_not_reached (); + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; } } } @@ -1621,12 +2030,13 @@ nm_settings_update_connection (NMSettings *self, * in-memory. The caller did not request to persist this to disk, however we need * to store the flags in run. */ nm_assert (cur_in_memory); - persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED; + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY; } - if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK) + if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK) new_in_memory = FALSE; - else if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED, + else if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY)) new_in_memory = TRUE; else { @@ -1651,60 +2061,96 @@ nm_settings_update_connection (NMSettings *self, | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE); } - if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST) { new_storage = g_object_ref (cur_storage); - new_connection = g_object_ref (connection); + new_connection_real = connection; _LOGT ("update[%s]: %s: update profile \"%s\" (not persisted)", nm_settings_storage_get_uuid (cur_storage), log_context_name, nm_connection_get_id (connection)); } else { - gboolean success; - gboolean migrate_storage; + NMSettingsStorage *shadowed_storage; + const char *cur_shadowed_storage_filename; + const char *new_shadowed_storage_filename = NULL; + gboolean cur_shadowed_owned; + gboolean new_shadowed_owned = FALSE; + NMSettingsStorage *update_storage = NULL; gs_free_error GError *local = NULL; + gboolean success; + + cur_shadowed_storage_filename = nm_settings_storage_get_shadowed_storage (cur_storage, &cur_shadowed_owned); + + shadowed_storage = _sett_conn_entry_find_shadowed_storage (sett_conn_entry, cur_shadowed_storage_filename, cur_storage); + if (!shadowed_storage) { + cur_shadowed_storage_filename = NULL; + cur_shadowed_owned = FALSE; + } - if (new_in_memory != cur_in_memory) - migrate_storage = TRUE; - else if ( !new_in_memory - && nm_settings_storage_is_keyfile_lib (cur_storage)) { + if ( new_in_memory + && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) { + if (cur_in_memory) { + drop_storage = shadowed_storage; + update_storage = cur_storage; + } else + drop_storage = cur_storage; + } else if ( !new_in_memory + && cur_in_memory + && shadowed_storage) { + drop_storage = cur_storage; + update_storage = shadowed_storage; + } else if (new_in_memory != cur_in_memory) { + if (!new_in_memory) + drop_storage = cur_storage; + else if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) + drop_storage = cur_storage; + else { + nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED)); + } + } else if (nm_settings_storage_is_keyfile_lib (cur_storage)) { /* the profile is a keyfile in /usr/lib. It cannot be overwritten, we must migrate it * from /usr/lib to /etc. */ - migrate_storage = TRUE; } else - migrate_storage = FALSE; + update_storage = cur_storage; + + if (new_in_memory) { + if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) { + /* pass */ + } else if (!cur_in_memory) { + new_shadowed_storage_filename = nm_settings_storage_get_filename_for_shadowed_storage (cur_storage); + if ( new_shadowed_storage_filename + && persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED) + new_shadowed_owned = TRUE; + } else { + new_shadowed_storage_filename = cur_shadowed_storage_filename; + if ( new_shadowed_storage_filename + && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY) + new_shadowed_owned = TRUE; + } + } - if (migrate_storage) { + if (!update_storage) { success = _add_connection_to_first_plugin (self, + sett_conn_entry, connection, new_in_memory, - NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED), - NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE), + sett_flags, + new_shadowed_storage_filename, + new_shadowed_owned, &new_storage, &new_connection, &local); } else { - NMSettingsPlugin *plugin; - - plugin = nm_settings_storage_get_plugin (cur_storage); - if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) { - success = nms_keyfile_plugin_update_connection (priv->keyfile_plugin, - cur_storage, - connection, - NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED), - NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE), - NM_FLAGS_HAS (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME), - &new_storage, - &new_connection, - &local); - } else { - success = nm_settings_plugin_update_connection (nm_settings_storage_get_plugin (cur_storage), - cur_storage, - connection, - &new_storage, - &new_connection, - &local); - } + success = _update_connection_to_plugin (self, + update_storage, + connection, + sett_flags, + update_reason, + new_shadowed_storage_filename, + new_shadowed_owned, + &new_storage, + &new_connection, + &local); } if (!success) { gboolean ignore_failure; @@ -1715,7 +2161,7 @@ nm_settings_update_connection (NMSettings *self, nm_settings_storage_get_uuid (cur_storage), log_context_name, ignore_failure ? "ignore " : "", - migrate_storage ? "write" : "update", + update_storage ? "update" : "write", nm_connection_get_id (connection), local->message); if (!ignore_failure) { @@ -1723,76 +2169,77 @@ nm_settings_update_connection (NMSettings *self, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "failed to %s connection: %s", - migrate_storage ? "write" : "update", + update_storage ? "update" : "write", local->message); return FALSE; } + new_storage = g_object_ref (cur_storage); - new_connection = g_object_ref (connection); + new_connection_real = connection; } else { + gs_unref_variant GVariant *agent_owned_secrets = NULL; + _LOGT ("update[%s]: %s: %s profile \"%s\"", nm_settings_storage_get_uuid (cur_storage), log_context_name, - migrate_storage ? "write" : "update", + update_storage ? "update" : "write", nm_connection_get_id (connection)); - } - } - - nm_assert_valid_settings_storage (NULL, new_storage); - nm_assert (NM_IS_CONNECTION (new_connection)); - nm_assert (nm_streq (uuid, nm_settings_storage_get_uuid (new_storage))); - if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST) - new_connection_real = new_connection; - else { - gs_unref_variant GVariant *agent_owned_secrets = NULL; - - agent_owned_secrets = nm_connection_to_dbus (connection, - NM_CONNECTION_SERIALIZE_ONLY_SECRETS - | NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED); - new_connection_real = _connection_changed_normalize_connection (new_storage, - new_connection, - agent_owned_secrets, - &new_connection_cloned); - if (!new_connection_real) { - nm_assert_not_reached (); - new_connection_real = new_connection; + nm_assert_valid_settings_storage (NULL, new_storage); + nm_assert (NM_IS_CONNECTION (new_connection)); + nm_assert (nm_streq (uuid, nm_settings_storage_get_uuid (new_storage))); + + + agent_owned_secrets = nm_connection_to_dbus (connection, + NM_CONNECTION_SERIALIZE_ONLY_SECRETS + | NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED); + new_connection_real = _connection_changed_normalize_connection (new_storage, + new_connection, + agent_owned_secrets, + &new_connection_cloned); + if (!new_connection_real) { + nm_assert_not_reached (); + new_connection_real = new_connection; + } } } + nm_assert (NM_IS_SETTINGS_STORAGE (new_storage)); nm_assert (NM_IS_CONNECTION (new_connection_real)); _connection_changed_track (self, new_storage, new_connection_real, TRUE); - if (new_storage != cur_storage) { + if ( drop_storage + && drop_storage != new_storage) { gs_free_error GError *local = NULL; - gboolean remove_from_disk; - if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED) - remove_from_disk = FALSE; - else if (nm_settings_storage_is_keyfile_lib (cur_storage)) - remove_from_disk = FALSE; - else - remove_from_disk = TRUE; - - if (remove_from_disk) { - if (!nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (cur_storage), - cur_storage, - &local)) { - const char *filename; - - filename = nm_settings_storage_get_filename (cur_storage); - _LOGW ("update[%s]: failed to delete moved storage "NM_SETTINGS_STORAGE_PRINT_FMT"%s%s%s: %s", - nm_settings_storage_get_uuid (cur_storage), - NM_SETTINGS_STORAGE_PRINT_ARG (cur_storage), - local->message, - NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", "")); - } + if (!nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (drop_storage), + drop_storage, + &local)) { + const char *filename; - _connection_changed_track (self, cur_storage, NULL, FALSE); - } + filename = nm_settings_storage_get_filename (drop_storage); + _LOGT ("update[%s]: failed to delete moved storage "NM_SETTINGS_STORAGE_PRINT_FMT"%s%s%s: %s", + nm_settings_storage_get_uuid (drop_storage), + NM_SETTINGS_STORAGE_PRINT_ARG (drop_storage), + NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""), + local->message); + /* there is no aborting back form this. We must get rid of the connection and + * cannot do better than log a message. Proceed, but remember to write tombstones. */ + if (nm_settings_storage_is_keyfile_run (cur_storage)) + tombstone_in_memory = TRUE; + else + tombstone_on_disk = TRUE; + } else + _connection_changed_track (self, drop_storage, NULL, FALSE); } + _set_nmmeta_tombstone (self, + uuid, + tombstone_on_disk, + tombstone_in_memory, + NULL); + _connection_changed_process_all_dirty (self, FALSE, sett_flags, @@ -1808,23 +2255,24 @@ nm_settings_delete_connection (NMSettings *self, NMSettingsConnection *sett_conn, gboolean allow_add_to_no_auto_default) { - NMSettingsPrivate *priv; NMSettingsStorage *cur_storage; + NMSettingsStorage *shadowed_storage; + NMSettingsStorage *shadowed_storage_unowned = NULL; + NMSettingsStorage *drop_storages[2] = { }; gs_free_error GError *local = NULL; SettConnEntry *sett_conn_entry; + const char *cur_shadowed_storage_filename; + const char *new_shadowed_storage_filename = NULL; + gboolean cur_shadowed_owned; const char *uuid; - gboolean delete; gboolean tombstone_in_memory = FALSE; gboolean tombstone_on_disk = FALSE; - gs_unref_object NMSettingsStorage *tombstone_1_storage = NULL; - gs_unref_object NMSettingsStorage *tombstone_2_storage = NULL; + int i; g_return_if_fail (NM_IS_SETTINGS (self)); g_return_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn)); g_return_if_fail (nm_settings_has_connection (self, sett_conn)); - priv = NM_SETTINGS_GET_PRIVATE (self); - cur_storage = nm_settings_connection_get_storage (sett_conn); nm_assert (NM_IS_SETTINGS_STORAGE (cur_storage)); @@ -1843,38 +2291,62 @@ nm_settings_delete_connection (NMSettings *self, if (NM_IN_SET (s->storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN, NMS_KEYFILE_STORAGE_TYPE_ETC)) - delete = TRUE; - else { + drop_storages[0] = cur_storage; + else tombstone_on_disk = TRUE; - delete = FALSE; - } } else - delete = TRUE; + drop_storages[0] = cur_storage; - if (delete) { + cur_shadowed_storage_filename = nm_settings_storage_get_shadowed_storage (cur_storage, &cur_shadowed_owned); + + shadowed_storage = _sett_conn_entry_find_shadowed_storage (sett_conn_entry, cur_shadowed_storage_filename, cur_storage); + if (shadowed_storage) { + if (!cur_shadowed_owned) + shadowed_storage_unowned = g_steal_pointer (&shadowed_storage); + } + drop_storages[1] = shadowed_storage; + + for (i = 0; i < (int) G_N_ELEMENTS (drop_storages); i++) { + NMSettingsStorage *storage; StorageData *sd; - if (!nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (cur_storage), - cur_storage, + storage = drop_storages[i]; + if (!storage) + continue; + + if (!nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (storage), + storage, &local)) { - _LOGW ("delete-connection: failed to delete storage "NM_SETTINGS_STORAGE_PRINT_FMT": %s", - NM_SETTINGS_STORAGE_PRINT_ARG (cur_storage), + _LOGT ("delete-connection: failed to delete storage "NM_SETTINGS_STORAGE_PRINT_FMT": %s", + NM_SETTINGS_STORAGE_PRINT_ARG (storage), local->message); g_clear_error (&local); /* there is no aborting back form this. We must get rid of the connection and - * cannot do better than warn. Proceed... */ - tombstone_in_memory = TRUE; - } - - sett_conn_entry = _connection_changed_track (self, cur_storage, NULL, FALSE); + * cannot do better than log a message. Proceed, but remember to write tombstones. */ + if (nm_settings_storage_is_keyfile_run (cur_storage)) + tombstone_in_memory = TRUE; + else + tombstone_on_disk = TRUE; + sett_conn_entry = _sett_conn_entries_get (self, uuid); + } else + sett_conn_entry = _connection_changed_track (self, storage, NULL, FALSE); c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) { - if (sd->storage == cur_storage) - continue; - if (nm_settings_storage_is_keyfile_tombstone (sd->storage)) + if (NM_IN_SET (sd->storage, drop_storages[0], + drop_storages[1])) continue; if (!_storage_data_is_alive (sd)) continue; + if (nm_settings_storage_is_meta_data (sd->storage)) + continue; + + if (sd->storage == shadowed_storage_unowned) { + /* this only happens if we leak a profile on disk after NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED. + * We need to write a tombstone and remember the shadowed-storage. */ + tombstone_in_memory = TRUE; + new_shadowed_storage_filename = nm_settings_storage_get_filename (shadowed_storage_unowned); + continue; + } /* we have still conflicting storages. We need to hide them with tombstones. */ if (nm_settings_storage_is_keyfile_run (sd->storage)) { @@ -1885,37 +2357,11 @@ nm_settings_delete_connection (NMSettings *self, } } - if (tombstone_on_disk) { - if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin, - FALSE, - uuid, - FALSE, - TRUE, - &tombstone_1_storage, - NULL)) - tombstone_in_memory = TRUE; - if (tombstone_1_storage) - _connection_changed_track (self, tombstone_1_storage, NULL, FALSE); - } - - if (tombstone_in_memory) { - if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin, - FALSE, - uuid, - TRUE, - TRUE, - &tombstone_2_storage, - NULL)) { - nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin, - TRUE, - uuid, - TRUE, - TRUE, - &tombstone_2_storage, - NULL); - } - _connection_changed_track (self, tombstone_2_storage, NULL, FALSE); - } + _set_nmmeta_tombstone (self, + uuid, + tombstone_on_disk, + tombstone_in_memory, + new_shadowed_storage_filename); _connection_changed_process_all_dirty (self, allow_add_to_no_auto_default, @@ -2164,7 +2610,7 @@ settings_add_connection_helper (NMSettings *self, } if (NM_FLAGS_HAS (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK)) - persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK; + persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; else { nm_assert (NM_FLAGS_HAS (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)); persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY; diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c index b65013a431..41b20850ad 100644 --- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c +++ b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c @@ -676,7 +676,6 @@ ifupdown_new_connection_from_if_block (if_block *block, NM_SETTING_CONNECTION_INTERFACE_NAME, block->name, NM_SETTING_CONNECTION_ID, idstr, NM_SETTING_CONNECTION_UUID, uuid, - NM_SETTING_CONNECTION_READ_ONLY, TRUE, NM_SETTING_CONNECTION_AUTOCONNECT, (gboolean) (!!autoconnect), NULL); diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c index de076c0122..fbe70ef4d2 100644 --- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c +++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c @@ -216,13 +216,22 @@ _read_from_file (const char *full_filename, struct stat *out_stat, NMTernary *out_is_nm_generated, NMTernary *out_is_volatile, + char **out_shadowed_storage, + NMTernary *out_shadowed_owned, GError **error) { 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); + connection = nms_keyfile_reader_from_file (full_filename, + plugin_dir, + out_stat, + out_is_nm_generated, + out_is_volatile, + out_shadowed_storage, + out_shadowed_owned, + 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))); @@ -244,7 +253,7 @@ _nm_assert_storage (gpointer plugin /* NMSKeyfilePlugin */, 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 (!((NMSKeyfileStorage *) storage)->is_tombstone || !(((NMSKeyfileStorage *) storage)->connection)); + nm_assert (({ const char *f = nms_keyfile_storage_get_filename (storage); f && f[0] == '/'; @@ -254,6 +263,11 @@ _nm_assert_storage (gpointer plugin /* NMSKeyfilePlugin */, nm_assert (nm_utils_is_uuid (uuid)); + nm_assert ( ((NMSKeyfileStorage *) storage)->is_meta_data + || !(((NMSKeyfileStorage *) storage)->u.conn_data.connection) + || ( NM_IS_CONNECTION ((((NMSKeyfileStorage *) storage)->u.conn_data.connection)) + && nm_streq0 (uuid, nm_connection_get_uuid ((((NMSKeyfileStorage *) storage)->u.conn_data.connection))))); + nm_assert ( !tracked || !plugin || c_list_contains (&NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin)->storages._storage_lst_head, @@ -264,7 +278,8 @@ _nm_assert_storage (gpointer plugin /* NMSKeyfilePlugin */, || storage == g_hash_table_lookup (NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin)->storages.idx_by_filename, nms_keyfile_storage_get_filename (storage))); - if (tracked && plugin) { + if ( tracked + && plugin) { sbuh = g_hash_table_lookup (NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin)->storages.idx_by_uuid, &uuid); nm_assert (sbuh); nm_assert (c_list_contains (&sbuh->_storage_by_uuid_lst_head, &((NMSKeyfileStorage *) storage)->parent._storage_by_uuid_lst)); @@ -285,6 +300,8 @@ _load_file (NMSKeyfilePlugin *self, gs_unref_object NMConnection *connection = NULL; NMTernary is_volatile_opt; NMTernary is_nm_generated_opt; + NMTernary shadowed_owned_opt; + gs_free char *shadowed_storage = NULL; gs_free_error GError *local = NULL; gs_free char *full_filename = NULL; struct stat st; @@ -292,6 +309,7 @@ _load_file (NMSKeyfilePlugin *self, if (_ignore_filename (storage_type, filename)) { gs_free char *nmmeta = NULL; gs_free char *loaded_path = NULL; + gs_free char *shadowed_storage_filename = NULL; if (!nms_keyfile_nmmeta_check_filename (filename, NULL)) { if (error) @@ -305,6 +323,7 @@ _load_file (NMSKeyfilePlugin *self, &full_filename, &nmmeta, &loaded_path, + &shadowed_storage_filename, NULL)) { if (error) nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "skip unreadable nmmeta file"); @@ -332,7 +351,8 @@ _load_file (NMSKeyfilePlugin *self, return nms_keyfile_storage_new_tombstone (self, nmmeta, full_filename, - storage_type); + storage_type, + shadowed_storage_filename); } full_filename = g_build_filename (dirname, filename, NULL); @@ -344,6 +364,8 @@ _load_file (NMSKeyfilePlugin *self, &st, &is_nm_generated_opt, &is_volatile_opt, + &shadowed_storage, + &shadowed_owned_opt, &local); if (!connection) { if (error) @@ -359,6 +381,8 @@ _load_file (NMSKeyfilePlugin *self, storage_type, is_nm_generated_opt, is_volatile_opt, + shadowed_storage, + shadowed_owned_opt, &st.st_mtim); } @@ -454,7 +478,7 @@ _storages_consolidate (NMSKeyfilePlugin *self, c_list_init (&storages_deleted); c_list_for_each_entry (storage_old, &priv->storages._storage_lst_head, parent._storage_lst) - storage_old->dirty = TRUE; + storage_old->is_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_keyfile_storage_get_filename (storage_new)); @@ -465,28 +489,28 @@ _storages_consolidate (NMSKeyfilePlugin *self, || !nm_streq (nms_keyfile_storage_get_uuid (storage_new), nms_keyfile_storage_get_uuid (storage_old))) { if (storage_old) { nm_sett_util_storages_steal (&priv->storages, storage_old); - c_list_link_tail (&storages_deleted, &storage_old->parent._storage_lst); + c_list_link_tail (&storages_deleted, &storage_old->parent._storage_by_uuid_lst); } - storage_new->dirty = FALSE; + storage_new->is_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; + storage_old->is_dirty = FALSE; nms_keyfile_storage_copy_content (storage_old, storage_new); nms_keyfile_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) + if (!storage_old->is_dirty) continue; if ( replace_all || ( storages_replaced && g_hash_table_contains (storages_replaced, storage_old))) { nm_sett_util_storages_steal (&priv->storages, storage_old); - c_list_link_tail (&storages_deleted, &storage_old->parent._storage_lst); + c_list_link_tail (&storages_deleted, &storage_old->parent._storage_by_uuid_lst); } } @@ -494,7 +518,7 @@ _storages_consolidate (NMSKeyfilePlugin *self, for (i = 0; i < storages_modified->len; i++) { storage = storages_modified->pdata[i]; - storage->dirty = TRUE; + storage->is_dirty = TRUE; } for (i = 0; i < storages_modified->len; i++) { @@ -502,19 +526,22 @@ _storages_consolidate (NMSKeyfilePlugin *self, storage = storages_modified->pdata[i]; - if (!storage->dirty) { - /* the entry is no longer dirty. In the meantime we already emited + if (!storage->is_dirty) { + /* the entry is no longer is_dirty. In the meantime we already emited * another signal for it. */ continue; } - storage->dirty = FALSE; - if (storage != nm_sett_util_storages_lookup_by_filename (&priv->storages, nms_keyfile_storage_get_filename (storage))) { + storage->is_dirty = FALSE; + + if (c_list_is_empty (&storage->parent._storage_lst)) { /* 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; } + nm_assert (storage == nm_sett_util_storages_lookup_by_filename (&priv->storages, nms_keyfile_storage_get_filename (storage))); + connection = nms_keyfile_storage_steal_connection (storage); callback (NM_SETTINGS_PLUGIN (self), @@ -523,9 +550,8 @@ _storages_consolidate (NMSKeyfilePlugin *self, user_data); } - while ((storage = c_list_first_entry (&storages_deleted, NMSKeyfileStorage, parent._storage_lst))) { - c_list_unlink (&storage->parent._storage_lst); - storage->is_tombstone = FALSE; + while ((storage = c_list_first_entry (&storages_deleted, NMSKeyfileStorage, parent._storage_by_uuid_lst))) { + c_list_unlink (&storage->parent._storage_by_uuid_lst); callback (NM_SETTINGS_PLUGIN (self), NM_SETTINGS_STORAGE (storage), NULL, @@ -717,9 +743,11 @@ load_connections (NMSettingsPlugin *plugin, gboolean nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self, NMConnection *connection, + gboolean in_memory, gboolean is_nm_generated, gboolean is_volatile, - gboolean in_memory, + const char *shadowed_storage, + gboolean shadowed_owned, NMSettingsStorage **out_storage, NMConnection **out_connection, GError **error) @@ -739,8 +767,16 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self, nm_assert (out_storage && !*out_storage); nm_assert (out_connection && !*out_connection); + nm_assert ( in_memory + || ( !is_nm_generated + && !is_volatile + && !shadowed_storage + && !shadowed_owned)); + uuid = nm_connection_get_uuid (connection); + /* Note that even if the caller requests persistent storage, we may switch to in-memory, if + * no /etc directory is configured. */ storage_type = !in_memory && priv->dirname_etc ? NMS_KEYFILE_STORAGE_TYPE_ETC : NMS_KEYFILE_STORAGE_TYPE_RUN; @@ -748,6 +784,8 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self, if (!nms_keyfile_writer_connection (connection, is_nm_generated, is_volatile, + shadowed_storage, + shadowed_owned, storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC ? priv->dirname_etc : priv->dirname_run, @@ -779,11 +817,12 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self, nm_assert (full_filename && full_filename[0] == '/'); nm_assert (!nm_sett_util_storages_lookup_by_filename (&priv->storages, full_filename)); - _LOGT ("commit: %s (%s) added as \"%s\"%s", + _LOGT ("commit: %s (%s) added as \"%s\"%s%s%s%s", uuid, nm_connection_get_id (connection), full_filename, - _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile)); + _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile), + NM_PRINT_FMT_QUOTED (shadowed_storage, " (shadows \"", shadowed_storage, shadowed_owned ? "\", owned)" : "\")", "")); storage = nms_keyfile_storage_new_connection (self, g_steal_pointer (&reread), @@ -791,6 +830,8 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self, storage_type, is_nm_generated ? NM_TERNARY_TRUE : NM_TERNARY_FALSE, is_volatile ? NM_TERNARY_TRUE : NM_TERNARY_FALSE, + shadowed_storage, + shadowed_owned ? NM_TERNARY_TRUE : NM_TERNARY_FALSE, nm_sett_util_stat_mtime (full_filename, FALSE, &mtime)); nm_sett_util_storages_add_take (&priv->storages, g_object_ref (storage)); @@ -813,6 +854,8 @@ add_connection (NMSettingsPlugin *plugin, FALSE, FALSE, FALSE, + NULL, + FALSE, out_storage, out_connection, error); @@ -824,6 +867,8 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self, NMConnection *connection, gboolean is_nm_generated, gboolean is_volatile, + const char *shadowed_storage, + gboolean shadowed_owned, gboolean force_rename, NMSettingsStorage **out_storage, NMConnection **out_connection, @@ -839,6 +884,7 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self, const char *previous_filename; gboolean reread_same; const char *uuid; + char strbuf[100]; _nm_assert_storage (self, storage, TRUE); nm_assert (NM_IS_CONNECTION (connection)); @@ -847,8 +893,13 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self, nm_assert (!error || !*error); nm_assert (NM_IN_SET (storage->storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC, NMS_KEYFILE_STORAGE_TYPE_RUN)); - nm_assert ( (!is_nm_generated && !is_volatile) - || storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN); + nm_assert (!storage->is_meta_data); + nm_assert ( storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN + || ( !is_nm_generated + && !is_volatile + && !shadowed_storage + && !shadowed_owned)); + nm_assert (!shadowed_owned || shadowed_storage); nm_assert ( priv->dirname_etc || storage->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC); @@ -858,6 +909,8 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self, if (!nms_keyfile_writer_connection (connection, is_nm_generated, is_volatile, + shadowed_storage, + shadowed_owned, storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC ? priv->dirname_etc : priv->dirname_run, @@ -890,14 +943,17 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self, nm_assert (_nm_connection_verify (reread, NULL) == NM_SETTING_VERIFY_SUCCESS); nm_assert (nm_streq (nm_connection_get_uuid (reread), uuid)); - _LOGT ("commit: \"%s\": profile %s (%s) written", + _LOGT ("commit: \"%s\": profile %s (%s) written%s%s%s%s", full_filename, uuid, - nm_connection_get_id (connection)); + nm_connection_get_id (connection), + _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile), + NM_PRINT_FMT_QUOTED (shadowed_storage, shadowed_owned ? " (owns \"" : " (shadows \"", shadowed_storage, "\")", "")); - storage->is_nm_generated = is_nm_generated; - storage->is_volatile = is_volatile; - storage->stat_mtime = *nm_sett_util_stat_mtime (full_filename, FALSE, &mtime); + storage->u.conn_data.is_nm_generated = is_nm_generated; + storage->u.conn_data.is_volatile = is_volatile; + storage->u.conn_data.stat_mtime = *nm_sett_util_stat_mtime (full_filename, FALSE, &mtime); + storage->u.conn_data.shadowed_owned = shadowed_owned; *out_storage = g_object_ref (NM_SETTINGS_STORAGE (storage)); *out_connection = g_steal_pointer (&reread); @@ -917,6 +973,8 @@ update_connection (NMSettingsPlugin *plugin, connection, FALSE, FALSE, + NULL, + FALSE, FALSE, out_storage, out_connection, @@ -969,14 +1027,13 @@ delete_connection (NMSettingsPlugin *plugin, _LOGT ("commit: deleted \"%s\", %s %s (%s%s%s%s)", previous_filename, - storage->is_tombstone ? "tombstone" : "profile", + storage->is_meta_data ? "meta-data" : "profile", uuid, operation_message, NM_PRINT_FMT_QUOTED (remove_from_disk_errmsg, ": ", remove_from_disk_errmsg, "", "")); if (success) { nm_sett_util_storages_steal (&priv->storages, storage); - storage->is_tombstone = FALSE; nms_keyfile_storage_destroy (storage); } @@ -998,6 +1055,9 @@ delete_connection (NMSettingsPlugin *plugin, * has no /etc directory configured, this results in a hard failure. * @set: if %TRUE, write the symlink to point to /dev/null. If %FALSE, * delete the nmmeta file (if it exists). + * @shadowed_storage: a tombstone can also shadow an existing storage. + * In combination with @set and @in_memory, this is allowed to store + * the shadowed storage filename. * @out_storage: (transfer full) (allow-none): the storage element that changes, or * NULL if nothing changed. Note that the file on disk is already as * we want to write it, then this still counts as a change. No change only @@ -1022,6 +1082,7 @@ nms_keyfile_plugin_set_nmmeta_tombstone (NMSKeyfilePlugin *self, const char *uuid, gboolean in_memory, gboolean set, + const char *shadowed_storage, NMSettingsStorage **out_storage, gboolean *out_hard_failure) { @@ -1038,6 +1099,7 @@ nms_keyfile_plugin_set_nmmeta_tombstone (NMSKeyfilePlugin *self, nm_assert (NMS_IS_KEYFILE_PLUGIN (self)); nm_assert (nm_utils_is_uuid (uuid)); nm_assert (!out_storage || !*out_storage); + nm_assert (!shadowed_storage || (set && in_memory)); priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self); @@ -1050,7 +1112,7 @@ nms_keyfile_plugin_set_nmmeta_tombstone (NMSKeyfilePlugin *self, dirname = priv->dirname_run; } else { if (!priv->dirname_etc) { - _LOGT ("commit: cannot %s%s nmmeta symlink for %s as there is no /etc directory", + _LOGT ("commit: cannot %s%s nmmeta file for %s as there is no /etc directory", simulate ? "simulate " : "", loaded_path ? "write" : "delete", uuid); @@ -1069,13 +1131,15 @@ nms_keyfile_plugin_set_nmmeta_tombstone (NMSKeyfilePlugin *self, uuid, loaded_path, FALSE, + shadowed_storage, &nmmeta_filename); } - _LOGT ("commit: %s nmmeta symlink \"%s\"%s%s%s %s", + _LOGT ("commit: %s nmmeta file \"%s\"%s%s%s%s%s%s %s", loaded_path ? "writing" : "deleting", nmmeta_filename, NM_PRINT_FMT_QUOTED (loaded_path, " (pointing to \"", loaded_path, "\")", ""), + NM_PRINT_FMT_QUOTED (shadowed_storage, " (shadows \"", shadowed_storage, "\")", ""), simulate ? "simulated" : ( nmmeta_success @@ -1088,7 +1152,7 @@ nms_keyfile_plugin_set_nmmeta_tombstone (NMSKeyfilePlugin *self, storage = nm_sett_util_storages_lookup_by_filename (&priv->storages, nmmeta_filename); nm_assert ( !storage - || ( storage->is_tombstone + || ( storage->is_meta_data && storage->storage_type == storage_type && nm_streq (nms_keyfile_storage_get_uuid (storage), uuid))); @@ -1098,16 +1162,18 @@ nms_keyfile_plugin_set_nmmeta_tombstone (NMSKeyfilePlugin *self, storage = nms_keyfile_storage_new_tombstone (self, uuid, nmmeta_filename, - storage_type); + storage_type, + shadowed_storage); nm_sett_util_storages_add_take (&priv->storages, storage); + } else { + g_free (storage->u.meta_data.shadowed_storage); + storage->u.meta_data.shadowed_storage = g_strdup (shadowed_storage); } storage_result = g_object_ref (storage); } else { - if (storage) { + if (storage) storage_result = nm_sett_util_storages_steal (&priv->storages, storage); - storage_result->is_tombstone = FALSE; - } } out: diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.h b/src/settings/plugins/keyfile/nms-keyfile-plugin.h index f3e6870861..4844096456 100644 --- a/src/settings/plugins/keyfile/nms-keyfile-plugin.h +++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.h @@ -42,9 +42,11 @@ NMSKeyfilePlugin *nms_keyfile_plugin_new (void); gboolean nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self, NMConnection *connection, + gboolean in_memory, gboolean is_nm_generated, gboolean is_volatile, - gboolean in_memory, + const char *shadowed_storage, + gboolean shadowed_owned, NMSettingsStorage **out_storage, NMConnection **out_connection, GError **error); @@ -54,6 +56,8 @@ gboolean nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self, NMConnection *connection, gboolean is_nm_generated, gboolean is_volatile, + const char *shadowed_storage, + gboolean shadowed_owned, gboolean force_rename, NMSettingsStorage **out_storage, NMConnection **out_connection, @@ -64,6 +68,7 @@ gboolean nms_keyfile_plugin_set_nmmeta_tombstone (NMSKeyfilePlugin *self, const char *uuid, gboolean in_memory, gboolean set, + const char *shadowed_storage, NMSettingsStorage **out_storage, gboolean *out_hard_failure); diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/settings/plugins/keyfile/nms-keyfile-reader.c index a6562a04dc..8d1f5599fe 100644 --- a/src/settings/plugins/keyfile/nms-keyfile-reader.c +++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c @@ -164,6 +164,8 @@ nms_keyfile_reader_from_file (const char *full_filename, struct stat *out_stat, NMTernary *out_is_nm_generated, NMTernary *out_is_volatile, + char **out_shadowed_storage, + NMTernary *out_shadowed_owned, GError **error) { gs_unref_keyfile GKeyFile *key_file = NULL; @@ -210,6 +212,16 @@ nms_keyfile_reader_from_file (const char *full_filename, NM_KEYFILE_KEY_NMMETA_VOLATILE, NM_TERNARY_DEFAULT)); + NM_SET_OUT (out_shadowed_storage, g_key_file_get_string (key_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE, + NULL)); + + NM_SET_OUT (out_shadowed_owned, nm_key_file_get_boolean (key_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED, + NM_TERNARY_DEFAULT)); + return connection; } diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.h b/src/settings/plugins/keyfile/nms-keyfile-reader.h index b17b6dd77b..f20e6d93ee 100644 --- a/src/settings/plugins/keyfile/nms-keyfile-reader.h +++ b/src/settings/plugins/keyfile/nms-keyfile-reader.h @@ -37,6 +37,8 @@ NMConnection *nms_keyfile_reader_from_file (const char *full_filename, struct stat *out_stat, NMTernary *out_is_nm_generated, NMTernary *out_is_volatile, + char **out_shadowed_storage, + NMTernary *out_shadowed_owned, GError **error); #endif /* __NMS_KEYFILE_READER_H__ */ diff --git a/src/settings/plugins/keyfile/nms-keyfile-storage.c b/src/settings/plugins/keyfile/nms-keyfile-storage.c index d38e187d22..d68d60c8ec 100644 --- a/src/settings/plugins/keyfile/nms-keyfile-storage.c +++ b/src/settings/plugins/keyfile/nms-keyfile-storage.c @@ -41,24 +41,39 @@ nms_keyfile_storage_copy_content (NMSKeyfileStorage *dst, { nm_assert (src != dst); nm_assert (nm_streq (nms_keyfile_storage_get_uuid (dst), nms_keyfile_storage_get_uuid (src))); - nm_assert (nms_keyfile_storage_get_filename (dst) && nm_streq (nms_keyfile_storage_get_filename (dst), nms_keyfile_storage_get_filename (src))); - - nm_g_object_ref_set (&dst->connection, src->connection); - dst->storage_type = src->storage_type; - dst->stat_mtime = src->stat_mtime; - dst->is_nm_generated = src->is_nm_generated; - dst->is_volatile = src->is_volatile; - dst->is_tombstone = src->is_tombstone; + nm_assert ( nms_keyfile_storage_get_filename (dst) + && nm_streq (nms_keyfile_storage_get_filename (dst), nms_keyfile_storage_get_filename (src))); + nm_assert (dst->storage_type == src->storage_type); + nm_assert (dst->is_meta_data == src->is_meta_data); + + if (dst->is_meta_data) { + gs_free char *shadowed_storage_to_free = NULL; + + shadowed_storage_to_free = g_steal_pointer (&dst->u.meta_data.shadowed_storage); + dst->u.meta_data = src->u.meta_data; + dst->u.meta_data.shadowed_storage = g_strdup (dst->u.meta_data.shadowed_storage); + } else { + gs_unref_object NMConnection *connection_to_free = NULL; + gs_free char *shadowed_storage_to_free = NULL; + + connection_to_free = g_steal_pointer (&dst->u.conn_data.connection); + shadowed_storage_to_free = g_steal_pointer (&dst->u.conn_data.shadowed_storage); + dst->u.conn_data = src->u.conn_data; + nm_g_object_ref (dst->u.conn_data.connection); + dst->u.conn_data.shadowed_storage = g_strdup (dst->u.conn_data.shadowed_storage); + } } NMConnection * nms_keyfile_storage_steal_connection (NMSKeyfileStorage *self) { nm_assert (NMS_IS_KEYFILE_STORAGE (self)); - nm_assert ( (!self->connection && self->is_tombstone) - || NM_IS_CONNECTION (self->connection)); + nm_assert ( self->is_meta_data + || NM_IS_CONNECTION (self->u.conn_data.connection)); - return g_steal_pointer (&self->connection); + return self->is_meta_data + ? NULL + : g_steal_pointer (&self->u.conn_data.connection); } /*****************************************************************************/ @@ -75,16 +90,19 @@ cmp_fcn (const NMSKeyfileStorage *a, * (inverse) priority. */ NM_CMP_FIELD_UNSAFE (b, a, storage_type); - /* tombstones are more important. */ - nm_assert (a->is_tombstone == nm_settings_storage_is_keyfile_tombstone (NM_SETTINGS_STORAGE (a))); - nm_assert (b->is_tombstone == nm_settings_storage_is_keyfile_tombstone (NM_SETTINGS_STORAGE (b))); - NM_CMP_FIELD_UNSAFE (a, b, is_tombstone); + /* meta-data is more important. */ + NM_CMP_FIELD_UNSAFE (a, b, is_meta_data); - /* newer files are more important. */ - NM_CMP_FIELD (a, b, stat_mtime.tv_sec); - NM_CMP_FIELD (a, b, stat_mtime.tv_nsec); + if (a->is_meta_data) { + nm_assert (nm_streq (nms_keyfile_storage_get_filename (a), nms_keyfile_storage_get_filename (b))); + NM_CMP_FIELD_UNSAFE (a, b, u.meta_data.is_tombstone); + } else { + /* newer files are more important. */ + NM_CMP_FIELD (a, b, u.conn_data.stat_mtime.tv_sec); + NM_CMP_FIELD (a, b, u.conn_data.stat_mtime.tv_nsec); - NM_CMP_DIRECT_STRCMP (nms_keyfile_storage_get_filename (a), nms_keyfile_storage_get_filename (b)); + NM_CMP_DIRECT_STRCMP (nms_keyfile_storage_get_filename (a), nms_keyfile_storage_get_filename (b)); + } return 0; } @@ -99,24 +117,35 @@ nms_keyfile_storage_init (NMSKeyfileStorage *self) static NMSKeyfileStorage * _storage_new (NMSKeyfilePlugin *plugin, const char *uuid, - const char *filename) + const char *filename, + gboolean is_meta_data, + NMSKeyfileStorageType storage_type) + { + NMSKeyfileStorage *self; + nm_assert (NMS_IS_KEYFILE_PLUGIN (plugin)); nm_assert (nm_utils_is_uuid (uuid)); nm_assert (filename && filename[0] == '/'); - return g_object_new (NMS_TYPE_KEYFILE_STORAGE, + self = g_object_new (NMS_TYPE_KEYFILE_STORAGE, NM_SETTINGS_STORAGE_PLUGIN, plugin, NM_SETTINGS_STORAGE_UUID, uuid, NM_SETTINGS_STORAGE_FILENAME, filename, NULL); + + *((bool *) &self->is_meta_data) = is_meta_data; + *((NMSKeyfileStorageType *) &self->storage_type) = storage_type; + + return self; } NMSKeyfileStorage * nms_keyfile_storage_new_tombstone (NMSKeyfilePlugin *plugin, const char *uuid, const char *filename, - NMSKeyfileStorageType storage_type) + NMSKeyfileStorageType storage_type, + const char *shadowed_storage) { NMSKeyfileStorage *self; @@ -126,12 +155,10 @@ nms_keyfile_storage_new_tombstone (NMSKeyfilePlugin *plugin, nm_assert (NM_IN_SET (storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC, NMS_KEYFILE_STORAGE_TYPE_RUN)); - self = _storage_new (plugin, uuid, filename); - - self->is_tombstone = TRUE; - - self->storage_type = storage_type; - + self = _storage_new (plugin, uuid, filename, TRUE, storage_type); + self->u.meta_data.is_tombstone = TRUE; + if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) + self->u.meta_data.shadowed_storage = g_strdup (shadowed_storage); return self; } @@ -142,6 +169,8 @@ nms_keyfile_storage_new_connection (NMSKeyfilePlugin *plugin, NMSKeyfileStorageType storage_type, NMTernary is_nm_generated_opt, NMTernary is_volatile_opt, + const char *shadowed_storage, + NMTernary shadowed_owned_opt, const struct timespec *stat_mtime) { NMSKeyfileStorage *self; @@ -154,19 +183,21 @@ nms_keyfile_storage_new_connection (NMSKeyfilePlugin *plugin, && storage_type <= _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST); nmtst_connection_assert_unchanging (connection_take); - self = _storage_new (plugin, nm_connection_get_uuid (connection_take), filename); + self = _storage_new (plugin, nm_connection_get_uuid (connection_take), filename, FALSE, storage_type); - self->connection = connection_take; /* take reference. */ + self->u.conn_data.connection = connection_take; /* take reference. */ - if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) { - self->is_nm_generated = (is_nm_generated_opt == NM_TERNARY_TRUE); - self->is_volatile = (is_volatile_opt == NM_TERNARY_TRUE); - } + self->u.conn_data.shadowed_storage = g_strdup (shadowed_storage); if (stat_mtime) - self->stat_mtime = *stat_mtime; + self->u.conn_data.stat_mtime = *stat_mtime; - self->storage_type = storage_type; + if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) { + self->u.conn_data.is_nm_generated = (is_nm_generated_opt == NM_TERNARY_TRUE); + self->u.conn_data.is_volatile = (is_volatile_opt == NM_TERNARY_TRUE); + self->u.conn_data.shadowed_owned = shadowed_storage + && (shadowed_owned_opt == NM_TERNARY_TRUE); + } return self; } @@ -176,7 +207,13 @@ _storage_clear (NMSKeyfileStorage *self) { c_list_unlink (&self->parent._storage_lst); c_list_unlink (&self->parent._storage_by_uuid_lst); - g_clear_object (&self->connection); + if (self->is_meta_data) + nm_clear_g_free (&self->u.meta_data.shadowed_storage); + else { + g_clear_object (&self->u.conn_data.connection); + nm_clear_g_free (&self->u.conn_data.shadowed_storage); + self->u.conn_data.shadowed_owned = FALSE; + } } static void @@ -226,12 +263,15 @@ nm_settings_storage_load_sett_flags (NMSettingsStorage *self, return; s = NMS_KEYFILE_STORAGE (self); + + if (s->is_meta_data) + return; if (s->storage_type != NMS_KEYFILE_STORAGE_TYPE_RUN) return; - if (s->is_nm_generated) + if (s->u.conn_data.is_nm_generated) *sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED; - if (s->is_volatile) + if (s->u.conn_data.is_volatile) *sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE; } diff --git a/src/settings/plugins/keyfile/nms-keyfile-storage.h b/src/settings/plugins/keyfile/nms-keyfile-storage.h index 0cc537b7bf..2252b47b7e 100644 --- a/src/settings/plugins/keyfile/nms-keyfile-storage.h +++ b/src/settings/plugins/keyfile/nms-keyfile-storage.h @@ -34,6 +34,12 @@ #define NMS_KEYFILE_STORAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass)) typedef struct { + /* whether this is a tombstone to hide a UUID (via symlink to /dev/null). */ + char *shadowed_storage; + bool is_tombstone:1; +} NMSettingsMetaData; + +typedef struct { NMSettingsStorage parent; /* The connection. Note that there are tombstones (loaded-uuid files to /dev/null) @@ -42,35 +48,63 @@ typedef struct { * Also, we don't actually remember the loaded connection after returning it * to NMSettings. So, also for regular storages (non-tombstones) this field * is often cleared. */ - NMConnection *connection; - - NMSKeyfileStorageType storage_type; - - /* the timestamp (stat's mtime) of the keyfile. For tombstones this - * is irrelevant. The purpose is that if the same storage type (directory) has - * multiple files with the same UUID, then the newer file gets preferred. */ - struct timespec stat_mtime; - - /* these flags are only relevant for storages with %NMS_KEYFILE_STORAGE_TYPE_RUN - * (and non-tombstones). This is to persist and reload these settings flags to - * /run. */ - bool is_nm_generated:1; - bool is_volatile:1; - - /* whether this is a tombstone to hide a UUID (via the loaded uuid symlinks). - * If this is falls, the storage contains a profile, though note that - * the connection field will be cleared when it's not used. So, a non-tombstone - * has a connection in principle, but the connection field may still be %NULL. - * - * Note that a tombstone instance doesn't have a connection, but NMSettings - * considers it alive because is_tombstone is %TRUE. That means, once a tombstone - * gets removed, this flag is cleared. Then the storage instance has no connnection - * and is no longer a tombstone, and NMSettings considers it ready for deletion. - */ - bool is_tombstone:1; + union { + struct { + NMConnection *connection; + + /* when we move a profile from permanent storage to unsaved (/run), then + * we may leave the profile on disk (depending on options for Update2()). + * + * Later, when we save the profile again to disk, we want to re-use that filename. + * Likewise, we delete the (now in-memory) profile, we may want to also delete + * the original filename. + * + * This is the original filename, and we store it inside [.nmmeta] in the + * keyfile in /run. Note that we don't store this in the .nmmeta file, because + * the information is tied to the particular keyfile in /run, not to all UUIDs + * in general. */ + char *shadowed_storage; + + /* the timestamp (stat's mtime) of the keyfile. For meta-data this + * is irrelevant. The purpose is that if the same storage type (directory) has + * multiple files with the same UUID, then the newer file gets preferred. */ + struct timespec stat_mtime; + + /* these flags are only relevant for storages with %NMS_KEYFILE_STORAGE_TYPE_RUN + * (and non-metadata). This is to persist and reload these settings flags to + * /run. + * + * Note that these flags are not stored in as meta-data. The reason is that meta-data + * is per UUID. But these flags are only relevant for a particular keyfile on disk. + * That is, it must be tied to the actual keyfile, and not to the UUID. */ + bool is_nm_generated:1; + bool is_volatile:1; + + /* if shadowed_storage is set, then this flag indicates whether the file + * is owned. The difference comes into play when deleting the in-memory, + * shadowing profile: a owned profile will also be deleted. */ + bool shadowed_owned:1; + + } conn_data; + + /* the content from the .nmmeta file. Note that the nmmeta file has the UUID + * in the filename, that means there can be only two variants of this file: + * in /etc and in /run. As such, this is really meta-data about the entire profile + * (the UUID), and not about the individual keyfile. */ + NMSettingsMetaData meta_data; + + } u; + + /* The storage type. This is directly related to the filename. Since + * the filename cannot change, this value is unchanging. */ + const NMSKeyfileStorageType storage_type; + + /* whether union "u" has meta_data or conn_data. Since the type of the storage + * depends on the (immutable) filename, this is also const. */ + const bool is_meta_data; /* this flag is only used during reload to mark and prune old entries. */ - bool dirty:1; + bool is_dirty:1; } NMSKeyfileStorage; @@ -83,7 +117,8 @@ struct _NMSKeyfilePlugin; NMSKeyfileStorage *nms_keyfile_storage_new_tombstone (struct _NMSKeyfilePlugin *self, const char *uuid, const char *filename, - NMSKeyfileStorageType storage_type); + NMSKeyfileStorageType storage_type, + const char *shadowed_storage); NMSKeyfileStorage *nms_keyfile_storage_new_connection (struct _NMSKeyfilePlugin *self, NMConnection *connection_take /* pass reference */, @@ -91,6 +126,8 @@ NMSKeyfileStorage *nms_keyfile_storage_new_connection (struct _NMSKeyfilePlugin NMSKeyfileStorageType storage_type, NMTernary is_nm_generated_opt, NMTernary is_volatile_opt, + const char *shadowed_storage, + NMTernary shadowed_owned_opt, const struct timespec *stat_mtime); void nms_keyfile_storage_destroy (NMSKeyfileStorage *storage); @@ -132,18 +169,88 @@ nm_settings_storage_is_keyfile_lib (const NMSettingsStorage *self) && (((NMSKeyfileStorage *) self)->storage_type >= NMS_KEYFILE_STORAGE_TYPE_LIB_BASE); } -static inline gboolean -nm_settings_storage_is_keyfile_tombstone (const NMSettingsStorage *self) +static inline const NMSettingsMetaData * +nm_settings_storage_is_meta_data (const NMSettingsStorage *storage) { - /* Only keyfile storage supports tombstones. They indicate that a uuid - * is shadowed via a symlink to /dev/null. + const NMSKeyfileStorage *self; + + if (!NMS_IS_KEYFILE_STORAGE (storage)) + return NULL; + + self = (NMSKeyfileStorage *) storage; + + if (!self->is_meta_data) + return NULL; + + return &self->u.meta_data; +} + +static inline const NMSettingsMetaData * +nm_settings_storage_is_meta_data_alive (const NMSettingsStorage *storage) +{ + const NMSettingsMetaData *meta_data; + + meta_data = nm_settings_storage_is_meta_data (storage); + + if (!meta_data) + return NULL; + + /* Regular (all other) storages are alive as long as they report a NMConnection, and + * they will be dropped, once they have no more connection. * - * Note that tombstones don't have a NMConnection instead they shadow - * a UUID. As such, NMSettings considers them alive also if they have - * not profile. That means, when a tombstone gets removed for good, - * the is_tombstone must be cleared (so that it becomes truly dead). */ - return NMS_IS_KEYFILE_STORAGE (self) - && ((NMSKeyfileStorage *) self)->is_tombstone; + * Meta-data storages are special: they never report a NMConnection. + * So, a meta-data storage is alive as long as it is tracked by the + * settings plugin. + * + * This function is used to ckeck for that. */ + + if (c_list_is_empty (&storage->_storage_lst)) + return NULL; + + return meta_data; +} + +static inline const char * +nm_settings_storage_get_shadowed_storage (const NMSettingsStorage *storage, + gboolean *out_shadowed_owned) +{ + if (NMS_IS_KEYFILE_STORAGE (storage)) { + const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage; + + if (self->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) { + if (!self->is_meta_data) { + if (self->u.conn_data.shadowed_storage) { + NM_SET_OUT (out_shadowed_owned, self->u.conn_data.shadowed_owned); + return self->u.conn_data.shadowed_storage; + } + } else { + NM_SET_OUT (out_shadowed_owned, FALSE); + return self->u.meta_data.shadowed_storage; + } + } + } + + NM_SET_OUT (out_shadowed_owned, FALSE); + return NULL; +} + +static inline const char * +nm_settings_storage_get_filename_for_shadowed_storage (const NMSettingsStorage *storage) +{ + g_return_val_if_fail (NM_IS_SETTINGS_STORAGE (storage), NULL); + + if (!storage->_filename) + return NULL; + + if (NMS_IS_KEYFILE_STORAGE (storage)) { + const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage; + + if ( self->is_meta_data + || self->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC) + return NULL; + } + + return storage->_filename; } /*****************************************************************************/ diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.c b/src/settings/plugins/keyfile/nms-keyfile-utils.c index 0d03132793..ea03e1b611 100644 --- a/src/settings/plugins/keyfile/nms-keyfile-utils.c +++ b/src/settings/plugins/keyfile/nms-keyfile-utils.c @@ -24,6 +24,7 @@ #include <stdlib.h> #include <sys/stat.h> +#include "nm-glib-aux/nm-io-utils.h" #include "nm-keyfile-internal.h" #include "nm-utils.h" #include "nm-setting-wired.h" @@ -33,6 +34,13 @@ /*****************************************************************************/ +#define NMMETA_KF_GROUP_NAME_NMMETA "nmmeta" +#define NMMETA_KF_KEY_NAME_NMMETA_UUID "uuid" +#define NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH "loaded-path" +#define NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE "shadowed-storage" + +/*****************************************************************************/ + const char * nms_keyfile_nmmeta_check_filename (const char *filename, guint *out_uuid_len) @@ -106,12 +114,16 @@ nms_keyfile_nmmeta_read (const char *dirname, char **out_full_filename, char **out_uuid, char **out_loaded_path, + char **out_shadowed_storage, struct stat *out_st) { const char *uuid; guint uuid_len; gs_free char *full_filename = NULL; - gs_free char *ln = NULL; + gs_free char *loaded_path = NULL; + gs_free char *shadowed_storage = NULL; + struct stat st_stack; + struct stat *st = out_st ?: &st_stack; nm_assert (dirname && dirname[0] == '/'); nm_assert (filename && filename[0] && !strchr (filename, '/')); @@ -122,19 +134,45 @@ nms_keyfile_nmmeta_read (const char *dirname, full_filename = g_build_filename (dirname, filename, NULL); - if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_NMLOADED, + if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_NMMETA, full_filename, - out_st, + st, NULL)) return FALSE; - ln = nm_utils_read_link_absolute (full_filename, NULL); - if (!ln) - return FALSE; + if (S_ISREG (st->st_mode)) { + gs_unref_keyfile GKeyFile *kf = NULL; + gs_free char *v_uuid = NULL; + + kf = g_key_file_new (); + + if (!g_key_file_load_from_file (kf, full_filename, G_KEY_FILE_NONE, NULL)) + return FALSE; + + v_uuid = g_key_file_get_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_UUID, NULL); + if (!nm_streq0 (v_uuid, uuid)) + return FALSE; + + loaded_path = g_key_file_get_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH, NULL); + shadowed_storage = g_key_file_get_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE, NULL); + + if ( !loaded_path + && !shadowed_storage) { + /* if there is no useful information in the file, it is the same as if + * the file is not present. Signal failure. */ + return FALSE; + } + + } else { + loaded_path = nm_utils_read_link_absolute (full_filename, NULL); + if (!loaded_path) + return FALSE; + } NM_SET_OUT (out_uuid, g_strndup (uuid, uuid_len)); NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename)); - NM_SET_OUT (out_loaded_path, g_steal_pointer (&ln)); + NM_SET_OUT (out_loaded_path, g_steal_pointer (&loaded_path)); + NM_SET_OUT (out_shadowed_storage, g_steal_pointer (&shadowed_storage)); return TRUE; } @@ -143,7 +181,8 @@ nms_keyfile_nmmeta_read_from_file (const char *full_filename, char **out_dirname, char **out_filename, char **out_uuid, - char **out_loaded_path) + char **out_loaded_path, + char **out_shadowed_storage) { gs_free char *dirname = NULL; gs_free char *filename = NULL; @@ -158,6 +197,7 @@ nms_keyfile_nmmeta_read_from_file (const char *full_filename, NULL, out_uuid, out_loaded_path, + out_shadowed_storage, NULL)) return FALSE; @@ -170,7 +210,8 @@ gboolean nms_keyfile_nmmeta_write (const char *dirname, const char *uuid, const char *loaded_path, - gboolean allow_relative, + gboolean loaded_path_allow_relative, + const char *shadowed_storage, char **out_full_filename) { gs_free char *full_filename_tmp = NULL; @@ -180,6 +221,7 @@ nms_keyfile_nmmeta_write (const char *dirname, nm_assert ( nm_utils_is_uuid (uuid) && !strchr (uuid, '/')); nm_assert (!loaded_path || loaded_path[0] == '/'); + nm_assert (!shadowed_storage || loaded_path); full_filename_tmp = nms_keyfile_nmmeta_filename (dirname, uuid, TRUE); @@ -198,7 +240,7 @@ nms_keyfile_nmmeta_write (const char *dirname, return success; } - if (allow_relative) { + if (loaded_path_allow_relative) { const char *f; f = nm_utils_file_is_in_path (loaded_path, dirname); @@ -209,18 +251,40 @@ nms_keyfile_nmmeta_write (const char *dirname, } } - if (symlink (loaded_path, full_filename_tmp) != 0) { - full_filename_tmp[strlen (full_filename_tmp) - 1] = '\0'; - NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp)); - return FALSE; - } + full_filename = g_strndup (full_filename_tmp, strlen (full_filename_tmp) - 1); - full_filename = g_strdup (full_filename_tmp); - full_filename[strlen (full_filename) - 1] = '\0'; - if (rename (full_filename_tmp, full_filename) != 0) { - (void) unlink (full_filename_tmp); - NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename)); - return FALSE; + if (shadowed_storage) { + gs_unref_keyfile GKeyFile *kf = NULL; + gs_free char *contents = NULL; + gsize length; + + kf = g_key_file_new (); + + g_key_file_set_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_UUID, uuid); + g_key_file_set_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH, loaded_path); + g_key_file_set_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE, shadowed_storage); + + contents = g_key_file_to_data (kf, &length, NULL); + + if (!nm_utils_file_set_contents (full_filename, contents, length, 0600, NULL)) { + NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp)); + return FALSE; + } + } else { + /* we only have the "loaded_path" to store. That is commonly used for the tombstones to + * link to /dev/null. A symlink is sufficient to store that ammount of information. + * No need to bother with a keyfile. */ + if (symlink (loaded_path, full_filename_tmp) != 0) { + full_filename_tmp[strlen (full_filename_tmp) - 1] = '\0'; + NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp)); + return FALSE; + } + + if (rename (full_filename_tmp, full_filename) != 0) { + (void) unlink (full_filename_tmp); + NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename)); + return FALSE; + } } NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename)); @@ -242,10 +306,11 @@ nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype, "file is not a regular file"); return FALSE; } - } else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) { - if (!S_ISLNK (st->st_mode)) { + } else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) { + if ( !S_ISLNK (st->st_mode) + && !S_ISREG (st->st_mode)) { g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, - "file is not a slink"); + "file is neither a symlink nor a regular file"); return FALSE; } } else @@ -259,7 +324,7 @@ nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype, return FALSE; } - if ( filetype == NMS_KEYFILE_FILETYPE_KEYFILE + if ( S_ISREG (st->st_mode) && (st->st_mode & 0077)) { g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, "File permissions (%03o) are insecure", @@ -289,7 +354,7 @@ nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype, "cannot access file: %s", nm_strerror_native (errsv)); return FALSE; } - } else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) { + } else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) { if (lstat (filename, &st) != 0) { errsv = errno; g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION, diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.h b/src/settings/plugins/keyfile/nms-keyfile-utils.h index 1f5280f727..723c443625 100644 --- a/src/settings/plugins/keyfile/nms-keyfile-utils.h +++ b/src/settings/plugins/keyfile/nms-keyfile-utils.h @@ -24,7 +24,7 @@ typedef enum { NMS_KEYFILE_FILETYPE_KEYFILE, - NMS_KEYFILE_FILETYPE_NMLOADED, + NMS_KEYFILE_FILETYPE_NMMETA, } NMSKeyfileFiletype; typedef enum { @@ -56,18 +56,21 @@ gboolean nms_keyfile_nmmeta_read (const char *dirname, char **out_full_filename, char **out_uuid, char **out_loaded_path, + char **out_shadowed_storage, struct stat *out_st); gboolean nms_keyfile_nmmeta_read_from_file (const char *full_filename, char **out_dirname, char **out_filename, char **out_uuid, - char **out_loaded_path); + char **out_loaded_path, + char **out_shadowed_storage); gboolean nms_keyfile_nmmeta_write (const char *dirname, const char *uuid, const char *loaded_path, - gboolean allow_relative, + gboolean loaded_path_allow_relative, + const char *shadowed_storage, char **out_full_filename); /*****************************************************************************/ diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c index 54e62b7373..5fbbb7a1c8 100644 --- a/src/settings/plugins/keyfile/nms-keyfile-writer.c +++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c @@ -170,6 +170,8 @@ static gboolean _internal_write_connection (NMConnection *connection, gboolean is_nm_generated, gboolean is_volatile, + const char *shadowed_storage, + gboolean shadowed_owned, const char *keyfile_dir, const char *profile_dir, gboolean with_extension, @@ -201,6 +203,8 @@ _internal_write_connection (NMConnection *connection, nm_assert (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS); + nm_assert (!shadowed_owned || shadowed_storage); + rename = force_rename || existing_path_read_only || ( existing_path @@ -229,6 +233,20 @@ _internal_write_connection (NMConnection *connection, TRUE); } + if (shadowed_storage) { + g_key_file_set_string (kf_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE, + shadowed_storage); + } + + if (shadowed_owned) { + g_key_file_set_boolean (kf_file, + NM_KEYFILE_GROUP_NMMETA, + NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED, + TRUE); + } + kf_content_buf = g_key_file_to_data (kf_file, &kf_content_len, error); if (!kf_content_buf) return FALSE; @@ -356,6 +374,8 @@ gboolean nms_keyfile_writer_connection (NMConnection *connection, gboolean is_nm_generated, gboolean is_volatile, + const char *shadowed_storage, + gboolean shadowed_owned, const char *keyfile_dir, const char *profile_dir, const char *existing_path, @@ -371,6 +391,8 @@ nms_keyfile_writer_connection (NMConnection *connection, return _internal_write_connection (connection, is_nm_generated, is_volatile, + shadowed_storage, + shadowed_owned, keyfile_dir, profile_dir, TRUE, @@ -400,6 +422,8 @@ nms_keyfile_writer_test_connection (NMConnection *connection, return _internal_write_connection (connection, FALSE, FALSE, + NULL, + FALSE, keyfile_dir, keyfile_dir, FALSE, diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.h b/src/settings/plugins/keyfile/nms-keyfile-writer.h index 4fb9a20638..99e86025ae 100644 --- a/src/settings/plugins/keyfile/nms-keyfile-writer.h +++ b/src/settings/plugins/keyfile/nms-keyfile-writer.h @@ -29,6 +29,8 @@ typedef gboolean (*NMSKeyfileWriterAllowFilenameCb) (const char *check_filename, gboolean nms_keyfile_writer_connection (NMConnection *connection, gboolean is_nm_generated, gboolean is_volatile, + const char *shadowed_storage, + gboolean shadowed_owned, const char *keyfile_dir, const char *profile_dir, const char *existing_path, diff --git a/src/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/settings/plugins/keyfile/tests/test-keyfile-settings.c index 5942f04bb1..f96111a2e4 100644 --- a/src/settings/plugins/keyfile/tests/test-keyfile-settings.c +++ b/src/settings/plugins/keyfile/tests/test-keyfile-settings.c @@ -75,6 +75,8 @@ check_ip_route (NMSettingIPConfig *config, int idx, const char *destination, int NULL, \ NULL, \ NULL, \ + NULL, \ + NULL, \ (nmtst_get_rand_uint32 () % 2) ? &_error : NULL); \ nmtst_assert_success (_connection, _error); \ nmtst_assert_connection_verifies_without_normalization (_connection); \ @@ -2541,7 +2543,7 @@ _assert_keyfile_nmmeta (const char *dirname, nm_clear_g_free (&full_filename); - g_assert (nms_keyfile_nmmeta_write (dirname, uuid, loaded_path, allow_relative, &full_filename)); + g_assert (nms_keyfile_nmmeta_write (dirname, uuid, loaded_path, allow_relative, NULL, &full_filename)); g_assert_cmpstr (full_filename, ==, exp_full_filename); nm_clear_g_free (&full_filename); @@ -2553,7 +2555,7 @@ _assert_keyfile_nmmeta (const char *dirname, g_assert_cmpstr (symlink_target, ==, exp_symlink_target); - success = nms_keyfile_nmmeta_read (dirname, filename, &full_filename, &uuid2, &loaded_path2, NULL); + success = nms_keyfile_nmmeta_read (dirname, filename, &full_filename, &uuid2, &loaded_path2, NULL, NULL); g_assert_cmpint (!!exp_uuid, ==, success); if (success) g_assert_cmpstr (full_filename, ==, exp_full_filename); @@ -2564,7 +2566,7 @@ _assert_keyfile_nmmeta (const char *dirname, g_assert_cmpstr (loaded_path2, ==, exp_loaded_path); - success = nms_keyfile_nmmeta_read_from_file (exp_full_filename, &dirname3, &filename3, &uuid3, &loaded_path3); + success = nms_keyfile_nmmeta_read_from_file (exp_full_filename, &dirname3, &filename3, &uuid3, &loaded_path3, NULL); g_assert_cmpint (!!exp_uuid, ==, success); if (success) { g_assert_cmpstr (dirname3, ==, dirname); |