/*
   Copyright (c) 2014 Red Hat, Inc. <http://www.redhat.com>
   This file is part of GlusterFS.

   This file is licensed to you under your choice of the GNU Lesser
   General Public License, version 3 or any later version (LGPLv3 or
   later), or the GNU General Public License, version 2 (GPLv2), in all
   cases as published by the Free Software Foundation.
*/
#include "snapview-server.h"
#include "snapview-server-mem-types.h"

#include <glusterfs/xlator.h>
#include "rpc-clnt.h"
#include "xdr-generic.h"
#include "protocol-common.h"
#include <pthread.h>

int
__svs_inode_ctx_set(xlator_t *this, inode_t *inode, svs_inode_t *svs_inode)
{
    uint64_t value = 0;
    int ret = -1;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, inode, out);
    GF_VALIDATE_OR_GOTO(this->name, svs_inode, out);

    value = (uint64_t)(long)svs_inode;

    ret = __inode_ctx_set(inode, this, &value);

out:
    return ret;
}

svs_inode_t *
__svs_inode_ctx_get(xlator_t *this, inode_t *inode)
{
    svs_inode_t *svs_inode = NULL;
    uint64_t value = 0;
    int ret = -1;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, inode, out);

    ret = __inode_ctx_get(inode, this, &value);
    if (ret)
        goto out;

    svs_inode = (svs_inode_t *)((long)value);

out:
    return svs_inode;
}

svs_inode_t *
svs_inode_ctx_get(xlator_t *this, inode_t *inode)
{
    svs_inode_t *svs_inode = NULL;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, inode, out);

    LOCK(&inode->lock);
    {
        svs_inode = __svs_inode_ctx_get(this, inode);
    }
    UNLOCK(&inode->lock);

out:
    return svs_inode;
}

int32_t
svs_inode_ctx_set(xlator_t *this, inode_t *inode, svs_inode_t *svs_inode)
{
    int32_t ret = -1;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, inode, out);
    GF_VALIDATE_OR_GOTO(this->name, svs_inode, out);

    LOCK(&inode->lock);
    {
        ret = __svs_inode_ctx_set(this, inode, svs_inode);
    }
    UNLOCK(&inode->lock);

out:
    return ret;
}

svs_inode_t *
svs_inode_new(void)
{
    svs_inode_t *svs_inode = NULL;

    svs_inode = GF_CALLOC(1, sizeof(*svs_inode), gf_svs_mt_svs_inode_t);

    return svs_inode;
}

svs_inode_t *
svs_inode_ctx_get_or_new(xlator_t *this, inode_t *inode)
{
    svs_inode_t *svs_inode = NULL;
    int ret = -1;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, inode, out);

    LOCK(&inode->lock);
    {
        svs_inode = __svs_inode_ctx_get(this, inode);
        if (!svs_inode) {
            svs_inode = svs_inode_new();
            if (svs_inode) {
                ret = __svs_inode_ctx_set(this, inode, svs_inode);
                if (ret) {
                    GF_FREE(svs_inode);
                    svs_inode = NULL;
                }
            }
        }
    }
    UNLOCK(&inode->lock);

out:
    return svs_inode;
}

svs_fd_t *
svs_fd_new(void)
{
    svs_fd_t *svs_fd = NULL;

    svs_fd = GF_CALLOC(1, sizeof(*svs_fd), gf_svs_mt_svs_fd_t);

    return svs_fd;
}

int
__svs_fd_ctx_set(xlator_t *this, fd_t *fd, svs_fd_t *svs_fd)
{
    uint64_t value = 0;
    int ret = -1;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, fd, out);
    GF_VALIDATE_OR_GOTO(this->name, svs_fd, out);

    value = (uint64_t)(long)svs_fd;

    ret = __fd_ctx_set(fd, this, value);

out:
    return ret;
}

svs_fd_t *
__svs_fd_ctx_get(xlator_t *this, fd_t *fd)
{
    svs_fd_t *svs_fd = NULL;
    uint64_t value = 0;
    int ret = -1;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, fd, out);

    ret = __fd_ctx_get(fd, this, &value);
    if (ret)
        return NULL;

    svs_fd = (svs_fd_t *)((long)value);

out:
    return svs_fd;
}

svs_fd_t *
svs_fd_ctx_get(xlator_t *this, fd_t *fd)
{
    svs_fd_t *svs_fd = NULL;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, fd, out);

    LOCK(&fd->lock);
    {
        svs_fd = __svs_fd_ctx_get(this, fd);
    }
    UNLOCK(&fd->lock);

out:
    return svs_fd;
}

int32_t
svs_fd_ctx_set(xlator_t *this, fd_t *fd, svs_fd_t *svs_fd)
{
    int32_t ret = -1;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, fd, out);
    GF_VALIDATE_OR_GOTO(this->name, svs_fd, out);

    LOCK(&fd->lock);
    {
        ret = __svs_fd_ctx_set(this, fd, svs_fd);
    }
    UNLOCK(&fd->lock);

