/*
  Copyright (c) 2016 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 "dht-lock.h"

static char *
dht_lock_asprintf(dht_lock_t *lock)
{
    char *lk_buf = NULL;
    char gfid[GF_UUID_BUF_SIZE] = {
        0,
    };

    if (lock == NULL)
        goto out;

    uuid_utoa_r(lock->loc.gfid, gfid);

    gf_asprintf(&lk_buf, "%s:%s", lock->xl->name, gfid);

out:
    return lk_buf;
}

static void
dht_log_lk_array(char *name, gf_loglevel_t log_level, dht_lock_t **lk_array,
                 int count)
{
    int i = 0;
    char *lk_buf = NULL;

    if ((lk_array == NULL) || (count == 0))
        goto out;

    for (i = 0; i < count; i++) {
        lk_buf = dht_lock_asprintf(lk_array[i]);
        if (!lk_buf)
            goto out;

        gf_smsg(name, log_level, 0, DHT_MSG_LK_ARRAY_INFO, "index=%d", i,
                "lk_buf=%s", lk_buf, NULL);
        GF_FREE(lk_buf);
    }

out:
    return;
}

static void
dht_lock_stack_destroy(call_frame_t *lock_frame, dht_lock_type_t lk)
{
    dht_local_t *local = NULL;

    local = lock_frame->local;

    if (lk == DHT_INODELK) {
        local->lock[0].layout.my_layout.locks = NULL;
        local->lock[0].layout.my_layout.lk_count = 0;
    } else {
        local->lock[0].ns.directory_ns.locks = NULL;
        local->lock[0].ns.directory_ns.lk_count = 0;
    }

    DHT_STACK_DESTROY(lock_frame);
    return;
}

static void
dht_lock_free(dht_lock_t *lock)
{
    if (lock == NULL)
        goto out;

    loc_wipe(&lock->loc);
    GF_FREE(lock->domain);
    GF_FREE(lock->basename);
    mem_put(lock);

out:
    return;
}

static void
dht_set_lkowner(dht_lock_t **lk_array, int count, gf_lkowner_t *lkowner)
{
    int i = 0;

    if (!lk_array || !lkowner)
        goto out;

    for (i = 0; i < count; i++) {
        lk_array[i]->lk_owner = *lkowner;
    }

out:
    return;
}

static int
dht_lock_request_cmp(const void *val1, const void *val2)
{
    dht_lock_t *lock1 = NULL;
    dht_lock_t *lock2 = NULL;
    int ret = -1;

    lock1 = *(dht_lock_t **)val1;
    lock2 = *(dht_lock_t **)val2;

    GF_VALIDATE_OR_GOTO("dht-locks", lock1, out);
    GF_VALIDATE_OR_GOTO("dht-locks", lock2, out);

    ret = strcmp(lock1->xl->name, lock2->xl->name);

    if (ret == 0) {
        ret = gf_uuid_compare(lock1->loc.gfid, lock2->loc.gfid);
    }

out:
    return ret;
}

static int
dht_lock_order_requests(dht_lock_t **locks, int count)
{
    int ret = -1;

    if (!locks || !count)
        goto out;

    qsort(locks, count, sizeof(*locks), dht_lock_request_cmp);
    ret = 0;

out:
    return ret;
}

void
dht_lock_array_free(dht_lock_t **lk_array, int count)
{
    int i = 0;
    dht_lock_t *lock = NULL;

    if (lk_array == NULL)
        goto out;

    for (i = 0; i < count; i++) {
        lock = lk_array[i];
        lk_array[i] = NULL;
        dht_lock_free(lock);
    }

out:
    return;
}

int32_t
dht_lock_count(dht_lock_t **lk_array, int lk_count)
{
    int i = 0, locked = 0;

    if ((lk_array == NULL) || (lk_count == 0))
        goto out;

    for (i = 0; i < lk_count; i++) {
        if (lk_array[i]->locked)
            locked++;
    }
out:
    return locked;
}

static call_frame_t *
dht_lock_frame(call_frame_t *parent_frame)
{
    call_frame_t *lock_frame = NULL;

    lock_frame = copy_frame(parent_frame);
    if (lock_frame == NULL)
        goto out;

    set_lk_owner_from_ptr(&lock_frame->root->lk_owner, parent_frame->root);

out:
    return lock_frame;
}

dht_lock_t *
dht_lock_new(xlator_t *this, xlator_t *xl, loc_t *loc, short type,
             const char *domain, const char *basename,
             dht_reaction_type_t do_on_failure)
{
    dht_conf_t *conf = NULL;
    dht_lock_t *lock = NULL;

    conf = this->private;

    lock = mem_get0(conf->lock_pool);
    if (lock == NULL)
        goto out;

    lock->xl = xl;
    lock->type = type;
    lock->do_on_failure = do_on_failure;

    lock->domain = gf_strdup(domain);
    if (lock->domain == NULL) {
        dht_lock_free(lock);
        lock = NULL;
        goto out;
    }

    if (basename) {
        lock->basename = gf_strdup(basename);
        if (lock->basename == NULL) {
            dht_lock_free(lock);
            lock = NULL;
            goto out;
        }
    }

    /* Fill only inode and gfid.
       posix and protocol/server give preference to pargfid/basename over
       gfid/inode for resolution if all the three parameters of loc_t are
       present. I want to avoid the following hypothetical situation:

       1. rebalance did a lookup on a dentry and got a gfid.
       2. rebalance acquires lock on loc_t which was filled with gfid and
          path (pargfid/bname) from step 1.
       3. somebody deleted and recreated the same file
       4. rename on the same path acquires lock on loc_t which now points
          to a different inode (and hence gets the lock).
       5. rebalance continues to migrate file (note that not all fops done
          by rebalance during migration are inode/gfid based Eg., unlink)
       6. rename continues.
    */
    lock->loc.inode = inode_ref(loc->inode);
    loc_gfid(loc, lock->loc.gfid);

