/*
  Copyright (c) 2011-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 <errno.h>

#include <glusterfs/defaults.h>

#include "posix-acl.h"
#include "posix-acl-xattr.h"
#include "posix-acl-mem-types.h"
#include "posix-acl-messages.h"

#define UINT64(ptr) ((uint64_t)((long)(ptr)))
#define PTR(num) ((void *)((long)(num)))

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

    if (!this)
        return ret;

    ret = xlator_mem_acct_init(this, gf_posix_acl_mt_end);

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

    return ret;
}

static uid_t
r00t()
{
    struct posix_acl_conf *conf = NULL;

    conf = THIS->private;

    return conf->super_uid;
}

static int
allowed_xattr(const char *key)
{
    if (!key)
        return 0;

    if (strcmp(POSIX_ACL_ACCESS_XATTR, key) == 0)
        return 1;
    if (strcmp(POSIX_ACL_DEFAULT_XATTR, key) == 0)
        return 1;
    if (strcmp(GF_POSIX_ACL_ACCESS, key) == 0)
        return 1;
    if (strcmp(GF_POSIX_ACL_DEFAULT, key) == 0)
        return 1;
    return 0;
}

int
frame_is_user(call_frame_t *frame, uid_t uid)
{
    return (frame->root->uid == uid);
}

int
frame_is_super_user(call_frame_t *frame)
{
    int ret;

    ret = frame_is_user(frame, r00t());
    if (!ret)
        ret = frame_is_user(frame, 0);

    return ret;
}

int
frame_in_group(call_frame_t *frame, gid_t gid)
{
    int i = 0;

    if (frame->root->gid == gid)
        return 1;

    for (i = 0; i < frame->root->ngrps; i++)
        if (frame->root->groups[i] == gid)
            return 1;
    return 0;
}

mode_t
posix_acl_access_set_mode(struct posix_acl *acl, struct posix_acl_ctx *ctx)
{
    struct posix_ace *ace = NULL;
    struct posix_ace *group_ce = NULL;
    struct posix_ace *mask_ce = NULL;
    int count = 0;
    int i = 0;
    mode_t mode = 0;
    int mask = 0;

    count = acl->count;

    ace = acl->entries;
    for (i = 0; i < count; i++) {
        switch (ace->tag) {
            case POSIX_ACL_USER_OBJ:
                mask |= S_IRWXU;
                mode |= (ace->perm << 6);
                break;
            case POSIX_ACL_GROUP_OBJ:
                group_ce = ace;
                break;
            case POSIX_ACL_MASK:
                mask_ce = ace;
                break;
            case POSIX_ACL_OTHER:
                mask |= S_IRWXO;
                mode |= (ace->perm);
                break;
        }
        ace++;
    }

    if (mask_ce) {
        mask |= S_IRWXG;
        mode |= (mask_ce->perm << 3);
    } else {
        if (!group_ce)
            goto out;
        mask |= S_IRWXG;
        mode |= (group_ce->perm << 3);
    }

out:
    ctx->perm = (ctx->perm & ~mask) | mode;

    return mode;
}

static int
sticky_permits(call_frame_t *frame, inode_t *parent, inode_t *inode)
{
    struct posix_acl_ctx *par = NULL;
    struct posix_acl_ctx *ctx = NULL;

    if ((0 > frame->root->pid) || frame_is_super_user(frame))
        return 1;

    par = posix_acl_ctx_get(parent, frame->this);
    if (par == NULL)
        return 0;

    ctx = posix_acl_ctx_get(inode, frame->this);
    if (ctx == NULL)
        return 0;

    if (!(par->perm & S_ISVTX))
        return 1;

    if (frame_is_user(frame, par->uid))
        return 1;

    if (frame_is_user(frame, ctx->uid))
        return 1;

    return 0;
}

static gf_boolean_t
_does_acl_exist(struct posix_acl *acl)
{
    if (acl && (acl->count > POSIX_ACL_MINIMAL_ACE_COUNT))
        return _gf_true;
    return _gf_false;
}

static void
posix_acl_get_acl_string(call_frame_t *frame, struct posix_acl *acl,
                         char **acl_str)
{
    int i = 0;
    size_t size_acl = 0;
    size_t offset = 0;
    struct posix_ace *ace = NULL;
    char tmp_str[1024] = {0};
#define NON_GRP_FMT "(tag:%" PRIu16 ",perm:%" PRIu16 ",id:%" PRIu32 ")"
#define GRP_FMT "(tag:%" PRIu16 ",perm:%" PRIu16 ",id:%" PRIu32 ",in-groups:%d)"

    if (!_does_acl_exist(acl))
        goto out;

    ace = acl->entries;
    for (i = 0; i < acl->count; i++) {
        if (ace->tag != POSIX_ACL_GROUP) {
            size_acl += snprintf(tmp_str, sizeof tmp_str, NON_GRP_FMT, ace->tag,
                                 ace->perm, ace->id);
        } else {
            size_acl += snprintf(tmp_str, sizeof tmp_str, GRP_FMT, ace->tag,
                                 ace->perm, ace->id,
                                 frame_in_group(frame, ace->id));
        }

        ace++;
    }

    *acl_str = GF_CALLOC(1, size_acl + 1, gf_posix_acl_mt_char);
    if (!*acl_str)
        goto out;

    ace = acl->entries;
    for (i = 0; i < acl->count; i++) {
        if (ace->tag != POSIX_ACL_GROUP) {
            offset += snprintf(*acl_str + offset, size_acl - offset,
                               NON_GRP_FMT, ace->tag, ace->perm, ace->id);
        } else {
            offset += snprintf(*acl_str + offset, size_acl - offset, GRP_FMT,
                               ace->tag, ace->perm, ace->id,
                               frame_in_group(frame, ace->id));
        }

        ace++;
    }
out:
    return;
}

static void
posix_acl_log_permit_denied(call_frame_t *frame, inode_t *inode, int want,
                            struct posix_acl_ctx *ctx, struct posix_acl *acl)
{
    char *acl_str = NULL;
    client_t *client = NULL;

    if (!frame || !inode || !ctx || !frame->root)
        goto out;

    client = frame->root->client;
    posix_acl_get_acl_string(frame, acl, &acl_str);

    gf_msg(frame->this->name, GF_LOG_INFO, EACCES, POSIX_ACL_MSG_EACCES,
           "client: %s, gfid: %s, req(uid:%d,gid:%d,perm:%d,"
           "ngrps:%" PRIu16
           "), ctx(uid:%d,gid:%d,in-groups:%d,perm:%d%d%d,"
           "updated-fop:%s, acl:%s)",
           client ? client->client_uid : "-", uuid_utoa(inode->gfid),
           frame->root->uid, frame->root->gid, want, frame->root->ngrps,
           ctx->uid, ctx->gid, frame_in_group(frame, ctx->gid),
           (ctx->perm & S_IRWXU) >> 6, (ctx->perm & S_IRWXG) >> 3,
           ctx->perm & S_IRWXO, gf_fop_string(ctx->fop),
           acl_str ? acl_str : "-");
out:
    GF_FREE(acl_str);
    return;
}

static int
acl_permits(call_frame_t *frame, inode_t *inode, int want)
{
    int verdict = 0;
    struct posix_acl *acl = NULL;
    struct posix_ace *ace = NULL;
    struct posix_acl_ctx *ctx = NULL;
    struct posix_acl_conf *conf = NULL;
    int i = 0;
    int perm = 0;
    int found = 0;
    int acl_present = 0;

    conf = frame->this->private;

    if ((0 > frame->root->pid) || frame_is_super_user(frame))
        goto green;

    ctx = posix_acl_ctx_get(inode, frame->this);
    if (!ctx)
        goto red;

    posix_acl_get(inode, frame->this, &acl, NULL);
    if (!acl) {
        acl = posix_acl_ref(frame->this, conf->minimal_acl);
    }

    ace = acl->entries;

    if (_does_acl_exist(acl))
        acl_present = 1;

    for (i = 0; i < acl->count; i++) {
        switch (ace->tag) {
            case POSIX_ACL_USER_OBJ:
                perm = ((ctx->perm & S_IRWXU) >> 6);
                if (frame_is_user(frame, ctx->uid))
                    goto perm_check;
                break;
            case POSIX_ACL_USER:
                perm = ace->perm;
                if (frame_is_user(frame, ace->id))
                    goto mask_check;
                break;
            case POSIX_ACL_GROUP_OBJ:
                if (acl_present)
                    perm = ace->perm;
                else
                    perm = ((ctx->perm & S_IRWXG) >> 3);
                if (frame_in_group(frame, ctx->gid)) {
                    found = 1;
                    if ((perm & want) == want)
                        goto mask_check;
                }
                break;
            case POSIX_ACL_GROUP:
                perm = ace->perm;
                if (frame_in_group(frame, ace->id)) {
                    found = 1;
                    if ((perm & want) == want)
                        goto mask_check;
                }
                break;
            case POSIX_ACL_MASK:
                break;
            case POSIX_ACL_OTHER:
                perm = (ctx->perm & S_IRWXO);
                if (!found)
                    goto perm_check;
                /* fall through */
            default:
                goto red;
        }

        ace++;
    }

