/*
  Copyright (c) 2008-2012 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 <libgen.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdlib.h>
#include <signal.h>

#include <glusterfs/glusterfs.h>
#include "afr.h"
#include <glusterfs/dict.h>
#include <glusterfs/logging.h>
#include <glusterfs/list.h>
#include <glusterfs/defaults.h>
#include <glusterfs/common-utils.h>
#include <glusterfs/compat-errno.h>
#include <glusterfs/compat.h>
#include <glusterfs/byte-order.h>

#include "afr.h"
#include "afr-transaction.h"

void
afr_mark_entry_pending_changelog(call_frame_t *frame, xlator_t *this);

int
afr_build_parent_loc(loc_t *parent, loc_t *child, int32_t *op_errno)
{
    int ret = -1;
    char *child_path = NULL;

    if (!child->parent) {
        if (op_errno)
            *op_errno = EINVAL;
        goto out;
    }

    child_path = gf_strdup(child->path);
    if (!child_path) {
        if (op_errno)
            *op_errno = ENOMEM;
        goto out;
    }

    parent->path = gf_strdup(dirname(child_path));
    if (!parent->path) {
        if (op_errno)
            *op_errno = ENOMEM;
        goto out;
    }

    parent->inode = inode_ref(child->parent);
    gf_uuid_copy(parent->gfid, child->pargfid);

    ret = 0;
out:
    GF_FREE(child_path);

    return ret;
}

static void
__afr_dir_write_finalize(call_frame_t *frame, xlator_t *this)
{
    afr_local_t *local = NULL;
    afr_private_t *priv = NULL;
    int inode_read_subvol = -1;
    int parent_read_subvol = -1;
    int parent2_read_subvol = -1;
    int i = 0;
    afr_read_subvol_args_t args = {
        0,
    };

    local = frame->local;
    priv = this->private;

    for (i = 0; i < priv->child_count; i++) {
        if (!local->replies[i].valid)
            continue;
        if (local->replies[i].op_ret == -1)
            continue;
        gf_uuid_copy(args.gfid, local->replies[i].poststat.ia_gfid);
        args.ia_type = local->replies[i].poststat.ia_type;
        break;
    }

    if (local->inode) {
        if (local->op != GF_FOP_RENAME && local->op != GF_FOP_LINK)
            afr_replies_interpret(frame, this, local->inode, NULL);

        inode_read_subvol = afr_data_subvol_get(local->inode, this, NULL, NULL,
                                                NULL, &args);
    }

    if (local->parent)
        parent_read_subvol = afr_data_subvol_get(local->parent, this, NULL,
                                                 local->readable, NULL, NULL);

    if (local->parent2)
        parent2_read_subvol = afr_data_subvol_get(local->parent2, this, NULL,
                                                  local->readable2, NULL, NULL);

    local->op_ret = -1;
    local->op_errno = afr_final_errno(local, priv);
    afr_pick_error_xdata(local, priv, local->parent, local->readable,
                         local->parent2, local->readable2);

    for (i = 0; i < priv->child_count; i++) {
        if (!local->replies[i].valid)
            continue;
        if (local->replies[i].op_ret < 0) {
            if (local->inode)
                afr_inode_need_refresh_set(local->inode, this);
            if (local->parent)
                afr_inode_need_refresh_set(local->parent, this);
            if (local->parent2)
                afr_inode_need_refresh_set(local->parent2, this);
            continue;
        }

        if (local->op_ret == -1) {
            local->op_ret = local->replies[i].op_ret;
            local->op_errno = local->replies[i].op_errno;

            local->cont.dir_fop.buf = local->replies[i].poststat;
            local->cont.dir_fop.preparent = local->replies[i].preparent;
            local->cont.dir_fop.postparent = local->replies[i].postparent;
            local->cont.dir_fop.prenewparent = local->replies[i].preparent2;
            local->cont.dir_fop.postnewparent = local->replies[i].postparent2;
            if (local->xdata_rsp) {
                dict_unref(local->xdata_rsp);
                local->xdata_rsp = NULL;
            }

            if (local->replies[i].xdata)
                local->xdata_rsp = dict_ref(local->replies[i].xdata);
            continue;
        }

        if (i == inode_read_subvol) {
            local->cont.dir_fop.buf = local->replies[i].poststat;
            if (local->replies[i].xdata) {
                if (local->xdata_rsp)
                    dict_unref(local->xdata_rsp);
                local->xdata_rsp = dict_ref(local->replies[i].xdata);
            }
        }

        if (i == parent_read_subvol) {
            local->cont.dir_fop.preparent = local->replies[i].preparent;
            local->cont.dir_fop.postparent = local->replies[i].postparent;
        }

        if (i == parent2_read_subvol) {
            local->cont.dir_fop.prenewparent = local->replies[i].preparent2;
            local->cont.dir_fop.postnewparent = local->replies[i].postparent2;
        }
    }
}

static void
__afr_dir_write_fill(call_frame_t *frame, xlator_t *this, int child_index,
                     int op_ret, int op_errno, struct iatt *poststat,
                     struct iatt *preparent, struct iatt *postparent,
                     struct iatt *preparent2, struct iatt *postparent2,
                     dict_t *xdata)
{
    afr_local_t *local = NULL;
    afr_fd_ctx_t *fd_ctx = NULL;

    local = frame->local;
    fd_ctx = local->fd_ctx;

    local->replies[child_index].valid = 1;
    local->replies[child_index].op_ret = op_ret;
    local->replies[child_index].op_errno = op_errno;
    if (xdata)
        local->replies[child_index].xdata = dict_ref(xdata);

    if (op_ret >= 0) {
        if (poststat)
            local->replies[child_index].poststat = *poststat;
        if (preparent)
            local->replies[child_index].preparent = *preparent;
        if (postparent)
            local->replies[child_index].postparent = *postparent;
        if (preparent2)
            local->replies[child_index].preparent2 = *preparent2;
        if (postparent2)
            local->replies[child_index].postparent2 = *postparent2;
        if (fd_ctx)
            fd_ctx->opened_on[child_index] = AFR_FD_OPENED;
    } else {
        if (op_errno != ENOTEMPTY)
            afr_transaction_fop_failed(frame, this, child_index);
        if (fd_ctx)
            fd_ctx->opened_on[child_index] = AFR_FD_NOT_OPENED;
    }

    return;
}

static int
__afr_dir_write_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                    int op_ret, int op_errno, struct iatt *buf,
                    struct iatt *preparent, struct iatt *postparent,
                    struct iatt *preparent2, struct iatt *postparent2,
                    dict_t *xdata)
{
    afr_local_t *local = NULL;
    int child_index = (long)cookie;
    int call_count = -1;
    afr_private_t *priv = NULL;

    priv = this->private;
    local = frame->local;

    LOCK(&frame->lock);
    {
        __afr_dir_write_fill(frame, this, child_index, op_ret, op_errno, buf,
                             preparent, postparent, preparent2, postparent2,
                             xdata);
        call_count = --local->call_count;
    }
    UNLOCK(&frame->lock);

    if (call_count == 0) {
        __afr_dir_write_finalize(frame, this);

        if (afr_txn_nothing_failed(frame, this)) {
            /*if it did pre-op, it will do post-op changing ctime*/
            if (priv->consistent_metadata && afr_needs_changelog_update(local))
                afr_zero_fill_stat(local);
            local->transaction.unwind(frame, this);
        }

        afr_mark_entry_pending_changelog(frame, this);

        afr_transaction_resume(frame, this);
    }

    return 0;
}

