/*
  Copyright (c) 2010-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 "server.h"
#include "server-helpers.h"
#include "server-messages.h"

int
server_resolve_all(call_frame_t *frame);
int
resolve_entry_simple(call_frame_t *frame);
int
resolve_inode_simple(call_frame_t *frame);
int
resolve_continue(call_frame_t *frame);
int
resolve_anonfd_simple(call_frame_t *frame);

int
resolve_loc_touchup(call_frame_t *frame)
{
    server_state_t *state = NULL;
    server_resolve_t *resolve = NULL;
    loc_t *loc = NULL;

    state = CALL_STATE(frame);

    resolve = state->resolve_now;
    loc = state->loc_now;

    loc_touchup(loc, resolve->bname);
    return 0;
}

int
resolve_gfid_entry_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                       int op_ret, int op_errno, inode_t *inode,
                       struct iatt *buf, dict_t *xdata, struct iatt *postparent)
{
    server_state_t *state = NULL;
    server_resolve_t *resolve = NULL;
    inode_t *link_inode = NULL;
    loc_t *resolve_loc = NULL;

    state = CALL_STATE(frame);
    resolve = state->resolve_now;
    resolve_loc = &resolve->resolve_loc;

    if (op_ret == -1) {
        if (op_errno == ENOENT) {
            gf_msg_debug(this->name, 0, "%s/%s: failed to resolve (%s)",
                         uuid_utoa(resolve_loc->pargfid), resolve_loc->name,
                         strerror(op_errno));
            if (resolve->type == RESOLVE_NOT) {
                do {
                    inode = inode_grep(state->itable, resolve_loc->parent,
                                       resolve->bname);

                    if (inode) {
                        gf_msg_debug(this->name, 0,
                                     "%s/%s: "
                                     "removing stale dentry",
                                     uuid_utoa(resolve_loc->pargfid),
                                     resolve->bname);
                        inode_unlink(inode, resolve_loc->parent,
                                     resolve->bname);
                    }
                } while (inode);
            }
        } else {
            gf_msg(this->name, GF_LOG_WARNING, op_errno,
                   PS_MSG_GFID_RESOLVE_FAILED,
                   "%s/%s: failed to "
                   "resolve (%s)",
                   uuid_utoa(resolve_loc->pargfid), resolve_loc->name,
                   strerror(op_errno));
        }
        goto out;
    }

    link_inode = inode_link(inode, resolve_loc->parent, resolve_loc->name, buf);

    if (!link_inode)
        goto out;

    inode_lookup(link_inode);

    inode_unref(link_inode);

out:
    loc_wipe(resolve_loc);

    resolve_continue(frame);
    return 0;
}

int
resolve_gfid_cbk(call_frame_t *frame, void *cookie, xlator_t *this, int op_ret,
                 int op_errno, inode_t *inode, struct iatt *buf, dict_t *xdata,
                 struct iatt *postparent)
{
    server_state_t *state = NULL;
    server_resolve_t *resolve = NULL;
    inode_t *link_inode = NULL;
    loc_t *resolve_loc = NULL;
    dict_t *dict = NULL;

    state = CALL_STATE(frame);
    resolve = state->resolve_now;
    resolve_loc = &resolve->resolve_loc;

    if (op_ret == -1) {
        if (op_errno == ENOENT) {
            gf_msg_debug(this->name, GF_LOG_DEBUG, "%s: failed to resolve (%s)",
                         uuid_utoa(resolve_loc->gfid), strerror(op_errno));
        } else {
            gf_msg(this->name, GF_LOG_WARNING, op_errno,
                   PS_MSG_GFID_RESOLVE_FAILED, "%s: failed to resolve (%s)",
                   uuid_utoa(resolve_loc->gfid), strerror(op_errno));
        }
        loc_wipe(&resolve->resolve_loc);
        goto out;
    }

    link_inode = inode_link(inode, NULL, NULL, buf);

    if (!link_inode) {
        loc_wipe(resolve_loc);
        goto out;
    }

    inode_lookup(link_inode);

    /* wipe the loc only after the inode has been linked to the inode
       table. Otherwise before inode gets linked to the inode table,
       inode would have been unrefed (this might have been destroyed
       if refcount becomes 0, and put back to mempool). So once the
       inode gets destroyed, inode_link is a redundant operation. But
       without knowing that the destroyed inode's pointer is saved in
       the resolved_loc as parent (while constructing loc for resolving
       the entry) and the inode_new call for resolving the entry will
       return the same pointer to the inode as the parent (because in
       reality the inode is a free inode present in cold list of the
       inode mem-pool).
    */
    loc_wipe(resolve_loc);

    if (gf_uuid_is_null(resolve->pargfid)) {
        inode_unref(link_inode);
        goto out;
    }

    resolve_loc->parent = link_inode;
    gf_uuid_copy(resolve_loc->pargfid, resolve_loc->parent->gfid);

    resolve_loc->name = resolve->bname;

    resolve_loc->inode = server_inode_new(state->itable, resolve_loc->gfid);

    inode_path(resolve_loc->parent, resolve_loc->name,
               (char **)&resolve_loc->path);

    if (state->xdata) {
        dict = dict_copy_with_ref(state->xdata, NULL);
        if (!dict)
            gf_msg(this->name, GF_LOG_ERROR, ENOMEM, PS_MSG_NO_MEMORY,
                   "BUG: dict allocation failed (pargfid: %s, name: %s), "
                   "still continuing",
                   uuid_utoa(resolve_loc->gfid), resolve_loc->name);
    }

    STACK_WIND(frame, resolve_gfid_entry_cbk, frame->root->client->bound_xl,
               frame->root->client->bound_xl->fops->lookup,
               &resolve->resolve_loc, dict);
    if (dict)
        dict_unref(dict);
    return 0;
