/*
   Copyright (c) 2013 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 "gfid-access.h"
#include <glusterfs/inode.h>
#include <glusterfs/byte-order.h>
#include <glusterfs/statedump.h>

int
ga_valid_inode_loc_copy(loc_t *dst, loc_t *src, xlator_t *this)
{
    int ret = 0;
    uint64_t value = 0;

    /* if its an entry operation, on the virtual */
    /* directory inode as parent, we need to handle */
    /* it properly */
    ret = loc_copy(dst, src);
    if (ret < 0)
        goto out;

    /*
     * Change ALL virtual inodes with real-inodes in loc
     */
    if (dst->parent) {
        ret = inode_ctx_get(dst->parent, this, &value);
        if (ret < 0) {
            ret = 0;  // real-inode
            goto out;
        }
        inode_unref(dst->parent);
        dst->parent = inode_ref((inode_t *)(uintptr_t)value);
        gf_uuid_copy(dst->pargfid, dst->parent->gfid);
    }

    if (dst->inode) {
        ret = inode_ctx_get(dst->inode, this, &value);
        if (ret < 0) {
            ret = 0;  // real-inode
            goto out;
        }
        inode_unref(dst->inode);
        dst->inode = inode_ref((inode_t *)(uintptr_t)value);
        gf_uuid_copy(dst->gfid, dst->inode->gfid);
    }
out:

    return ret;
}

void
ga_newfile_args_free(ga_newfile_args_t *args)
{
    if (!args)
        goto out;

    GF_FREE(args->bname);

    if (S_ISLNK(args->st_mode) && args->args.symlink.linkpath) {
        GF_FREE(args->args.symlink.linkpath);
        args->args.symlink.linkpath = NULL;
    }

    mem_put(args);
out:
    return;
}

void
ga_heal_args_free(ga_heal_args_t *args)
{
    if (!args)
        goto out;

    GF_FREE(args->bname);

    mem_put(args);
out:
    return;
}

ga_newfile_args_t *
ga_newfile_parse_args(xlator_t *this, data_t *data)
{
    ga_newfile_args_t *args = NULL;
    ga_private_t *priv = NULL;
    int len = 0;
    int blob_len = 0;
    int min_len = 0;
    void *blob = NULL;

    priv = this->private;

    blob = data->data;
    blob_len = data->len;

    min_len = sizeof(args->uid) + sizeof(args->gid) + sizeof(args->gfid) +
              sizeof(args->st_mode) + 2 + 2;
    if (blob_len < min_len) {
        gf_log(this->name, GF_LOG_ERROR,
               "Invalid length: Total length is less "
               "than minimum length.");
        goto err;
    }

    args = mem_get0(priv->newfile_args_pool);
    if (args == NULL)
        goto err;

    args->uid = ntoh32(*(uint32_t *)blob);
    blob += sizeof(uint32_t);
    blob_len -= sizeof(uint32_t);

    args->gid = ntoh32(*(uint32_t *)blob);
    blob += sizeof(uint32_t);
    blob_len -= sizeof(uint32_t);

    memcpy(args->gfid, blob, sizeof(args->gfid));
    blob += sizeof(args->gfid);
    blob_len -= sizeof(args->gfid);

    args->st_mode = ntoh32(*(uint32_t *)blob);
    blob += sizeof(uint32_t);
    blob_len -= sizeof(uint32_t);

    len = strnlen(blob, blob_len);
    if (len == blob_len) {
        gf_log(this->name, GF_LOG_ERROR, "gfid: %s. No null byte present.",
               args->gfid);
        goto err;
    }

    args->bname = GF_MALLOC(len + 1, gf_common_mt_char);
    if (args->bname == NULL)
        goto err;

    memcpy(args->bname, blob, (len + 1));
    blob += (len + 1);
    blob_len -= (len + 1);

    if (S_ISDIR(args->st_mode)) {
        if (blob_len < sizeof(uint32_t)) {
            gf_log(this->name, GF_LOG_ERROR, "gfid: %s. Invalid length",
                   args->gfid);
            goto err;
        }
        args->args.mkdir.mode = ntoh32(*(uint32_t *)blob);
        blob += sizeof(uint32_t);
        blob_len -= sizeof(uint32_t);

        if (blob_len < sizeof(uint32_t)) {
            gf_log(this->name, GF_LOG_ERROR, "gfid: %s. Invalid length",
                   args->gfid);
            goto err;
        }
        args->args.mkdir.umask = ntoh32(*(uint32_t *)blob);
        blob_len -= sizeof(uint32_t);
        if (blob_len < 0) {
            gf_log(this->name, GF_LOG_ERROR, "gfid: %s. Invalid length",
                   args->gfid);
            goto err;
        }
    } else if (S_ISLNK(args->st_mode)) {
        len = strnlen(blob, blob_len);
        if (len == blob_len) {
            gf_log(this->name, GF_LOG_ERROR, "gfid: %s. Invalid length",
                   args->gfid);
            goto err;
        }
        args->args.symlink.linkpath = GF_MALLOC(len + 1, gf_common_mt_char);
        if (args->args.symlink.linkpath == NULL)
            goto err;

        memcpy(args->args.symlink.linkpath, blob, (len + 1));
        blob_len -= (len + 1);
    } else {
        if (blob_len < sizeof(uint32_t)) {
            gf_log(this->name, GF_LOG_ERROR, "gfid: %s. Invalid length",
                   args->gfid);
            goto err;
        }
        args->args.mknod.mode = ntoh32(*(uint32_t *)blob);
        blob += sizeof(uint32_t);
        blob_len -= sizeof(uint32_t);

        if (blob_len < sizeof(uint32_t)) {
            gf_log(this->name, GF_LOG_ERROR, "gfid: %s. Invalid length",
                   args->gfid);
            goto err;
        }
        args->args.mknod.rdev = ntoh32(*(uint32_t *)blob);
        blob += sizeof(uint32_t);
        blob_len -= sizeof(uint32_t);

        if (blob_len < sizeof(uint32_t)) {
            gf_log(this->name, GF_LOG_ERROR, "gfid: %s. Invalid length",
                   args->gfid);
            goto err;
        }
        args->args.mknod.umask = ntoh32(*(uint32_t *)blob);
        blob_len -= sizeof(uint32_t);
    }

    if (blob_len) {
        gf_log(this->name, GF_LOG_ERROR, "gfid: %s. Invalid length",
               args->gfid);
        goto err;
    }

    return args;

err:
    if (args)
        ga_newfile_args_free(args);

    return NULL;
}