int
afr_mark_new_entry_changelog_cbk(call_frame_t *frame, void *cookie,
                                 xlator_t *this, int op_ret, int op_errno,
                                 dict_t *xattr, dict_t *xdata)
{
    int call_count = 0;

    call_count = afr_frame_return(frame);

    if (call_count == 0)
        AFR_STACK_DESTROY(frame);

    return 0;
}

void
afr_mark_new_entry_changelog(call_frame_t *frame, xlator_t *this)
{
    call_frame_t *new_frame = NULL;
    afr_local_t *local = NULL;
    afr_local_t *new_local = NULL;
    afr_private_t *priv = NULL;
    dict_t *xattr = NULL;
    int32_t **changelog = NULL;
    int i = 0;
    int op_errno = ENOMEM;
    unsigned char *pending = NULL;
    int call_count = 0;

    local = frame->local;
    priv = this->private;

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

    new_local = AFR_FRAME_INIT(new_frame, op_errno);
    if (!new_local)
        goto out;

    xattr = dict_new();
    if (!xattr)
        goto out;

    pending = alloca0(priv->child_count);

    for (i = 0; i < priv->child_count; i++) {
        if (local->transaction.pre_op[i] &&
            !local->transaction.failed_subvols[i]) {
            call_count++;
            continue;
        }
        pending[i] = 1;
    }

    changelog = afr_mark_pending_changelog(priv, pending, xattr,
                                           local->cont.dir_fop.buf.ia_type);
    if (!changelog)
        goto out;

    new_local->pending = changelog;
    gf_uuid_copy(new_local->loc.gfid, local->cont.dir_fop.buf.ia_gfid);
    new_local->loc.inode = inode_ref(local->inode);

    new_local->call_count = call_count;

    for (i = 0; i < priv->child_count; i++) {
        if (pending[i])
            continue;

        STACK_WIND_COOKIE(new_frame, afr_mark_new_entry_changelog_cbk,
                          (void *)(long)i, priv->children[i],
                          priv->children[i]->fops->xattrop, &new_local->loc,
                          GF_XATTROP_ADD_ARRAY, xattr, NULL);
        if (!--call_count)
            break;
    }

    new_frame = NULL;
out:
    if (new_frame)
        AFR_STACK_DESTROY(new_frame);
    if (xattr)
        dict_unref(xattr);
    return;
}