out:
    resolve_continue(frame);
    return 0;
}

int
resolve_gfid(call_frame_t *frame)
{
    server_state_t *state = NULL;
    xlator_t *this = NULL;
    server_resolve_t *resolve = NULL;
    loc_t *resolve_loc = NULL;
    dict_t *xdata = NULL;

    state = CALL_STATE(frame);
    this = frame->this;
    resolve = state->resolve_now;
    resolve_loc = &resolve->resolve_loc;

    if (!gf_uuid_is_null(resolve->pargfid))
        gf_uuid_copy(resolve_loc->gfid, resolve->pargfid);
    else if (!gf_uuid_is_null(resolve->gfid))
        gf_uuid_copy(resolve_loc->gfid, resolve->gfid);

    resolve_loc->inode = server_inode_new(state->itable, resolve_loc->gfid);
    (void)loc_path(resolve_loc, NULL);

    if (state->xdata) {
        xdata = dict_copy_with_ref(state->xdata, NULL);
        if (!xdata)
            gf_msg(this->name, GF_LOG_ERROR, ENOMEM, PS_MSG_NO_MEMORY,
                   "BUG: dict allocation failed (gfid: %s), "
                   "still continuing",
                   uuid_utoa(resolve_loc->gfid));
    }

    STACK_WIND(frame, resolve_gfid_cbk, frame->root->client->bound_xl,
               frame->root->client->bound_xl->fops->lookup,
               &resolve->resolve_loc, xdata);

    if (xdata)
        dict_unref(xdata);

    return 0;
}

int
resolve_continue(call_frame_t *frame)
{
    server_state_t *state = NULL;
    xlator_t *this = NULL;
    server_resolve_t *resolve = NULL;
    int ret = 0;

    state = CALL_STATE(frame);
    this = frame->this;
    resolve = state->resolve_now;

    resolve->op_ret = 0;
    resolve->op_errno = 0;

    if (resolve->fd_no != -1) {
        ret = resolve_anonfd_simple(frame);
        goto out;
    } else if (!gf_uuid_is_null(resolve->pargfid))
        ret = resolve_entry_simple(frame);
    else if (!gf_uuid_is_null(resolve->gfid))
        ret = resolve_inode_simple(frame);
    if (ret)
        gf_msg_debug(this->name, 0,
                     "return value of resolve_*_"
                     "simple %d",
                     ret);

    resolve_loc_touchup(frame);
out:
    server_resolve_all(frame);

    return 0;
}

/*
  Check if the requirements are fulfilled by entries in the inode cache itself
  Return value:
  <= 0 - simple resolution was decisive and complete (either success or failure)
  > 0  - indecisive, need to perform deep resolution
*/