ga_heal_args_t *
ga_heal_parse_args(xlator_t *this, data_t *data)
{
    ga_heal_args_t *args = NULL;
    ga_private_t *priv = NULL;
    void *blob = NULL;
    int len = 0;
    int blob_len = 0;

    blob = data->data;
    blob_len = data->len;

    priv = this->private;

    /* bname should at least contain a character */
    if (blob_len < (sizeof(args->gfid) + 2))
        goto err;

    args = mem_get0(priv->heal_args_pool);
    if (!args)
        goto err;

    memcpy(args->gfid, blob, sizeof(args->gfid));
    blob += sizeof(args->gfid);
    blob_len -= sizeof(args->gfid);

    len = strnlen(blob, blob_len);
    if (len == blob_len)
        goto err;

    args->bname = GF_MALLOC(len + 1, gf_common_mt_char);
    if (!args->bname)
        goto err;

    memcpy(args->bname, blob, len);
    args->bname[len] = '\0';
    blob_len -= (len + 1);

    if (blob_len)
        goto err;

    return args;

err:
    if (args)
        ga_heal_args_free(args);

    return NULL;
}

static int32_t
ga_fill_tmp_loc(loc_t *loc, xlator_t *this, uuid_t gfid, char *bname,
                dict_t *xdata, loc_t *new_loc)
{
    int ret = -1;
    uint64_t value = 0;
    inode_t *parent = NULL;
    unsigned char *gfid_ptr = NULL;

    parent = loc->inode;
    ret = inode_ctx_get(loc->inode, this, &value);
    if (!ret) {
        parent = (void *)(uintptr_t)value;
        if (gf_uuid_is_null(parent->gfid))
            parent = loc->inode;
    }

    /* parent itself should be looked up */
    gf_uuid_copy(new_loc->pargfid, parent->gfid);
    new_loc->parent = inode_ref(parent);

    new_loc->inode = inode_grep(parent->table, parent, bname);
    if (!new_loc->inode) {
        new_loc->inode = inode_new(parent->table);
        gf_uuid_copy(new_loc->inode->gfid, gfid);
    }

    loc_path(new_loc, bname);
    if (new_loc->path) {
        new_loc->name = strrchr(new_loc->path, '/');
        if (new_loc->name)
            new_loc->name++;
    }

    gfid_ptr = GF_MALLOC(sizeof(uuid_t), gf_common_mt_uuid_t);
    if (!gfid_ptr) {
        ret = -1;
        goto out;
    }
    gf_uuid_copy(gfid_ptr, gfid);
    ret = dict_set_gfuuid(xdata, "gfid-req", gfid_ptr, false);
    if (ret < 0)
        goto out;

    ret = 0;

out:
    if (ret && gfid_ptr)
        GF_FREE(gfid_ptr);
    return ret;
}

static gf_boolean_t
__is_gfid_access_dir(uuid_t gfid)
{
    static uuid_t aux_gfid = {0, 0, 0, 0, 0, 0, 0, 0,
                              0, 0, 0, 0, 0, 0, 0, GF_AUX_GFID};

    if (gf_uuid_compare(gfid, aux_gfid) == 0)
        return _gf_true;

    return _gf_false;
}

int32_t
ga_forget(xlator_t *this, inode_t *inode)
{
    int ret = -1;
    uint64_t value = 0;
    inode_t *tmp_inode = NULL;

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

    tmp_inode = (void *)(uintptr_t)value;
    inode_unref(tmp_inode);

out:
    return 0;
}

static int
ga_heal_cbk(call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret,
            int32_t op_errno, inode_t *inode, struct iatt *stat, dict_t *dict,
            struct iatt *postparent)
{
    call_frame_t *orig_frame = NULL;