void
afr_mark_entry_pending_changelog(call_frame_t *frame, xlator_t *this)
{
    afr_local_t *local = NULL;
    afr_private_t *priv = NULL;
    int pre_op_count = 0;
    int failed_count = 0;
    unsigned char *success_replies = NULL;

    local = frame->local;
    priv = this->private;

    if (local->op_ret < 0)
        return;

    if (local->op != GF_FOP_CREATE && local->op != GF_FOP_MKNOD &&
        local->op != GF_FOP_MKDIR)
        return;

    pre_op_count = AFR_COUNT(local->transaction.pre_op, priv->child_count);
    failed_count = AFR_COUNT(local->transaction.failed_subvols,
                             priv->child_count);

    /* FOP succeeded on all bricks. */
    if (pre_op_count == priv->child_count && !failed_count)
        return;

    /* FOP did not suceed on quorum no. of bricks. */
    success_replies = alloca0(priv->child_count);
    afr_fill_success_replies(local, priv, success_replies);
    if (!afr_has_quorum(success_replies, this, NULL))
        return;

    if (priv->thin_arbiter_count) {
        /*Mark new entry using ta file*/
        local->is_new_entry = _gf_true;
        return;
    }

    afr_mark_new_entry_changelog(frame, this);

    return;
}

/* {{{ create */

int
afr_create_unwind(call_frame_t *frame, xlator_t *this)
{
    call_frame_t *main_frame = NULL;
    afr_local_t *local = NULL;

    local = frame->local;

    main_frame = afr_transaction_detach_fop_frame(frame);

    if (!main_frame)
        return 0;

    AFR_STACK_UNWIND(create, main_frame, local->op_ret, local->op_errno,
                     local->cont.create.fd, local->inode,
                     &local->cont.dir_fop.buf, &local->cont.dir_fop.preparent,
                     &local->cont.dir_fop.postparent, local->xdata_rsp);
    return 0;
}

int
afr_create_wind_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                    int32_t op_ret, int32_t op_errno, fd_t *fd, inode_t *inode,
                    struct iatt *buf, struct iatt *preparent,
                    struct iatt *postparent, dict_t *xdata)
{
    return __afr_dir_write_cbk(frame, cookie, this, op_ret, op_errno, buf,
                               preparent, postparent, NULL, NULL, xdata);
}

int
afr_create_wind(call_frame_t *frame, xlator_t *this, int subvol)
{
    afr_local_t *local = NULL;
    afr_private_t *priv = NULL;

    local = frame->local;
    priv = this->private;

    STACK_WIND_COOKIE(frame, afr_create_wind_cbk, (void *)(long)subvol,
                      priv->children[subvol],
                      priv->children[subvol]->fops->create, &local->loc,
                      local->cont.create.flags, local->cont.create.mode,
                      local->umask, local->cont.create.fd, local->xdata_req);
    return 0;
}