out:
    return ret;
}

svs_fd_t *
__svs_fd_ctx_get_or_new(xlator_t *this, fd_t *fd)
{
    svs_fd_t *svs_fd = NULL;
    int ret = -1;
    glfs_t *fs = NULL;
    glfs_object_t *object = NULL;
    svs_inode_t *inode_ctx = NULL;
    glfs_fd_t *glfd = NULL;
    inode_t *inode = NULL;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, fd, out);

    inode = fd->inode;
    svs_fd = __svs_fd_ctx_get(this, fd);
    if (svs_fd) {
        ret = 0;
        goto out;
    }

    svs_fd = svs_fd_new();
    if (!svs_fd) {
        gf_msg(this->name, GF_LOG_ERROR, 0, SVS_MSG_NEW_FD_CTX_FAILED,
               "failed to allocate new fd "
               "context for gfid %s",
               uuid_utoa(inode->gfid));
        goto out;
    }

    if (fd_is_anonymous(fd)) {
        inode_ctx = svs_inode_ctx_get(this, inode);
        if (!inode_ctx) {
            gf_msg(this->name, GF_LOG_ERROR, 0,
                   SVS_MSG_GET_INODE_CONTEXT_FAILED,
                   "failed to get inode "
                   "context for %s",
                   uuid_utoa(inode->gfid));
            goto out;
        }

        fs = inode_ctx->fs;
        object = inode_ctx->object;

        if (inode->ia_type == IA_IFDIR) {
            glfd = glfs_h_opendir(fs, object);
            if (!glfd) {
                gf_msg(this->name, GF_LOG_ERROR, errno, SVS_MSG_OPENDIR_FAILED,
                       "failed to "
                       "open the directory %s",
                       uuid_utoa(inode->gfid));
                goto out;
            }
        }

        if (inode->ia_type == IA_IFREG) {
            glfd = glfs_h_open(fs, object, O_RDONLY | O_LARGEFILE);
            if (!glfd) {
                gf_msg(this->name, GF_LOG_ERROR, errno, SVS_MSG_OPEN_FAILED,
                       "failed to "
                       "open the file %s",
                       uuid_utoa(inode->gfid));
                goto out;
            }
        }

        svs_fd->fd = glfd;
    }

    ret = __svs_fd_ctx_set(this, fd, svs_fd);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, 0, SVS_MSG_SET_FD_CONTEXT_FAILED,
               "failed to set fd context "
               "for gfid %s",
               uuid_utoa(inode->gfid));
        if (svs_fd->fd) {
            if (inode->ia_type == IA_IFDIR) {
                ret = glfs_closedir(svs_fd->fd);
                if (ret)
                    gf_msg(this->name, GF_LOG_ERROR, errno,
                           SVS_MSG_CLOSEDIR_FAILED,
                           "failed to close the fd for %s",
                           uuid_utoa(inode->gfid));
            }
            if (inode->ia_type == IA_IFREG) {
                ret = glfs_close(svs_fd->fd);
                if (ret)
                    gf_msg(this->name, GF_LOG_ERROR, 0, SVS_MSG_CLOSE_FAILED,
                           "failed to close the fd for %s",
                           uuid_utoa(inode->gfid));
            }
        }
        ret = -1;
    }

out:
    if (ret) {
        GF_FREE(svs_fd);
        svs_fd = NULL;
    }

    return svs_fd;
}

svs_fd_t *
svs_fd_ctx_get_or_new(xlator_t *this, fd_t *fd)
{
    svs_fd_t *svs_fd = NULL;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, fd, out);

    LOCK(&fd->lock);
    {
        svs_fd = __svs_fd_ctx_get_or_new(this, fd);
    }
    UNLOCK(&fd->lock);

out:
    return svs_fd;
}

int
svs_uuid_generate(xlator_t *this, uuid_t gfid, char *snapname,
                  uuid_t origin_gfid)
{
    char ino_string[NAME_MAX + 32] = "";
    uuid_t tmp = {
        0,
    };
    int ret = -1;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, snapname, out);

    (void)snprintf(ino_string, sizeof(ino_string), "%s%s", snapname,
                   uuid_utoa(origin_gfid));

    if (gf_gfid_generate_from_xxh64(tmp, ino_string)) {
        gf_msg(this->name, GF_LOG_WARNING, 0, SVS_MSG_GFID_GEN_FAILED,
               "failed to generate "
               "gfid for object with actual gfid of %s "
               "(snapname: %s, key: %s)",
               uuid_utoa(origin_gfid), snapname, ino_string);
        goto out;
    }

    gf_uuid_copy(gfid, tmp);

    ret = 0;

    gf_msg_debug(this->name, 0, "gfid generated is %s ", uuid_utoa(gfid));