    orig_frame = frame->local;
    frame->local = NULL;

    /* don't worry about inode linking and other stuff. They'll happen on
     * the next lookup.
     */
    STACK_DESTROY(frame->root);

    STACK_UNWIND_STRICT(setxattr, orig_frame, op_ret, op_errno, dict);

    return 0;
}

static int
ga_newentry_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                int32_t op_ret, int32_t op_errno, inode_t *inode,
                struct iatt *buf, struct iatt *preparent,
                struct iatt *postparent, dict_t *xdata)
{
    ga_local_t *local = NULL;

    local = frame->local;

    /* don't worry about inode linking and other stuff. They'll happen on
     * the next lookup.
     */
    frame->local = NULL;
    STACK_DESTROY(frame->root);

    STACK_UNWIND_STRICT(setxattr, local->orig_frame, op_ret, op_errno, xdata);

    if (local->xdata)
        dict_unref(local->xdata);
    loc_wipe(&local->loc);
    mem_put(local);

    return 0;
}

static int
ga_newentry_lookup_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                       int32_t op_ret, int32_t op_errno, inode_t *inode,
                       struct iatt *stat, dict_t *xdata,
                       struct iatt *postparent)

{
    ga_local_t *local = NULL;

    local = frame->local;

    if ((op_ret < 0) && ((op_errno != ENOENT) && (op_errno != ESTALE)))
        goto err;

    STACK_WIND(frame, ga_newentry_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->mknod, &local->loc, local->mode,
               local->rdev, local->umask, local->xdata);
    return 0;

err:
    frame->local = NULL;
    STACK_DESTROY(frame->root);
    STACK_UNWIND_STRICT(setxattr, local->orig_frame, op_ret, op_errno, xdata);
    if (local->xdata)
        dict_unref(local->xdata);
    loc_wipe(&local->loc);
    mem_put(local);

    return 0;
}

int32_t
ga_new_entry(call_frame_t *frame, xlator_t *this, loc_t *loc, data_t *data,
             dict_t *xdata)
{
    int ret = -1;
    ga_newfile_args_t *args = NULL;
    loc_t tmp_loc = {
        0,
    };
    call_frame_t *new_frame = NULL;
    ga_local_t *local = NULL;
    uuid_t gfid = {
        0,
    };

    if (!xdata) {
        xdata = dict_new();
    } else {
        xdata = dict_ref(xdata);
    }

    if (!xdata) {
        ret = -1;
        goto out;
    }

    args = ga_newfile_parse_args(this, data);
    if (!args)
        goto out;

    ret = gf_uuid_parse(args->gfid, gfid);
    if (ret)
        goto out;

    ret = ga_fill_tmp_loc(loc, this, gfid, args->bname, xdata, &tmp_loc);
    if (ret)
        goto out;

    new_frame = copy_frame(frame);
    if (!new_frame)
        goto out;

    local = mem_get0(this->local_pool);
    local->orig_frame = frame;

    loc_copy(&local->loc, &tmp_loc);

    new_frame->local = local;
    new_frame->root->uid = args->uid;
    new_frame->root->gid = args->gid;

    if (S_ISDIR(args->st_mode)) {
        STACK_WIND(new_frame, ga_newentry_cbk, FIRST_CHILD(this),
                   FIRST_CHILD(this)->fops->mkdir, &tmp_loc,
                   args->args.mkdir.mode, args->args.mkdir.umask, xdata);
    } else if (S_ISLNK(args->st_mode)) {
        STACK_WIND(new_frame, ga_newentry_cbk, FIRST_CHILD(this),
                   FIRST_CHILD(this)->fops->symlink,
                   args->args.symlink.linkpath, &tmp_loc, 0, xdata);
    } else {
        /* use 07777 (4 7s) for considering the Sticky bits etc) */
        ((ga_local_t *)new_frame->local)->mode = (S_IFMT & args->st_mode) |
                                                 (07777 &
                                                  args->args.mknod.mode);

        ((ga_local_t *)new_frame->local)->umask = args->args.mknod.umask;
        ((ga_local_t *)new_frame->local)->rdev = args->args.mknod.rdev;
        ((ga_local_t *)new_frame->local)->xdata = dict_ref(xdata);

        /* send a named lookup, so that dht can cleanup up stale linkto
         * files etc.
         */
        STACK_WIND(new_frame, ga_newentry_lookup_cbk, FIRST_CHILD(this),
                   FIRST_CHILD(this)->fops->lookup, &tmp_loc, NULL);
    }

    ret = 0;
out:
    ga_newfile_args_free(args);

    if (xdata)
        dict_unref(xdata);

    loc_wipe(&tmp_loc);

    return ret;
}