mask_check:
    ace = acl->entries;

    for (i = 0; i < acl->count; i++, ace++) {
        if (ace->tag != POSIX_ACL_MASK)
            continue;
        if ((ace->perm & perm & want) == want) {
            goto green;
        }
        goto red;
    }

perm_check:
    if ((perm & want) == want) {
        goto green;
    } else {
        goto red;
    }

green:
    verdict = 1;
    goto out;
red:
    verdict = 0;
    posix_acl_log_permit_denied(frame, inode, want, ctx, acl);
out:
    if (acl)
        posix_acl_unref(frame->this, acl);

    return verdict;
}

struct posix_acl_ctx *
__posix_acl_ctx_get(inode_t *inode, xlator_t *this, gf_boolean_t create)
{
    struct posix_acl_ctx *ctx = NULL;
    uint64_t int_ctx = 0;
    int ret = 0;

    ret = __inode_ctx_get(inode, this, &int_ctx);
    if ((ret == 0) && (int_ctx))
        return PTR(int_ctx);

    if (create == _gf_false)
        return NULL;

    ctx = GF_CALLOC(1, sizeof(*ctx), gf_posix_acl_mt_ctx_t);
    if (!ctx)
        return NULL;

    ret = __inode_ctx_put(inode, this, UINT64(ctx));
    if (ret) {
        GF_FREE(ctx);
        ctx = NULL;
    }

    return ctx;
}

struct posix_acl_ctx *
posix_acl_ctx_new(inode_t *inode, xlator_t *this)
{
    struct posix_acl_ctx *ctx = NULL;

    if (inode == NULL) {
        gf_log_callingfn(this->name, GF_LOG_WARNING, "inode is NULL");
        return NULL;
    }

    LOCK(&inode->lock);
    {
        ctx = __posix_acl_ctx_get(inode, this, _gf_true);
    }
    UNLOCK(&inode->lock);

    if (ctx == NULL)
        gf_log_callingfn(this->name, GF_LOG_ERROR,
                         "creating inode ctx"
                         "failed for %s",
                         uuid_utoa(inode->gfid));
    return ctx;
}

struct posix_acl_ctx *
posix_acl_ctx_get(inode_t *inode, xlator_t *this)
{
    struct posix_acl_ctx *ctx = NULL;

    if (inode == NULL) {
        gf_log_callingfn(this->name, GF_LOG_WARNING, "inode is NULL");
        return NULL;
    }

    LOCK(&inode->lock);
    {
        ctx = __posix_acl_ctx_get(inode, this, _gf_false);
    }
    UNLOCK(&inode->lock);

    if (ctx == NULL)
        gf_log_callingfn(this->name, GF_LOG_ERROR,
                         "inode ctx is NULL "
                         "for %s",
                         uuid_utoa(inode->gfid));
    return ctx;
}

int
__posix_acl_set_specific(inode_t *inode, xlator_t *this, gf_boolean_t is_access,
                         struct posix_acl *acl)
{
    int ret = 0;
    struct posix_acl_ctx *ctx = NULL;

    ctx = posix_acl_ctx_get(inode, this);
    if (!ctx) {
        ret = -1;
        goto out;
    }

    if (is_access)
        ctx->acl_access = acl;
    else
        ctx->acl_default = acl;
out:
    return ret;
}

int
__posix_acl_set(inode_t *inode, xlator_t *this, struct posix_acl *acl_access,
                struct posix_acl *acl_default)
{
    int ret = 0;
    struct posix_acl_ctx *ctx = NULL;

    ctx = posix_acl_ctx_get(inode, this);
    if (!ctx)
        goto out;

    ctx->acl_access = acl_access;
    ctx->acl_default = acl_default;

out:
    return ret;
}

int
__posix_acl_get(inode_t *inode, xlator_t *this, struct posix_acl **acl_access_p,
                struct posix_acl **acl_default_p)
{
    int ret = 0;
    struct posix_acl_ctx *ctx = NULL;

    ctx = posix_acl_ctx_get(inode, this);
    if (!ctx)
        goto out;

    if (acl_access_p)
        *acl_access_p = ctx->acl_access;
    if (acl_default_p)
        *acl_default_p = ctx->acl_default;

out:
    return ret;
}

struct posix_acl *
posix_acl_new(xlator_t *this, int entrycnt)
{
    struct posix_acl *acl = NULL;
    struct posix_ace *ace = NULL;

    acl = GF_CALLOC(1, sizeof(*acl) + (entrycnt * sizeof(*ace)),
                    gf_posix_acl_mt_posix_ace_t);
    if (!acl)
        return NULL;

    acl->count = entrycnt;

    posix_acl_ref(this, acl);

    return acl;
}

void
posix_acl_destroy(xlator_t *this, struct posix_acl *acl)
{
    GF_FREE(acl);

    return;
}

struct posix_acl *
posix_acl_ref(xlator_t *this, struct posix_acl *acl)
{
    struct posix_acl_conf *conf = NULL;

    conf = this->private;

    LOCK(&conf->acl_lock);
    {
        acl->refcnt++;
    }
    UNLOCK(&conf->acl_lock);

    return acl;
}

struct posix_acl *
posix_acl_dup(xlator_t *this, struct posix_acl *acl)
{
    struct posix_acl *dup = NULL;

    dup = posix_acl_new(this, acl->count);
    if (!dup)
        return NULL;

    memcpy(dup->entries, acl->entries, sizeof(struct posix_ace) * acl->count);

    return dup;
}

void
posix_acl_unref(xlator_t *this, struct posix_acl *acl)
{
    struct posix_acl_conf *conf = NULL;
    int refcnt = 0;

    conf = this->private;
    if (!conf)
        goto out;

    LOCK(&conf->acl_lock);
    {
        refcnt = --acl->refcnt;
    }
    UNLOCK(&conf->acl_lock);
out:
    if (!refcnt)
        posix_acl_destroy(this, acl);
}

int
posix_acl_set_specific(inode_t *inode, xlator_t *this, struct posix_acl *acl,
                       gf_boolean_t is_access)
{
    int ret = 0;
    int oldret = 0;
    struct posix_acl *old_acl = NULL;
    struct posix_acl_conf *conf = NULL;

    conf = this->private;

    LOCK(&conf->acl_lock);
    {
        if (is_access)
            oldret = __posix_acl_get(inode, this, &old_acl, NULL);
        else
            oldret = __posix_acl_get(inode, this, NULL, &old_acl);
        if (acl)
            acl->refcnt++;
        ret = __posix_acl_set_specific(inode, this, is_access, acl);
    }
    UNLOCK(&conf->acl_lock);

    if (!oldret && old_acl) {
        posix_acl_unref(this, old_acl);
    }

    return ret;
}