out:
    return ret;
}

void
svs_fill_ino_from_gfid(struct iatt *buf)
{
    xlator_t *this = NULL;

    this = THIS;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, buf, out);

    /* consider least significant 8 bytes of value out of gfid */
    if (gf_uuid_is_null(buf->ia_gfid)) {
        buf->ia_ino = -1;
        goto out;
    }

    buf->ia_ino = gfid_to_ino(buf->ia_gfid);
out:
    return;
}

void
svs_iatt_fill(uuid_t gfid, struct iatt *buf)
{
    xlator_t *this = NULL;

    this = THIS;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, buf, out);

    buf->ia_type = IA_IFDIR;
    buf->ia_uid = 0;
    buf->ia_gid = 0;
    buf->ia_size = 0;
    buf->ia_nlink = 2;
    buf->ia_blocks = 8;
    buf->ia_size = 4096;

    gf_uuid_copy(buf->ia_gfid, gfid);
    svs_fill_ino_from_gfid(buf);

    buf->ia_prot = ia_prot_from_st_mode(0755);

    /* TODO: it would be better to use real times, but they need to be stable
     *       once created (i.e. mtime should only change when an entry has
     *       been added or removed for example. Otherwise the returned time
     *       should always be the same). */
    buf->ia_mtime = buf->ia_atime = buf->ia_ctime = 0;
    buf->ia_mtime_nsec = buf->ia_atime_nsec = buf->ia_ctime_nsec = 0;
out:
    return;
}

/* priv->snaplist_lock should be held before calling this function */
snap_dirent_t *
__svs_get_snap_dirent(xlator_t *this, const char *name)
{
    svs_private_t *private = NULL;
    int i = 0;
    snap_dirent_t *dirents = NULL;
    snap_dirent_t *tmp_dirent = NULL;
    snap_dirent_t *dirent = NULL;

   private
    = this->private;

    dirents = private->dirents;
    if (!dirents) {
        goto out;
    }

    tmp_dirent = dirents;
    for (i = 0; i < private->num_snaps; i++) {
        if (!strcmp(tmp_dirent->name, name)) {
            dirent = tmp_dirent;
            break;
        }
        tmp_dirent++;
    }

out:
    return dirent;
}

glfs_t *
__svs_initialise_snapshot_volume(xlator_t *this, const char *name,
                                 int32_t *op_errno)
{
    svs_private_t *priv = NULL;
    int32_t ret = -1;
    int32_t local_errno = ESTALE;
    snap_dirent_t *dirent = NULL;
    char volname[PATH_MAX] = {
        0,
    };
    glfs_t *fs = NULL;
    int loglevel = GF_LOG_INFO;
    char logfile[PATH_MAX] = {
        0,
    };
    char *volfile_server = NULL;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, this->private, out);
    GF_VALIDATE_OR_GOTO(this->name, name, out);

    priv = this->private;

    dirent = __svs_get_snap_dirent(this, name);
    if (!dirent) {
        gf_msg_debug(this->name, 0,
                     "snap entry for "
                     "name %s not found",
                     name);
        local_errno = ENOENT;
        goto out;
    }

    if (dirent->fs) {
        ret = 0;
        fs = dirent->fs;
        goto out;
    }

    snprintf(volname, sizeof(volname), "/snaps/%s/%s/%s", dirent->name,
             dirent->snap_volname, dirent->snap_volname);

    fs = glfs_new(volname);
    if (!fs) {
        local_errno = ENOMEM;
        gf_msg(this->name, GF_LOG_ERROR, local_errno, SVS_MSG_GLFS_NEW_FAILED,
               "glfs instance for snap volume %s "
               "failed",
               dirent->name);
        goto out;
    }

    /*
     * Before, localhost was used as the volfile server. But, with that
     * method, accessing snapshots started giving ENOENT error if a
     * specific bind address is mentioned in the glusterd volume file.
     * Check the bug https://bugzilla.redhat.com/show_bug.cgi?id=1725211.
     * So, the new method is tried below, where, snapview-server first
     * uses the volfile server used by the snapd (obtained from the
     * command line arguments saved in the global context of the process).
     * If the volfile server in global context is NULL, then localhost
     * is tried (like before).
     */
    if (this->ctx->cmd_args.volfile_server) {
        volfile_server = gf_strdup(this->ctx->cmd_args.volfile_server);
        if (!volfile_server) {
            gf_msg(this->name, GF_LOG_WARNING, ENOMEM,
                   SVS_MSG_VOLFILE_SERVER_GET_FAIL,
                   "failed to copy volfile server %s. ",
                   this->ctx->cmd_args.volfile_server);
            ret = -1;
            goto out;
        }
    } else {
        gf_msg(this->name, GF_LOG_WARNING, ENOMEM,
               SVS_MSG_VOLFILE_SERVER_GET_FAIL,
               "volfile server is NULL in cmd args. "
               "Trying with localhost");
        volfile_server = gf_strdup("localhost");
        if (!volfile_server) {
            gf_msg(this->name, GF_LOG_WARNING, ENOMEM,
                   SVS_MSG_VOLFILE_SERVER_GET_FAIL,
                   "failed to copy volfile server localhost.");
            ret = -1;
            goto out;
        }
    }

    ret = glfs_set_volfile_server(fs, "tcp", volfile_server, 24007);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, local_errno,
               SVS_MSG_SET_VOLFILE_SERVR_FAILED,
               "setting the "
               "volfile server %s for snap volume %s "
               "failed",
               volfile_server, dirent->name);
        goto out;
    }

    snprintf(logfile, sizeof(logfile),
             DEFAULT_SVD_LOG_FILE_DIRECTORY "/snaps/%s/%s-%s.log",
             priv->volname, name, dirent->uuid);

    ret = glfs_set_logging(fs, logfile, loglevel);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, local_errno,
               SVS_MSG_SET_LOGGING_FAILED,
               "failed to set the "
               "log file path");
        goto out;
    }

    ret = glfs_init(fs);
    if (ret) {
        gf_msg(this->name, GF_LOG_ERROR, local_errno, SVS_MSG_GLFS_INIT_FAILED,
               "initing the "
               "fs for %s failed",
               dirent->name);
        goto out;
    }

    ret = 0;

