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

#include "src/core/nm-default-daemon.h"

#include "nms-keyfile-storage.h"

#include "libnm-glib-aux/nm-uuid.h"
#include "nm-utils.h"
#include "libnm-core-intern/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_uuid_is_normalized(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_uuid_is_normalized(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;
}