int
resolve_entry_simple(call_frame_t *frame)
{
    server_state_t *state = NULL;
    xlator_t *this = NULL;
    server_resolve_t *resolve = NULL;
    inode_t *parent = NULL;
    inode_t *inode = NULL;
    int ret = 0;

    state = CALL_STATE(frame);
    this = frame->this;
    resolve = state->resolve_now;

    parent = inode_find(state->itable, resolve->pargfid);
    if (!parent) {
        /* simple resolution is indecisive. need to perform
           deep resolution */
        resolve->op_ret = -1;
        resolve->op_errno = ESTALE;
        ret = 1;
        goto out;
    }

    if (parent->ia_type != IA_IFDIR) {
        /* Parent type should be 'directory', and nothing else */
        gf_msg(this->name, GF_LOG_ERROR, EPERM, PS_MSG_GFID_RESOLVE_FAILED,
               "%s: parent type not directory (%d)", uuid_utoa(parent->gfid),
               parent->ia_type);
        resolve->op_ret = -1;
        resolve->op_errno = EPERM;
        ret = 1;
        goto out;
    }

    /* expected @parent was found from the inode cache */
    gf_uuid_copy(state->loc_now->pargfid, resolve->pargfid);
    state->loc_now->parent = inode_ref(parent);
    if (strchr(resolve->bname, '/')) {
        /* basename should be a string (without '/') in a directory,
           it can't span multiple levels. This can also lead to
           resolving outside the parent's tree, which is not allowed */
        gf_msg(this->name, GF_LOG_ERROR, EPERM, PS_MSG_GFID_RESOLVE_FAILED,
               "%s: basename sent by client not allowed", resolve->bname);
        resolve->op_ret = -1;
        resolve->op_errno = EPERM;
        ret = 1;
        goto out;
    }

    state->loc_now->name = resolve->bname;

    inode = inode_grep(state->itable, parent, resolve->bname);
    if (!inode) {
        switch (resolve->type) {
            case RESOLVE_DONTCARE:
            case RESOLVE_NOT:
                ret = 0;
                break;
            case RESOLVE_MAY:
                ret = 1;
                break;
            default:
                resolve->op_ret = -1;
                resolve->op_errno = ENOENT;
                ret = 1;
                break;
        }

        goto out;
    }

    if (resolve->type == RESOLVE_NOT) {
        gf_msg_debug(this->name, 0,
                     "inode (pointer: %p gfid:%s found"
                     " for path (%s) while type is RESOLVE_NOT. "
                     "Performing lookup on backend to rule out any "
                     "possible stale dentries in inode table",
                     inode, uuid_utoa(inode->gfid), resolve->path);
        resolve->op_ret = -1;
        resolve->op_errno = EEXIST;
        ret = 1;
        goto out;
    }

    ret = 0;

    state->loc_now->inode = inode_ref(inode);

out:
    if (parent)
        inode_unref(parent);

    if (inode)
        inode_unref(inode);

    return ret;
}

int
server_resolve_entry(call_frame_t *frame)
{
    server_state_t *state = NULL;
    int ret = 0;
    loc_t *loc = NULL;

    state = CALL_STATE(frame);
    loc = state->loc_now;

    ret = resolve_entry_simple(frame);

    if (ret > 0) {
        loc_wipe(loc);
        resolve_gfid(frame);
        return 0;
    }

    if (ret == 0)
        resolve_loc_touchup(frame);

    server_resolve_all(frame);

    return 0;
}

int
resolve_inode_simple(call_frame_t *frame)
{
    server_state_t *state = NULL;
    server_resolve_t *resolve = NULL;
    inode_t *inode = NULL;
    int ret = 0;

    state = CALL_STATE(frame);
    resolve = state->resolve_now;

    inode = inode_find(state->itable, resolve->gfid);

    if (!inode) {
        resolve->op_ret = -1;
        resolve->op_errno = ESTALE;
        ret = 1;
        goto out;
    }

    ret = 0;

    state->loc_now->inode = inode_ref(inode);
    gf_uuid_copy(state->loc_now->gfid, resolve->gfid);

out:
    if (inode)
        inode_unref(inode);

    return ret;
}

int
server_resolve_inode(call_frame_t *frame)
{
    server_state_t *state = NULL;
    int ret = 0;
    loc_t *loc = NULL;

    state = CALL_STATE(frame);
    loc = state->loc_now;

    ret = resolve_inode_simple(frame);

    if (ret > 0) {
        loc_wipe(loc);
        resolve_gfid(frame);
        return 0;
    }

    if (ret == 0)
        resolve_loc_touchup(frame);

    server_resolve_all(frame);

    return 0;
}

int
resolve_anonfd_simple(call_frame_t *frame)
{
    server_state_t *state = NULL;
    server_resolve_t *resolve = NULL;
    inode_t *inode = NULL;
    int ret = 0;

    state = CALL_STATE(frame);
    resolve = state->resolve_now;

    inode = inode_find(state->itable, resolve->gfid);

    if (!inode) {
        resolve->op_ret = -1;
        resolve->op_errno = ENOENT;
        ret = 1;
        goto out;
    }

    ret = 0;

    if (frame->root->op == GF_FOP_READ || frame->root->op == GF_FOP_WRITE)
        state->fd = fd_anonymous_with_flags(inode, state->flags);
    else
        state->fd = fd_anonymous(inode);
out:
    if (inode)
        inode_unref(inode);

    if (ret != 0)
        gf_msg_debug("server", 0,
                     "inode for the gfid"
                     "(%s) is not found. anonymous fd creation failed",
                     uuid_utoa(resolve->gfid));
    return ret;
}

int
server_resolve_anonfd(call_frame_t *frame)
{
    server_state_t *state = NULL;
    int ret = 0;
    loc_t *loc = NULL;

    state = CALL_STATE(frame);
    loc = state->loc_now;

    ret = resolve_anonfd_simple(frame);

    if (ret > 0) {
        loc_wipe(loc);
        resolve_gfid(frame);
        return 0;
    }

    server_resolve_all(frame);

    return 0;
}