out:
    if (ret) {
        if (op_errno)
            *op_errno = local_errno;

        if (fs)
            glfs_fini(fs);
        fs = NULL;
    }

    if (fs) {
        dirent->fs = fs;
    }

    GF_FREE(volfile_server);
    return fs;
}

glfs_t *
svs_initialise_snapshot_volume(xlator_t *this, const char *name,
                               int32_t *op_errno)
{
    glfs_t *fs = NULL;
    svs_private_t *priv = NULL;

    GF_VALIDATE_OR_GOTO("snapview-server", this, out);
    GF_VALIDATE_OR_GOTO(this->name, this->private, out);
    GF_VALIDATE_OR_GOTO(this->name, name, out);

    priv = this->private;

    LOCK(&priv->snaplist_lock);
    {
        fs = __svs_initialise_snapshot_volume(this, name, op_errno);
    }
    UNLOCK(&priv->snaplist_lock);

out:

    return fs;
}

snap_dirent_t *
svs_get_latest_snap_entry(xlator_t *this)
{
    svs_private_t *priv = NULL;
    snap_dirent_t *dirents = NULL;
    snap_dirent_t *dirent = NULL;

    GF_VALIDATE_OR_GOTO("svs", this, out);

    priv = this->private;

    LOCK(&priv->snaplist_lock);
    {
        dirents = priv->dirents;
        if (!dirents) {
            goto unlock;
        }
        if (priv->num_snaps)
            dirent = &dirents[priv->num_snaps - 1];
    }
unlock:
    UNLOCK(&priv->snaplist_lock);

out:
    return dirent;
}

glfs_t *
svs_get_latest_snapshot(xlator_t *this)
{
    glfs_t *fs = NULL;
    snap_dirent_t *dirent = NULL;
    svs_private_t *priv = NULL;

    GF_VALIDATE_OR_GOTO("svs", this, out);
    priv = this->private;

    dirent = svs_get_latest_snap_entry(this);

    if (dirent) {
        LOCK(&priv->snaplist_lock);
        {
            fs = dirent->fs;
        }
        UNLOCK(&priv->snaplist_lock);
    }

out:
    return fs;
}

glfs_t *
svs_inode_ctx_glfs_mapping(xlator_t *this, svs_inode_t *inode_ctx)
{
    glfs_t *fs = NULL;

    GF_VALIDATE_OR_GOTO("svs", this, out);
    GF_VALIDATE_OR_GOTO(this->name, inode_ctx, out);

    fs = inode_ctx->fs;

    SVS_CHECK_VALID_SNAPSHOT_HANDLE(fs, this);

out:
    return fs;
}

glfs_t *
svs_inode_glfs_mapping(xlator_t *this, inode_t *inode)
{
    svs_inode_t *inode_ctx = NULL;
    glfs_t *fs = NULL;

    inode_ctx = svs_inode_ctx_get(this, inode);
    if (!inode_ctx) {
        gf_msg(this->name, GF_LOG_ERROR, 0, SVS_MSG_GET_INODE_CONTEXT_FAILED,
               "inode context not found for"
               " the inode %s",
               uuid_utoa(inode->gfid));
        goto out;
    }

    fs = svs_inode_ctx_glfs_mapping(this, inode_ctx);

out:
    return fs;
}
