/*
 * Copyright (c) 2012-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 <glusterfs/defaults.h>
#include "rpcsvc.h"
#include <glusterfs/dict.h>
#include <glusterfs/xlator.h>
#include "nfs.h"
#include <glusterfs/mem-pool.h>
#include <glusterfs/logging.h>
#include "nfs-fops.h"
#include "nfs3.h"
#include "nfs-mem-types.h"
#include "nfs3-helpers.h"
#include "nfs3-fh.h"
#include "nfs-generics.h"
#include "acl3.h"
#include <glusterfs/byte-order.h>
#include <glusterfs/compat-errno.h>
#include "nfs-messages.h"

static int
acl3_nfs_acl_to_xattr(aclentry *ace, void *xattrbuf, int aclcount, int defacl);

static int
acl3_nfs_acl_from_xattr(aclentry *ace, void *xattrbuf, int bufsize, int defacl);

typedef ssize_t (*acl3_serializer)(struct iovec outmsg, void *args);

extern void
nfs3_call_state_wipe(nfs3_call_state_t *cs);

extern nfs3_call_state_t *
nfs3_call_state_init(struct nfs3_state *s, rpcsvc_request_t *req, xlator_t *v);

extern int
nfs3_fh_validate(struct nfs3_fh *fh);

extern void
nfs3_stat_to_fattr3(struct iatt *buf, fattr3 *fa);

#define acl3_validate_nfs3_state(request, state, status, label, retval)        \
    do {                                                                       \
        state = rpcsvc_request_program_private(request);                       \
        if (!state) {                                                          \
            gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_STATE_MISSING,         \
                   "NFSv3 state "                                              \
                   "missing from RPC request");                                \
            rpcsvc_request_seterr(req, SYSTEM_ERR);                            \
            status = NFS3ERR_SERVERFAULT;                                      \
            goto label;                                                        \
        }                                                                      \
    } while (0);

#define acl3_validate_gluster_fh(handle, status, errlabel)                     \
    do {                                                                       \
        if (!nfs3_fh_validate(handle)) {                                       \
            gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_BAD_HANDLE,           \
                   "Bad Handle");                                              \
            status = NFS3ERR_BADHANDLE;                                        \
            goto errlabel;                                                     \
        }                                                                      \
    } while (0)

extern xlator_t *
nfs3_fh_to_xlator(struct nfs3_state *nfs3, struct nfs3_fh *fh);

#define acl3_map_fh_to_volume(nfs3state, handle, req, volume, status, label)   \
    do {                                                                       \
        char exportid[256], gfid[256];                                         \
        rpc_transport_t *trans = NULL;                                         \
        volume = nfs3_fh_to_xlator((nfs3state), handle);                       \
        if (!volume) {                                                         \
            gf_uuid_unparse(handle->exportid, exportid);                       \
            gf_uuid_unparse(handle->gfid, gfid);                               \
            trans = rpcsvc_request_transport(req);                             \
            gf_msg(GF_ACL, GF_LOG_ERROR, 0, NFS_MSG_FH_TO_VOL_FAIL,            \
                   "Failed to map "                                            \
                   "FH to vol: client=%s, exportid=%s, gfid=%s",               \
                   trans->peerinfo.identifier, exportid, gfid);                \
            gf_msg(GF_ACL, GF_LOG_ERROR, ESTALE, NFS_MSG_VOLUME_ERROR,         \
                   "Stale nfs client %s must be trying to "                    \
                   "connect to a deleted volume, please "                      \
                   "unmount it.",                                              \
                   trans->peerinfo.identifier);                                \
            status = NFS3ERR_STALE;                                            \
            goto label;                                                        \
        } else {                                                               \
            gf_msg_trace(GF_ACL, 0, "FH to Volume: %s", volume->name);         \
            rpcsvc_request_set_private(req, volume);                           \
        }                                                                      \
    } while (0);

#define acl3_volume_started_check(nfs3state, vlm, rtval, erlbl)                \
    do {                                                                       \
        if ((!nfs_subvolume_started(nfs_state(nfs3state->nfsx), vlm))) {       \
            gf_msg(GF_ACL, GF_LOG_ERROR, 0, NFS_MSG_VOL_DISABLE,               \
                   "Volume is disabled: %s", vlm->name);                       \
            rtval = RPCSVC_ACTOR_IGNORE;                                       \
            goto erlbl;                                                        \
        }                                                                      \
    } while (0)

#define acl3_check_fh_resolve_status(cst, nfstat, erlabl)                      \
    do {                                                                       \
        xlator_t *xlatorp = NULL;                                              \
        char buf[256], gfid[GF_UUID_BUF_SIZE];                                 \
        rpc_transport_t *trans = NULL;                                         \
        if ((cst)->resolve_ret < 0) {                                          \
            trans = rpcsvc_request_transport(cst->req);                        \
            xlatorp = nfs3_fh_to_xlator(cst->nfs3state, &cst->resolvefh);      \
            gf_uuid_unparse(cst->resolvefh.gfid, gfid);                        \
            snprintf(buf, sizeof(buf), "(%s) %s : %s",                         \
                     trans->peerinfo.identifier,                               \
                     xlatorp ? xlatorp->name : "ERR", gfid);                   \
            gf_msg(GF_ACL, GF_LOG_ERROR, cst->resolve_errno,                   \
                   NFS_MSG_RESOLVE_FH_FAIL,                                    \
                   "Unable to resolve "                                        \
                   "FH: %s",                                                   \
                   buf);                                                       \
            nfstat = nfs3_errno_to_nfsstat3(cst->resolve_errno);               \
            goto erlabl;                                                       \
        }                                                                      \
    } while (0)

#define acl3_handle_call_state_init(nfs3state, calls, rq, v, opstat, errlabel) \
    do {                                                                       \
        calls = nfs3_call_state_init((nfs3state), (rq), v);                    \
        if (!calls) {                                                          \
            gf_msg(GF_ACL, GF_LOG_ERROR, 0, NFS_MSG_INIT_CALL_STAT_FAIL,       \
                   "Failed to "                                                \
                   "init call state");                                         \
            opstat = NFS3ERR_SERVERFAULT;                                      \
            rpcsvc_request_seterr(req, SYSTEM_ERR);                            \
            goto errlabel;                                                     \
        }                                                                      \
    } while (0)

int
acl3svc_submit_reply(rpcsvc_request_t *req, void *arg, acl3_serializer sfunc)
{
    struct iovec outmsg = {
        0,
    };
    struct iobuf *iob = NULL;
    struct nfs3_state *nfs3 = NULL;
    int ret = -1;
    ssize_t msglen = 0;
    struct iobref *iobref = NULL;

    if (!req)
        return -1;

    nfs3 = (struct nfs3_state *)rpcsvc_request_program_private(req);
    if (!nfs3) {
        gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_MNT_STATE_NOT_FOUND,
               "mount state not found");
        goto ret;
    }

    /* First, get the io buffer into which the reply in arg will
     * be serialized.
     */
    iob = iobuf_get(nfs3->iobpool);
    if (!iob) {
        gf_msg(GF_ACL, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
               "Failed to get iobuf");
        goto ret;
    }

    iobuf_to_iovec(iob, &outmsg);
    /* Use the given serializer to translate the give C structure in arg
     * to XDR format which will be written into the buffer in outmsg.
     */
    msglen = sfunc(outmsg, arg);
    if (msglen < 0) {
        gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_ENCODE_MSG_FAIL,
               "Failed to encode message");
        goto ret;
    }
    outmsg.iov_len = msglen;

    iobref = iobref_new();
    if (iobref == NULL) {
        gf_msg(GF_ACL, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
               "Failed to get iobref");
        goto ret;
    }

    ret = iobref_add(iobref, iob);
    if (ret) {
        gf_msg(GF_ACL, GF_LOG_ERROR, ENOMEM, NFS_MSG_NO_MEMORY,
               "Failed to add iob to iobref");
        goto ret;
    }

    /* Then, submit the message for transmission. */
    ret = rpcsvc_submit_message(req, &outmsg, 1, NULL, 0, iobref);
    if (ret == -1) {
        gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_REP_SUBMIT_FAIL,
               "Reply submission failed");
        goto ret;
    }

    ret = 0;
