aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorNeil Brown <neilb@cse.unsw.edu.au>2004-08-22 23:01:32 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2004-08-22 23:01:32 -0700
commitd99ad02c5b21eb0678e2197911f2cf26be5ce126 (patch)
treec76495baeab8003c9ab4687f9ee97f9a6f61e5cb /fs
parent770d5607d037080907204dede7c920c35823f492 (diff)
downloadhistory-d99ad02c5b21eb0678e2197911f2cf26be5ce126.tar.gz
[PATCH] nfsd: POSIX<->NFSv4 acl translation for nfsd
Code to translate between Linux's POSIX ACLs and NFSv4 ACLs. Since NFSv4 ACLs are fundamentally richer, we are able to translate any POSIX ACL to NFSv4, but can only map NFSv4 ACLs that follow a certain format; see http://www.citi.umich.edu/u/marius/draft-eriksen-nfsv4-acl-02.txt for details of the mapping. Signed-off-by: J. Bruce Fields <bfields@citi.umich.edu> Signed-off-by: Neil Brown <neilb@cse.unsw.edu.au> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'fs')
-rw-r--r--fs/Kconfig2
-rw-r--r--fs/nfsd/nfs4acl.c771
2 files changed, 772 insertions, 1 deletions
diff --git a/fs/Kconfig b/fs/Kconfig
index 06e902ab6b5da6..347525531ab306 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -289,7 +289,7 @@ config FS_POSIX_ACL
# Never use this symbol for ifdefs.
#
bool
- depends on EXT2_FS_POSIX_ACL || EXT3_FS_POSIX_ACL || JFS_POSIX_ACL || REISERFS_FS_POSIX_ACL
+ depends on EXT2_FS_POSIX_ACL || EXT3_FS_POSIX_ACL || JFS_POSIX_ACL || REISERFS_FS_POSIX_ACL || NFSD_V4
default y
config XFS_FS
diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c
index 9328106a457fcd..172393942ac0c9 100644
--- a/fs/nfsd/nfs4acl.c
+++ b/fs/nfsd/nfs4acl.c
@@ -43,9 +43,780 @@
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/nfs_fs.h>
+#include <linux/posix_acl.h>
#include <linux/nfs4.h>
#include <linux/nfs4_acl.h>
+
+/* mode bit translations: */
+#define NFS4_READ_MODE (NFS4_ACE_READ_DATA | NFS4_ACE_READ_NAMED_ATTRS)
+#define NFS4_WRITE_MODE (NFS4_ACE_WRITE_DATA | NFS4_ACE_WRITE_NAMED_ATTRS | NFS4_ACE_APPEND_DATA)
+#define NFS4_EXECUTE_MODE NFS4_ACE_EXECUTE
+#define NFS4_ANYONE_MODE (NFS4_ACE_READ_ATTRIBUTES | NFS4_ACE_READ_ACL | NFS4_ACE_SYNCHRONIZE)
+#define NFS4_OWNER_MODE (NFS4_ACE_WRITE_ATTRIBUTES | NFS4_ACE_WRITE_ACL)
+
+/* flags used to simulate posix default ACLs */
+#define NFS4_INHERITANCE_FLAGS (NFS4_ACE_FILE_INHERIT_ACE \
+ | NFS4_ACE_DIRECTORY_INHERIT_ACE | NFS4_ACE_INHERIT_ONLY_ACE)
+
+#define MASK_EQUAL(mask1, mask2) \
+ ( ((mask1) & NFS4_ACE_MASK_ALL) == ((mask2) & NFS4_ACE_MASK_ALL) )
+
+static u32
+mask_from_posix(unsigned short perm, unsigned int flags)
+{
+ int mask = NFS4_ANYONE_MODE;
+
+ if (flags & NFS4_ACL_OWNER)
+ mask |= NFS4_OWNER_MODE;
+ if (perm & ACL_READ)
+ mask |= NFS4_READ_MODE;
+ if (perm & ACL_WRITE)
+ mask |= NFS4_WRITE_MODE;
+ if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
+ mask |= NFS4_ACE_DELETE_CHILD;
+ if (perm & ACL_EXECUTE)
+ mask |= NFS4_EXECUTE_MODE;
+ return mask;
+}
+
+static u32
+deny_mask(u32 allow_mask, unsigned int flags)
+{
+ u32 ret = ~allow_mask & ~NFS4_ACE_DELETE;
+ if (!(flags & NFS4_ACL_DIR))
+ ret &= ~NFS4_ACE_DELETE_CHILD;
+ return ret;
+}
+
+static int
+mode_from_nfs4(u32 perm, unsigned short *mode, unsigned int flags)
+{
+ u32 ignore = 0;
+
+ if (!(flags & NFS4_ACL_DIR))
+ ignore |= NFS4_ACE_DELETE_CHILD; /* ignore it */
+ perm |= ignore;
+ *mode = 0;
+ if ((perm & NFS4_READ_MODE) == NFS4_READ_MODE)
+ *mode |= ACL_READ;
+ if ((perm & NFS4_WRITE_MODE) == NFS4_WRITE_MODE)
+ *mode |= ACL_WRITE;
+ if ((perm & NFS4_EXECUTE_MODE) == NFS4_EXECUTE_MODE)
+ *mode |= ACL_EXECUTE;
+ if (!MASK_EQUAL(perm, ignore|mask_from_posix(*mode, flags)))
+ return -EINVAL;
+ return 0;
+}
+
+struct ace_container {
+ struct nfs4_ace *ace;
+ struct list_head ace_l;
+};
+
+static short ace2type(struct nfs4_ace *);
+static int _posix_to_nfsv4_one(struct posix_acl *, struct nfs4_acl *, unsigned int);
+static struct posix_acl *_nfsv4_to_posix_one(struct nfs4_acl *, unsigned int);
+int nfs4_acl_add_ace(struct nfs4_acl *, u32, u32, u32, int, uid_t);
+int nfs4_acl_split(struct nfs4_acl *, struct nfs4_acl *);
+
+struct nfs4_acl *
+nfs4_acl_posix_to_nfsv4(struct posix_acl *pacl, struct posix_acl *dpacl,
+ unsigned int flags)
+{
+ struct nfs4_acl *acl;
+ int error = -EINVAL;
+
+ if ((pacl != NULL &&
+ (posix_acl_valid(pacl) < 0 || pacl->a_count == 0)) ||
+ (dpacl != NULL &&
+ (posix_acl_valid(dpacl) < 0 || dpacl->a_count == 0)))
+ goto out_err;
+
+ acl = nfs4_acl_new();
+ if (acl == NULL) {
+ error = -ENOMEM;
+ goto out_err;
+ }
+
+ if (pacl != NULL) {
+ error = _posix_to_nfsv4_one(pacl, acl,
+ flags & ~NFS4_ACL_TYPE_DEFAULT);
+ if (error < 0)
+ goto out_acl;
+ }
+
+ if (dpacl != NULL) {
+ error = _posix_to_nfsv4_one(dpacl, acl,
+ flags | NFS4_ACL_TYPE_DEFAULT);
+ if (error < 0)
+ goto out_acl;
+ }
+
+ return acl;
+
+out_acl:
+ nfs4_acl_free(acl);
+out_err:
+ acl = ERR_PTR(error);
+
+ return acl;
+}
+
+static int
+nfs4_acl_add_pair(struct nfs4_acl *acl, int eflag, u32 mask, int whotype,
+ uid_t owner, unsigned int flags)
+{
+ int error;
+
+ error = nfs4_acl_add_ace(acl, NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE,
+ eflag, mask, whotype, owner);
+ if (error < 0)
+ return error;
+ error = nfs4_acl_add_ace(acl, NFS4_ACE_ACCESS_DENIED_ACE_TYPE,
+ eflag, deny_mask(mask, flags), whotype, owner);
+ return error;
+}
+
+/* We assume the acl has been verified with posix_acl_valid. */
+static int
+_posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
+ unsigned int flags)
+{
+ struct posix_acl_entry *pa, *pe, *group_owner_entry;
+ int error = -EINVAL;
+ u32 mask, mask_mask;
+ int eflag = ((flags & NFS4_ACL_TYPE_DEFAULT) ?
+ NFS4_INHERITANCE_FLAGS : 0);
+
+ BUG_ON(pacl->a_count < 3);
+ pe = pacl->a_entries + pacl->a_count;
+ pa = pe - 2; /* if mask entry exists, it's second from the last. */
+ if (pa->e_tag == ACL_MASK)
+ mask_mask = deny_mask(mask_from_posix(pa->e_perm, flags), flags);
+ else
+ mask_mask = 0;
+
+ pa = pacl->a_entries;
+ BUG_ON(pa->e_tag != ACL_USER_OBJ);
+ mask = mask_from_posix(pa->e_perm, flags | NFS4_ACL_OWNER);
+ error = nfs4_acl_add_pair(acl, eflag, mask, NFS4_ACL_WHO_OWNER, 0, flags);
+ if (error < 0)
+ goto out;
+ pa++;
+
+ while (pa->e_tag == ACL_USER) {
+ mask = mask_from_posix(pa->e_perm, flags);
+ error = nfs4_acl_add_ace(acl, NFS4_ACE_ACCESS_DENIED_ACE_TYPE,
+ eflag, mask_mask, NFS4_ACL_WHO_NAMED, pa->e_id);
+ if (error < 0)
+ goto out;
+
+
+ error = nfs4_acl_add_pair(acl, eflag, mask,
+ NFS4_ACL_WHO_NAMED, pa->e_id, flags);
+ if (error < 0)
+ goto out;
+ pa++;
+ }
+
+ /* In the case of groups, we apply allow ACEs first, then deny ACEs,
+ * since a user can be in more than one group. */
+
+ /* allow ACEs */
+
+ if (pacl->a_count > 3) {
+ BUG_ON(pa->e_tag != ACL_GROUP_OBJ);
+ error = nfs4_acl_add_ace(acl, NFS4_ACE_ACCESS_DENIED_ACE_TYPE,
+ NFS4_ACE_IDENTIFIER_GROUP | eflag, mask_mask,
+ NFS4_ACL_WHO_GROUP, 0);
+ if (error < 0)
+ goto out;
+ }
+ group_owner_entry = pa;
+ mask = mask_from_posix(pa->e_perm, flags);
+ error = nfs4_acl_add_ace(acl, NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE,
+ NFS4_ACE_IDENTIFIER_GROUP | eflag, mask,
+ NFS4_ACL_WHO_GROUP, 0);
+ if (error < 0)
+ goto out;
+ pa++;
+
+ while (pa->e_tag == ACL_GROUP) {
+ mask = mask_from_posix(pa->e_perm, flags);
+ error = nfs4_acl_add_ace(acl, NFS4_ACE_ACCESS_DENIED_ACE_TYPE,
+ NFS4_ACE_IDENTIFIER_GROUP | eflag, mask_mask,
+ NFS4_ACL_WHO_NAMED, pa->e_id);
+ if (error < 0)
+ goto out;
+
+ error = nfs4_acl_add_ace(acl, NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE,
+ NFS4_ACE_IDENTIFIER_GROUP | eflag, mask,
+ NFS4_ACL_WHO_NAMED, pa->e_id);
+ if (error < 0)
+ goto out;
+ pa++;
+ }
+
+ /* deny ACEs */
+
+ pa = group_owner_entry;
+ mask = mask_from_posix(pa->e_perm, flags);
+ error = nfs4_acl_add_ace(acl, NFS4_ACE_ACCESS_DENIED_ACE_TYPE,
+ NFS4_ACE_IDENTIFIER_GROUP | eflag,
+ deny_mask(mask, flags), NFS4_ACL_WHO_GROUP, 0);
+ if (error < 0)
+ goto out;
+ pa++;
+ while (pa->e_tag == ACL_GROUP) {
+ mask = mask_from_posix(pa->e_perm, flags);
+ error = nfs4_acl_add_ace(acl, NFS4_ACE_ACCESS_DENIED_ACE_TYPE,
+ NFS4_ACE_IDENTIFIER_GROUP | eflag,
+ deny_mask(mask, flags), NFS4_ACL_WHO_NAMED, pa->e_id);
+ if (error < 0)
+ goto out;
+ pa++;
+ }
+
+ if (pa->e_tag == ACL_MASK)
+ pa++;
+ BUG_ON(pa->e_tag != ACL_OTHER);
+ mask = mask_from_posix(pa->e_perm, flags);
+ error = nfs4_acl_add_pair(acl, eflag, mask, NFS4_ACL_WHO_EVERYONE, 0, flags);
+
+out:
+ return error;
+}
+
+static void
+sort_pacl_range(struct posix_acl *pacl, int start, int end) {
+ int sorted = 0, i;
+ struct posix_acl_entry tmp;
+
+ /* We just do a bubble sort; easy to do in place, and we're not
+ * expecting acl's to be long enough to justify anything more. */
+ while (!sorted) {
+ sorted = 1;
+ for (i = start; i < end; i++) {
+ if (pacl->a_entries[i].e_id
+ > pacl->a_entries[i+1].e_id) {
+ sorted = 0;
+ tmp = pacl->a_entries[i];
+ pacl->a_entries[i] = pacl->a_entries[i+1];
+ pacl->a_entries[i+1] = tmp;
+ }
+ }
+ }
+}
+
+static void
+sort_pacl(struct posix_acl *pacl)
+{
+ /* posix_acl_valid requires that users and groups be in order
+ * by uid/gid. */
+ int i, j;
+
+ if (pacl->a_count <= 4)
+ return; /* no users or groups */
+ i = 1;
+ while (pacl->a_entries[i].e_tag == ACL_USER)
+ i++;
+ sort_pacl_range(pacl, 1, i-1);
+
+ BUG_ON(pacl->a_entries[i].e_tag != ACL_GROUP_OBJ);
+ j = i++;
+ while (pacl->a_entries[j].e_tag == ACL_GROUP)
+ j++;
+ sort_pacl_range(pacl, i, j-1);
+ return;
+}
+
+static int
+write_pace(struct nfs4_ace *ace, struct posix_acl *pacl,
+ struct posix_acl_entry **pace, short tag, unsigned int flags)
+{
+ struct posix_acl_entry *this = *pace;
+
+ if (*pace == pacl->a_entries + pacl->a_count)
+ return -EINVAL; /* fell off the end */
+ (*pace)++;
+ this->e_tag = tag;
+ if (tag == ACL_USER_OBJ)
+ flags |= NFS4_ACL_OWNER;
+ if (mode_from_nfs4(ace->access_mask, &this->e_perm, flags))
+ return -EINVAL;
+ this->e_id = (tag == ACL_USER || tag == ACL_GROUP ?
+ ace->who : ACL_UNDEFINED_ID);
+ return 0;
+}
+
+static struct nfs4_ace *
+get_next_v4_ace(struct list_head **p, struct list_head *head)
+{
+ struct nfs4_ace *ace;
+
+ *p = (*p)->next;
+ if (*p == head)
+ return NULL;
+ ace = list_entry(*p, struct nfs4_ace, l_ace);
+
+ return ace;
+}
+
+int
+nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl, struct posix_acl **pacl,
+ struct posix_acl **dpacl, unsigned int flags)
+{
+ struct nfs4_acl *dacl;
+ int error = -ENOMEM;
+
+ *pacl = NULL;
+ *dpacl = NULL;
+
+ dacl = nfs4_acl_new();
+ if (dacl == NULL)
+ goto out;
+
+ error = nfs4_acl_split(acl, dacl);
+ if (error < 0)
+ goto out_acl;
+
+ if (pacl != NULL) {
+ if (acl->naces == 0) {
+ error = -ENODATA;
+ goto try_dpacl;
+ }
+
+ *pacl = _nfsv4_to_posix_one(acl, flags);
+ if (IS_ERR(*pacl)) {
+ error = PTR_ERR(*pacl);
+ *pacl = NULL;
+ goto out_acl;
+ }
+ }
+
+try_dpacl:
+ if (dpacl != NULL) {
+ if (dacl->naces == 0) {
+ if (pacl == NULL || *pacl == NULL)
+ error = -ENODATA;
+ goto out_acl;
+ }
+
+ error = 0;
+ *dpacl = _nfsv4_to_posix_one(dacl, flags);
+ if (IS_ERR(*dpacl)) {
+ error = PTR_ERR(*dpacl);
+ *dpacl = NULL;
+ goto out_acl;
+ }
+ }
+
+out_acl:
+ if (error && pacl) {
+ posix_acl_release(*pacl);
+ *pacl = NULL;
+ }
+ nfs4_acl_free(dacl);
+out:
+ return error;
+}
+
+static int
+same_who(struct nfs4_ace *a, struct nfs4_ace *b)
+{
+ return a->whotype == b->whotype &&
+ (a->whotype != NFS4_ACL_WHO_NAMED || a->who == b->who);
+}
+
+static int
+complementary_ace_pair(struct nfs4_ace *allow, struct nfs4_ace *deny,
+ unsigned int flags)
+{
+ int ignore = 0;
+ if (!(flags & NFS4_ACL_DIR))
+ ignore |= NFS4_ACE_DELETE_CHILD;
+ return MASK_EQUAL(ignore|deny_mask(allow->access_mask, flags),
+ ignore|deny->access_mask) &&
+ allow->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE &&
+ deny->type == NFS4_ACE_ACCESS_DENIED_ACE_TYPE &&
+ allow->flag == deny->flag &&
+ same_who(allow, deny);
+}
+
+static inline int
+user_obj_from_v4(struct nfs4_acl *n4acl, struct list_head **p,
+ struct posix_acl *pacl, struct posix_acl_entry **pace,
+ unsigned int flags)
+{
+ int error = -EINVAL;
+ struct nfs4_ace *ace, *ace2;
+
+ ace = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace == NULL)
+ goto out;
+ if (ace2type(ace) != ACL_USER_OBJ)
+ goto out;
+ error = write_pace(ace, pacl, pace, ACL_USER_OBJ, flags);
+ if (error < 0)
+ goto out;
+ error = -EINVAL;
+ ace2 = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace2 == NULL)
+ goto out;
+ if (!complementary_ace_pair(ace, ace2, flags))
+ goto out;
+ error = 0;
+out:
+ return error;
+}
+
+static inline int
+users_from_v4(struct nfs4_acl *n4acl, struct list_head **p,
+ struct nfs4_ace **mask_ace,
+ struct posix_acl *pacl, struct posix_acl_entry **pace,
+ unsigned int flags)
+{
+ int error = -EINVAL;
+ struct nfs4_ace *ace, *ace2;
+
+ ace = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace == NULL)
+ goto out;
+ while (ace2type(ace) == ACL_USER) {
+ if (ace->type != NFS4_ACE_ACCESS_DENIED_ACE_TYPE)
+ goto out;
+ if (*mask_ace &&
+ !MASK_EQUAL(ace->access_mask, (*mask_ace)->access_mask))
+ goto out;
+ *mask_ace = ace;
+ ace = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace == NULL)
+ goto out;
+ if (ace->type != NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE)
+ goto out;
+ error = write_pace(ace, pacl, pace, ACL_USER, flags);
+ if (error < 0)
+ goto out;
+ error = -EINVAL;
+ ace2 = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace2 == NULL)
+ goto out;
+ if (!complementary_ace_pair(ace, ace2, flags))
+ goto out;
+ if ((*mask_ace)->flag != ace2->flag ||
+ !same_who(*mask_ace, ace2))
+ goto out;
+ ace = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace == NULL)
+ goto out;
+ }
+ error = 0;
+out:
+ return error;
+}
+
+static inline int
+group_obj_and_groups_from_v4(struct nfs4_acl *n4acl, struct list_head **p,
+ struct nfs4_ace **mask_ace,
+ struct posix_acl *pacl, struct posix_acl_entry **pace,
+ unsigned int flags)
+{
+ int error = -EINVAL;
+ struct nfs4_ace *ace, *ace2;
+ struct ace_container *ac;
+ struct list_head group_l;
+
+ INIT_LIST_HEAD(&group_l);
+ ace = list_entry(*p, struct nfs4_ace, l_ace);
+
+ /* group owner (mask and allow aces) */
+
+ if (pacl->a_count != 3) {
+ /* then the group owner should be preceded by mask */
+ if (ace->type != NFS4_ACE_ACCESS_DENIED_ACE_TYPE)
+ goto out;
+ if (*mask_ace &&
+ !MASK_EQUAL(ace->access_mask, (*mask_ace)->access_mask))
+ goto out;
+ *mask_ace = ace;
+ ace = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace == NULL)
+ goto out;
+
+ if ((*mask_ace)->flag != ace->flag || !same_who(*mask_ace, ace))
+ goto out;
+ }
+
+ if (ace2type(ace) != ACL_GROUP_OBJ)
+ goto out;
+
+ ac = kmalloc(sizeof(*ac), GFP_KERNEL);
+ error = -ENOMEM;
+ if (ac == NULL)
+ goto out;
+ ac->ace = ace;
+ list_add_tail(&ac->ace_l, &group_l);
+
+ error = -EINVAL;
+ if (ace->type != NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE)
+ goto out;
+
+ error = write_pace(ace, pacl, pace, ACL_GROUP_OBJ, flags);
+ if (error < 0)
+ goto out;
+
+ error = -EINVAL;
+ ace = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace == NULL)
+ goto out;
+
+ /* groups (mask and allow aces) */
+
+ while (ace2type(ace) == ACL_GROUP) {
+ if (*mask_ace == NULL)
+ goto out;
+
+ if (ace->type != NFS4_ACE_ACCESS_DENIED_ACE_TYPE ||
+ !MASK_EQUAL(ace->access_mask, (*mask_ace)->access_mask))
+ goto out;
+ *mask_ace = ace;
+
+ ace = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace == NULL)
+ goto out;
+ ac = kmalloc(sizeof(*ac), GFP_KERNEL);
+ error = -ENOMEM;
+ if (ac == NULL)
+ goto out;
+ error = -EINVAL;
+ if (ace->type != NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE ||
+ !same_who(ace, *mask_ace))
+ goto out;
+
+ ac->ace = ace;
+ list_add_tail(&ac->ace_l, &group_l);
+
+ error = write_pace(ace, pacl, pace, ACL_GROUP, flags);
+ if (error < 0)
+ goto out;
+ error = -EINVAL;
+ ace = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace == NULL)
+ goto out;
+ }
+
+ /* group owner (deny ace) */
+
+ if (ace2type(ace) != ACL_GROUP_OBJ)
+ goto out;
+ ac = list_entry(group_l.next, struct ace_container, ace_l);
+ ace2 = ac->ace;
+ if (!complementary_ace_pair(ace2, ace, flags))
+ goto out;
+ list_del(group_l.next);
+ kfree(ac);
+
+ /* groups (deny aces) */
+
+ while (!list_empty(&group_l)) {
+ ace = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace == NULL)
+ goto out;
+ if (ace2type(ace) != ACL_GROUP)
+ goto out;
+ ac = list_entry(group_l.next, struct ace_container, ace_l);
+ ace2 = ac->ace;
+ if (!complementary_ace_pair(ace2, ace, flags))
+ goto out;
+ list_del(group_l.next);
+ kfree(ac);
+ }
+
+ ace = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace == NULL)
+ goto out;
+ if (ace2type(ace) != ACL_OTHER)
+ goto out;
+ error = 0;
+out:
+ while (!list_empty(&group_l)) {
+ ac = list_entry(group_l.next, struct ace_container, ace_l);
+ list_del(group_l.next);
+ kfree(ac);
+ }
+ return error;
+}
+
+static inline int
+mask_from_v4(struct nfs4_acl *n4acl, struct list_head **p,
+ struct nfs4_ace **mask_ace,
+ struct posix_acl *pacl, struct posix_acl_entry **pace,
+ unsigned int flags)
+{
+ int error = -EINVAL;
+ struct nfs4_ace *ace;
+
+ ace = list_entry(*p, struct nfs4_ace, l_ace);
+ if (pacl->a_count != 3) {
+ if (*mask_ace == NULL)
+ goto out;
+ (*mask_ace)->access_mask = deny_mask((*mask_ace)->access_mask, flags);
+ write_pace(*mask_ace, pacl, pace, ACL_MASK, flags);
+ }
+ error = 0;
+out:
+ return error;
+}
+
+static inline int
+other_from_v4(struct nfs4_acl *n4acl, struct list_head **p,
+ struct posix_acl *pacl, struct posix_acl_entry **pace,
+ unsigned int flags)
+{
+ int error = -EINVAL;
+ struct nfs4_ace *ace, *ace2;
+
+ ace = list_entry(*p, struct nfs4_ace, l_ace);
+ if (ace->type != NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE)
+ goto out;
+ error = write_pace(ace, pacl, pace, ACL_OTHER, flags);
+ if (error < 0)
+ goto out;
+ error = -EINVAL;
+ ace2 = get_next_v4_ace(p, &n4acl->ace_head);
+ if (ace2 == NULL)
+ goto out;
+ if (!complementary_ace_pair(ace, ace2, flags))
+ goto out;
+ error = 0;
+out:
+ return error;
+}
+
+static int
+calculate_posix_ace_count(struct nfs4_acl *n4acl)
+{
+ if (n4acl->naces == 6) /* owner, owner group, and other only */
+ return 3;
+ else { /* Otherwise there must be a mask entry. */
+ /* Also, the remaining entries are for named users and
+ * groups, and come in threes (mask, allow, deny): */
+ if (n4acl->naces < 7)
+ return -1;
+ if ((n4acl->naces - 7) % 3)
+ return -1;
+ return 4 + (n4acl->naces - 7)/3;
+ }
+}
+
+
+static struct posix_acl *
+_nfsv4_to_posix_one(struct nfs4_acl *n4acl, unsigned int flags)
+{
+ struct posix_acl *pacl;
+ int error = -EINVAL, nace = 0;
+ struct list_head *p;
+ struct nfs4_ace *mask_ace = NULL;
+ struct posix_acl_entry *pace;
+
+ nace = calculate_posix_ace_count(n4acl);
+ if (nace < 0)
+ goto out_err;
+
+ pacl = posix_acl_alloc(nace, GFP_KERNEL);
+ error = -ENOMEM;
+ if (pacl == NULL)
+ goto out_err;
+
+ pace = &pacl->a_entries[0];
+ p = &n4acl->ace_head;
+
+ error = user_obj_from_v4(n4acl, &p, pacl, &pace, flags);
+ if (error)
+ goto out_acl;
+
+ error = users_from_v4(n4acl, &p, &mask_ace, pacl, &pace, flags);
+ if (error)
+ goto out_acl;
+
+ error = group_obj_and_groups_from_v4(n4acl, &p, &mask_ace, pacl, &pace,
+ flags);
+ if (error)
+ goto out_acl;
+
+ error = mask_from_v4(n4acl, &p, &mask_ace, pacl, &pace, flags);
+ if (error)
+ goto out_acl;
+ error = other_from_v4(n4acl, &p, pacl, &pace, flags);
+ if (error)
+ goto out_acl;
+
+ error = -EINVAL;
+ if (p->next != &n4acl->ace_head)
+ goto out_acl;
+ if (pace != pacl->a_entries + pacl->a_count)
+ goto out_acl;
+
+ sort_pacl(pacl);
+
+ return pacl;
+out_acl:
+ posix_acl_release(pacl);
+out_err:
+ pacl = ERR_PTR(error);
+ return pacl;
+}
+
+int
+nfs4_acl_split(struct nfs4_acl *acl, struct nfs4_acl *dacl)
+{
+ struct list_head *h, *n;
+ struct nfs4_ace *ace;
+ int error = 0;
+
+ list_for_each_safe(h, n, &acl->ace_head) {
+ ace = list_entry(h, struct nfs4_ace, l_ace);
+
+ if ((ace->flag & NFS4_INHERITANCE_FLAGS)
+ != NFS4_INHERITANCE_FLAGS)
+ continue;
+
+ error = nfs4_acl_add_ace(dacl, ace->type, ace->flag,
+ ace->access_mask, ace->whotype, ace->who) == -1;
+ if (error < 0)
+ goto out;
+
+ list_del(h);
+ kfree(ace);
+ acl->naces--;
+ }
+
+out:
+ return error;
+}
+
+static short
+ace2type(struct nfs4_ace *ace)
+{
+ switch (ace->whotype) {
+ case NFS4_ACL_WHO_NAMED:
+ return (ace->flag & NFS4_ACE_IDENTIFIER_GROUP ?
+ ACL_GROUP : ACL_USER);
+ case NFS4_ACL_WHO_OWNER:
+ return ACL_USER_OBJ;
+ case NFS4_ACL_WHO_GROUP:
+ return ACL_GROUP_OBJ;
+ case NFS4_ACL_WHO_EVERYONE:
+ return ACL_OTHER;
+ }
+ BUG();
+ return -1;
+}
+
+EXPORT_SYMBOL(nfs4_acl_posix_to_nfsv4);
+EXPORT_SYMBOL(nfs4_acl_nfsv4_to_posix);
+
struct nfs4_acl *
nfs4_acl_new(void)
{