int
afr_create(call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t flags,
           mode_t mode, mode_t umask, fd_t *fd, dict_t *xdata)
{
    afr_local_t *local = NULL;
    call_frame_t *transaction_frame = NULL;
    int ret = -1;
    int op_errno = ENOMEM;

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

    local = AFR_FRAME_INIT(transaction_frame, op_errno);
    if (!local)
        goto out;

    loc_copy(&local->loc, loc);

    local->fd_ctx = afr_fd_ctx_get(fd, this);
    if (!local->fd_ctx)
        goto out;

    local->inode = inode_ref(loc->inode);
    local->parent = inode_ref(loc->parent);

    local->op = GF_FOP_CREATE;
    local->cont.create.flags = flags;
    local->fd_ctx->flags = flags;
    local->cont.create.mode = mode;
    local->cont.create.fd = fd_ref(fd);
    local->umask = umask;

    if (xdata)
        local->xdata_req = dict_copy_with_ref(xdata, NULL);
    else
        local->xdata_req = dict_new();

    if (!local->xdata_req)
        goto out;

    local->transaction.wind = afr_create_wind;
    local->transaction.unwind = afr_create_unwind;

    ret = afr_build_parent_loc(&local->transaction.parent_loc, loc, &op_errno);
    if (ret)
        goto out;

    local->transaction.main_frame = frame;
    local->transaction.basename = AFR_BASENAME(loc->path);
    ret = afr_transaction(transaction_frame, this, AFR_ENTRY_TRANSACTION);
    if (ret < 0) {
        op_errno = -ret;
        goto out;
    }

    return 0;
out:
    if (transaction_frame)
        AFR_STACK_DESTROY(transaction_frame);

    AFR_STACK_UNWIND(create, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL,
                     NULL);
    return 0;
}

/* }}} */

/* {{{ mknod */

int
afr_mknod_unwind(call_frame_t *frame, xlator_t *this)
{
    call_frame_t *main_frame = NULL;
    afr_local_t *local = NULL;

    local = frame->local;

    main_frame = afr_transaction_detach_fop_frame(frame);
    if (!main_frame)
        return 0;

    AFR_STACK_UNWIND(mknod, main_frame, local->op_ret, local->op_errno,
                     local->inode, &local->cont.dir_fop.buf,
                     &local->cont.dir_fop.preparent,
                     &local->cont.dir_fop.postparent, local->xdata_rsp);
    return 0;
}

int
afr_mknod_wind_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)
{
    return __afr_dir_write_cbk(frame, cookie, this, op_ret, op_errno, buf,
                               preparent, postparent, NULL, NULL, xdata);
}

int
afr_mknod_wind(call_frame_t *frame, xlator_t *this, int subvol)
{
    afr_local_t *local = NULL;
    afr_private_t *priv = NULL;

    local = frame->local;
    priv = this->private;

    STACK_WIND_COOKIE(frame, afr_mknod_wind_cbk, (void *)(long)subvol,
                      priv->children[subvol],
                      priv->children[subvol]->fops->mknod, &local->loc,
                      local->cont.mknod.mode, local->cont.mknod.dev,
                      local->umask, local->xdata_req);
    return 0;
}

int
afr_mknod(call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode,
          dev_t dev, mode_t umask, dict_t *xdata)
{
    afr_local_t *local = NULL;
    call_frame_t *transaction_frame = NULL;
    int ret = -1;
    int op_errno = ENOMEM;

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

    local = AFR_FRAME_INIT(transaction_frame, op_errno);
    if (!local)
        goto out;

    loc_copy(&local->loc, loc);
    local->inode = inode_ref(loc->inode);
    local->parent = inode_ref(loc->parent);

    local->op = GF_FOP_MKNOD;
    local->cont.mknod.mode = mode;
    local->cont.mknod.dev = dev;
    local->umask = umask;

    if (xdata)
        local->xdata_req = dict_copy_with_ref(xdata, NULL);
    else
        local->xdata_req = dict_new();

    if (!local->xdata_req)
        goto out;

    local->transaction.wind = afr_mknod_wind;
    local->transaction.unwind = afr_mknod_unwind;

    ret = afr_build_parent_loc(&local->transaction.parent_loc, loc, &op_errno);
    if (ret)
        goto out;

    local->transaction.main_frame = frame;
    local->transaction.basename = AFR_BASENAME(loc->path);
    ret = afr_transaction(transaction_frame, this, AFR_ENTRY_TRANSACTION);
    if (ret < 0) {
        op_errno = -ret;
        goto out;
    }

    return 0;
out:
    if (transaction_frame)
        AFR_STACK_DESTROY(transaction_frame);

    AFR_STACK_UNWIND(mknod, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL);
    return 0;
}

/* }}} */

/* {{{ mkdir */

int
afr_mkdir_unwind(call_frame_t *frame, xlator_t *this)
{
    call_frame_t *main_frame = NULL;
    afr_local_t *local = NULL;

    local = frame->local;

    main_frame = afr_transaction_detach_fop_frame(frame);
    if (!main_frame)
        return 0;

    AFR_STACK_UNWIND(mkdir, main_frame, local->op_ret, local->op_errno,
                     local->inode, &local->cont.dir_fop.buf,
                     &local->cont.dir_fop.preparent,
                     &local->cont.dir_fop.postparent, local->xdata_rsp);
    return 0;
}