ret:
    if (iob)
        iobuf_unref(iob);
    if (iobref)
        iobref_unref(iobref);

    return ret;
}

int
acl3svc_null(rpcsvc_request_t *req)
{
    struct iovec dummyvec = {
        0,
    };

    if (!req) {
        gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY,
               "Got NULL request!");
        return 0;
    }
    rpcsvc_submit_generic(req, &dummyvec, 1, NULL, 0, NULL);
    return 0;
}

int
acl3_getacl_reply(rpcsvc_request_t *req, getaclreply *reply)
{
    acl3svc_submit_reply(req, (void *)reply,
                         (acl3_serializer)xdr_serialize_getaclreply);
    return 0;
}

int
acl3_setacl_reply(rpcsvc_request_t *req, setaclreply *reply)
{
    acl3svc_submit_reply(req, (void *)reply,
                         (acl3_serializer)xdr_serialize_setaclreply);
    return 0;
}

/* acl3_getacl_cbk: fetch and decode the ACL in the POSIX_ACL_ACCESS_XATTR
 *
 * The POSIX_ACL_ACCESS_XATTR can be set on files and directories.
 */
int
acl3_getacl_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                int32_t op_ret, int32_t op_errno, dict_t *dict, dict_t *xdata)
{
    nfsstat3 stat = NFS3ERR_SERVERFAULT;
    nfs3_call_state_t *cs = NULL;
    data_t *data = NULL;
    getaclreply *getaclreply = NULL;
    int aclcount = 0;
    int defacl = 1; /* DEFAULT ACL */

    if (!frame->local) {
        gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY,
               "Invalid argument, frame->local NULL");
        return -EINVAL;
    }
    cs = frame->local;
    getaclreply = &cs->args.getaclreply;
    if ((op_ret < 0) && (op_errno != ENODATA && op_errno != ENOATTR)) {
        stat = nfs3_cbk_errno_status(op_ret, op_errno);
        goto err;
    } else if (!dict) {
        /* no ACL has been set */
        stat = NFS3_OK;
        goto err;
    }

    getaclreply->aclentry.aclentry_val = cs->aclentry;

    /* getfacl: NFS USER ACL */
    data = dict_get(dict, POSIX_ACL_ACCESS_XATTR);
    if (data && data->data) {
        aclcount = acl3_nfs_acl_from_xattr(cs->aclentry, data->data, data->len,
                                           !defacl);
        if (aclcount < 0) {
            gf_msg(GF_ACL, GF_LOG_ERROR, aclcount, NFS_MSG_GET_USER_ACL_FAIL,
                   "Failed to get USER ACL");
            stat = nfs3_errno_to_nfsstat3(-aclcount);
            goto err;
        }
        getaclreply->aclcount = aclcount;
        getaclreply->aclentry.aclentry_len = aclcount;
    }

    acl3_getacl_reply(cs->req, getaclreply);
    nfs3_call_state_wipe(cs);
    return 0;