out:
    return lock;
}

static int
dht_local_entrylk_init(call_frame_t *frame, dht_lock_t **lk_array, int lk_count,
                       fop_entrylk_cbk_t entrylk_cbk)
{
    int ret = -1;
    dht_local_t *local = NULL;

    local = frame->local;

    if (local == NULL) {
        local = dht_local_init(frame, NULL, NULL, 0);
    }

    if (local == NULL) {
        goto out;
    }

    local->lock[0].ns.directory_ns.entrylk_cbk = entrylk_cbk;
    local->lock[0].ns.directory_ns.locks = lk_array;
    local->lock[0].ns.directory_ns.lk_count = lk_count;

    ret = dht_lock_order_requests(local->lock[0].ns.directory_ns.locks,
                                  local->lock[0].ns.directory_ns.lk_count);
    if (ret < 0)
        goto out;

    ret = 0;
out:
    return ret;
}

static void
dht_entrylk_done(call_frame_t *lock_frame)
{
    fop_entrylk_cbk_t entrylk_cbk = NULL;
    call_frame_t *main_frame = NULL;
    dht_local_t *local = NULL;

    local = lock_frame->local;
    main_frame = local->main_frame;

    local->lock[0].ns.directory_ns.locks = NULL;
    local->lock[0].ns.directory_ns.lk_count = 0;

    entrylk_cbk = local->lock[0].ns.directory_ns.entrylk_cbk;
    local->lock[0].ns.directory_ns.entrylk_cbk = NULL;

    entrylk_cbk(main_frame, NULL, main_frame->this,
                local->lock[0].ns.directory_ns.op_ret,
                local->lock[0].ns.directory_ns.op_errno, NULL);

    dht_lock_stack_destroy(lock_frame, DHT_ENTRYLK);
    return;
}

static int32_t
dht_unlock_entrylk_done(call_frame_t *frame, void *cookie, xlator_t *this,
                        int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    dht_local_t *local = NULL;
    char gfid[GF_UUID_BUF_SIZE] = {0};

    local = frame->local;
    gf_uuid_unparse(local->lock[0].ns.directory_ns.locks[0]->loc.inode->gfid,
                    gfid);

    if (op_ret < 0) {
        gf_smsg(this->name, GF_LOG_WARNING, op_errno,
                DHT_MSG_UNLOCK_GFID_FAILED, "gfid=%s", gfid,
                "DHT_LAYOUT_HEAL_DOMAIN", NULL);
    }

    DHT_STACK_DESTROY(frame);
    return 0;
}

static int32_t
dht_unlock_entrylk_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                       int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    dht_local_t *local = NULL;
    int lk_index = 0, call_cnt = 0;
    char gfid[GF_UUID_BUF_SIZE] = {0};

    lk_index = (long)cookie;

    local = frame->local;

    uuid_utoa_r(local->lock[0].ns.directory_ns.locks[lk_index]->loc.gfid, gfid);

    if (op_ret < 0) {
        gf_smsg(this->name, GF_LOG_WARNING, op_errno, DHT_MSG_UNLOCKING_FAILED,
                "name=%s",
                local->lock[0].ns.directory_ns.locks[lk_index]->xl->name,
                "gfid=%s", gfid, NULL);
    } else {
        local->lock[0].ns.directory_ns.locks[lk_index]->locked = 0;
    }

    call_cnt = dht_frame_return(frame);
    if (is_last_call(call_cnt)) {
        dht_entrylk_done(frame);
    }

    return 0;
}

static int32_t
dht_unlock_entrylk(call_frame_t *frame, dht_lock_t **lk_array, int lk_count,
                   fop_entrylk_cbk_t entrylk_cbk)
{
    dht_local_t *local = NULL;
    int ret = -1, i = 0;
    call_frame_t *lock_frame = NULL;
    int call_cnt = 0;

    GF_VALIDATE_OR_GOTO("dht-locks", frame, done);
    GF_VALIDATE_OR_GOTO(frame->this->name, lk_array, done);
    GF_VALIDATE_OR_GOTO(frame->this->name, entrylk_cbk, done);

    call_cnt = dht_lock_count(lk_array, lk_count);
    if (call_cnt == 0) {
        ret = 0;
        goto done;
    }

    lock_frame = dht_lock_frame(frame);
    if (lock_frame == NULL) {
        gf_smsg(frame->this->name, GF_LOG_WARNING, 0,
                DHT_MSG_ALLOC_FRAME_FAILED_NOT_UNLOCKING_FOLLOWING_ENTRYLKS,
                NULL);

        dht_log_lk_array(frame->this->name, GF_LOG_WARNING, lk_array, lk_count);
        goto done;
    }

    ret = dht_local_entrylk_init(lock_frame, lk_array, lk_count, entrylk_cbk);
    if (ret < 0) {
        gf_smsg(frame->this->name, GF_LOG_WARNING, 0,
                DHT_MSG_LOCAL_LOCKS_STORE_FAILED_UNLOCKING_FOLLOWING_ENTRYLK,
                NULL);

        dht_log_lk_array(frame->this->name, GF_LOG_WARNING, lk_array, lk_count);

        goto done;
    }

    local = lock_frame->local;
    local->main_frame = frame;
    local->call_cnt = call_cnt;

    for (i = 0; i < local->lock[0].ns.directory_ns.lk_count; i++) {
        if (!local->lock[0].ns.directory_ns.locks[i]->locked)
            continue;

        lock_frame->root
            ->lk_owner = local->lock[0].ns.directory_ns.locks[i]->lk_owner;
        STACK_WIND_COOKIE(
            lock_frame, dht_unlock_entrylk_cbk, (void *)(long)i,
            local->lock[0].ns.directory_ns.locks[i]->xl,
            local->lock[0].ns.directory_ns.locks[i]->xl->fops->entrylk,
            local->lock[0].ns.directory_ns.locks[i]->domain,
            &local->lock[0].ns.directory_ns.locks[i]->loc,
            local->lock[0].ns.directory_ns.locks[i]->basename, ENTRYLK_UNLOCK,
            ENTRYLK_WRLCK, NULL);
        if (!--call_cnt)
            break;
    }

    return 0;

done:
    if (lock_frame)
        dht_lock_stack_destroy(lock_frame, DHT_ENTRYLK);

    /* no locks acquired, invoke entrylk_cbk */
    if (ret == 0)
        entrylk_cbk(frame, NULL, frame->this, 0, 0, NULL);

    return ret;
}