int
afr_mkdir_wind_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)
{
    return __afr_dir_write_cbk(frame, cookie, this, op_ret, op_errno, buf,
                               preparent, postparent, NULL, NULL, xdata);
}

int
afr_mkdir_wind(call_frame_t *frame, xlator_t *this, int subvol)
{
    afr_local_t *local = NULL;
    afr_private_t *priv = NULL;

    local = frame->local;
    priv = this->private;

    STACK_WIND_COOKIE(frame, afr_mkdir_wind_cbk, (void *)(long)subvol,
                      priv->children[subvol],
                      priv->children[subvol]->fops->mkdir, &local->loc,
                      local->cont.mkdir.mode, local->umask, local->xdata_req);
    return 0;
}

int
afr_mkdir(call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode,
          mode_t umask, dict_t *xdata)
{
    afr_local_t *local = NULL;
    call_frame_t *transaction_frame = NULL;
    int ret = -1;
    int op_errno = ENOMEM;

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

    local = AFR_FRAME_INIT(transaction_frame, op_errno);
    if (!local)
        goto out;

    loc_copy(&local->loc, loc);
    local->inode = inode_ref(loc->inode);
    local->parent = inode_ref(loc->parent);

    local->cont.mkdir.mode = mode;
    local->umask = umask;

    if (!xdata || !dict_get_sizen(xdata, "gfid-req")) {
        op_errno = EPERM;
        gf_msg_callingfn(this->name, GF_LOG_WARNING, op_errno,
                         AFR_MSG_GFID_NULL,
                         "mkdir: %s is received "
                         "without gfid-req %p",
                         loc->path, xdata);
        goto out;
    }

    local->xdata_req = dict_copy_with_ref(xdata, NULL);
    if (!local->xdata_req) {
        op_errno = ENOMEM;
        goto out;
    }

    local->op = GF_FOP_MKDIR;
    local->transaction.wind = afr_mkdir_wind;
    local->transaction.unwind = afr_mkdir_unwind;

    ret = afr_build_parent_loc(&local->transaction.parent_loc, loc, &op_errno);
    if (ret)
        goto out;

    local->transaction.main_frame = frame;
    local->transaction.basename = AFR_BASENAME(loc->path);
    ret = afr_transaction(transaction_frame, this, AFR_ENTRY_TRANSACTION);
    if (ret < 0) {
        op_errno = -ret;
        goto out;
    }

    return 0;
out:
    if (transaction_frame)
        AFR_STACK_DESTROY(transaction_frame);

    AFR_STACK_UNWIND(mkdir, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL);
    return 0;
}

/* }}} */

/* {{{ link */

int
afr_link_unwind(call_frame_t *frame, xlator_t *this)
{
    call_frame_t *main_frame = NULL;
    afr_local_t *local = NULL;

    local = frame->local;

    main_frame = afr_transaction_detach_fop_frame(frame);
    if (!main_frame)
        return 0;

    AFR_STACK_UNWIND(link, main_frame, local->op_ret, local->op_errno,
                     local->inode, &local->cont.dir_fop.buf,
                     &local->cont.dir_fop.preparent,
                     &local->cont.dir_fop.postparent, local->xdata_rsp);
    return 0;
}

int
afr_link_wind_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)
{
    return __afr_dir_write_cbk(frame, cookie, this, op_ret, op_errno, buf,
                               preparent, postparent, NULL, NULL, xdata);
}

int
afr_link_wind(call_frame_t *frame, xlator_t *this, int subvol)
{
    afr_local_t *local = NULL;
    afr_private_t *priv = NULL;

    local = frame->local;
    priv = this->private;

    STACK_WIND_COOKIE(frame, afr_link_wind_cbk, (void *)(long)subvol,
                      priv->children[subvol],
                      priv->children[subvol]->fops->link, &local->loc,
                      &local->newloc, local->xdata_req);
    return 0;
}

