settings: support storing "shadowed-storage" to .nmmeta files

Before, the .nmmeta file could only contain one piece of information:
the loaded-path. This was persisted to disk by writing a "$UUID.nmmeta"
symlink that links to the loaded-path. Also, in practice this is used
for tombstones, so the only valid loaded-path is "/dev/null" (all other
paths are ignored).

Extend the .nmmeta file format to also be able to store additional data: the
shadowed-storage path. We will need that later but the idea is that if
we have a tombstone on disk, then this tombstone might explicitly shadow
another file. The use is when re-adding a profile with the same UUID, then
the existing storage is used (instead of creating a new file). This will
be necessary with Update2(NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED)
flag. This flag first allows to clone a profile from persistent storage
to a profile in /run. Later, when this profile gets deleted, the
original profile will be left on disk. If the same profile then gets
re-created with AddConnection(), then the original filename must be
taken over again. This is to avoid duplication of profiles on disk.

Note that this piece of information is relevent per-UUID, and as such
it's correct to store it in the .nmmeta file. That is related to the
"shadowed-storage" information that we store in the [.nmmeta] section
of keyfiles.
This commit is contained in:
Thomas Haller 2019-07-24 16:26:16 +02:00
parent e3b5b1e64b
commit 064544cc07
8 changed files with 137 additions and 37 deletions

View file

@ -1914,6 +1914,7 @@ nm_settings_delete_connection (NMSettings *self,
uuid,
FALSE,
TRUE,
NULL,
&tombstone_1_storage,
NULL))
tombstone_in_memory = TRUE;
@ -1927,6 +1928,7 @@ nm_settings_delete_connection (NMSettings *self,
uuid,
TRUE,
TRUE,
NULL,
&tombstone_2_storage,
NULL)) {
nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
@ -1934,6 +1936,7 @@ nm_settings_delete_connection (NMSettings *self,
uuid,
TRUE,
TRUE,
NULL,
&tombstone_2_storage,
NULL);
}

View file

@ -309,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)
@ -322,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");
@ -349,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);
@ -1052,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
@ -1076,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)
{
@ -1092,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);
@ -1104,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);
@ -1123,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
@ -1152,8 +1162,12 @@ 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);

View file

@ -68,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);

View file

@ -46,9 +46,13 @@ nms_keyfile_storage_copy_content (NMSKeyfileStorage *dst,
nm_assert (dst->storage_type == src->storage_type);
nm_assert (dst->is_meta_data == src->is_meta_data);
if (dst->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;
else {
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;
@ -140,7 +144,8 @@ 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;
@ -152,6 +157,8 @@ nms_keyfile_storage_new_tombstone (NMSKeyfilePlugin *plugin,
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;
}
@ -200,7 +207,9 @@ _storage_clear (NMSKeyfileStorage *self)
{
c_list_unlink (&self->parent._storage_lst);
c_list_unlink (&self->parent._storage_by_uuid_lst);
if (!self->is_meta_data) {
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;

View file

@ -35,6 +35,7 @@
typedef struct {
/* whether this is a tombstone to hide a UUID (via symlink to /dev/null). */
char *shadowed_storage;
bool is_tombstone:1;
} NMSettingsMetaData;
@ -116,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 */,
@ -221,6 +223,9 @@ nm_settings_storage_get_shadowed_storage (const NMSettingsStorage *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;
}
}
}

View file

@ -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, '/'));
@ -124,17 +136,43 @@ nms_keyfile_nmmeta_read (const char *dirname,
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));
@ -243,9 +307,10 @@ nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype,
return FALSE;
}
} else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) {
if (!S_ISLNK (st->st_mode)) {
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",

View file

@ -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);
/*****************************************************************************/

View file

@ -2543,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);
@ -2555,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);
@ -2566,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);