err:
    if (getaclreply)
        getaclreply->status = stat;
    acl3_getacl_reply(cs->req, getaclreply);
    nfs3_call_state_wipe(cs);
    return 0;
}

/* acl3_default_getacl_cbk: fetch and decode the ACL set in the
 * POSIX_ACL_DEFAULT_XATTR xattr.
 *
 * The POSIX_ACL_DEFAULT_XATTR xattr is only set on directories, not on files.
 *
 * When done with POSIX_ACL_DEFAULT_XATTR, we also need to get and decode the
 * ACL that can be set in POSIX_ACL_DEFAULT_XATTR.
 */
int
acl3_default_getacl_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                        int32_t op_ret, int32_t op_errno, dict_t *dict,
                        dict_t *xdata)
{
    nfsstat3 stat = NFS3ERR_SERVERFAULT;
    nfs3_call_state_t *cs = NULL;
    data_t *data = NULL;
    getaclreply *getaclreply = NULL;
    int aclcount = 0;
    int defacl = 1; /* DEFAULT ACL */
    nfs_user_t nfu = {
        0,
    };
    int ret = -1;

    if (!frame->local) {
        gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY,
               "Invalid argument, frame->local NULL");
        return -EINVAL;
    }
    cs = frame->local;
    getaclreply = &cs->args.getaclreply;
    if ((op_ret < 0) && (op_errno != ENODATA && op_errno != ENOATTR)) {
        stat = nfs3_cbk_errno_status(op_ret, op_errno);
        goto err;
    } else if (!dict) {
        /* no ACL has been set */
        stat = NFS3_OK;
        goto err;
    }

    getaclreply->daclentry.daclentry_val = cs->daclentry;

    /* getfacl: NFS DEFAULT ACL */
    data = dict_get(dict, POSIX_ACL_DEFAULT_XATTR);
    if (data && data->data) {
        aclcount = acl3_nfs_acl_from_xattr(cs->daclentry, data->data, data->len,
                                           defacl);
        if (aclcount < 0) {
            gf_msg(GF_ACL, GF_LOG_ERROR, aclcount, NFS_MSG_GET_DEF_ACL_FAIL,
                   "Failed to get DEFAULT ACL");
            stat = nfs3_errno_to_nfsstat3(-aclcount);
            goto err;
        }

        getaclreply->daclcount = aclcount;
        getaclreply->daclentry.daclentry_len = aclcount;
    }

    getaclreply->attr_follows = TRUE;
    nfs_request_user_init(&nfu, cs->req);
    ret = nfs_getxattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc,
                       POSIX_ACL_ACCESS_XATTR, NULL, acl3_getacl_cbk, cs);
    if (ret < 0) {
        stat = nfs3_errno_to_nfsstat3(-ret);
        goto err;
    }

    return 0;