int32_t
dht_unlock_entrylk_wrapper(call_frame_t *frame, dht_elock_wrap_t *entrylk)
{
    dht_local_t *local = NULL, *lock_local = NULL;
    call_frame_t *lock_frame = NULL;
    char pgfid[GF_UUID_BUF_SIZE] = {0};
    int ret = 0;

    local = frame->local;

    if (!entrylk || !entrylk->locks)
        goto out;

    gf_uuid_unparse(local->loc.parent->gfid, pgfid);

    lock_frame = copy_frame(frame);
    if (lock_frame == NULL) {
        gf_smsg(frame->this->name, GF_LOG_WARNING, ENOMEM,
                DHT_MSG_COPY_FRAME_FAILED, "pgfid=%s", pgfid, "name=%s",
                local->loc.name, "path=%s", local->loc.path, NULL);
        goto done;
    }

    lock_local = dht_local_init(lock_frame, NULL, NULL, 0);
    if (lock_local == NULL) {
        gf_smsg(frame->this->name, GF_LOG_WARNING, ENOMEM,
                DHT_MSG_CREATE_FAILED, "local", "pgfid=%s", pgfid, "name=%s",
                local->loc.name, "path=%s", local->loc.path, NULL);
        goto done;
    }

    lock_frame->local = lock_local;

    lock_local->lock[0].ns.directory_ns.locks = entrylk->locks;
    lock_local->lock[0].ns.directory_ns.lk_count = entrylk->lk_count;
    entrylk->locks = NULL;
    entrylk->lk_count = 0;

    ret = dht_unlock_entrylk(
        lock_frame, lock_local->lock[0].ns.directory_ns.locks,
        lock_local->lock[0].ns.directory_ns.lk_count, dht_unlock_entrylk_done);
    if (ret)
        goto done;

    lock_frame = NULL;

done:
    if (lock_frame != NULL) {
        DHT_STACK_DESTROY(lock_frame);
    }

out:
    return 0;
}

static int
dht_entrylk_cleanup_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                        int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    dht_entrylk_done(frame);
    return 0;
}

static void
dht_entrylk_cleanup(call_frame_t *lock_frame)
{
    dht_lock_t **lk_array = NULL;
    int lk_count = 0, lk_acquired = 0;
    dht_local_t *local = NULL;

    local = lock_frame->local;

    lk_array = local->lock[0].ns.directory_ns.locks;
    lk_count = local->lock[0].ns.directory_ns.lk_count;

    lk_acquired = dht_lock_count(lk_array, lk_count);
    if (lk_acquired != 0) {
        dht_unlock_entrylk(lock_frame, lk_array, lk_count,
                           dht_entrylk_cleanup_cbk);
    } else {
        dht_entrylk_done(lock_frame);
    }

    return;
}

static int32_t
dht_blocking_entrylk_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                         int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    int lk_index = 0;
    int i = 0;
    dht_local_t *local = NULL;

    lk_index = (long)cookie;

    local = frame->local;
    if (op_ret == 0) {
        local->lock[0].ns.directory_ns.locks[lk_index]->locked = _gf_true;
    } else {
        switch (op_errno) {
            case ESTALE:
            case ENOENT:
                if (local->lock[0]
                        .ns.directory_ns.locks[lk_index]
                        ->do_on_failure != IGNORE_ENOENT_ESTALE) {
                    local->lock[0].ns.directory_ns.op_ret = -1;
                    local->lock[0].ns.directory_ns.op_errno = op_errno;
                    goto cleanup;
                }
                break;
            default:
                local->lock[0].ns.directory_ns.op_ret = -1;
                local->lock[0].ns.directory_ns.op_errno = op_errno;
                goto cleanup;
        }
    }

    if (lk_index == (local->lock[0].ns.directory_ns.lk_count - 1)) {
        for (i = 0; (i < local->lock[0].ns.directory_ns.lk_count) &&
                    (!local->lock[0].ns.directory_ns.locks[i]->locked);
             i++)
            ;

        if (i == local->lock[0].ns.directory_ns.lk_count) {
            local->lock[0].ns.directory_ns.op_ret = -1;
            local->lock[0].ns.directory_ns.op_errno = op_errno;
        }

        dht_entrylk_done(frame);
    } else {
        dht_blocking_entrylk_rec(frame, ++lk_index);
    }

    return 0;

cleanup:
    dht_entrylk_cleanup(frame);

    return 0;
}