int
posix_acl_set(inode_t *inode, xlator_t *this, struct posix_acl *acl_access,
              struct posix_acl *acl_default)
{
    int ret = 0;
    int oldret = 0;
    struct posix_acl *old_access = NULL;
    struct posix_acl *old_default = NULL;
    struct posix_acl_conf *conf = NULL;

    conf = this->private;

    LOCK(&conf->acl_lock);
    {
        oldret = __posix_acl_get(inode, this, &old_access, &old_default);
        if (acl_access)
            acl_access->refcnt++;
        if (acl_default)
            acl_default->refcnt++;

        ret = __posix_acl_set(inode, this, acl_access, acl_default);
    }
    UNLOCK(&conf->acl_lock);

    if (oldret == 0) {
        if (old_access)
            posix_acl_unref(this, old_access);
        if (old_default)
            posix_acl_unref(this, old_default);
    }

    return ret;
}

int
posix_acl_get(inode_t *inode, xlator_t *this, struct posix_acl **acl_access_p,
              struct posix_acl **acl_default_p)
{
    struct posix_acl_conf *conf = NULL;
    struct posix_acl *acl_access = NULL;
    struct posix_acl *acl_default = NULL;
    int ret = 0;

    conf = this->private;

    LOCK(&conf->acl_lock);
    {
        ret = __posix_acl_get(inode, this, &acl_access, &acl_default);

        if (ret != 0)
            goto unlock;

        if (acl_access && acl_access_p)
            acl_access->refcnt++;
        if (acl_default && acl_default_p)
            acl_default->refcnt++;
    }
unlock:
    UNLOCK(&conf->acl_lock);

    if (acl_access_p)
        *acl_access_p = acl_access;
    if (acl_default_p)
        *acl_default_p = acl_default;

    return ret;
}

mode_t
posix_acl_inherit_mode(struct posix_acl *acl, mode_t modein)
{
    struct posix_ace *ace = NULL;
    int count = 0;
    int i = 0;
    mode_t newmode = 0;
    mode_t mode = 0;
    struct posix_ace *mask_ce = NULL;
    struct posix_ace *group_ce = NULL;

    newmode = mode = modein;

    count = acl->count;

    ace = acl->entries;
    for (i = 0; i < count; i++) {
        switch (ace->tag) {
            case POSIX_ACL_USER_OBJ:
                ace->perm &= (mode >> 6) | ~S_IRWXO;
                mode &= (ace->perm << 6) | ~S_IRWXU;
                break;
            case POSIX_ACL_GROUP_OBJ:
                group_ce = ace;
                break;
            case POSIX_ACL_MASK:
                mask_ce = ace;
                break;
            case POSIX_ACL_OTHER:
                ace->perm &= (mode) | ~S_IRWXO;
                mode &= (ace->perm) | ~S_IRWXO;
                break;
        }
        ace++;
    }

    if (mask_ce) {
        mask_ce->perm &= (mode >> 3) | ~S_IRWXO;
        mode &= (mask_ce->perm << 3) | ~S_IRWXG;
    } else if (group_ce) {
        group_ce->perm &= (mode >> 3) | ~S_IRWXO;
        mode &= (group_ce->perm << 3) | ~S_IRWXG;
    }

    newmode = ((modein & (S_IFMT | S_ISUID | S_ISGID | S_ISVTX)) |
               (mode & (S_IRWXU | S_IRWXG | S_IRWXO)));

    return newmode;
}

mode_t
posix_acl_inherit(xlator_t *this, loc_t *loc, dict_t *params, mode_t mode,
                  int32_t umask, int is_dir)
{
    int ret = 0;
    struct posix_acl *par_default = NULL;
    struct posix_acl *acl_default = NULL;
    struct posix_acl *acl_access = NULL;
    struct posix_acl_ctx *ctx = NULL;
    char *xattr_default = NULL;
    char *xattr_access = NULL;
    int size_default = 0;
    int size_access = 0;
    mode_t retmode = 0;
    int16_t tmp_mode = 0;
    mode_t client_umask = 0;

    retmode = mode;
    client_umask = umask;
    ret = dict_get_int16(params, "umask", &tmp_mode);
    if (ret == 0) {
        client_umask = (mode_t)tmp_mode;
        dict_del(params, "umask");
        ret = dict_get_int16(params, "mode", &tmp_mode);
        if (ret == 0) {
            retmode = (mode_t)tmp_mode;
            dict_del(params, "mode");
        } else {
            gf_log(this->name, GF_LOG_ERROR,
                   "client sent umask, but not the original mode");
        }
    }

    ret = posix_acl_get(loc->parent, this, NULL, &par_default);

    if (!par_default)
        goto out;

    ctx = posix_acl_ctx_new(loc->inode, this);

    acl_access = posix_acl_dup(this, par_default);
    if (!acl_access)
        goto out;

    client_umask = 0;  // No umask if we inherit an ACL
    retmode = posix_acl_inherit_mode(acl_access, retmode);
    ctx->perm = retmode;

    size_access = posix_acl_to_xattr(this, acl_access, NULL, 0);
    xattr_access = GF_CALLOC(1, size_access, gf_posix_acl_mt_char);
    if (!xattr_access) {
        gf_log(this->name, GF_LOG_ERROR, "out of memory");
        ret = -1;
        goto out;
    }
    posix_acl_to_xattr(this, acl_access, xattr_access, size_access);

    ret = dict_set_bin(params, POSIX_ACL_ACCESS_XATTR, xattr_access,
                       size_access);
    if (ret) {
        gf_log(this->name, GF_LOG_ERROR, "out of memory");
        GF_FREE(xattr_access);
        ret = -1;
        goto out;
    }

    if (!is_dir)
        goto set;

    acl_default = posix_acl_ref(this, par_default);

    size_default = posix_acl_to_xattr(this, acl_default, NULL, 0);
    xattr_default = GF_CALLOC(1, size_default, gf_posix_acl_mt_char);
    if (!xattr_default) {
        gf_log(this->name, GF_LOG_ERROR, "out of memory");
        ret = -1;
        goto out;
    }
    posix_acl_to_xattr(this, acl_default, xattr_default, size_default);

    ret = dict_set_bin(params, POSIX_ACL_DEFAULT_XATTR, xattr_default,
                       size_default);
    if (ret) {
        gf_log(this->name, GF_LOG_ERROR, "out of memory");
        GF_FREE(xattr_default);
        ret = -1;
        goto out;
    }

set:
    ret = posix_acl_set(loc->inode, this, acl_access, acl_default);
    if (ret != 0)
        goto out;

out:
    retmode &= ~client_umask;

    if (par_default)
        posix_acl_unref(this, par_default);
    if (acl_access)
        posix_acl_unref(this, acl_access);
    if (acl_default)
        posix_acl_unref(this, acl_default);

    return retmode;
}

mode_t
posix_acl_inherit_dir(xlator_t *this, loc_t *loc, dict_t *params, mode_t mode,
                      int32_t umask)
{
    mode_t retmode = 0;

    retmode = posix_acl_inherit(this, loc, params, mode, umask, 1);

    return retmode;
}

mode_t
posix_acl_inherit_file(xlator_t *this, loc_t *loc, dict_t *params, mode_t mode,
                       int32_t umask)
{
    mode_t retmode = 0;

    retmode = posix_acl_inherit(this, loc, params, mode, umask, 0);

    return retmode;
}