int
afr_link(call_frame_t *frame, xlator_t *this, loc_t *oldloc, loc_t *newloc,
         dict_t *xdata)
{
    afr_local_t *local = NULL;
    call_frame_t *transaction_frame = NULL;
    int ret = -1;
    int op_errno = ENOMEM;

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

    local = AFR_FRAME_INIT(transaction_frame, op_errno);
    if (!local)
        goto out;

    loc_copy(&local->loc, oldloc);
    loc_copy(&local->newloc, newloc);

    local->inode = inode_ref(oldloc->inode);
    local->parent = inode_ref(newloc->parent);

    if (xdata)
        local->xdata_req = dict_copy_with_ref(xdata, NULL);
    else
        local->xdata_req = dict_new();

    if (!local->xdata_req)
        goto out;

    local->op = GF_FOP_LINK;

    local->transaction.wind = afr_link_wind;
    local->transaction.unwind = afr_link_unwind;

    ret = afr_build_parent_loc(&local->transaction.parent_loc, newloc,
                               &op_errno);
    if (ret)
        goto out;

    local->transaction.main_frame = frame;
    local->transaction.basename = AFR_BASENAME(newloc->path);
    ret = afr_transaction(transaction_frame, this, AFR_ENTRY_TRANSACTION);
    if (ret < 0) {
        op_errno = -ret;
        goto out;
    }

    return 0;
out:
    if (transaction_frame)
        AFR_STACK_DESTROY(transaction_frame);

    AFR_STACK_UNWIND(link, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL);
    return 0;
}

/* }}} */

/* {{{ symlink */

int
afr_symlink_unwind(call_frame_t *frame, xlator_t *this)
{
    call_frame_t *main_frame = NULL;
    afr_local_t *local = NULL;

    local = frame->local;

    main_frame = afr_transaction_detach_fop_frame(frame);
    if (!main_frame)
        return 0;

    AFR_STACK_UNWIND(symlink, main_frame, local->op_ret, local->op_errno,
                     local->inode, &local->cont.dir_fop.buf,
                     &local->cont.dir_fop.preparent,
                     &local->cont.dir_fop.postparent, local->xdata_rsp);
    return 0;
}

int
afr_symlink_wind_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)
{
    return __afr_dir_write_cbk(frame, cookie, this, op_ret, op_errno, buf,
                               preparent, postparent, NULL, NULL, xdata);
}

int
afr_symlink_wind(call_frame_t *frame, xlator_t *this, int subvol)
{
    afr_local_t *local = NULL;
    afr_private_t *priv = NULL;

    local = frame->local;
    priv = this->private;

    STACK_WIND_COOKIE(frame, afr_symlink_wind_cbk, (void *)(long)subvol,
                      priv->children[subvol],
                      priv->children[subvol]->fops->symlink,
                      local->cont.symlink.linkpath, &local->loc, local->umask,
                      local->xdata_req);
    return 0;
}

int
afr_symlink(call_frame_t *frame, xlator_t *this, const char *linkpath,
            loc_t *loc, mode_t umask, dict_t *xdata)
{
    afr_local_t *local = NULL;
    call_frame_t *transaction_frame = NULL;
    int ret = -1;
    int op_errno = ENOMEM;

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

    local = AFR_FRAME_INIT(transaction_frame, op_errno);
    if (!local)
        goto out;

    loc_copy(&local->loc, loc);
    local->inode = inode_ref(loc->inode);
    local->parent = inode_ref(loc->parent);

    local->cont.symlink.linkpath = gf_strdup(linkpath);
    local->umask = umask;

    if (xdata)
        local->xdata_req = dict_copy_with_ref(xdata, NULL);
    else
        local->xdata_req = dict_new();

    if (!local->xdata_req)
        goto out;

    local->op = GF_FOP_SYMLINK;
    local->transaction.wind = afr_symlink_wind;
    local->transaction.unwind = afr_symlink_unwind;

    ret = afr_build_parent_loc(&local->transaction.parent_loc, loc, &op_errno);
    if (ret)
        goto out;

    local->transaction.main_frame = frame;
    local->transaction.basename = AFR_BASENAME(loc->path);
    ret = afr_transaction(transaction_frame, this, AFR_ENTRY_TRANSACTION);
    if (ret < 0) {
        op_errno = -ret;
        goto out;
    }

    return 0;
out:
    if (transaction_frame)
        AFR_STACK_DESTROY(transaction_frame);

    AFR_STACK_UNWIND(symlink, frame, -1, op_errno, NULL, NULL, NULL, NULL,
                     NULL);
    return 0;
}

/* }}} */

/* {{{ rename */

int
afr_rename_unwind(call_frame_t *frame, xlator_t *this)
{
    call_frame_t *main_frame = NULL;
    afr_local_t *local = NULL;

    local = frame->local;

    main_frame = afr_transaction_detach_fop_frame(frame);
    if (!main_frame)
        return 0;

    AFR_STACK_UNWIND(rename, main_frame, local->op_ret, local->op_errno,
                     &local->cont.dir_fop.buf, &local->cont.dir_fop.preparent,
                     &local->cont.dir_fop.postparent,
                     &local->cont.dir_fop.prenewparent,
                     &local->cont.dir_fop.postnewparent, local->xdata_rsp);
    return 0;
}