void
dht_blocking_entrylk_rec(call_frame_t *frame, int i)
{
    dht_local_t *local = NULL;

    local = frame->local;

    STACK_WIND_COOKIE(
        frame, dht_blocking_entrylk_cbk, (void *)(long)i,
        local->lock[0].ns.directory_ns.locks[i]->xl,
        local->lock[0].ns.directory_ns.locks[i]->xl->fops->entrylk,
        local->lock[0].ns.directory_ns.locks[i]->domain,
        &local->lock[0].ns.directory_ns.locks[i]->loc,
        local->lock[0].ns.directory_ns.locks[i]->basename, ENTRYLK_LOCK,
        ENTRYLK_WRLCK, NULL);

    return;
}

int
dht_blocking_entrylk(call_frame_t *frame, dht_lock_t **lk_array, int lk_count,
                     fop_entrylk_cbk_t entrylk_cbk)
{
    int ret = -1;
    call_frame_t *lock_frame = NULL;
    dht_local_t *local = NULL;

    GF_VALIDATE_OR_GOTO("dht-locks", frame, out);
    GF_VALIDATE_OR_GOTO(frame->this->name, lk_array, out);
    GF_VALIDATE_OR_GOTO(frame->this->name, entrylk_cbk, out);

    lock_frame = dht_lock_frame(frame);
    if (lock_frame == NULL)
        goto out;

    ret = dht_local_entrylk_init(lock_frame, lk_array, lk_count, entrylk_cbk);
    if (ret < 0) {
        goto out;
    }

    dht_set_lkowner(lk_array, lk_count, &lock_frame->root->lk_owner);

    local = lock_frame->local;
    local->main_frame = frame;

    dht_blocking_entrylk_rec(lock_frame, 0);

    return 0;
out:
    if (lock_frame)
        dht_lock_stack_destroy(lock_frame, DHT_ENTRYLK);

    return -1;
}

static int
dht_local_inodelk_init(call_frame_t *frame, dht_lock_t **lk_array, int lk_count,
                       fop_inodelk_cbk_t inodelk_cbk)
{
    int ret = -1;
    dht_local_t *local = NULL;

    local = frame->local;

    if (local == NULL) {
        local = dht_local_init(frame, NULL, NULL, 0);
    }

    if (local == NULL) {
        goto out;
    }

    local->lock[0].layout.my_layout.inodelk_cbk = inodelk_cbk;
    local->lock[0].layout.my_layout.locks = lk_array;
    local->lock[0].layout.my_layout.lk_count = lk_count;

    ret = dht_lock_order_requests(local->lock[0].layout.my_layout.locks,
                                  local->lock[0].layout.my_layout.lk_count);
    if (ret < 0)
        goto out;

    ret = 0;
out:
    return ret;
}

static void
dht_inodelk_done(call_frame_t *lock_frame)
{
    fop_inodelk_cbk_t inodelk_cbk = NULL;
    call_frame_t *main_frame = NULL;
    dht_local_t *local = NULL;

    local = lock_frame->local;
    main_frame = local->main_frame;

    local->lock[0].layout.my_layout.locks = NULL;
    local->lock[0].layout.my_layout.lk_count = 0;

    inodelk_cbk = local->lock[0].layout.my_layout.inodelk_cbk;
    local->lock[0].layout.my_layout.inodelk_cbk = NULL;

    inodelk_cbk(main_frame, NULL, main_frame->this,
                local->lock[0].layout.my_layout.op_ret,
                local->lock[0].layout.my_layout.op_errno, NULL);

    dht_lock_stack_destroy(lock_frame, DHT_INODELK);
    return;
}

static int32_t
dht_unlock_inodelk_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                       int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    dht_local_t *local = NULL;
    int lk_index = 0, call_cnt = 0;
    char gfid[GF_UUID_BUF_SIZE] = {0};

    lk_index = (long)cookie;

    local = frame->local;
    if (op_ret < 0) {
        uuid_utoa_r(local->lock[0].layout.my_layout.locks[lk_index]->loc.gfid,
                    gfid);

        gf_smsg(this->name, GF_LOG_WARNING, op_errno, DHT_MSG_UNLOCKING_FAILED,
                "name=%s",
                local->lock[0].layout.my_layout.locks[lk_index]->xl->name,
                "gfid=%s", gfid, NULL);
    } else {
        local->lock[0].layout.my_layout.locks[lk_index]->locked = 0;
    }

    call_cnt = dht_frame_return(frame);
    if (is_last_call(call_cnt)) {
        dht_inodelk_done(frame);
    }

    return 0;
}

static int32_t
dht_unlock_inodelk_done(call_frame_t *frame, void *cookie, xlator_t *this,
                        int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    dht_local_t *local = NULL;
    char gfid[GF_UUID_BUF_SIZE] = {0};

    local = frame->local;
    gf_uuid_unparse(local->lock[0].layout.my_layout.locks[0]->loc.inode->gfid,
                    gfid);

    if (op_ret < 0) {
        gf_smsg(this->name, GF_LOG_WARNING, op_errno,
                DHT_MSG_UNLOCK_GFID_FAILED, "DHT_LAYOUT_HEAL_DOMAIN gfid=%s",
                gfid, NULL);
    }

    DHT_STACK_DESTROY(frame);
    return 0;
}