int
posix_acl_ctx_update(inode_t *inode, xlator_t *this, struct iatt *buf,
                     glusterfs_fop_t fop)
{
    struct posix_acl_ctx *ctx = NULL;
    struct posix_acl *acl = NULL;
    struct posix_ace *ace = NULL;
    struct posix_ace *mask_ce = NULL;
    struct posix_ace *group_ce = NULL;
    int ret = 0;
    int i = 0;

    if (!buf || !buf->ia_ctime) {
        /* No need to update ctx if buf is empty */
        gf_log_callingfn(this->name, GF_LOG_DEBUG, "iatt struct is empty (%d)",
                         fop);
        goto out;
    }

    LOCK(&inode->lock);
    {
        ctx = __posix_acl_ctx_get(inode, this, _gf_true);
        if (!ctx) {
            ret = -1;
            goto unlock;
        }

        ctx->uid = buf->ia_uid;
        ctx->gid = buf->ia_gid;
        ctx->perm = st_mode_from_ia(buf->ia_prot, buf->ia_type);
        ctx->fop = fop;

        acl = ctx->acl_access;
        if (!_does_acl_exist(acl))
            goto unlock;

        /* This is an extended ACL (not minimal acl). In case we
           are only refreshing from iatt and not ACL xattrs (for
           e.g. from postattributes of setattr() call, we need to
           update the corresponding ACEs as well.
        */
        ace = acl->entries;
        for (i = 0; i < acl->count; i++) {
            switch (ace->tag) {
                case POSIX_ACL_USER_OBJ:
                    ace->perm = (ctx->perm & S_IRWXU) >> 6;
                    break;
                case POSIX_ACL_USER:
                case POSIX_ACL_GROUP:
                    break;
                case POSIX_ACL_GROUP_OBJ:
                    group_ce = ace;
                    break;
                case POSIX_ACL_MASK:
                    mask_ce = ace;
                    break;
                case POSIX_ACL_OTHER:
                    ace->perm = (ctx->perm & S_IRWXO);
                    break;
            }
            ace++;
        }

        if (mask_ce)
            mask_ce->perm = (ctx->perm & S_IRWXG) >> 3;
        else if (group_ce)
            group_ce->perm = (ctx->perm & S_IRWXG) >> 3;
        else
            ret = -1;
    }
unlock:
    UNLOCK(&inode->lock);
out:
    return ret;
}

int
posix_acl_lookup_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                     int op_ret, int op_errno, inode_t *inode, struct iatt *buf,
                     dict_t *xattr, struct iatt *postparent)
{
    struct posix_acl *acl_access = NULL;
    struct posix_acl *acl_default = NULL;
    struct posix_acl *old_access = NULL;
    struct posix_acl *old_default = NULL;
    struct posix_acl_ctx *ctx = NULL;
    data_t *data = NULL;
    int ret = 0;
    dict_t *my_xattr = NULL;

    if (op_ret != 0)
        goto unwind;

    ctx = posix_acl_ctx_new(inode, this);
    if (!ctx) {
        op_ret = -1;
        op_errno = ENOMEM;
        goto unwind;
    }

    ret = posix_acl_get(inode, this, &old_access, &old_default);

    if (xattr == NULL)
        goto acl_set;

    data = dict_get(xattr, POSIX_ACL_ACCESS_XATTR);
    if (!data)
        goto acl_default;

    if (old_access &&
        posix_acl_matches_xattr(this, old_access, data->data, data->len)) {
        acl_access = posix_acl_ref(this, old_access);
    } else {
        acl_access = posix_acl_from_xattr(this, data->data, data->len);
    }

acl_default:
    data = dict_get(xattr, POSIX_ACL_DEFAULT_XATTR);
    if (!data)
        goto acl_set;

    if (old_default &&
        posix_acl_matches_xattr(this, old_default, data->data, data->len)) {
        acl_default = posix_acl_ref(this, old_default);
    } else {
        acl_default = posix_acl_from_xattr(this, data->data, data->len);
    }

acl_set:
    posix_acl_ctx_update(inode, this, buf, GF_FOP_LOOKUP);

    ret = posix_acl_set(inode, this, acl_access, acl_default);
    if (ret)
        gf_log(this->name, GF_LOG_WARNING, "failed to set ACL in context");
unwind:
    my_xattr = frame->local;
    frame->local = NULL;
    STACK_UNWIND_STRICT(lookup, frame, op_ret, op_errno, inode, buf, xattr,
                        postparent);

    if (acl_access)
        posix_acl_unref(this, acl_access);
    if (acl_default)
        posix_acl_unref(this, acl_default);
    if (old_access)
        posix_acl_unref(this, old_access);
    if (old_default)
        posix_acl_unref(this, old_default);
    if (my_xattr)
        dict_unref(my_xattr);

    return 0;
}

int
posix_acl_lookup(call_frame_t *frame, xlator_t *this, loc_t *loc, dict_t *xattr)
{
    int ret = 0;
    dict_t *my_xattr = NULL;

    if (!loc->parent)
        /* lookup of / is always permitted */
        goto green;

    if (acl_permits(frame, loc->parent, POSIX_ACL_EXECUTE))
        goto green;
    else
        goto red;

green:
    if (xattr) {
        my_xattr = dict_ref(xattr);
    } else {
        my_xattr = dict_new();
    }

    ret = dict_set_int8(my_xattr, POSIX_ACL_ACCESS_XATTR, 0);
    if (ret)
        gf_log(this->name, GF_LOG_WARNING, "failed to set key %s",
               POSIX_ACL_ACCESS_XATTR);

    ret = dict_set_int8(my_xattr, POSIX_ACL_DEFAULT_XATTR, 0);
    if (ret)
        gf_log(this->name, GF_LOG_WARNING, "failed to set key %s",
               POSIX_ACL_DEFAULT_XATTR);

    frame->local = my_xattr;
    STACK_WIND(frame, posix_acl_lookup_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->lookup, loc, my_xattr);
    return 0;
red:
    STACK_UNWIND_STRICT(lookup, frame, -1, EACCES, NULL, NULL, NULL, NULL);

    return 0;
}

int
posix_acl_access(call_frame_t *frame, xlator_t *this, loc_t *loc, int mask,
                 dict_t *xdata)
{
    int op_ret = 0;
    int op_errno = 0;
    int perm = 0;
    int mode = 0;
    int is_fuse_call = 0;

    is_fuse_call = __is_fuse_call(frame);

    if (mask & R_OK)
        perm |= POSIX_ACL_READ;
    if (mask & W_OK)
        perm |= POSIX_ACL_WRITE;
    if (mask & X_OK)
        perm |= POSIX_ACL_EXECUTE;
    if (!mask) {
        goto unwind;
    }
    if (!perm) {
        op_ret = -1;
        op_errno = EINVAL;
        goto unwind;
    }

    if (is_fuse_call) {
        mode = acl_permits(frame, loc->inode, perm);
        if (mode) {
            op_ret = 0;
            op_errno = 0;
        } else {
            op_ret = -1;
            op_errno = EACCES;
        }
    } else {
        if (perm & POSIX_ACL_READ) {
            if (acl_permits(frame, loc->inode, POSIX_ACL_READ))
                mode |= POSIX_ACL_READ;
        }

        if (perm & POSIX_ACL_WRITE) {
            if (acl_permits(frame, loc->inode, POSIX_ACL_WRITE))
                mode |= POSIX_ACL_WRITE;
        }

        if (perm & POSIX_ACL_EXECUTE) {
            if (acl_permits(frame, loc->inode, POSIX_ACL_EXECUTE))
                mode |= POSIX_ACL_EXECUTE;
        }
    }

unwind:
    if (is_fuse_call)
        STACK_UNWIND_STRICT(access, frame, op_ret, op_errno, NULL);
    else
        STACK_UNWIND_STRICT(access, frame, 0, mode, NULL);
    return 0;
}