int32_t
ga_heal_entry(call_frame_t *frame, xlator_t *this, loc_t *loc, data_t *data,
              dict_t *xdata)
{
    int ret = -1;
    gf_boolean_t xdata_ref = _gf_false;
    ga_heal_args_t *args = NULL;
    loc_t tmp_loc = {
        0,
    };
    call_frame_t *new_frame = NULL;
    uuid_t gfid = {
        0,
    };

    args = ga_heal_parse_args(this, data);
    if (!args)
        goto out;

    ret = gf_uuid_parse(args->gfid, gfid);
    if (ret)
        goto out;

    if (!xdata)
        xdata = dict_new();
    else
        xdata = dict_ref(xdata);

    if (!xdata) {
        ret = -1;
        goto out;
    }
    xdata_ref = _gf_true;

    ret = ga_fill_tmp_loc(loc, this, gfid, args->bname, xdata, &tmp_loc);
    if (ret)
        goto out;

    new_frame = copy_frame(frame);
    if (!new_frame)
        goto out;

    new_frame->local = (void *)frame;

    STACK_WIND(new_frame, ga_heal_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->lookup, &tmp_loc, xdata);

    ret = 0;
out:
    if (args)
        ga_heal_args_free(args);

    loc_wipe(&tmp_loc);

    if (xdata_ref && xdata)
        dict_unref(xdata);

    return ret;
}

int32_t
ga_setxattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    STACK_UNWIND_STRICT(setxattr, frame, op_ret, op_errno, xdata);
    return 0;
}

int32_t
ga_setxattr(call_frame_t *frame, xlator_t *this, loc_t *loc, dict_t *dict,
            int32_t flags, dict_t *xdata)
{
    data_t *data = NULL;
    int op_errno = ENOMEM;
    int ret = 0;
    loc_t ga_loc = {
        0,
    };

    GFID_ACCESS_INODE_OP_CHECK(loc, op_errno, err);

    data = dict_get(dict, GF_FUSE_AUX_GFID_NEWFILE);
    if (data) {
        ret = ga_new_entry(frame, this, loc, data, xdata);
        if (ret)
            goto err;
        return 0;
    }

    data = dict_get(dict, GF_FUSE_AUX_GFID_HEAL);
    if (data) {
        ret = ga_heal_entry(frame, this, loc, data, xdata);
        if (ret)
            goto err;
        return 0;
    }

    // If the inode is a virtual inode change the inode otherwise perform
    // the operation on same inode
    ret = ga_valid_inode_loc_copy(&ga_loc, loc, this);
    if (ret < 0)
        goto err;

    STACK_WIND(frame, ga_setxattr_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->setxattr, &ga_loc, dict, flags, xdata);

    loc_wipe(&ga_loc);
    return 0;
err:
    STACK_UNWIND_STRICT(setxattr, frame, -1, op_errno, xdata);
    return 0;
}

int32_t
ga_virtual_lookup_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                      int32_t op_ret, int32_t op_errno, inode_t *inode,
                      struct iatt *buf, dict_t *xdata, struct iatt *postparent)
{
    int ret = 0;
    inode_t *cbk_inode = NULL;
    inode_t *true_inode = NULL;
    uuid_t random_gfid = {
        0,
    };
    inode_t *linked_inode = NULL;

    if (frame->local)
        cbk_inode = frame->local;
    else
        cbk_inode = inode_ref(inode);

    frame->local = NULL;
    if (op_ret)
        goto unwind;

    if (!IA_ISDIR(buf->ia_type))
        goto unwind;

    /* need to send back a different inode for linking in itable */
    if (cbk_inode == inode) {
        /* check if the inode is in the 'itable' or
           if its just previously discover()'d inode */
        true_inode = inode_find(inode->table, buf->ia_gfid);
        if (!true_inode) {
            /* This unref is for 'inode_ref()' done in beginning.
               This is needed as cbk_inode is allocated new inode
               whose unref is taken at the end*/
            inode_unref(cbk_inode);
            cbk_inode = inode_new(inode->table);

            if (!cbk_inode) {
                op_ret = -1;
                op_errno = ENOMEM;
                goto unwind;
            }
            /* the inode is not present in itable, ie, the actual
               path is not yet looked up. Use the current inode
               itself for now */

            linked_inode = inode_link(inode, NULL, NULL, buf);
            inode = linked_inode;
        } else {
            /* 'inode_ref()' has been done in inode_find() */
            inode = true_inode;
        }

        ret = inode_ctx_put(cbk_inode, this, (uint64_t)(uintptr_t)inode);
        if (ret) {
            gf_log(this->name, GF_LOG_WARNING,
                   "failed to set the inode ctx with"
                   "the actual inode");
            if (inode)
                inode_unref(inode);
        }
        inode = NULL;
    }

    if (!gf_uuid_is_null(cbk_inode->gfid)) {
        /* if the previous linked inode is used, use the
           same gfid */
        gf_uuid_copy(random_gfid, cbk_inode->gfid);
    } else {
        /* replace the buf->ia_gfid to a random gfid
           for directory, for files, what we received is fine */
        gf_uuid_generate(random_gfid);
    }

    gf_uuid_copy(buf->ia_gfid, random_gfid);

    buf->ia_ino = gfid_to_ino(buf->ia_gfid);

unwind:
    /* Lookup on non-existing gfid returns ESTALE.
       Convert into ENOENT for virtual lookup*/
    if (op_errno == ESTALE)
        op_errno = ENOENT;

    STACK_UNWIND_STRICT(lookup, frame, op_ret, op_errno, cbk_inode, buf, xdata,
                        postparent);