err:
    if (getaclreply)
        getaclreply->status = stat;
    acl3_getacl_reply(cs->req, getaclreply);
    nfs3_call_state_wipe(cs);
    return 0;
}

int
acl3_stat_cbk(call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret,
              int32_t op_errno, struct iatt *buf, dict_t *xdata)
{
    nfsstat3 stat = NFS3ERR_SERVERFAULT;
    nfs3_call_state_t *cs = NULL;
    getaclreply *getaclreply = NULL;
    int ret = -1;
    nfs_user_t nfu = {
        0,
    };
    uint64_t deviceid = 0;

    if (!frame->local) {
        gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_INVALID_ENTRY,
               "Invalid argument, frame->local NULL");
        return EINVAL;
    }

    cs = frame->local;
    getaclreply = &cs->args.getaclreply;

    if (op_ret == -1) {
        stat = nfs3_cbk_errno_status(op_ret, op_errno);
        goto err;
    }

    /* Fill the attrs before xattrs */
    getaclreply->attr_follows = TRUE;
    deviceid = nfs3_request_xlator_deviceid(cs->req);
    nfs3_map_deviceid_to_statdev(buf, deviceid);
    nfs3_stat_to_fattr3(buf, &(getaclreply->attr));

    nfs_request_user_init(&nfu, cs->req);
    if (buf->ia_type == IA_IFDIR) {
        ret = nfs_getxattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc,
                           POSIX_ACL_DEFAULT_XATTR, NULL,
                           acl3_default_getacl_cbk, cs);
    } else {
        ret = nfs_getxattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc,
                           POSIX_ACL_ACCESS_XATTR, NULL, acl3_getacl_cbk, cs);
    }

    if (ret < 0) {
        stat = nfs3_errno_to_nfsstat3(-ret);
        goto err;
    }

    return 0;
err:
    getaclreply->status = stat;
    acl3_getacl_reply(cs->req, getaclreply);
    nfs3_call_state_wipe(cs);
    return 0;
}

int
acl3_getacl_resume(void *carg)
{
    int ret = -1;
    nfs3_call_state_t *cs = NULL;
    nfsstat3 stat = NFS3ERR_SERVERFAULT;
    nfs_user_t nfu = {
        0,
    };

    if (!carg)
        return ret;

    cs = (nfs3_call_state_t *)carg;
    acl3_check_fh_resolve_status(cs, stat, acl3err);
    nfs_request_user_init(&nfu, cs->req);

    ret = nfs_stat(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, acl3_stat_cbk,
                   cs);
    stat = -ret;
acl3err:
    if (ret < 0) {
        gf_msg(GF_ACL, GF_LOG_ERROR, stat, NFS_MSG_OPEN_FAIL,
               "unable to open_and_resume");
        cs->args.getaclreply.status = nfs3_errno_to_nfsstat3(stat);
        acl3_getacl_reply(cs->req, &cs->args.getaclreply);
        nfs3_call_state_wipe(cs);
    }

    return ret;
}