int
posix_acl_truncate(call_frame_t *frame, xlator_t *this, loc_t *loc, off_t off,
                   dict_t *xdata)
{
    struct posix_acl_ctx *ctx = NULL;

    if (acl_permits(frame, loc->inode, POSIX_ACL_WRITE))
        goto green;
    /* NFS does a truncate through SETATTR, the owner does not need write
     * permissions for this. Group permissions and root are checked above.
     */
    else if (frame->root->pid == NFS_PID) {
        ctx = posix_acl_ctx_get(loc->inode, frame->this);

        if (ctx && frame_is_user(frame, ctx->uid))
            goto green;
    }

    /* fail by default */
    STACK_UNWIND_STRICT(truncate, frame, -1, EACCES, NULL, NULL, NULL);
    return 0;

green:
    STACK_WIND(frame, default_truncate_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->truncate, loc, off, xdata);
    return 0;
}

int
posix_acl_open(call_frame_t *frame, xlator_t *this, loc_t *loc, int flags,
               fd_t *fd, dict_t *xdata)
{
    int perm = 0;

    switch (flags & O_ACCMODE) {
        case O_RDONLY:
            perm = POSIX_ACL_READ;

            /* If O_FMODE_EXEC is present, its good enough
               to have '--x' perm, and its not covered in
               O_ACCMODE bits */
            if (flags & O_FMODE_EXEC)
                perm = POSIX_ACL_EXECUTE;

            break;
        case O_WRONLY:
            perm = POSIX_ACL_WRITE;
            break;
        case O_RDWR:
            perm = POSIX_ACL_READ | POSIX_ACL_WRITE;
            break;
    }

    if (flags & (O_TRUNC | O_APPEND))
        perm |= POSIX_ACL_WRITE;

    if (acl_permits(frame, loc->inode, perm))
        goto green;
    else
        goto red;
green:
    STACK_WIND(frame, default_open_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->open, loc, flags, fd, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(open, frame, -1, EACCES, NULL, NULL);
    return 0;
}

int
posix_acl_readv(call_frame_t *frame, xlator_t *this, fd_t *fd, size_t size,
                off_t offset, uint32_t flags, dict_t *xdata)
{
    if (__is_fuse_call(frame))
        goto green;

    if (acl_permits(frame, fd->inode, POSIX_ACL_READ))
        goto green;
    else
        goto red;

green:
    STACK_WIND(frame, default_readv_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->readv, fd, size, offset, flags, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(readv, frame, -1, EACCES, NULL, 0, NULL, NULL, NULL);
    return 0;
}

int
posix_acl_writev(call_frame_t *frame, xlator_t *this, fd_t *fd,
                 struct iovec *vector, int count, off_t offset, uint32_t flags,
                 struct iobref *iobref, dict_t *xdata)
{
    if (__is_fuse_call(frame))
        goto green;

    if (acl_permits(frame, fd->inode, POSIX_ACL_WRITE))
        goto green;
    else
        goto red;

green:
    STACK_WIND(frame, default_writev_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->writev, fd, vector, count, offset,
               flags, iobref, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(writev, frame, -1, EACCES, NULL, NULL, NULL);
    return 0;
}

int
posix_acl_ftruncate(call_frame_t *frame, xlator_t *this, fd_t *fd, off_t offset,
                    dict_t *xdata)
{
    if (__is_fuse_call(frame))
        goto green;

    if (acl_permits(frame, fd->inode, POSIX_ACL_WRITE))
        goto green;
    else
        goto red;

green:
    STACK_WIND(frame, default_ftruncate_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->ftruncate, fd, offset, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(ftruncate, frame, -1, EACCES, NULL, NULL, NULL);
    return 0;
}

int
posix_acl_opendir(call_frame_t *frame, xlator_t *this, loc_t *loc, fd_t *fd,
                  dict_t *xdata)
{
    if (acl_permits(frame, loc->inode, POSIX_ACL_READ))
        goto green;
    else
        goto red;
green:
    STACK_WIND(frame, default_opendir_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->opendir, loc, fd, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(opendir, frame, -1, EACCES, NULL, NULL);
    return 0;
}

int
posix_acl_mkdir_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                    int op_ret, int op_errno, inode_t *inode, struct iatt *buf,
                    struct iatt *preparent, struct iatt *postparent,
                    dict_t *xdata)
{
    if (op_ret != 0)
        goto unwind;

    posix_acl_ctx_update(inode, this, buf, GF_FOP_MKDIR);

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

int
posix_acl_mkdir(call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode,
                mode_t umask, dict_t *xdata)
{
    mode_t newmode = 0;

    newmode = mode;
    if (acl_permits(frame, loc->parent, POSIX_ACL_WRITE | POSIX_ACL_EXECUTE))
        goto green;
    else
        goto red;
green:
    newmode = posix_acl_inherit_dir(this, loc, xdata, mode, umask);

    STACK_WIND(frame, posix_acl_mkdir_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->mkdir, loc, newmode, umask, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(mkdir, frame, -1, EACCES, NULL, NULL, NULL, NULL, NULL);
    return 0;
}

int
posix_acl_mknod_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                    int op_ret, int op_errno, inode_t *inode, struct iatt *buf,
                    struct iatt *preparent, struct iatt *postparent,
                    dict_t *xdata)
{
    if (op_ret != 0)
        goto unwind;

    posix_acl_ctx_update(inode, this, buf, GF_FOP_MKNOD);

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

int
posix_acl_mknod(call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode,
                dev_t rdev, mode_t umask, dict_t *xdata)
{
    mode_t newmode = 0;

    newmode = mode;
    if (acl_permits(frame, loc->parent, POSIX_ACL_WRITE | POSIX_ACL_EXECUTE))
        goto green;
    else
        goto red;
green:
    newmode = posix_acl_inherit_file(this, loc, xdata, mode, umask);

    STACK_WIND(frame, posix_acl_mknod_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->mknod, loc, newmode, rdev, umask,
               xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(mknod, frame, -1, EACCES, NULL, NULL, NULL, NULL, NULL);
    return 0;
}

int
posix_acl_create_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                     int op_ret, int op_errno, fd_t *fd, inode_t *inode,
                     struct iatt *buf, struct iatt *preparent,
                     struct iatt *postparent, dict_t *xdata)
{
    if (op_ret != 0)
        goto unwind;

    posix_acl_ctx_update(inode, this, buf, GF_FOP_CREATE);

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

int
posix_acl_create(call_frame_t *frame, xlator_t *this, loc_t *loc, int flags,
                 mode_t mode, mode_t umask, fd_t *fd, dict_t *xdata)
{
    mode_t newmode = 0;

    newmode = mode;
    if (acl_permits(frame, loc->parent, POSIX_ACL_WRITE | POSIX_ACL_EXECUTE))
        goto green;
    else
        goto red;
green:
    newmode = posix_acl_inherit_file(this, loc, xdata, mode, umask);

    STACK_WIND(frame, posix_acl_create_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->create, loc, flags, newmode, umask, fd,
               xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(create, frame, -1, EACCES, NULL, NULL, NULL, NULL, NULL,
                        NULL);
    return 0;
}

int
posix_acl_symlink_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                      int op_ret, int op_errno, inode_t *inode,
                      struct iatt *buf, struct iatt *preparent,
                      struct iatt *postparent, dict_t *xdata)
{
    if (op_ret != 0)
        goto unwind;

    posix_acl_ctx_update(inode, this, buf, GF_FOP_SYMLINK);

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

int
posix_acl_symlink(call_frame_t *frame, xlator_t *this, const char *linkname,
                  loc_t *loc, mode_t umask, dict_t *xdata)
{
    if (acl_permits(frame, loc->parent, POSIX_ACL_WRITE | POSIX_ACL_EXECUTE))
        goto green;
    else
        goto red;
green:
    STACK_WIND(frame, posix_acl_symlink_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->symlink, linkname, loc, umask, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(symlink, frame, -1, EACCES, NULL, NULL, NULL, NULL,
                        NULL);
    return 0;
}

int
posix_acl_unlink(call_frame_t *frame, xlator_t *this, loc_t *loc, int xflag,
                 dict_t *xdata)
{
    if (!sticky_permits(frame, loc->parent, loc->inode))
        goto red;

    if (acl_permits(frame, loc->parent, POSIX_ACL_WRITE | POSIX_ACL_EXECUTE))
        goto green;
    else
        goto red;
green:
    STACK_WIND(frame, default_unlink_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->unlink, loc, xflag, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(unlink, frame, -1, EACCES, NULL, NULL, NULL);
    return 0;
}

int
posix_acl_rmdir(call_frame_t *frame, xlator_t *this, loc_t *loc, int flags,
                dict_t *xdata)
{
    if (!sticky_permits(frame, loc->parent, loc->inode))
        goto red;

    if (acl_permits(frame, loc->parent, POSIX_ACL_WRITE | POSIX_ACL_EXECUTE))
        goto green;
    else
        goto red;
green:
    STACK_WIND(frame, default_rmdir_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->rmdir, loc, flags, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(rmdir, frame, -1, EACCES, NULL, NULL, NULL);
    return 0;
}

int
posix_acl_rename(call_frame_t *frame, xlator_t *this, loc_t *old, loc_t *new,
                 dict_t *xdata)
{
    if (!acl_permits(frame, old->parent, POSIX_ACL_WRITE))
        goto red;

    if (!acl_permits(frame, new->parent, POSIX_ACL_WRITE))
        goto red;

    if (!sticky_permits(frame, old->parent, old->inode))
        goto red;

    if (new->inode) {
        if (!sticky_permits(frame, new->parent, new->inode))
            goto red;
    }

    STACK_WIND(frame, default_rename_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->rename, old, new, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(rename, frame, -1, EACCES, NULL, NULL, NULL, NULL, NULL,
                        NULL);
    return 0;
}

int
posix_acl_link(call_frame_t *frame, xlator_t *this, loc_t *old, loc_t *new,
               dict_t *xdata)
{
    struct posix_acl_ctx *ctx = NULL;
    int op_errno = 0;

    ctx = posix_acl_ctx_get(old->inode, this);
    if (!ctx) {
        op_errno = EIO;
        goto red;
    }

    if (!acl_permits(frame, new->parent, POSIX_ACL_WRITE)) {
        op_errno = EACCES;
        goto red;
    }

    if (!sticky_permits(frame, new->parent, new->inode)) {
        op_errno = EACCES;
        goto red;
    }

    STACK_WIND(frame, default_link_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->link, old, new, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(link, frame, -1, op_errno, NULL, NULL, NULL, NULL,
                        NULL);

    return 0;
}

int
posix_acl_readdir(call_frame_t *frame, xlator_t *this, fd_t *fd, size_t size,
                  off_t offset, dict_t *xdata)
{
    if (acl_permits(frame, fd->inode, POSIX_ACL_READ))
        goto green;
    else
        goto red;
green:
    STACK_WIND(frame, default_readdir_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->readdir, fd, size, offset, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(readdir, frame, -1, EACCES, NULL, NULL);

    return 0;
}

int
posix_acl_readdirp_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                       int op_ret, int op_errno, gf_dirent_t *entries,
                       dict_t *xdata)
{
    gf_dirent_t *entry = NULL;
    struct posix_acl *acl_access = NULL;
    struct posix_acl *acl_default = NULL;
    struct posix_acl_ctx *ctx = NULL;
    data_t *data = NULL;
    int ret = 0;

    if (op_ret <= 0)
        goto unwind;

    list_for_each_entry(entry, &entries->list, list)
    {
        /* Update the inode ctx */
        if (!entry->dict || !entry->inode)
            continue;

        ctx = posix_acl_ctx_new(entry->inode, this);
        if (!ctx) {
            op_ret = -1;
            op_errno = ENOMEM;
            goto unwind;
        }

        ret = posix_acl_get(entry->inode, this, &acl_access, &acl_default);

        data = dict_get(entry->dict, POSIX_ACL_ACCESS_XATTR);
        if (!data)
            goto acl_default;

        if (acl_access &&
            posix_acl_matches_xattr(this, acl_access, data->data, data->len))
            goto acl_default;

        if (acl_access)
            posix_acl_unref(this, acl_access);

        acl_access = posix_acl_from_xattr(this, data->data, data->len);

    acl_default:
        data = dict_get(entry->dict, POSIX_ACL_DEFAULT_XATTR);
        if (!data)
            goto acl_set;

        if (acl_default &&
            posix_acl_matches_xattr(this, acl_default, data->data, data->len))
            goto acl_set;

        if (acl_default)
            posix_acl_unref(this, acl_default);

        acl_default = posix_acl_from_xattr(this, data->data, data->len);

    acl_set:
        posix_acl_ctx_update(entry->inode, this, &entry->d_stat,
                             GF_FOP_READDIRP);

        ret = posix_acl_set(entry->inode, this, acl_access, acl_default);
        if (ret)
            gf_log(this->name, GF_LOG_WARNING, "failed to set ACL in context");

        if (acl_access)
            posix_acl_unref(this, acl_access);
        if (acl_default)
            posix_acl_unref(this, acl_default);
    }

unwind:
    STACK_UNWIND_STRICT(readdirp, frame, op_ret, op_errno, entries, xdata);
    return 0;
}

int
posix_acl_readdirp(call_frame_t *frame, xlator_t *this, fd_t *fd, size_t size,
                   off_t offset, dict_t *dict)
{
    int ret = 0;
    dict_t *alloc_dict = NULL;

    if (acl_permits(frame, fd->inode, POSIX_ACL_READ))
        goto green;
    else
        goto red;
green:
    if (!dict)
        dict = alloc_dict = dict_new();

    if (dict) {
        ret = dict_set_int8(dict, POSIX_ACL_ACCESS_XATTR, 0);
        if (ret)
            gf_log(this->name, GF_LOG_WARNING, "failed to set key %s",
                   POSIX_ACL_ACCESS_XATTR);

        ret = dict_set_int8(dict, POSIX_ACL_DEFAULT_XATTR, 0);
        if (ret)
            gf_log(this->name, GF_LOG_WARNING, "failed to set key %s",
                   POSIX_ACL_DEFAULT_XATTR);
    }

    STACK_WIND(frame, posix_acl_readdirp_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->readdirp, fd, size, offset, dict);

    if (alloc_dict)
        dict_unref(alloc_dict);
    return 0;
red:
    STACK_UNWIND_STRICT(readdirp, frame, -1, EACCES, NULL, NULL);

    return 0;
}

int
setattr_scrutiny(call_frame_t *frame, inode_t *inode, struct iatt *buf,
                 int valid)
{
    struct posix_acl_ctx *ctx = NULL;

    if (frame_is_super_user(frame))
        return 0;

    ctx = posix_acl_ctx_get(inode, frame->this);
    if (!ctx)
        return EIO;

    if (valid & GF_SET_ATTR_MODE) {
        /*
               The effective UID of the calling process must match the  owner of
           the file,  or  the  process  must  be  privileged
        */
        if (!frame_is_user(frame, ctx->uid))
            return EPERM;
        /*
               If the calling process is not privileged  (Linux:  does  not have
           the CAP_FSETID  capability),  and  the group of the file does not
           match the effective group ID of the process or one  of  its
           supplementary  group IDs,  the  S_ISGID  bit  will be turned off, but
           this will not cause an error to be returned.

        */
        if (!frame_in_group(frame, ctx->gid))
            buf->ia_prot.sgid = 0;
    }

    if (valid & (GF_SET_ATTR_ATIME | GF_SET_ATTR_MTIME)) {
        /*
               Changing timestamps is permitted when: either the process has
           appropri? ate  privileges,  or  the  effective  user ID equals the
           user ID of the file, or times is NULL and the process has  write
           permission  for  the file.
        */
        if (!frame_is_user(frame, ctx->uid) &&
            !acl_permits(frame, inode, POSIX_ACL_WRITE))
            return EACCES;
    }

    if (valid & GF_SET_ATTR_UID) {
        if ((!frame_is_super_user(frame)) && (buf->ia_uid != ctx->uid))
            return EPERM;
    }

    if (valid & GF_SET_ATTR_GID) {
        if (!frame_is_user(frame, ctx->uid))
            return EPERM;
        if (!frame_in_group(frame, buf->ia_gid))
            return EPERM;
    }

    return 0;
}

int
posix_acl_setattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                      int op_ret, int op_errno, struct iatt *prebuf,
                      struct iatt *postbuf, dict_t *xdata)
{
    inode_t *inode = NULL;

    inode = frame->local;
    frame->local = NULL;

    if (op_ret != 0)
        goto unwind;

    posix_acl_ctx_update(inode, this, postbuf, GF_FOP_SETATTR);

unwind:
    STACK_UNWIND_STRICT(setattr, frame, op_ret, op_errno, prebuf, postbuf,
                        xdata);
    return 0;
}

int
posix_acl_setattr(call_frame_t *frame, xlator_t *this, loc_t *loc,
                  struct iatt *buf, int valid, dict_t *xdata)
{
    int op_errno = 0;

    op_errno = setattr_scrutiny(frame, loc->inode, buf, valid);

    if (op_errno)
        goto red;

    frame->local = loc->inode;

    STACK_WIND(frame, posix_acl_setattr_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->setattr, loc, buf, valid, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(setattr, frame, -1, op_errno, NULL, NULL, NULL);

    return 0;
}

int
posix_acl_fsetattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                       int op_ret, int op_errno, struct iatt *prebuf,
                       struct iatt *postbuf, dict_t *xdata)
{
    inode_t *inode = NULL;

    inode = frame->local;
    frame->local = NULL;

    if (op_ret != 0)
        goto unwind;

    posix_acl_ctx_update(inode, this, postbuf, GF_FOP_FSETATTR);

unwind:
    STACK_UNWIND_STRICT(fsetattr, frame, op_ret, op_errno, prebuf, postbuf,
                        xdata);
    return 0;
}

int
posix_acl_fsetattr(call_frame_t *frame, xlator_t *this, fd_t *fd,
                   struct iatt *buf, int valid, dict_t *xdata)
{
    int op_errno = 0;

    op_errno = setattr_scrutiny(frame, fd->inode, buf, valid);

    if (op_errno)
        goto red;

    frame->local = fd->inode;

    STACK_WIND(frame, posix_acl_fsetattr_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->fsetattr, fd, buf, valid, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(fsetattr, frame, -1, EACCES, NULL, NULL, NULL);

    return 0;
}

int
setxattr_scrutiny(call_frame_t *frame, inode_t *inode, dict_t *xattr)
{
    struct posix_acl_ctx *ctx = NULL;
    int found = 0;

    if (frame_is_super_user(frame))
        return 0;

    ctx = posix_acl_ctx_get(inode, frame->this);
    if (!ctx)
        return EIO;

    if (dict_get(xattr, POSIX_ACL_ACCESS_XATTR) ||
        dict_get(xattr, POSIX_ACL_DEFAULT_XATTR) ||
        dict_get(xattr, GF_POSIX_ACL_ACCESS) ||
        dict_get(xattr, GF_POSIX_ACL_DEFAULT)) {
        found = 1;
        if (!frame_is_user(frame, ctx->uid))
            return EPERM;
    }

    if (!found && !acl_permits(frame, inode, POSIX_ACL_WRITE))
        return EACCES;

    return 0;
}

struct posix_acl *
posix_acl_xattr_update(xlator_t *this, inode_t *inode, dict_t *xattr,
                       char *name, struct posix_acl *old)
{
    struct posix_acl *acl = NULL;
    data_t *data = NULL;

    data = dict_get(xattr, name);
    if (data) {
        acl = posix_acl_from_xattr(this, data->data, data->len);
    }

    if (!acl && old)
        acl = posix_acl_ref(this, old);

    return acl;
}

int
posix_acl_setxattr_update(xlator_t *this, inode_t *inode, dict_t *xattr)
{
    struct posix_acl *acl_access = NULL;
    struct posix_acl *acl_default = NULL;
    struct posix_acl *old_access = NULL;
    struct posix_acl *old_default = NULL;
    struct posix_acl_ctx *ctx = NULL;
    int ret = 0;

    ctx = posix_acl_ctx_get(inode, this);
    if (!ctx)
        return -1;

    ret = posix_acl_get(inode, this, &old_access, &old_default);

    acl_access = posix_acl_xattr_update(this, inode, xattr,
                                        POSIX_ACL_ACCESS_XATTR, old_access);

    acl_default = posix_acl_xattr_update(this, inode, xattr,
                                         POSIX_ACL_DEFAULT_XATTR, old_default);

    ret = posix_acl_set(inode, this, acl_access, acl_default);

    if (acl_access && acl_access != old_access) {
        posix_acl_access_set_mode(acl_access, ctx);
    }

    if (acl_access)
        posix_acl_unref(this, acl_access);
    if (acl_default)
        posix_acl_unref(this, acl_default);
    if (old_access)
        posix_acl_unref(this, old_access);
    if (old_default)
        posix_acl_unref(this, old_default);

    return ret;
}

/* *
 * Posix acl can be set using other xattr such as GF_POSIX_ACL_ACCESS/
 * GF_POSIX_ACL_DEFAULT  which requires to update the context of
 * access-control translator
 */
int
handling_other_acl_related_xattr(xlator_t *this, inode_t *inode, dict_t *xattr)
{
    struct posix_acl *acl = NULL;
    struct posix_acl_ctx *ctx = NULL;
    data_t *data = NULL;
    int ret = 0;

    if (!this || !xattr || !inode)
        goto out;

    data = dict_get(xattr, POSIX_ACL_ACCESS_XATTR);
    if (data) {
        acl = posix_acl_from_xattr(this, data->data, data->len);
        if (!acl) {
            ret = -1;
            goto out;
        }

        ret = posix_acl_set_specific(inode, this, acl, _gf_true);
        if (ret)
            goto out;

        ctx = posix_acl_ctx_get(inode, this);
        if (!ctx) {
            ret = -1;
            goto out;
        }

        posix_acl_access_set_mode(acl, ctx);
        posix_acl_unref(this, acl);
        acl = NULL;
    }

    data = dict_get(xattr, POSIX_ACL_DEFAULT_XATTR);
    if (data) {
        acl = posix_acl_from_xattr(this, data->data, data->len);
        if (!acl) {
            ret = -1;
            goto out;
        }

        ret = posix_acl_set_specific(inode, this, acl, _gf_false);
    }

out:
    if (acl)
        posix_acl_unref(this, acl);

    return ret;
}
int
posix_acl_setxattr_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
                       int op_ret, int op_errno, dict_t *xdata)
{
    /*
     * Update the context of posix_acl_translator, if any of
     * POSIX_ACL_*_XATTR set in the call back
     */
    handling_other_acl_related_xattr(this, (inode_t *)cookie, xdata);

    STACK_UNWIND_STRICT(setxattr, frame, op_ret, op_errno, xdata);

    return 0;
}

int
posix_acl_setxattr(call_frame_t *frame, xlator_t *this, loc_t *loc,
                   dict_t *xattr, int flags, dict_t *xdata)
{
    int op_errno = 0;

    op_errno = setxattr_scrutiny(frame, loc->inode, xattr);

    if (op_errno != 0)
        goto red;

    if (dict_get(xattr, POSIX_ACL_ACCESS_XATTR) ||
        dict_get(xattr, POSIX_ACL_DEFAULT_XATTR))
        posix_acl_setxattr_update(this, loc->inode, xattr);

    /*
     * inode is required in call back function to update the context
     * this translator
     */
    STACK_WIND_COOKIE(frame, posix_acl_setxattr_cbk, loc->inode,
                      FIRST_CHILD(this), FIRST_CHILD(this)->fops->setxattr, loc,
                      xattr, flags, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(setxattr, frame, -1, op_errno, NULL);

    return 0;
}

int
posix_acl_fsetxattr(call_frame_t *frame, xlator_t *this, fd_t *fd,
                    dict_t *xattr, int flags, dict_t *xdata)
{
    int op_errno = 0;

    op_errno = setxattr_scrutiny(frame, fd->inode, xattr);

    if (op_errno != 0)
        goto red;

    if (dict_get(xattr, POSIX_ACL_ACCESS_XATTR) ||
        dict_get(xattr, POSIX_ACL_DEFAULT_XATTR))
        posix_acl_setxattr_update(this, fd->inode, xattr);

    STACK_WIND(frame, default_fsetxattr_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->fsetxattr, fd, xattr, flags, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(fsetxattr, frame, -1, op_errno, NULL);

    return 0;
}

int
posix_acl_getxattr(call_frame_t *frame, xlator_t *this, loc_t *loc,
                   const char *name, dict_t *xdata)
{
    if (allowed_xattr(name))
        goto green;

    if (acl_permits(frame, loc->inode, POSIX_ACL_READ))
        goto green;
    else
        goto red;

green:
    STACK_WIND(frame, default_getxattr_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->getxattr, loc, name, xdata);
    return 0;

red:
    STACK_UNWIND_STRICT(getxattr, frame, -1, EACCES, NULL, NULL);

    return 0;
}

int
posix_acl_fgetxattr(call_frame_t *frame, xlator_t *this, fd_t *fd,
                    const char *name, dict_t *xdata)
{
    if (allowed_xattr(name))
        goto green;

    if (acl_permits(frame, fd->inode, POSIX_ACL_READ))
        goto green;
    else
        goto red;
green:
    STACK_WIND(frame, default_fgetxattr_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->fgetxattr, fd, name, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(fgetxattr, frame, -1, EACCES, NULL, NULL);

    return 0;
}

int
posix_acl_removexattr(call_frame_t *frame, xlator_t *this, loc_t *loc,
                      const char *name, dict_t *xdata)
{
    struct posix_acl_ctx *ctx = NULL;
    int op_errno = EACCES;

    if (frame_is_super_user(frame))
        goto green;

    ctx = posix_acl_ctx_get(loc->inode, this);
    if (!ctx) {
        op_errno = EIO;
        goto red;
    }

    if (allowed_xattr(name)) {
        if (!frame_is_user(frame, ctx->uid)) {
            op_errno = EPERM;
            goto red;
        }
    }

    if (acl_permits(frame, loc->inode, POSIX_ACL_WRITE))
        goto green;
    else
        goto red;
green:
    STACK_WIND(frame, default_removexattr_cbk, FIRST_CHILD(this),
               FIRST_CHILD(this)->fops->removexattr, loc, name, xdata);
    return 0;
red:
    STACK_UNWIND_STRICT(removexattr, frame, -1, op_errno, NULL);

    return 0;
}

int
posix_acl_forget(xlator_t *this, inode_t *inode)
{
    struct posix_acl_ctx *ctx = NULL;

    ctx = posix_acl_ctx_get(inode, this);
    if (!ctx)
        goto out;

    if (ctx->acl_access)
        posix_acl_unref(this, ctx->acl_access);

    if (ctx->acl_default)
        posix_acl_unref(this, ctx->acl_default);

    GF_FREE(ctx);
out:
    return 0;
}

int
reconfigure(xlator_t *this, dict_t *options)
{
    struct posix_acl_conf *conf = NULL;

    conf = this->private;

    GF_OPTION_RECONF("super-uid", conf->super_uid, options, uint32, err);

    return 0;
err:
    return -1;
}

int
init(xlator_t *this)
{
    struct posix_acl_conf *conf = NULL;
    struct posix_acl *minacl = NULL;
    struct posix_ace *minace = NULL;

    conf = GF_CALLOC(1, sizeof(*conf), gf_posix_acl_mt_conf_t);
    if (!conf) {
        gf_log(this->name, GF_LOG_ERROR, "out of memory");
        return -1;
    }

    LOCK_INIT(&conf->acl_lock);

    this->private = conf;

    minacl = posix_acl_new(this, 3);
    if (!minacl)
        return -1;

    minace = minacl->entries;
    minace[0].tag = POSIX_ACL_USER_OBJ;
    minace[1].tag = POSIX_ACL_GROUP_OBJ;
    minace[2].tag = POSIX_ACL_OTHER;

    conf->minimal_acl = minacl;

    GF_OPTION_INIT("super-uid", conf->super_uid, uint32, err);

    return 0;
err:
    return -1;
}

void
fini(xlator_t *this)
{
    struct posix_acl_conf *conf = NULL;
    struct posix_acl *minacl = NULL;

    conf = this->private;
    if (!conf)
        return;
    this->private = NULL;

    minacl = conf->minimal_acl;

    LOCK(&conf->acl_lock);
    {
        conf->minimal_acl = NULL;
    }
    UNLOCK(&conf->acl_lock);

    LOCK_DESTROY(&conf->acl_lock);

    GF_FREE(minacl);

    GF_FREE(conf);

    return;
}

struct xlator_fops fops = {
    .lookup = posix_acl_lookup,
    .open = posix_acl_open,
#if FD_MODE_CHECK_IS_IMPLEMENTED
    .readv = posix_acl_readv,
    .writev = posix_acl_writev,
    .ftruncate = posix_acl_ftruncate,
    .fsetattr = posix_acl_fsetattr,
    .fsetxattr = posix_acl_fsetxattr,
    .fgetxattr = posix_acl_fgetxattr,
#endif
    .access = posix_acl_access,
    .truncate = posix_acl_truncate,
    .mkdir = posix_acl_mkdir,
    .mknod = posix_acl_mknod,
    .create = posix_acl_create,
    .symlink = posix_acl_symlink,
    .unlink = posix_acl_unlink,
    .rmdir = posix_acl_rmdir,
    .rename = posix_acl_rename,
    .link = posix_acl_link,
    .opendir = posix_acl_opendir,
    .readdir = posix_acl_readdir,
    .readdirp = posix_acl_readdirp,
    .setattr = posix_acl_setattr,
    .setxattr = posix_acl_setxattr,
    .getxattr = posix_acl_getxattr,
    .removexattr = posix_acl_removexattr,
};

struct xlator_cbks cbks = {.forget = posix_acl_forget};

struct volume_options options[] = {
    {
        .key = {"super-uid"},
        .type = GF_OPTION_TYPE_INT,
        .default_value = "0",
        .description = "UID to be treated as super user's id instead of 0",
    },
    {.key = {NULL}},
};

xlator_api_t xlator_api = {
    .init = init,
    .fini = fini,
    .reconfigure = reconfigure,
    .mem_acct_init = mem_acct_init,
    .op_version = {1}, /* Present from the initial version */
    .fops = &fops,
    .cbks = &cbks,
    .options = options,
    .identifier = "access-control",
    .category = GF_MAINTAINED,
};