    /* Also handles inode_unref of frame->local if done in ga_lookup */
    if (cbk_inode)
        inode_unref(cbk_inode);

    return 0;
}

int32_t
ga_lookup_cbk(call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret,
              int32_t op_errno, inode_t *inode, struct iatt *buf, dict_t *xdata,
              struct iatt *postparent)
{
    ga_private_t *priv = NULL;

    /* if the entry in question is not 'root',
       then follow the normal path */
    if (op_ret || !__is_root_gfid(buf->ia_gfid))
        goto unwind;

    priv = this->private;

    /* do we need to copy root stbuf every time? */
    /* mostly yes, as we want to have the 'stat' info show latest
       in every _cbk() */

    /* keep the reference for root stat buf */
    priv->root_stbuf = *buf;
    priv->gfiddir_stbuf = priv->root_stbuf;
    priv->gfiddir_stbuf.ia_gfid[15] = GF_AUX_GFID;
    priv->gfiddir_stbuf.ia_ino = GF_AUX_GFID;

unwind:
    STACK_UNWIND_STRICT(lookup, frame, op_ret, op_errno, inode, buf, xdata,
                        postparent);
    return 0;
}

int32_t
ga_lookup(call_frame_t *frame, xlator_t *this, loc_t *loc, dict_t *xdata)
{
    ga_private_t *priv = NULL;
    int ret = -1;
    uuid_t tmp_gfid = {
        0,
    };
    loc_t tmp_loc = {
        0,
    };
    uint64_t value = 0;
    inode_t *inode = NULL;
    inode_t *true_inode = NULL;
    int32_t op_errno = ENOENT;

    priv = this->private;

    /* Handle nameless lookup on ".gfid" */
    if (!loc->parent && __is_gfid_access_dir(loc->gfid)) {
        STACK_UNWIND_STRICT(lookup, frame, 0, 0, loc->inode,
                            &priv->gfiddir_stbuf, xdata, &priv->root_stbuf);
        return 0;
    }

    /* if its discover(), no need for any action here */
    if (!loc->name)
        goto wind;

    /* if its revalidate, and inode is not of type directory,
       proceed with 'wind' */
    if (loc->inode && loc->inode->ia_type && !IA_ISDIR(loc->inode->ia_type)) {
        /* a revalidate on ".gfid/<dentry>" is possible, check for it */
        if (((loc->parent && __is_gfid_access_dir(loc->parent->gfid)) ||
             __is_gfid_access_dir(loc->pargfid))) {
            /* here, just send 'loc->gfid' and 'loc->inode' */
            tmp_loc.inode = inode_ref(loc->inode);
            gf_uuid_copy(tmp_loc.gfid, loc->inode->gfid);

            STACK_WIND(frame, default_lookup_cbk, FIRST_CHILD(this),
                       FIRST_CHILD(this)->fops->lookup, &tmp_loc, xdata);

            inode_unref(tmp_loc.inode);

            return 0;
        }

        /* not something to bother, continue the flow */
        goto wind;
    }

    /* need to check if the lookup is on virtual dir */
    if ((loc->name && !strcmp(GF_GFID_DIR, loc->name)) &&
        ((loc->parent && __is_root_gfid(loc->parent->gfid)) ||
         __is_root_gfid(loc->pargfid))) {
        /* this means, the query is on '/.gfid', return the fake stat,
           and say success */

        STACK_UNWIND_STRICT(lookup, frame, 0, 0, loc->inode,
                            &priv->gfiddir_stbuf, xdata, &priv->root_stbuf);
        return 0;
    }

    /* now, check if the lookup() is on an existing entry,
       but on gfid-path */
    if (!((loc->parent && __is_gfid_access_dir(loc->parent->gfid)) ||
          __is_gfid_access_dir(loc->pargfid))) {
        if (!loc->parent)
            goto wind;

        ret = inode_ctx_get(loc->parent, this, &value);
        if (ret)
            goto wind;

        inode = (inode_t *)(uintptr_t)value;

        ret = loc_copy_overload_parent(&tmp_loc, loc, inode);
        if (ret)
            goto err;

        STACK_WIND(frame, ga_lookup_cbk, FIRST_CHILD(this),
                   FIRST_CHILD(this)->fops->lookup, &tmp_loc, xdata);

        loc_wipe(&tmp_loc);
        return 0;
    }

    /* make sure the 'basename' is actually a 'canonical-gfid',
       otherwise, return error */
    ret = gf_uuid_parse(loc->name, tmp_gfid);
    if (ret)
        goto err;

    /* if its fresh lookup, go ahead and send it down, if not,
       for directory, we need indirection to actual dir inode */
    if (!(loc->inode && loc->inode->ia_type))
        goto discover;

    /* revalidate on directory */
    ret = inode_ctx_get(loc->inode, this, &value);
    if (ret)
        goto err;

    inode = (void *)(uintptr_t)value;

    /* valid inode, already looked up, work on that */
    if (inode->ia_type)
        goto discover;

    /* check if the inode is in the 'itable' or
       if its just previously discover()'d inode */
    true_inode = inode_find(loc->inode->table, tmp_gfid);
    if (true_inode) {
        /* time do another lookup and update the context
           with proper inode */
        op_errno = ESTALE;
        /* 'inode_ref()' done in inode_find */
        inode_unref(true_inode);
        goto err;
    }

discover:
    /* for the virtual entries, we don't need to send 'gfid-req' key, as
       for these entries, we don't want to 'set' a new gfid */
    if (xdata)
        dict_del(xdata, "gfid-req");

    gf_uuid_copy(tmp_loc.gfid, tmp_gfid);

    /* if revalidate, then we need to have the proper reference */
    if (inode) {
        tmp_loc.inode = inode_ref(inode);
        frame->local = inode_ref(loc->inode);
    } else {
        tmp_loc.inode = inode_ref(loc->inode);
    }

    STACK_WIND(frame, ga_virtual_lookup_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->lookup, &tmp_loc, xdata);

    inode_unref(tmp_loc.inode);

    return 0;

wind:
    /* used for all the normal lookup path */
    STACK_WIND(frame, ga_lookup_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->lookup, loc, xdata);

    return 0;

err:
    STACK_UNWIND_STRICT(lookup, frame, -1, op_errno, loc->inode,
                        &priv->gfiddir_stbuf, xdata, &priv->root_stbuf);
    return 0;
}