int32_t
dht_unlock_inodelk(call_frame_t *frame, dht_lock_t **lk_array, int lk_count,
                   fop_inodelk_cbk_t inodelk_cbk)
{
    dht_local_t *local = NULL;
    struct gf_flock flock = {
        0,
    };
    int ret = -1, i = 0;
    call_frame_t *lock_frame = NULL;
    int call_cnt = 0;

    GF_VALIDATE_OR_GOTO("dht-locks", frame, done);
    GF_VALIDATE_OR_GOTO(frame->this->name, lk_array, done);
    GF_VALIDATE_OR_GOTO(frame->this->name, inodelk_cbk, done);

    call_cnt = dht_lock_count(lk_array, lk_count);
    if (call_cnt == 0) {
        ret = 0;
        goto done;
    }

    lock_frame = dht_lock_frame(frame);
    if (lock_frame == NULL) {
        gf_smsg(frame->this->name, GF_LOG_WARNING, 0,
                DHT_MSG_ALLOC_FRAME_FAILED_NOT_UNLOCKING_FOLLOWING_ENTRYLKS,
                NULL);

        dht_log_lk_array(frame->this->name, GF_LOG_WARNING, lk_array, lk_count);
        goto done;
    }

    ret = dht_local_inodelk_init(lock_frame, lk_array, lk_count, inodelk_cbk);
    if (ret < 0) {
        gf_smsg(frame->this->name, GF_LOG_WARNING, 0,
                DHT_MSG_LOCAL_LOCKS_STORE_FAILED_UNLOCKING_FOLLOWING_ENTRYLK,
                NULL);

        dht_log_lk_array(frame->this->name, GF_LOG_WARNING, lk_array, lk_count);

        goto done;
    }

    local = lock_frame->local;
    local->main_frame = frame;
    local->call_cnt = call_cnt;

    flock.l_type = F_UNLCK;

    for (i = 0; i < local->lock[0].layout.my_layout.lk_count; i++) {
        if (!local->lock[0].layout.my_layout.locks[i]->locked)
            continue;

        lock_frame->root
            ->lk_owner = local->lock[0].layout.my_layout.locks[i]->lk_owner;
        STACK_WIND_COOKIE(
            lock_frame, dht_unlock_inodelk_cbk, (void *)(long)i,
            local->lock[0].layout.my_layout.locks[i]->xl,
            local->lock[0].layout.my_layout.locks[i]->xl->fops->inodelk,
            local->lock[0].layout.my_layout.locks[i]->domain,
            &local->lock[0].layout.my_layout.locks[i]->loc, F_SETLK, &flock,
            NULL);
        if (!--call_cnt)
            break;
    }

    return 0;

done:
    if (lock_frame)
        dht_lock_stack_destroy(lock_frame, DHT_INODELK);

    /* no locks acquired, invoke inodelk_cbk */
    if (ret == 0)
        inodelk_cbk(frame, NULL, frame->this, 0, 0, NULL);

    return ret;
}

int32_t
dht_unlock_inodelk_wrapper(call_frame_t *frame, dht_ilock_wrap_t *inodelk)
{
    dht_local_t *local = NULL, *lock_local = NULL;
    call_frame_t *lock_frame = NULL;
    char pgfid[GF_UUID_BUF_SIZE] = {0};
    int ret = 0;

    local = frame->local;

    if (!inodelk || !inodelk->locks)
        goto out;

    gf_uuid_unparse(local->loc.parent->gfid, pgfid);

    lock_frame = copy_frame(frame);
    if (lock_frame == NULL) {
        gf_smsg(frame->this->name, GF_LOG_WARNING, ENOMEM,
                DHT_MSG_COPY_FRAME_FAILED, "pgfid=%s", pgfid, "name=%s",
                local->loc.name, "path=%s", local->loc.path, NULL);
        goto done;
    }

    lock_local = dht_local_init(lock_frame, NULL, NULL, 0);
    if (lock_local == NULL) {
        gf_smsg(frame->this->name, GF_LOG_WARNING, ENOMEM,
                DHT_MSG_CREATE_FAILED, "local", "gfid=%s", pgfid, "name=%s",
                local->loc.name, "path=%s", local->loc.path, NULL);
        goto done;
    }

    lock_frame->local = lock_local;

    lock_local->lock[0].layout.my_layout.locks = inodelk->locks;
    lock_local->lock[0].layout.my_layout.lk_count = inodelk->lk_count;
    inodelk->locks = NULL;
    inodelk->lk_count = 0;

    ret = dht_unlock_inodelk(
        lock_frame, lock_local->lock[0].layout.my_layout.locks,
        lock_local->lock[0].layout.my_layout.lk_count, dht_unlock_inodelk_done);

    if (ret)
        goto done;

    lock_frame = NULL;

done:
    if (lock_frame != NULL) {
        DHT_STACK_DESTROY(lock_frame);
    }
out:
    return 0;
}

static int
dht_inodelk_cleanup_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                        int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    dht_inodelk_done(frame);
    return 0;
}

static void
dht_inodelk_cleanup(call_frame_t *lock_frame)
{
    dht_lock_t **lk_array = NULL;
    int lk_count = 0, lk_acquired = 0;
    dht_local_t *local = NULL;

    local = lock_frame->local;

    lk_array = local->lock[0].layout.my_layout.locks;
    lk_count = local->lock[0].layout.my_layout.lk_count;

    lk_acquired = dht_lock_count(lk_array, lk_count);
    if (lk_acquired != 0) {
        dht_unlock_inodelk(lock_frame, lk_array, lk_count,
                           dht_inodelk_cleanup_cbk);
    } else {
        dht_inodelk_done(lock_frame);
    }

    return;
}

static int32_t
dht_nonblocking_inodelk_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                            int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    dht_local_t *local = NULL;
    int lk_index = 0, call_cnt = 0;
    char gfid[GF_UUID_BUF_SIZE] = {0};
    dht_ilock_wrap_t *my_layout;

    local = frame->local;
    lk_index = (long)cookie;

    my_layout = &(local->lock[0].layout.my_layout);

    if (op_ret == -1) {
        my_layout->op_ret = -1;
        my_layout->op_errno = op_errno;

        if (my_layout->locks[lk_index]) {
            uuid_utoa_r(my_layout->locks[lk_index]->loc.inode->gfid, gfid);

            gf_msg_debug(this->name, op_errno,
                         "inodelk failed on gfid: %s "
                         "subvolume: %s",
                         gfid, my_layout->locks[lk_index]->xl->name);
        }

        goto out;
    }

    my_layout->locks[lk_index]->locked = _gf_true;