int
server_resolve_fd(call_frame_t *frame)
{
    server_ctx_t *serv_ctx = NULL;
    server_state_t *state = NULL;
    client_t *client = NULL;
    server_resolve_t *resolve = NULL;
    uint64_t fd_no = -1;

    state = CALL_STATE(frame);
    resolve = state->resolve_now;

    fd_no = resolve->fd_no;

    if (fd_no == GF_ANON_FD_NO) {
        server_resolve_anonfd(frame);
        return 0;
    }

    client = frame->root->client;

    serv_ctx = server_ctx_get(client, client->this);

    if (serv_ctx == NULL) {
        gf_msg("", GF_LOG_INFO, ENOMEM, PS_MSG_NO_MEMORY,
               "server_ctx_get() failed");
        resolve->op_ret = -1;
        resolve->op_errno = ENOMEM;
        return 0;
    }

    /*
     * With copy_file_range, there will be 2 fds to resolve.
     * This same function is called to resolve both the source
     * fd and the destination fd. As of now, this function does
     * not have any mechanism to distinguish between the 2 fds
     * being resolved except for checking the value of state->fd.
     * The assumption is that, if source fd the one which is
     * being resolved here, then state->fd would be NULL. If it
     * is not NULL, then it is the destination fd which is being
     * resolved.
     * This  method (provided the above assumption is true) is
     * to achieve the ability to distinguish between 2 fds with
     * minimum changes being done to this function. If this way
     * is not correct, then more changes might be needed.
     */
    if (!state->fd) {
        state->fd = gf_fd_fdptr_get(serv_ctx->fdtable, fd_no);
        if (!state->fd) {
            gf_msg("", GF_LOG_INFO, EBADF, PS_MSG_FD_NOT_FOUND,
                   "fd not "
                   "found in context");
            resolve->op_ret = -1;
            resolve->op_errno = EBADF;
        }
    } else {
        state->fd_out = gf_fd_fdptr_get(serv_ctx->fdtable, fd_no);
        if (!state->fd_out) {
            gf_msg("", GF_LOG_INFO, EBADF, PS_MSG_FD_NOT_FOUND,
                   "fd not "
                   "found in context");
            resolve->op_ret = -1;
            resolve->op_errno = EBADF;
        }
    }

    server_resolve_all(frame);

    return 0;
}

int
server_resolve(call_frame_t *frame)
{
    server_state_t *state = NULL;
    server_resolve_t *resolve = NULL;

    state = CALL_STATE(frame);
    resolve = state->resolve_now;

    if (resolve->fd_no != -1) {
        server_resolve_fd(frame);

    } else if (!gf_uuid_is_null(resolve->pargfid)) {
        server_resolve_entry(frame);

    } else if (!gf_uuid_is_null(resolve->gfid)) {
        server_resolve_inode(frame);

    } else {
        if (resolve == &state->resolve)
            gf_msg(frame->this->name, GF_LOG_WARNING, 0, PS_MSG_INVALID_ENTRY,
                   "no resolution type for %s (%s)", resolve->path,
                   gf_fop_list[frame->root->op]);

        resolve->op_ret = -1;
        resolve->op_errno = EINVAL;

        server_resolve_all(frame);
    }

    return 0;
}

int
server_resolve_done(call_frame_t *frame)
{
    server_state_t *state = NULL;

    state = CALL_STATE(frame);

    server_print_request(frame);

    state->resume_fn(frame, frame->root->client->bound_xl);

    return 0;
}

/*
 * This function is called multiple times, once per resolving one location/fd.
 * state->resolve_now is used to decide which location/fd is to be resolved now
 */
int
server_resolve_all(call_frame_t *frame)
{
    server_state_t *state = NULL;
    xlator_t *this = NULL;

    this = frame->this;
    state = CALL_STATE(frame);

    if (state->resolve_now == NULL) {
        state->resolve_now = &state->resolve;
        state->loc_now = &state->loc;

        server_resolve(frame);

    } else if (state->resolve_now == &state->resolve) {
        state->resolve_now = &state->resolve2;
        state->loc_now = &state->loc2;

        server_resolve(frame);

    } else if (state->resolve_now == &state->resolve2) {
        server_resolve_done(frame);

    } else {
        gf_msg(this->name, GF_LOG_ERROR, EINVAL, PS_MSG_INVALID_ENTRY,
               "Invalid pointer for "
               "state->resolve_now");
    }

    return 0;
}

int
resolve_and_resume(call_frame_t *frame, server_resume_fn_t fn)
{
    server_state_t *state = NULL;

    state = CALL_STATE(frame);
    state->resume_fn = fn;

    server_resolve_all(frame);

    return 0;
}