int
acl3svc_getacl(rpcsvc_request_t *req)
{
    xlator_t *vol = NULL;
    struct nfs_state *nfs = NULL;
    nfs3_state_t *nfs3 = NULL;
    nfs3_call_state_t *cs = NULL;
    int ret = RPCSVC_ACTOR_ERROR;
    nfsstat3 stat = NFS3ERR_SERVERFAULT;
    struct nfs3_fh fh, *fhp = NULL;
    getaclargs getaclargs;
    getaclreply getaclreply;

    if (!req)
        return ret;

    acl3_validate_nfs3_state(req, nfs3, stat, rpcerr, ret);
    nfs = nfs_state(nfs3->nfsx);
    memset(&getaclargs, 0, sizeof(getaclargs));
    memset(&getaclreply, 0, sizeof(getaclreply));
    getaclargs.fh.n_bytes = (char *)&fh;
    if (xdr_to_getaclargs(req->msg[0], &getaclargs) <= 0) {
        gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_ARGS_DECODE_ERROR,
               "Error decoding args");
        rpcsvc_request_seterr(req, GARBAGE_ARGS);
        goto rpcerr;
    }

    /* Validate ACL mask */
    if (getaclargs.mask & ~(NFS_ACL | NFS_ACLCNT | NFS_DFACL | NFS_DFACLCNT)) {
        stat = NFS3ERR_INVAL;
        goto acl3err;
    }

    fhp = &fh;
    acl3_validate_gluster_fh(&fh, stat, acl3err);
    acl3_map_fh_to_volume(nfs->nfs3state, fhp, req, vol, stat, acl3err);
    acl3_volume_started_check(nfs3, vol, ret, rpcerr);
    acl3_handle_call_state_init(nfs->nfs3state, cs, req, vol, stat, acl3err);

    cs->vol = vol;
    cs->args.getaclreply.mask = getaclargs.mask;

    ret = nfs3_fh_resolve_and_resume(cs, fhp, NULL, acl3_getacl_resume);
    stat = nfs3_errno_to_nfsstat3(-ret);

acl3err:
    if (ret < 0) {
        gf_msg(GF_ACL, GF_LOG_ERROR, -ret, NFS_MSG_RESOLVE_ERROR,
               "unable to resolve and resume");
        getaclreply.status = stat;
        acl3_getacl_reply(req, &getaclreply);
        nfs3_call_state_wipe(cs);
        return 0;
    }

rpcerr:
    return ret;
}

int
acl3_setacl_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                int32_t op_ret, int32_t op_errno, dict_t *xdata)
{
    nfs3_call_state_t *cs = NULL;
    cs = frame->local;
    if (op_ret < 0) {
        nfsstat3 status = nfs3_cbk_errno_status(op_ret, op_errno);
        cs->args.setaclreply.status = status;
    }

    acl3_setacl_reply(cs->req, &cs->args.setaclreply);

    nfs3_call_state_wipe(cs);

    return 0;
}

int
acl3_setacl_resume(void *carg)
{
    int ret = -1;
    nfs3_call_state_t *cs = NULL;
    nfsstat3 stat = NFS3ERR_SERVERFAULT;
    nfs_user_t nfu = {
        0,
    };
    dict_t *xattr = NULL;

    if (!carg)
        return ret;
    cs = (nfs3_call_state_t *)carg;
    acl3_check_fh_resolve_status(cs, stat, acl3err);
    nfs_request_user_init(&nfu, cs->req);
    xattr = dict_new();
    if (xattr == NULL) {
        gf_msg(GF_NLM, GF_LOG_ERROR, ENOMEM, NFS_MSG_GFID_DICT_CREATE_FAIL,
               "dict allocation failed");
        goto acl3err;
    }

    if (cs->aclcount)
        ret = dict_set_static_bin(xattr, POSIX_ACL_ACCESS_XATTR, cs->aclxattr,
                                  posix_acl_xattr_size(cs->aclcount));
    if (cs->daclcount)
        ret = dict_set_static_bin(xattr, POSIX_ACL_DEFAULT_XATTR, cs->daclxattr,
                                  posix_acl_xattr_size(cs->daclcount));

    ret = nfs_setxattr(cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, xattr, 0,
                       NULL, acl3_setacl_cbk, cs);
    dict_unref(xattr);

acl3err:
    if (ret < 0) {
        stat = -ret;
        gf_msg(GF_ACL, GF_LOG_ERROR, stat, NFS_MSG_OPEN_FAIL,
               "unable to open_and_resume");
        cs->args.setaclreply.status = nfs3_errno_to_nfsstat3(stat);
        acl3_setacl_reply(cs->req, &cs->args.setaclreply);
        nfs3_call_state_wipe(cs);
    }

    return ret;
}