int
afr_rename_wind_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                    int32_t op_ret, int32_t op_errno, struct iatt *buf,
                    struct iatt *preoldparent, struct iatt *postoldparent,
                    struct iatt *prenewparent, struct iatt *postnewparent,
                    dict_t *xdata)
{
    return __afr_dir_write_cbk(frame, cookie, this, op_ret, op_errno, buf,
                               preoldparent, postoldparent, prenewparent,
                               postnewparent, xdata);
}

int
afr_rename_wind(call_frame_t *frame, xlator_t *this, int subvol)
{
    afr_local_t *local = NULL;
    afr_private_t *priv = NULL;

    local = frame->local;
    priv = this->private;

    STACK_WIND_COOKIE(frame, afr_rename_wind_cbk, (void *)(long)subvol,
                      priv->children[subvol],
                      priv->children[subvol]->fops->rename, &local->loc,
                      &local->newloc, local->xdata_req);
    return 0;
}

int
afr_rename(call_frame_t *frame, xlator_t *this, loc_t *oldloc, loc_t *newloc,
           dict_t *xdata)
{
    afr_local_t *local = NULL;
    call_frame_t *transaction_frame = NULL;
    int ret = -1;
    int op_errno = ENOMEM;

    transaction_frame = copy_frame(frame);
    if (!transaction_frame) {
        op_errno = ENOMEM;
        goto out;
    }

    local = AFR_FRAME_INIT(transaction_frame, op_errno);
    if (!local)
        goto out;

    loc_copy(&local->loc, oldloc);
    loc_copy(&local->newloc, newloc);

    local->inode = inode_ref(oldloc->inode);
    local->parent = inode_ref(oldloc->parent);
    local->parent2 = inode_ref(newloc->parent);

    if (xdata)
        local->xdata_req = dict_copy_with_ref(xdata, NULL);
    else
        local->xdata_req = dict_new();

    if (!local->xdata_req)
        goto out;

    local->op = GF_FOP_RENAME;
    local->transaction.wind = afr_rename_wind;
    local->transaction.unwind = afr_rename_unwind;

    ret = afr_build_parent_loc(&local->transaction.parent_loc, oldloc,
                               &op_errno);
    if (ret)
        goto out;
    ret = afr_build_parent_loc(&local->transaction.new_parent_loc, newloc,
                               &op_errno);
    if (ret)
        goto out;

    local->transaction.main_frame = frame;
    local->transaction.basename = AFR_BASENAME(oldloc->path);
    local->transaction.new_basename = AFR_BASENAME(newloc->path);
    ret = afr_transaction(transaction_frame, this,
                          AFR_ENTRY_RENAME_TRANSACTION);
    if (ret < 0) {
        op_errno = -ret;
        goto out;
    }

    return 0;
out:
    if (transaction_frame)
        AFR_STACK_DESTROY(transaction_frame);

    AFR_STACK_UNWIND(rename, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL,
                     NULL);
    return 0;
}

/* }}} */

/* {{{ unlink */

int
afr_unlink_unwind(call_frame_t *frame, xlator_t *this)
{
    call_frame_t *main_frame = NULL;
    afr_local_t *local = NULL;

    local = frame->local;

    main_frame = afr_transaction_detach_fop_frame(frame);
    if (!main_frame)
        return 0;

    AFR_STACK_UNWIND(unlink, main_frame, local->op_ret, local->op_errno,
                     &local->cont.dir_fop.preparent,
                     &local->cont.dir_fop.postparent, local->xdata_rsp);
    return 0;
}

int
afr_unlink_wind_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                    int32_t op_ret, int32_t op_errno, struct iatt *preparent,
                    struct iatt *postparent, dict_t *xdata)
{
    return __afr_dir_write_cbk(frame, cookie, this, op_ret, op_errno, NULL,
                               preparent, postparent, NULL, NULL, xdata);
}

int
afr_unlink_wind(call_frame_t *frame, xlator_t *this, int subvol)
{
    afr_local_t *local = NULL;
    afr_private_t *priv = NULL;

    local = frame->local;
    priv = this->private;

    STACK_WIND_COOKIE(frame, afr_unlink_wind_cbk, (void *)(long)subvol,
                      priv->children[subvol],
                      priv->children[subvol]->fops->unlink, &local->loc,
                      local->xflag, local->xdata_req);
    return 0;
}

