// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2018 Red Hat, Inc.
 */

#include "nm-default.h"

#include "nms-keyfile-storage.h"

#include "nm-utils.h"
#include "nm-core-internal.h"
#include "nms-keyfile-plugin.h"

/*****************************************************************************/

struct _NMSKeyfileStorageClass {
	NMSettingsStorageClass parent;
};

G_DEFINE_TYPE (NMSKeyfileStorage, nms_keyfile_storage, NM_TYPE_SETTINGS_STORAGE)

/*****************************************************************************/

void
nms_keyfile_storage_copy_content (NMSKeyfileStorage *dst,
                                  const NMSKeyfileStorage *src)
{
	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_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->is_meta_data
	           || NM_IS_CONNECTION (self->u.conn_data.connection));

	return   self->is_meta_data
	       ? NULL
	       : g_steal_pointer (&self->u.conn_data.connection);
}

/*****************************************************************************/

static int
cmp_fcn (const NMSKeyfileStorage *a,
         const NMSKeyfileStorage *b)
{
	nm_assert (NMS_IS_KEYFILE_STORAGE (a));
	nm_assert (NMS_IS_KEYFILE_STORAGE (b));
	nm_assert (a != b);

	/* sort by storage-type, which also has a numeric value according to their
	 * (inverse) priority. */
	NM_CMP_FIELD_UNSAFE (b, a, storage_type);

	/* meta-data is more important. */
	NM_CMP_FIELD_UNSAFE (a, b, is_meta_data);

	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));
	}

	return 0;
}

/*****************************************************************************/

static void
nms_keyfile_storage_init (NMSKeyfileStorage *self)
{
}

static NMSKeyfileStorage *
_storage_new (NMSKeyfilePlugin *plugin,
              const char *uuid,
              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] == '/');

	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,
                                   const char *shadowed_storage)
{
	NMSKeyfileStorage *self;

	nm_assert (nm_utils_is_uuid (uuid));
	nm_assert (filename && filename[0] == '/');
	nm_assert (nms_keyfile_nmmeta_check_filename (filename, NULL));
	nm_assert (NM_IN_SET (storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC,
	                                    NMS_KEYFILE_STORAGE_TYPE_RUN));

	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;
}

NMSKeyfileStorage *
nms_keyfile_storage_new_connection (NMSKeyfilePlugin *plugin,
                                    NMConnection *connection_take /* pass reference */,
                                    const char *filename,
                                    NMSKeyfileStorageType storage_type,
                                    NMTernary is_nm_generated_opt,
                                    NMTernary is_volatile_opt,
                                    NMTernary is_external_opt,
                                    const char *shadowed_storage,
                                    NMTernary shadowed_owned_opt,
                                    const struct timespec *stat_mtime)
{
	NMSKeyfileStorage *self;

	nm_assert (NMS_IS_KEYFILE_PLUGIN (plugin));
	nm_assert (NM_IS_CONNECTION (connection_take));
	nm_assert (_nm_connection_verify (connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS);
	nm_assert (filename && filename[0] == '/');
	nm_assert (   storage_type >= NMS_KEYFILE_STORAGE_TYPE_RUN
	           && 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, FALSE, storage_type);

	self->u.conn_data.connection = connection_take; /* take reference. */

	self->u.conn_data.shadowed_storage = g_strdup (shadowed_storage);

	if (stat_mtime)
		self->u.conn_data.stat_mtime = *stat_mtime;

	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.is_external     = (is_external_opt == NM_TERNARY_TRUE);
		self->u.conn_data.shadowed_owned  =    shadowed_storage
		                                    && (shadowed_owned_opt == NM_TERNARY_TRUE);
	}

	return self;
}

static void
_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)
		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
dispose (GObject *object)
{
	NMSKeyfileStorage *self = NMS_KEYFILE_STORAGE (object);

	_storage_clear (self);

	G_OBJECT_CLASS (nms_keyfile_storage_parent_class)->dispose (object);
}

void
nms_keyfile_storage_destroy (NMSKeyfileStorage *self)
{
	_storage_clear (self);
	g_object_unref (self);
}

static void
nms_keyfile_storage_class_init (NMSKeyfileStorageClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	NMSettingsStorageClass *storage_class = NM_SETTINGS_STORAGE_CLASS (klass);

	object_class->dispose = dispose;

	storage_class->cmp_fcn = (int (*) (NMSettingsStorage *, NMSettingsStorage *)) cmp_fcn;
}

/*****************************************************************************/

#include "settings/nm-settings-connection.h"

void
nm_settings_storage_load_sett_flags (NMSettingsStorage *self,
                                     NMSettingsConnectionIntFlags *sett_flags,
                                     NMSettingsConnectionIntFlags *sett_mask)
{
	NMSKeyfileStorage *s;

	*sett_flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
	*sett_mask =   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
	             | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
	             | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL;

	if (!NMS_IS_KEYFILE_STORAGE (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->u.conn_data.is_nm_generated)
		*sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED;

	if (s->u.conn_data.is_volatile)
		*sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;

	if (s->u.conn_data.is_external)
		*sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL;
}