int
acl3svc_setacl(rpcsvc_request_t *req)
{
    xlator_t *vol = NULL;
    struct nfs_state *nfs = NULL;
    nfs3_state_t *nfs3 = NULL;
    nfs3_call_state_t *cs = NULL;
    int ret = RPCSVC_ACTOR_ERROR;
    nfsstat3 stat = NFS3ERR_SERVERFAULT;
    struct nfs3_fh fh;
    struct nfs3_fh *fhp = NULL;
    setaclargs setaclargs;
    setaclreply setaclreply;
    aclentry *daclentry = NULL;
    aclentry *aclentry = NULL;
    int aclerrno = 0;
    int defacl = 1;

    if (!req)
        return ret;
    aclentry = GF_CALLOC(NFS_ACL_MAX_ENTRIES, sizeof(*aclentry), gf_nfs_mt_arr);
    if (!aclentry) {
        goto rpcerr;
    }
    daclentry = GF_CALLOC(NFS_ACL_MAX_ENTRIES, sizeof(*daclentry),
                          gf_nfs_mt_arr);
    if (!daclentry) {
        goto rpcerr;
    }

    acl3_validate_nfs3_state(req, nfs3, stat, rpcerr, ret);
    nfs = nfs_state(nfs3->nfsx);
    memset(&setaclargs, 0, sizeof(setaclargs));
    memset(&setaclreply, 0, sizeof(setaclreply));
    memset(&fh, 0, sizeof(fh));
    setaclargs.fh.n_bytes = (char *)&fh;
    setaclargs.aclentry.aclentry_val = aclentry;
    setaclargs.daclentry.daclentry_val = daclentry;
    if (xdr_to_setaclargs(req->msg[0], &setaclargs) <= 0) {
        gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_ARGS_DECODE_ERROR,
               "Error decoding args");
        rpcsvc_request_seterr(req, GARBAGE_ARGS);
        goto rpcerr;
    }

    /* Validate ACL mask */
    if (setaclargs.mask & ~(NFS_ACL | NFS_ACLCNT | NFS_DFACL | NFS_DFACLCNT)) {
        stat = NFS3ERR_INVAL;
        goto acl3err;
    }

    fhp = &fh;
    acl3_validate_gluster_fh(fhp, stat, acl3err);
    acl3_map_fh_to_volume(nfs->nfs3state, fhp, req, vol, stat, acl3err);
    acl3_volume_started_check(nfs3, vol, ret, rpcerr);
    acl3_handle_call_state_init(nfs->nfs3state, cs, req, vol, stat, acl3err);

    cs->vol = vol;
    cs->aclcount = setaclargs.aclcount;
    cs->daclcount = setaclargs.daclcount;

    /* setfacl: NFS USER ACL */
    aclerrno = acl3_nfs_acl_to_xattr(aclentry, cs->aclxattr, cs->aclcount,
                                     !defacl);
    if (aclerrno < 0) {
        gf_msg(GF_ACL, GF_LOG_ERROR, -aclerrno, NFS_MSG_SET_USER_ACL_FAIL,
               "Failed to set USER ACL");
        stat = nfs3_errno_to_nfsstat3(-aclerrno);
        goto acl3err;
    }

    /* setfacl: NFS DEFAULT ACL */
    aclerrno = acl3_nfs_acl_to_xattr(daclentry, cs->daclxattr, cs->daclcount,
                                     defacl);
    if (aclerrno < 0) {
        gf_msg(GF_ACL, GF_LOG_ERROR, -aclerrno, NFS_MSG_SET_DEF_ACL_FAIL,
               "Failed to set DEFAULT ACL");
        stat = nfs3_errno_to_nfsstat3(-aclerrno);
        goto acl3err;
    }

    ret = nfs3_fh_resolve_and_resume(cs, fhp, NULL, acl3_setacl_resume);
    stat = nfs3_errno_to_nfsstat3(-ret);