out:
    call_cnt = dht_frame_return(frame);
    if (is_last_call(call_cnt)) {
        if (my_layout->op_ret < 0) {
            dht_inodelk_cleanup(frame);
            return 0;
        }

        dht_inodelk_done(frame);
    }

    return 0;
}

int
dht_nonblocking_inodelk(call_frame_t *frame, dht_lock_t **lk_array,
                        int lk_count, fop_inodelk_cbk_t inodelk_cbk)
{
    struct gf_flock flock = {
        0,
    };
    int i = 0, ret = 0;
    dht_local_t *local = NULL;
    call_frame_t *lock_frame = NULL;

    GF_VALIDATE_OR_GOTO("dht-locks", frame, out);
    GF_VALIDATE_OR_GOTO(frame->this->name, lk_array, out);
    GF_VALIDATE_OR_GOTO(frame->this->name, inodelk_cbk, out);

    lock_frame = dht_lock_frame(frame);
    if (lock_frame == NULL)
        goto out;

    ret = dht_local_inodelk_init(lock_frame, lk_array, lk_count, inodelk_cbk);
    if (ret < 0) {
        goto out;
    }

    dht_set_lkowner(lk_array, lk_count, &lock_frame->root->lk_owner);

    local = lock_frame->local;
    local->main_frame = frame;

    local->call_cnt = lk_count;

    for (i = 0; i < lk_count; i++) {
        flock.l_type = local->lock[0].layout.my_layout.locks[i]->type;

        STACK_WIND_COOKIE(
            lock_frame, dht_nonblocking_inodelk_cbk, (void *)(long)i,
            local->lock[0].layout.my_layout.locks[i]->xl,
            local->lock[0].layout.my_layout.locks[i]->xl->fops->inodelk,
            local->lock[0].layout.my_layout.locks[i]->domain,
            &local->lock[0].layout.my_layout.locks[i]->loc, F_SETLK, &flock,
            NULL);
    }

    return 0;

out:
    if (lock_frame)
        dht_lock_stack_destroy(lock_frame, DHT_INODELK);

    return -1;
}

static int32_t
dht_blocking_inodelk_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                         int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    int lk_index = 0;
    int i = 0;
    dht_local_t *local = NULL;
    char gfid[GF_UUID_BUF_SIZE] = {
        0,
    };
    dht_reaction_type_t reaction = 0;

    lk_index = (long)cookie;

    local = frame->local;
    if (op_ret == 0) {
        local->lock[0].layout.my_layout.locks[lk_index]->locked = _gf_true;
    } else {
        switch (op_errno) {
            case ESTALE:
            case ENOENT:
                reaction = local->lock[0]
                               .layout.my_layout.locks[lk_index]
                               ->do_on_failure;
                if ((reaction != IGNORE_ENOENT_ESTALE) &&
                    (reaction != IGNORE_ENOENT_ESTALE_EIO)) {
                    gf_uuid_unparse(local->lock[0]
                                        .layout.my_layout.locks[lk_index]
                                        ->loc.gfid,
                                    gfid);
                    local->lock[0].layout.my_layout.op_ret = -1;
                    local->lock[0].layout.my_layout.op_errno = op_errno;
                    gf_smsg(this->name, GF_LOG_ERROR, op_errno,
                            DHT_MSG_INODELK_FAILED, "subvol=%s",
                            local->lock[0]
                                .layout.my_layout.locks[lk_index]
                                ->xl->name,
                            "gfid=%s", gfid, NULL);
                    goto cleanup;
                }
                break;
            case EIO:
                reaction = local->lock[0]
                               .layout.my_layout.locks[lk_index]
                               ->do_on_failure;
                if (reaction != IGNORE_ENOENT_ESTALE_EIO) {
                    gf_uuid_unparse(local->lock[0]
                                        .layout.my_layout.locks[lk_index]
                                        ->loc.gfid,
                                    gfid);
                    local->lock[0].layout.my_layout.op_ret = -1;
                    local->lock[0].layout.my_layout.op_errno = op_errno;
                    gf_smsg(this->name, GF_LOG_ERROR, op_errno,
                            DHT_MSG_INODELK_FAILED, "subvol=%s",
                            local->lock[0]
                                .layout.my_layout.locks[lk_index]
                                ->xl->name,
                            "gfid=%s", gfid, NULL);
                    goto cleanup;
                }
                break;

            default:
                gf_uuid_unparse(
                    local->lock[0].layout.my_layout.locks[lk_index]->loc.gfid,
                    gfid);
                local->lock[0].layout.my_layout.op_ret = -1;
                local->lock[0].layout.my_layout.op_errno = op_errno;
                gf_smsg(
                    this->name, GF_LOG_ERROR, op_errno, DHT_MSG_INODELK_FAILED,
                    "subvol=%s",
                    local->lock[0].layout.my_layout.locks[lk_index]->xl->name,
                    "gfid=%s", gfid, NULL);
                goto cleanup;
        }
    }

    if (lk_index == (local->lock[0].layout.my_layout.lk_count - 1)) {
        for (i = 0; (i < local->lock[0].layout.my_layout.lk_count) &&
                    (!local->lock[0].layout.my_layout.locks[i]->locked);
             i++)
            ;

        if (i == local->lock[0].layout.my_layout.lk_count) {
            local->lock[0].layout.my_layout.op_ret = -1;
            local->lock[0].layout.my_layout.op_errno = op_errno;
        }

        dht_inodelk_done(frame);
    } else {
        dht_blocking_inodelk_rec(frame, ++lk_index);
    }

    return 0;

