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

#ifndef __NMS_KEYFILE_STORAGE_H__
#define __NMS_KEYFILE_STORAGE_H__

#include "c-list/src/c-list.h"
#include "settings/nm-settings-storage.h"
#include "nms-keyfile-utils.h"

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

#define NMS_TYPE_KEYFILE_STORAGE (nms_keyfile_storage_get_type())
#define NMS_KEYFILE_STORAGE(obj) \
    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorage))
#define NMS_KEYFILE_STORAGE_CLASS(klass) \
    (G_TYPE_CHECK_CLASS_CAST((klass), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass))
#define NMS_IS_KEYFILE_STORAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMS_TYPE_KEYFILE_STORAGE))
#define NMS_IS_KEYFILE_STORAGE_CLASS(klass) \
    (G_TYPE_CHECK_CLASS_TYPE((klass), NMS_TYPE_KEYFILE_STORAGE))
#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)
     * that don't have a connection.
     *
     * 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. */
    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;
            bool is_external : 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 is_dirty : 1;

} NMSKeyfileStorage;

typedef struct _NMSKeyfileStorageClass NMSKeyfileStorageClass;

GType nms_keyfile_storage_get_type(void);

struct _NMSKeyfilePlugin;

NMSKeyfileStorage *nms_keyfile_storage_new_tombstone(struct _NMSKeyfilePlugin *self,
                                                     const char *              uuid,
                                                     const char *              filename,
                                                     NMSKeyfileStorageType     storage_type,
                                                     const char *              shadowed_storage);

NMSKeyfileStorage *
nms_keyfile_storage_new_connection(struct _NMSKeyfilePlugin *self,
                                   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);

void nms_keyfile_storage_destroy(NMSKeyfileStorage *storage);

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

void nms_keyfile_storage_copy_content(NMSKeyfileStorage *dst, const NMSKeyfileStorage *src);

NMConnection *nms_keyfile_storage_steal_connection(NMSKeyfileStorage *storage);

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

static inline const char *
nms_keyfile_storage_get_uuid(const NMSKeyfileStorage *self)
{
    return nm_settings_storage_get_uuid((const NMSettingsStorage *) self);
}

static inline const char *
nms_keyfile_storage_get_filename(const NMSKeyfileStorage *self)
{
    return nm_settings_storage_get_filename((const NMSettingsStorage *) self);
}

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

static inline gboolean
nm_settings_storage_is_keyfile_run(const NMSettingsStorage *self)
{
    return NMS_IS_KEYFILE_STORAGE(self)
           && (((NMSKeyfileStorage *) self)->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN);
}

static inline gboolean
nm_settings_storage_is_keyfile_lib(const NMSettingsStorage *self)
{
    return NMS_IS_KEYFILE_STORAGE(self)
           && (((NMSKeyfileStorage *) self)->storage_type >= NMS_KEYFILE_STORAGE_TYPE_LIB_BASE);
}

static inline const NMSettingsMetaData *
nm_settings_storage_is_meta_data(const NMSettingsStorage *storage)
{
    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.
     *
     * 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;
}

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

enum _NMSettingsConnectionIntFlags;

void nm_settings_storage_load_sett_flags(NMSettingsStorage *                 self,
                                         enum _NMSettingsConnectionIntFlags *sett_flags,
                                         enum _NMSettingsConnectionIntFlags *sett_mask);

#endif /* __NMS_KEYFILE_STORAGE_H__ */