int
ga_mkdir(call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode,
         mode_t umask, dict_t *xdata)
{
    int op_errno = ENOMEM;

    GFID_ACCESS_ENTRY_OP_CHECK(loc, op_errno, err);

    STACK_WIND(frame, default_mkdir_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->mkdir, loc, mode, umask, xdata);

    return 0;

err:
    STACK_UNWIND_STRICT(mkdir, frame, -1, op_errno, loc->inode, NULL, NULL,
                        NULL, xdata);
    return 0;
}

int
ga_create(call_frame_t *frame, xlator_t *this, loc_t *loc, int flags,
          mode_t mode, mode_t umask, fd_t *fd, dict_t *xdata)
{
    int op_errno = ENOMEM;

    GFID_ACCESS_ENTRY_OP_CHECK(loc, op_errno, err);

    STACK_WIND(frame, default_create_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->create, loc, flags, mode, umask, fd,
               xdata);
    return 0;
err:
    STACK_UNWIND_STRICT(create, frame, -1, op_errno, NULL, NULL, NULL, NULL,
                        NULL, xdata);

    return 0;
}

int
ga_symlink(call_frame_t *frame, xlator_t *this, const char *linkname,
           loc_t *loc, mode_t umask, dict_t *xdata)
{
    int op_errno = ENOMEM;

    GFID_ACCESS_ENTRY_OP_CHECK(loc, op_errno, err);

    STACK_WIND(frame, default_symlink_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->symlink, linkname, loc, umask, xdata);
    return 0;
err:
    STACK_UNWIND_STRICT(symlink, frame, -1, op_errno, NULL, NULL, NULL, NULL,
                        xdata);

    return 0;
}

int
ga_mknod(call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode,
         dev_t rdev, mode_t umask, dict_t *xdata)
{
    int op_errno = ENOMEM;

    GFID_ACCESS_ENTRY_OP_CHECK(loc, op_errno, err);

    STACK_WIND(frame, default_mknod_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->mknod, loc, mode, rdev, umask, xdata);

    return 0;
err:
    STACK_UNWIND_STRICT(mknod, frame, -1, op_errno, NULL, NULL, NULL, NULL,
                        xdata);

    return 0;
}

int
ga_rmdir(call_frame_t *frame, xlator_t *this, loc_t *loc, int flag,
         dict_t *xdata)
{
    int op_errno = ENOMEM;
    int ret = -1;
    loc_t ga_loc = {
        0,
    };

    GFID_ACCESS_ENTRY_OP_CHECK(loc, op_errno, err);

    ret = ga_valid_inode_loc_copy(&ga_loc, loc, this);
    if (ret < 0)
        goto err;

    STACK_WIND(frame, default_rmdir_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->rmdir, &ga_loc, flag, xdata);

    loc_wipe(&ga_loc);
    return 0;
err:
    STACK_UNWIND_STRICT(rmdir, frame, -1, op_errno, NULL, NULL, xdata);

    return 0;
}

int
ga_unlink(call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t xflag,
          dict_t *xdata)
{
    int op_errno = ENOMEM;
    int ret = -1;
    loc_t ga_loc = {
        0,
    };

    GFID_ACCESS_ENTRY_OP_CHECK(loc, op_errno, err);

    ret = ga_valid_inode_loc_copy(&ga_loc, loc, this);
    if (ret < 0)
        goto err;

    STACK_WIND(frame, default_unlink_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->unlink, &ga_loc, xflag, xdata);

    loc_wipe(&ga_loc);
    return 0;
err:
    STACK_UNWIND_STRICT(unlink, frame, -1, op_errno, NULL, NULL, xdata);

    return 0;
}