int
afr_unlink(call_frame_t *frame, xlator_t *this, loc_t *loc, int xflag,
           dict_t *xdata)
{
    afr_local_t *local = NULL;
    call_frame_t *transaction_frame = NULL;
    int ret = -1;
    int op_errno = ENOMEM;

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

    local = AFR_FRAME_INIT(transaction_frame, op_errno);
    if (!local)
        goto out;

    loc_copy(&local->loc, loc);
    local->xflag = xflag;

    local->inode = inode_ref(loc->inode);
    local->parent = inode_ref(loc->parent);

    if (xdata)
        local->xdata_req = dict_copy_with_ref(xdata, NULL);
    else
        local->xdata_req = dict_new();

    if (!local->xdata_req)
        goto out;

    local->op = GF_FOP_UNLINK;
    local->transaction.wind = afr_unlink_wind;
    local->transaction.unwind = afr_unlink_unwind;

    ret = afr_build_parent_loc(&local->transaction.parent_loc, loc, &op_errno);
    if (ret)
        goto out;

    local->transaction.main_frame = frame;
    local->transaction.basename = AFR_BASENAME(loc->path);
    ret = afr_transaction(transaction_frame, this, AFR_ENTRY_TRANSACTION);
    if (ret < 0) {
        op_errno = -ret;
        goto out;
    }

    return 0;
out:
    if (transaction_frame)
        AFR_STACK_DESTROY(transaction_frame);

    AFR_STACK_UNWIND(unlink, frame, -1, op_errno, NULL, NULL, NULL);
    return 0;
}

/* }}} */

/* {{{ rmdir */

int
afr_rmdir_unwind(call_frame_t *frame, xlator_t *this)
{
    call_frame_t *main_frame = NULL;
    afr_local_t *local = NULL;

    local = frame->local;

    main_frame = afr_transaction_detach_fop_frame(frame);
    if (!main_frame)
        return 0;

    AFR_STACK_UNWIND(rmdir, main_frame, local->op_ret, local->op_errno,
                     &local->cont.dir_fop.preparent,
                     &local->cont.dir_fop.postparent, local->xdata_rsp);
    return 0;
}

int
afr_rmdir_wind_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                   int32_t op_ret, int32_t op_errno, struct iatt *preparent,
                   struct iatt *postparent, dict_t *xdata)
{
    return __afr_dir_write_cbk(frame, cookie, this, op_ret, op_errno, NULL,
                               preparent, postparent, NULL, NULL, xdata);
}

int
afr_rmdir_wind(call_frame_t *frame, xlator_t *this, int subvol)
{
    afr_local_t *local = NULL;
    afr_private_t *priv = NULL;

    local = frame->local;
    priv = this->private;

    STACK_WIND_COOKIE(frame, afr_rmdir_wind_cbk, (void *)(long)subvol,
                      priv->children[subvol],
                      priv->children[subvol]->fops->rmdir, &local->loc,
                      local->cont.rmdir.flags, local->xdata_req);
    return 0;
}

int
afr_rmdir(call_frame_t *frame, xlator_t *this, loc_t *loc, int flags,
          dict_t *xdata)
{
    afr_local_t *local = NULL;
    call_frame_t *transaction_frame = NULL;
    int ret = -1;
    int op_errno = ENOMEM;

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

    local = AFR_FRAME_INIT(transaction_frame, op_errno);
    if (!local)
        goto out;

    loc_copy(&local->loc, loc);
    local->inode = inode_ref(loc->inode);
    local->parent = inode_ref(loc->parent);

    local->cont.rmdir.flags = flags;

    if (xdata)
        local->xdata_req = dict_copy_with_ref(xdata, NULL);
    else
        local->xdata_req = dict_new();

    if (!local->xdata_req)
        goto out;

    local->op = GF_FOP_RMDIR;
    local->transaction.wind = afr_rmdir_wind;
    local->transaction.unwind = afr_rmdir_unwind;

    ret = afr_build_parent_loc(&local->transaction.parent_loc, loc, &op_errno);
    if (ret)
        goto out;

    local->transaction.main_frame = frame;
    local->transaction.basename = AFR_BASENAME(loc->path);
    ret = afr_transaction(transaction_frame, this, AFR_ENTRY_TRANSACTION);
    if (ret < 0) {
        op_errno = -ret;
        goto out;
    }

    return 0;
out:
    if (transaction_frame)
        AFR_STACK_DESTROY(transaction_frame);

    AFR_STACK_UNWIND(rmdir, frame, -1, op_errno, NULL, NULL, NULL);
    return 0;
}

/* }}} */
