/*
  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 "xlator.h"
#include "glusterfs.h"

#include "posix-acl.h"
#include "posix-acl-xattr.h"
#include "posix-acl-mem-types.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 + 1);

        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;
}


int
whitelisted_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;
        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;

        par = posix_acl_ctx_get (parent, frame->this);
        ctx = posix_acl_ctx_get (inode, frame->this);

        if (frame_is_super_user (frame))
                return 1;

        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 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;

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

        if (frame_is_super_user (frame))
                goto green;

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

        ace = acl->entries;

        if (acl->count > POSIX_ACL_MINIMAL_ACE_COUNT)
                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;
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)
{
        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);

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

        ret = inode_ctx_put (inode, this, UINT64 (ctx));

        return ctx;
}


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;

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

        if (!refcnt)
                posix_acl_destroy (this, acl);
}


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_get (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");
                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");
                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)
{
        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;

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

        LOCK(&inode->lock);
        {
                ctx->uid   = buf->ia_uid;
                ctx->gid   = buf->ia_gid;
                ctx->perm  = st_mode_from_ia (buf->ia_prot, buf->ia_type);

		acl = ctx->acl_access;
		if (!acl || !(acl->count > POSIX_ACL_MINIMAL_ACE_COUNT))
			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;
        data_t               *data = NULL;
        int                   ret = 0;
        dict_t               *my_xattr = NULL;

        if (op_ret != 0)
                goto unwind;

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

        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);

        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_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)
{
        STACK_UNWIND_STRICT (truncate, frame, op_ret, op_errno, prebuf,
                             postbuf, xdata);

        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, posix_acl_truncate_cbk,
                    FIRST_CHILD(this), FIRST_CHILD(this)->fops->truncate,
                    loc, off, xdata);
        return 0;
}


int
posix_acl_open_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                    int op_ret, int op_errno, fd_t *fd, dict_t *xdata)
{
        STACK_UNWIND_STRICT (open, frame, op_ret, op_errno, fd, 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:
        case O_APPEND:
        case O_TRUNC:
                perm = POSIX_ACL_WRITE;
                break;
        case O_RDWR:
                perm = POSIX_ACL_READ|POSIX_ACL_WRITE;
                break;
        }

        if (acl_permits (frame, loc->inode, perm))
                goto green;
        else
                goto red;
green:
        STACK_WIND (frame, posix_acl_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, xdata);
        return 0;
}


int
posix_acl_readv_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                     int op_ret, int op_errno, struct iovec *vector,
                     int count, struct iatt *stbuf, struct iobref *iobref,
                     dict_t *xdata)
{
        STACK_UNWIND_STRICT (readv, frame, op_ret, op_errno, vector, count,
                             stbuf, iobref, xdata);
        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, posix_acl_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, xdata);
        return 0;
}


int
posix_acl_writev_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)
{
        STACK_UNWIND_STRICT (writev, frame, op_ret, op_errno,
                             prebuf, postbuf, xdata);
        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, posix_acl_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, xdata);
        return 0;
}



int
posix_acl_ftruncate_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)
{
        STACK_UNWIND_STRICT (ftruncate, frame, op_ret, op_errno,
                             prebuf, postbuf, xdata);
        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, posix_acl_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, xdata);
        return 0;
}


int
posix_acl_opendir_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                       int op_ret, int op_errno, fd_t *fd, dict_t *xdata)
{
        STACK_UNWIND_STRICT (opendir, frame, op_ret, op_errno, fd, xdata);

        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, posix_acl_opendir_cbk,
                    FIRST_CHILD(this), FIRST_CHILD(this)->fops->opendir,
                    loc, fd, xdata);
        return 0;
red:
        STACK_UNWIND_STRICT (opendir, frame, -1, EACCES, NULL, xdata);
        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);

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);

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);

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);

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, xdata);
        return 0;
}


int
posix_acl_unlink_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                      int op_ret, int op_errno, struct iatt *preparent,
                      struct iatt *postparent, dict_t *xdata)
{
        if (op_ret != 0)
                goto unwind;
unwind:
        STACK_UNWIND_STRICT (unlink, frame, op_ret, op_errno,
                             preparent, postparent, xdata);
        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, posix_acl_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, xdata);
        return 0;
}


int
posix_acl_rmdir_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                     int op_ret, int op_errno,
                     struct iatt *preparent, struct iatt *postparent, dict_t *xdata)
{
        if (op_ret != 0)
                goto unwind;
unwind:
        STACK_UNWIND_STRICT (rmdir, frame, op_ret, op_errno,
                             preparent, postparent, xdata);
        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, posix_acl_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, xdata);
        return 0;
}


int
posix_acl_rename_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                      int op_ret, int op_errno, struct iatt *buf,
                      struct iatt *preoldparent, struct iatt *postoldparent,
                      struct iatt *prenewparent, struct iatt *postnewparent,
                      dict_t *xdata)
{
        if (op_ret != 0)
                goto unwind;
unwind:
        STACK_UNWIND_STRICT (rename, frame, op_ret, op_errno, buf,
                             preoldparent, postoldparent,
                             prenewparent, postnewparent, xdata);
        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, posix_acl_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_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;
unwind:
        STACK_UNWIND_STRICT (link, frame, op_ret, op_errno, inode, buf,
                             preparent, postparent, xdata);
        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, posix_acl_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, xdata);

        return 0;
}


int
posix_acl_readdir_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                       int op_ret, int op_errno, gf_dirent_t *entries,
                       dict_t *xdata)
{
        if (op_ret != 0)
                goto unwind;
unwind:
        STACK_UNWIND_STRICT (readdir, frame, op_ret, op_errno, entries, xdata);
        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, posix_acl_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, xdata);

        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;
        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;

                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);

                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);

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, xdata);

        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);

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, xdata);

        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)) {
                found = 1;
                if (!frame_is_user (frame, ctx->uid))
                        return EPERM;
        }

        if (dict_get (xattr, POSIX_ACL_DEFAULT_XATTR)) {
                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;
}


int
posix_acl_setxattr_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                        int op_ret, int op_errno, dict_t *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;

        posix_acl_setxattr_update (this, loc->inode, xattr);

        STACK_WIND (frame, posix_acl_setxattr_cbk,
                    FIRST_CHILD(this), FIRST_CHILD(this)->fops->setxattr,
                    loc, xattr, flags, xdata);
        return 0;
red:
        STACK_UNWIND_STRICT (setxattr, frame, -1, op_errno, xdata);

        return 0;
}


int
posix_acl_fsetxattr_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                         int op_ret, int op_errno, dict_t *xdata)
{
        STACK_UNWIND_STRICT (fsetxattr, frame, op_ret, op_errno, xdata);

        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;

        posix_acl_setxattr_update (this, fd->inode, xattr);

        STACK_WIND (frame, posix_acl_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, xdata);

        return 0;
}


int
posix_acl_getxattr_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                        int op_ret, int op_errno, dict_t *xattr, dict_t *xdata)
{
        STACK_UNWIND_STRICT (getxattr, frame, op_ret, op_errno, xattr, xdata);

        return 0;
}


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

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

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

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

        return 0;
}


int
posix_acl_fgetxattr_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                         int op_ret, int op_errno, dict_t *xattr, dict_t *xdata)
{
        STACK_UNWIND_STRICT (fgetxattr, frame, op_ret, op_errno, xattr, xdata);

        return 0;
}


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

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

        return 0;
}


int
posix_acl_removexattr_cbk (call_frame_t *frame, void *cookie, xlator_t *this,
                           int op_ret, int op_errno, dict_t *xdata)
{
        STACK_UNWIND_STRICT (removexattr, frame, op_ret, op_errno, xdata);

        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 (whitelisted_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, posix_acl_removexattr_cbk,
                    FIRST_CHILD(this), FIRST_CHILD(this)->fops->removexattr,
                    loc, name, xdata);
        return 0;
red:
        STACK_UNWIND_STRICT (removexattr, frame, -1, op_errno, xdata);

        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;
}


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

        conf = this->private;
        if (!conf)
                return 0;
        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 0;
}


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} },
};