int
ga_rename(call_frame_t *frame, xlator_t *this, loc_t *oldloc, loc_t *newloc,
          dict_t *xdata)
{
    int op_errno = ENOMEM;
    int ret = 0;
    loc_t ga_oldloc = {
        0,
    };
    loc_t ga_newloc = {
        0,
    };

    GFID_ACCESS_ENTRY_OP_CHECK(oldloc, op_errno, err);
    GFID_ACCESS_ENTRY_OP_CHECK(newloc, op_errno, err);

    ret = ga_valid_inode_loc_copy(&ga_oldloc, oldloc, this);
    if (ret < 0)
        goto err;

    ret = ga_valid_inode_loc_copy(&ga_newloc, newloc, this);
    if (ret < 0) {
        loc_wipe(&ga_oldloc);
        goto err;
    }

    STACK_WIND(frame, default_rename_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->rename, &ga_oldloc, &ga_newloc, xdata);

    loc_wipe(&ga_newloc);
    loc_wipe(&ga_oldloc);
    return 0;
err:
    STACK_UNWIND_STRICT(rename, frame, -1, op_errno, NULL, NULL, NULL, NULL,
                        NULL, xdata);

    return 0;
}

int
ga_link(call_frame_t *frame, xlator_t *this, loc_t *oldloc, loc_t *newloc,
        dict_t *xdata)
{
    int op_errno = ENOMEM;
    int ret = 0;
    loc_t ga_oldloc = {
        0,
    };
    loc_t ga_newloc = {
        0,
    };

    GFID_ACCESS_ENTRY_OP_CHECK(oldloc, op_errno, err);
    GFID_ACCESS_ENTRY_OP_CHECK(newloc, op_errno, err);

    ret = ga_valid_inode_loc_copy(&ga_oldloc, oldloc, this);
    if (ret < 0)
        goto err;

    ret = ga_valid_inode_loc_copy(&ga_newloc, newloc, this);
    if (ret < 0) {
        loc_wipe(&ga_oldloc);
        goto err;
    }

    STACK_WIND(frame, default_link_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->link, &ga_oldloc, &ga_newloc, xdata);

    loc_wipe(&ga_newloc);
    loc_wipe(&ga_oldloc);
    return 0;

err:
    STACK_UNWIND_STRICT(link, frame, -1, op_errno, NULL, NULL, NULL, NULL,
                        xdata);

    return 0;
}

int32_t
ga_opendir(call_frame_t *frame, xlator_t *this, loc_t *loc, fd_t *fd,
           dict_t *xdata)
{
    int op_errno = ENOMEM;

    GFID_ACCESS_INODE_OP_CHECK(loc, op_errno, err);

    /* also check if the loc->inode itself is virtual
       inode, if yes, return with failure, mainly because we
       can't handle all the readdirp and other things on it. */
    if (inode_ctx_get(loc->inode, this, NULL) == 0) {
        op_errno = ENOTSUP;
        goto err;
    }

    STACK_WIND(frame, default_opendir_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->opendir, loc, fd, xdata);
    return 0;
err:
    STACK_UNWIND_STRICT(opendir, frame, -1, op_errno, NULL, xdata);

    return 0;
}

int32_t
ga_getxattr(call_frame_t *frame, xlator_t *this, loc_t *loc, const char *name,
            dict_t *xdata)
{
    int op_errno = ENOMEM;
    int ret = -1;
    loc_t ga_loc = {
        0,
    };

    GFID_ACCESS_INODE_OP_CHECK(loc, op_errno, err);
    ret = ga_valid_inode_loc_copy(&ga_loc, loc, this);
    if (ret < 0)
        goto err;

    STACK_WIND(frame, default_getxattr_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->getxattr, &ga_loc, name, xdata);

    loc_wipe(&ga_loc);

    return 0;
err:
    STACK_UNWIND_STRICT(getxattr, frame, -1, op_errno, NULL, xdata);

    return 0;
}

int32_t
ga_stat(call_frame_t *frame, xlator_t *this, loc_t *loc, dict_t *xdata)
{
    int op_errno = ENOMEM;
    int ret = -1;
    loc_t ga_loc = {
        0,
    };
    ga_private_t *priv = NULL;

    priv = this->private;
    /* If stat is on ".gfid" itself, do not wind further,
     * return fake stat and return success.
     */
    if (__is_gfid_access_dir(loc->gfid))
        goto out;

    ret = ga_valid_inode_loc_copy(&ga_loc, loc, this);
    if (ret < 0)
        goto err;

    STACK_WIND(frame, default_stat_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->stat, &ga_loc, xdata);

    loc_wipe(&ga_loc);
    return 0;

err:
    STACK_UNWIND_STRICT(stat, frame, -1, op_errno, NULL, xdata);

    return 0;

out:
    STACK_UNWIND_STRICT(stat, frame, 0, 0, &priv->gfiddir_stbuf, xdata);
    return 0;
}

int32_t
ga_setattr(call_frame_t *frame, xlator_t *this, loc_t *loc, struct iatt *stbuf,
           int32_t valid, dict_t *xdata)
{
    int op_errno = ENOMEM;
    int ret = -1;
    loc_t ga_loc = {
        0,
    };

    GFID_ACCESS_INODE_OP_CHECK(loc, op_errno, err);
    ret = ga_valid_inode_loc_copy(&ga_loc, loc, this);
    if (ret < 0)
        goto err;

    STACK_WIND(frame, default_setattr_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->setattr, &ga_loc, stbuf, valid, xdata);

    loc_wipe(&ga_loc);
    return 0;
err:
    STACK_UNWIND_STRICT(setattr, frame, -1, op_errno, NULL, NULL, xdata);

    return 0;
}