acl3err:
    if (ret < 0) {
        gf_msg(GF_ACL, GF_LOG_ERROR, -ret, NFS_MSG_RESOLVE_ERROR,
               "unable to resolve and resume");
        setaclreply.status = stat;
        acl3_setacl_reply(req, &setaclreply);
        nfs3_call_state_wipe(cs);
        GF_FREE(aclentry);
        GF_FREE(daclentry);
        return 0;
    }

rpcerr:
    if (ret < 0)
        nfs3_call_state_wipe(cs);
    if (aclentry)
        GF_FREE(aclentry);
    if (daclentry)
        GF_FREE(daclentry);
    return ret;
}

static rpcsvc_actor_t acl3svc_actors[ACL3_PROC_COUNT] = {
    {"NULL", acl3svc_null, NULL, ACL3_NULL, DRC_NA, 0},
    {"GETACL", acl3svc_getacl, NULL, ACL3_GETACL, DRC_NA, 0},
    {"SETACL", acl3svc_setacl, NULL, ACL3_SETACL, DRC_NA, 0},
};

static rpcsvc_program_t acl3prog = {
    .progname = "ACL3",
    .prognum = ACL_PROGRAM,
    .progver = ACLV3_VERSION,
    .progport = GF_NFS3_PORT,
    .actors = acl3svc_actors,
    .numactors = ACL3_PROC_COUNT,
    .min_auth = AUTH_NULL,
};

rpcsvc_program_t *
acl3svc_init(xlator_t *nfsx)
{
    struct nfs3_state *ns = NULL;
    struct nfs_state *nfs = NULL;
    dict_t *options = NULL;
    int ret = -1;
    static gf_boolean_t acl3_inited = _gf_false;

    /* Already inited */
    if (acl3_inited)
        return &acl3prog;

    nfs = (struct nfs_state *)nfsx->private;

    ns = nfs->nfs3state;
    if (!ns) {
        gf_msg(GF_ACL, GF_LOG_ERROR, EINVAL, NFS_MSG_ACL_INIT_FAIL,
               "ACL3 init failed");
        goto err;
    }
    acl3prog.private = ns;

    options = dict_new();
    if (options == NULL) {
        gf_msg(GF_ACL, GF_LOG_ERROR, ENOMEM, NFS_MSG_GFID_DICT_CREATE_FAIL,
               "dict allocation failed");
        goto err;
    }

    ret = dict_set_str(options, "transport.socket.listen-port", GF_ACL3_PORT);
    if (ret == -1)
        goto err;
    ret = dict_set_str(options, "transport-type", "socket");
    if (ret == -1) {
        gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED,
               "dict_set_str error");
        goto err;
    }

    if (nfs->allow_insecure) {
        ret = dict_set_str(options, "rpc-auth-allow-insecure", "on");
        if (ret == -1) {
            gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED,
                   "dict_set_str error");
            goto err;
        }
        ret = dict_set_str(options, "rpc-auth.ports.insecure", "on");
        if (ret == -1) {
            gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED,
                   "dict_set_str error");
            goto err;
        }
    }

    ret = dict_set_str(options, "transport.address-family", "inet");
    if (ret == -1) {
        gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_DICT_SET_FAILED,
               "dict_set_str error");
        goto err;
    }

    ret = rpcsvc_create_listeners(nfs->rpcsvc, options, "ACL");
    if (ret == -1) {
        gf_msg(GF_ACL, GF_LOG_ERROR, errno, NFS_MSG_LISTENERS_CREATE_FAIL,
               "Unable to create listeners");
        goto err;
    }

    if (options)
        dict_unref(options);

    acl3_inited = _gf_true;
    return &acl3prog;
err:
    if (options)
        dict_unref(options);
    return NULL;
}