cleanup:
    dht_inodelk_cleanup(frame);

    return 0;
}

void
dht_blocking_inodelk_rec(call_frame_t *frame, int i)
{
    dht_local_t *local = NULL;
    struct gf_flock flock = {
        0,
    };

    local = frame->local;

    flock.l_type = local->lock[0].layout.my_layout.locks[i]->type;

    STACK_WIND_COOKIE(
        frame, dht_blocking_inodelk_cbk, (void *)(long)i,
        local->lock[0].layout.my_layout.locks[i]->xl,
        local->lock[0].layout.my_layout.locks[i]->xl->fops->inodelk,
        local->lock[0].layout.my_layout.locks[i]->domain,
        &local->lock[0].layout.my_layout.locks[i]->loc, F_SETLKW, &flock, NULL);

    return;
}

int
dht_blocking_inodelk(call_frame_t *frame, dht_lock_t **lk_array, int lk_count,
                     fop_inodelk_cbk_t inodelk_cbk)
{
    int ret = -1;
    call_frame_t *lock_frame = NULL;
    dht_local_t *local = NULL;
    dht_local_t *tmp_local = NULL;
    char gfid[GF_UUID_BUF_SIZE] = {
        0,
    };

    GF_VALIDATE_OR_GOTO("dht-locks", frame, out);
    GF_VALIDATE_OR_GOTO(frame->this->name, lk_array, out);
    GF_VALIDATE_OR_GOTO(frame->this->name, inodelk_cbk, out);

    tmp_local = frame->local;

    lock_frame = dht_lock_frame(frame);
    if (lock_frame == NULL) {
        gf_uuid_unparse(tmp_local->loc.gfid, gfid);
        gf_smsg("dht", GF_LOG_ERROR, ENOMEM, DHT_MSG_LOCK_FRAME_FAILED,
                "gfid=%s", gfid, "path=%s", tmp_local->loc.path, NULL);
        goto out;
    }

    ret = dht_local_inodelk_init(lock_frame, lk_array, lk_count, inodelk_cbk);
    if (ret < 0) {
        gf_uuid_unparse(tmp_local->loc.gfid, gfid);
        gf_smsg("dht", GF_LOG_ERROR, ENOMEM, DHT_MSG_LOCAL_LOCK_INIT_FAILED,
                "gfid=%s", gfid, "path=%s", tmp_local->loc.path, NULL);
        goto out;
    }

    dht_set_lkowner(lk_array, lk_count, &lock_frame->root->lk_owner);

    local = lock_frame->local;
    local->main_frame = frame;

    dht_blocking_inodelk_rec(lock_frame, 0);

    return 0;
out:
    if (lock_frame)
        dht_lock_stack_destroy(lock_frame, DHT_INODELK);

    return -1;
}

void
dht_unlock_namespace(call_frame_t *frame, dht_dir_transaction_t *lock)
{
    GF_VALIDATE_OR_GOTO("dht-locks", frame, out);
    GF_VALIDATE_OR_GOTO(frame->this->name, lock, out);

    dht_unlock_entrylk_wrapper(frame, &lock->ns.directory_ns);
    dht_unlock_inodelk_wrapper(frame, &lock->ns.parent_layout);

out:
    return;
}

static int32_t
dht_protect_namespace_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                          int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    dht_local_t *local = NULL;

    local = frame->local;
    if (op_ret != 0)
        dht_unlock_inodelk_wrapper(frame, &local->current->ns.parent_layout);

    local->current->ns.ns_cbk(frame, cookie, this, op_ret, op_errno, xdata);
    return 0;
}

int32_t
dht_blocking_entrylk_after_inodelk(call_frame_t *frame, void *cookie,
                                   xlator_t *this, int32_t op_ret,
                                   int32_t op_errno, dict_t *xdata)
{
    dht_local_t *local = NULL;
    int ret = -1;
    loc_t *loc = NULL;
    dht_lock_t **lk_array = NULL;
    char pgfid[GF_UUID_BUF_SIZE] = {0};
    int count = 0;
    dht_elock_wrap_t *entrylk = NULL;

    local = frame->local;
    entrylk = &local->current->ns.directory_ns;

    if (op_ret < 0) {
        local->op_ret = -1;
        local->op_errno = op_errno;
        goto err;
    }

    loc = &entrylk->locks[0]->loc;
    gf_uuid_unparse(loc->gfid, pgfid);

    local->op_ret = 0;
    lk_array = entrylk->locks;
    count = entrylk->lk_count;

    ret = dht_blocking_entrylk(frame, lk_array, count,
                               dht_protect_namespace_cbk);

    if (ret < 0) {
        local->op_ret = -1;
        local->op_errno = EIO;
        gf_smsg(this->name, GF_LOG_WARNING, local->op_errno,
                DHT_MSG_ENTRYLK_FAILED_AFT_INODELK, "fop=%s",
                gf_fop_list[local->fop], "pgfid=%s", pgfid, "basename=%s",
                entrylk->locks[0]->basename, NULL);
        goto err;
    }

    return 0;

err:
    if (lk_array != NULL) {
        dht_lock_array_free(lk_array, count);
        GF_FREE(lk_array);
        entrylk->locks = NULL;
        entrylk->lk_count = 0;
    }

    /* Unlock inodelk. No harm calling unlock twice */
    dht_unlock_inodelk_wrapper(frame, &local->current->ns.parent_layout);
    /* Call ns_cbk. It will take care of unwinding */
    local->current->ns.ns_cbk(frame, NULL, this, local->op_ret, local->op_errno,
                              NULL);
    return 0;
}