int32_t
ga_removexattr(call_frame_t *frame, xlator_t *this, loc_t *loc,
               const char *name, dict_t *xdata)
{
    int op_errno = ENOMEM;
    int ret = -1;
    loc_t ga_loc = {
        0,
    };

    GFID_ACCESS_INODE_OP_CHECK(loc, op_errno, err);
    ret = ga_valid_inode_loc_copy(&ga_loc, loc, this);
    if (ret < 0)
        goto err;

    STACK_WIND(frame, default_removexattr_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->removexattr, &ga_loc, name, xdata);

    loc_wipe(&ga_loc);
    return 0;

err:
    STACK_UNWIND_STRICT(removexattr, frame, -1, op_errno, xdata);

    return 0;
}

int32_t
mem_acct_init(xlator_t *this)
{
    int ret = -1;

    if (!this)
        return ret;

    ret = xlator_mem_acct_init(this, gf_gfid_access_mt_end);

    if (ret != 0) {
        gf_log(this->name, GF_LOG_WARNING,
               "Memory accounting"
               " init failed");
        return ret;
    }

    return ret;
}

int32_t
init(xlator_t *this)
{
    ga_private_t *priv = NULL;
    int ret = -1;

    if (!this->children || this->children->next) {
        gf_log(this->name, GF_LOG_ERROR,
               "not configured with exactly one child. exiting");
        goto out;
    }

    /* This can be the top of graph in certain cases */
    if (!this->parents) {
        gf_log(this->name, GF_LOG_DEBUG, "dangling volume. check volfile ");
    }

    /* TODO: define a mem-type structure */
    priv = GF_CALLOC(1, sizeof(*priv), gf_gfid_access_mt_priv_t);
    if (!priv)
        goto out;

    priv->newfile_args_pool = mem_pool_new(ga_newfile_args_t, 512);
    if (!priv->newfile_args_pool)
        goto out;

    priv->heal_args_pool = mem_pool_new(ga_heal_args_t, 512);
    if (!priv->heal_args_pool)
        goto out;

    this->local_pool = mem_pool_new(ga_local_t, 16);
    if (!this->local_pool) {
        gf_log(this->name, GF_LOG_ERROR,
               "failed to create local_t's memory pool");
        goto out;
    }

    this->private = priv;

    ret = 0;
out:
    if (ret && priv) {
        if (priv->newfile_args_pool)
            mem_pool_destroy(priv->newfile_args_pool);
        GF_FREE(priv);
    }

    return ret;
}

void
fini(xlator_t *this)
{
    ga_private_t *priv = NULL;
    priv = this->private;
    this->private = NULL;

    if (priv) {
        if (priv->newfile_args_pool)
            mem_pool_destroy(priv->newfile_args_pool);
        if (priv->heal_args_pool)
            mem_pool_destroy(priv->heal_args_pool);
        GF_FREE(priv);
    }

    return;
}

int32_t
ga_dump_inodectx(xlator_t *this, inode_t *inode)
{
    int ret = -1;
    uint64_t value = 0;
    inode_t *tmp_inode = NULL;
    char key_prefix[GF_DUMP_MAX_BUF_LEN] = {
        0,
    };

    ret = inode_ctx_get(inode, this, &value);
    if (ret == 0) {
        tmp_inode = (void *)(uintptr_t)value;
        gf_proc_dump_build_key(key_prefix, this->name, "inode");
        gf_proc_dump_add_section("%s", key_prefix);
        gf_proc_dump_write("real-gfid", "%s", uuid_utoa(tmp_inode->gfid));
    }

    return 0;
}

struct xlator_fops fops = {
    .lookup = ga_lookup,

    /* entry fops */
    .mkdir = ga_mkdir,
    .mknod = ga_mknod,
    .create = ga_create,
    .symlink = ga_symlink,
    .link = ga_link,
    .unlink = ga_unlink,
    .rmdir = ga_rmdir,
    .rename = ga_rename,

    /* handle any other directory operations here */
    .opendir = ga_opendir,
    .stat = ga_stat,
    .setattr = ga_setattr,
    .getxattr = ga_getxattr,
    .removexattr = ga_removexattr,

    /* special fop to handle more entry creations */
    .setxattr = ga_setxattr,
};

struct xlator_cbks cbks = {
    .forget = ga_forget,
};

struct xlator_dumpops dumpops = {
    .inodectx = ga_dump_inodectx,
};

struct volume_options options[] = {
    /* This translator doesn't take any options, or provide any options */
    {.key = {NULL}},
};

xlator_api_t xlator_api = {
    .init = init,
    .fini = fini,
    .mem_acct_init = mem_acct_init,
    .op_version = {1},
    .fops = &fops,
    .cbks = &cbks,
    .options = options,
    .identifier = "gfid-access",
    .category = GF_MAINTAINED,
};