static int
acl3_nfs_acl_to_xattr(aclentry *ace,  /* ACL entries to be read */
                      void *xattrbuf, /* XATTR buf to be populated */
                      int aclcount,   /* No of ACLs to be read */
                      int defacl)     /* 1 if DEFAULT ACL */
{
    int idx = 0;
    posix_acl_xattr_header *xheader = NULL;
    posix_acl_xattr_entry *xentry = NULL;

    if ((!ace) || (!xattrbuf))
        return (-EINVAL);

    /* ACL count is ZERO, nothing to do */
    if (!aclcount)
        return (0);

    if ((aclcount < 0) || (aclcount > NFS_ACL_MAX_ENTRIES))
        return (-EINVAL);

    xheader = (posix_acl_xattr_header *)(xattrbuf);
    xentry = (posix_acl_xattr_entry *)(xheader + 1);

    /*
     * For "default ACL", NFSv3 handles the 'type' differently
     * i.e. by logical OR'ing 'type' with NFS_ACL_DEFAULT.
     * Which the backend File system does not understand and
     * that needs to be masked OFF.
     */
    xheader->version = POSIX_ACL_XATTR_VERSION;

    for (idx = 0; idx < aclcount; idx++) {
        xentry->tag = ace->type;
        if (defacl)
            xentry->tag &= ~NFS_ACL_DEFAULT;
        xentry->perm = ace->perm;

        switch (xentry->tag) {
            case POSIX_ACL_USER:
            case POSIX_ACL_GROUP:
                if (xentry->perm & ~S_IRWXO)
                    return (-EINVAL);
                xentry->id = ace->uid;
                break;
            case POSIX_ACL_USER_OBJ:
            case POSIX_ACL_GROUP_OBJ:
            case POSIX_ACL_OTHER:
                if (xentry->perm & ~S_IRWXO)
                    return (-EINVAL);
                xentry->id = POSIX_ACL_UNDEFINED_ID;
                break;
            case POSIX_ACL_MASK:
                /* Solaris sometimes sets additional bits in
                 * the mask.
                 */
                xentry->perm &= S_IRWXO;
                xentry->id = POSIX_ACL_UNDEFINED_ID;
                break;
            default:
                return (-EINVAL);
        }

        xentry++;
        ace++;
    }

    /* SUCCESS */
    return (0);
}

static int
acl3_nfs_acl_from_xattr(aclentry *ace,  /* ACL entries to be filled */
                        void *xattrbuf, /* XATTR buf to be read */
                        int bufsize,    /* Size of XATTR buffer */
                        int defacl)     /*  1 if DEFAULT ACL */
{
    int idx = 0;
    ssize_t aclcount = 0;
    posix_acl_xattr_header *xheader = NULL;
    posix_acl_xattr_entry *xentry = NULL;

    if ((!xattrbuf) || (!ace))
        return (-EINVAL);

    aclcount = posix_acl_xattr_count(bufsize);
    if ((aclcount < 0) || (aclcount > NFS_ACL_MAX_ENTRIES))
        return (-EINVAL);

    xheader = (posix_acl_xattr_header *)(xattrbuf);
    xentry = (posix_acl_xattr_entry *)(xheader + 1);

    /* Check for supported POSIX ACL xattr version */
    if (xheader->version != POSIX_ACL_XATTR_VERSION)
        return (-ENOSYS);

    for (idx = 0; idx < (int)aclcount; idx++) {
        ace->type = xentry->tag;
        if (defacl) {
            /*
             * SET the NFS_ACL_DEFAULT flag for default
             * ACL which was masked OFF during setfacl().
             */
            ace->type |= NFS_ACL_DEFAULT;
        }
        ace->perm = (xentry->perm & S_IRWXO);

        switch (xentry->tag) {
            case POSIX_ACL_USER:
            case POSIX_ACL_GROUP:
                ace->uid = xentry->id;
                break;
            case POSIX_ACL_USER_OBJ:
            case POSIX_ACL_GROUP_OBJ:
            case POSIX_ACL_MASK:
            case POSIX_ACL_OTHER:
                ace->uid = POSIX_ACL_UNDEFINED_ID;
                break;
            default:
                return (-EINVAL);
        }

        xentry++;
        ace++;
    }

    /* SUCCESS: ACL count */
    return aclcount;
}