/* Given the loc and the subvol, this routine takes the inodelk on
 * the parent inode and entrylk on (parent, loc->name). This routine
 * is specific as it supports only one subvol on which it takes inodelk
 * and then entrylk serially.
 */
int
dht_protect_namespace(call_frame_t *frame, loc_t *loc, xlator_t *subvol,
                      struct dht_namespace *ns, fop_entrylk_cbk_t ns_cbk)
{
    dht_ilock_wrap_t *inodelk = NULL;
    dht_elock_wrap_t *entrylk = NULL;
    dht_lock_t **lk_array = NULL;
    dht_local_t *local = NULL;
    xlator_t *this = NULL;
    loc_t parent = {
        0,
    };
    int ret = -1;
    char pgfid[GF_UUID_BUF_SIZE] = {0};
    int32_t op_errno = 0;
    int count = 1;

    GF_VALIDATE_OR_GOTO("dht-locks", frame, out);
    GF_VALIDATE_OR_GOTO(frame->this->name, loc, out);
    GF_VALIDATE_OR_GOTO(frame->this->name, loc->parent, out);
    GF_VALIDATE_OR_GOTO(frame->this->name, subvol, out);

    local = frame->local;
    this = frame->this;

    inodelk = &ns->parent_layout;
    entrylk = &ns->directory_ns;

    /* Initialize entrylk_cbk and parent loc */
    ns->ns_cbk = ns_cbk;

    ret = dht_build_parent_loc(this, &parent, loc, &op_errno);
    if (ret) {
        gf_smsg(this->name, GF_LOG_ERROR, op_errno, DHT_MSG_LOC_FAILED,
                "gfid=%s", loc->gfid, "name=%s", loc->name, "path=%s",
                loc->path, NULL);
        goto out;
    }
    gf_uuid_unparse(parent.gfid, pgfid);

    /* Alloc inodelk */
    inodelk->locks = GF_CALLOC(count, sizeof(*lk_array), gf_common_mt_pointer);
    if (inodelk->locks == NULL) {
        local->op_errno = ENOMEM;
        gf_smsg(this->name, GF_LOG_WARNING, local->op_errno,
                DHT_MSG_CALLOC_FAILED, "fop=%s", gf_fop_list[local->fop],
                "pgfid=%s", pgfid, "name=%s", loc->name, "path=%s", loc->path,
                NULL);
        goto out;
    }

    inodelk->locks[0] = dht_lock_new(this, subvol, &parent, F_RDLCK,
                                     DHT_LAYOUT_HEAL_DOMAIN, NULL,
                                     FAIL_ON_ANY_ERROR);
    if (inodelk->locks[0] == NULL) {
        local->op_errno = ENOMEM;
        gf_smsg(this->name, GF_LOG_WARNING, local->op_errno,
                DHT_MSG_LOCK_ALLOC_FAILED, "inodelk-fop=%s",
                gf_fop_list[local->fop], "pgfid=%s", pgfid, "name=%s",
                loc->name, "path=%s", loc->path, NULL);
        goto err;
    }
    inodelk->lk_count = count;

    /* Allock entrylk */
    entrylk->locks = GF_CALLOC(count, sizeof(*lk_array), gf_common_mt_pointer);
    if (entrylk->locks == NULL) {
        local->op_errno = ENOMEM;
        gf_smsg(this->name, GF_LOG_WARNING, local->op_errno,
                DHT_MSG_CALLOC_FAILED, "entrylk-fop=%s",
                gf_fop_list[local->fop], "pgfid=%s", pgfid, "name=%s",
                loc->name, "path=%s", loc->path, NULL);

        goto err;
    }

    entrylk->locks[0] = dht_lock_new(this, subvol, &parent, F_WRLCK,
                                     DHT_ENTRY_SYNC_DOMAIN, loc->name,
                                     FAIL_ON_ANY_ERROR);
    if (entrylk->locks[0] == NULL) {
        local->op_errno = ENOMEM;
        gf_smsg(this->name, GF_LOG_WARNING, local->op_errno,
                DHT_MSG_LOCK_ALLOC_FAILED, "entrylk-fop=%s",
                gf_fop_list[local->fop], "pgfid=%s", pgfid, "name=%s",
                loc->name, "path=%s", loc->path, NULL);

        goto err;
    }
    entrylk->lk_count = count;

    /* Take read inodelk on parent. If it is successful, take write entrylk
     * on name in cbk.
     */
    lk_array = inodelk->locks;
    ret = dht_blocking_inodelk(frame, lk_array, count,
                               dht_blocking_entrylk_after_inodelk);
    if (ret < 0) {
        local->op_errno = EIO;
        gf_smsg(this->name, GF_LOG_WARNING, local->op_errno,
                DHT_MSG_BLOCK_INODELK_FAILED, "fop=%s", gf_fop_list[local->fop],
                "pgfid=%s", pgfid, "name=%s", loc->name, "path=%s", loc->path,
                NULL);

        goto err;
    }

    loc_wipe(&parent);

    return 0;
err:
    if (entrylk->locks != NULL) {
        dht_lock_array_free(entrylk->locks, count);
        GF_FREE(entrylk->locks);
        entrylk->locks = NULL;
        entrylk->lk_count = 0;
    }

    if (inodelk->locks != NULL) {
        dht_lock_array_free(inodelk->locks, count);
        GF_FREE(inodelk->locks);
        inodelk->locks = NULL;
        inodelk->lk_count = 0;
    }

    loc_wipe(&parent);
out:
    return -1;
}
