This patch implements NFSv4 ACLs on the ext3 filesystem. When an ext3 filesystem is mounted with -o acl=nfs4 or -o acl=nfs4+max, the filesystem will support NFSv4 ACLs instead of POSIX ACLs. Signed-off-by: Andreas Gruenbacher --- fs/Kconfig | 12 fs/Makefile | 4 fs/ext3/Makefile | 3 fs/ext3/acl.c | 8 fs/ext3/acl.h | 4 fs/ext3/file.c | 1 fs/ext3/ialloc.c | 6 fs/ext3/inode.c | 9 fs/ext3/namei.c | 11 fs/ext3/namei.h | 1 fs/ext3/nfs4acl.c | 281 +++++++++++++++ fs/ext3/nfs4acl.h | 26 + fs/ext3/super.c | 54 ++- fs/ext3/xattr.c | 9 fs/ext3/xattr.h | 5 fs/nfs4acl_base.c | 532 ++++++++++++++++++++++++++++++ fs/nfs4acl_compat.c | 743 ++++++++++++++++++++++++++++++++++++++++++ fs/nfs4acl_xattr.c | 153 ++++++++ include/linux/ext3_fs.h | 2 include/linux/nfs4acl.h | 181 ++++++++++ include/linux/nfs4acl_xattr.h | 31 + 21 files changed, 2051 insertions(+), 25 deletions(-) Index: linux-2.6.18-rc6/include/linux/nfs4acl.h =================================================================== --- /dev/null +++ linux-2.6.18-rc6/include/linux/nfs4acl.h @@ -0,0 +1,181 @@ +#ifndef __NFS4ACL_H +#define __NFS4ACL_H + +struct nfs4ace { + unsigned short e_type; + unsigned short e_flags; + unsigned int e_mask; + union { + unsigned int e_id; + const char *e_who; + } u; +}; + +struct nfs4acl { + unsigned int a_owner_mask; + unsigned int a_group_mask; + unsigned int a_other_mask; + unsigned int a_count; + struct nfs4ace a_entries[0]; +}; + +#define nfs4acl_for_each_entry(_ace, _acl) \ + for (_ace = _acl->a_entries; \ + _ace != _acl->a_entries + _acl->a_count; \ + _ace++) + +#define nfs4acl_for_each_entry_reverse(_ace, _acl) \ + for (_ace = _acl->a_entries + _acl->a_count - 1; \ + _ace != _acl->a_entries - 1; \ + _ace--) + +/* e_type values */ +#define ACE4_ACCESS_ALLOWED_ACE_TYPE 0x0000 +#define ACE4_ACCESS_DENIED_ACE_TYPE 0x0001 +/*#define ACE4_SYSTEM_AUDIT_ACE_TYPE 0x0002*/ +/*#define ACE4_SYSTEM_ALARM_ACE_TYPE 0x0003*/ + +/* e_flags bitflags */ +#define ACE4_FILE_INHERIT_ACE 0x0001 +#define ACE4_DIRECTORY_INHERIT_ACE 0x0002 +#define ACE4_NO_PROPAGATE_INHERIT_ACE 0x0004 +#define ACE4_INHERIT_ONLY_ACE 0x0008 +/*#define ACE4_SUCCESSFUL_ACCESS_ACE_FLAG 0x0010*/ +/*#define ACE4_FAILED_ACCESS_ACE_FLAG 0x0020*/ +#define ACE4_IDENTIFIER_GROUP 0x0040 +#define ACE4_SPECIAL_WHO 0x4000 /* in-kernel only */ + +#define ACE4_VALID_FLAGS ( \ + ACE4_FILE_INHERIT_ACE | \ + ACE4_DIRECTORY_INHERIT_ACE | \ + ACE4_NO_PROPAGATE_INHERIT_ACE | \ + ACE4_INHERIT_ONLY_ACE | \ + ACE4_IDENTIFIER_GROUP ) + +/* e_mask bitflags */ +#define ACE4_READ_DATA 0x00000001 +#define ACE4_LIST_DIRECTORY 0x00000001 +#define ACE4_WRITE_DATA 0x00000002 +#define ACE4_ADD_FILE 0x00000002 +#define ACE4_APPEND_DATA 0x00000004 +#define ACE4_ADD_SUBDIRECTORY 0x00000004 +#define ACE4_READ_NAMED_ATTRS 0x00000008 +#define ACE4_WRITE_NAMED_ATTRS 0x00000010 +#define ACE4_EXECUTE 0x00000020 +#define ACE4_DELETE_CHILD 0x00000040 +#define ACE4_READ_ATTRIBUTES 0x00000080 +#define ACE4_WRITE_ATTRIBUTES 0x00000100 +#define ACE4_DELETE 0x00010000 +#define ACE4_READ_ACL 0x00020000 +#define ACE4_WRITE_ACL 0x00040000 +#define ACE4_WRITE_OWNER 0x00080000 +#define ACE4_SYNCHRONIZE 0x00100000 + +#define ACE4_VALID_MASK ( \ + ACE4_READ_DATA | \ + ACE4_LIST_DIRECTORY | \ + ACE4_WRITE_DATA | \ + ACE4_ADD_FILE | \ + ACE4_APPEND_DATA | \ + ACE4_ADD_SUBDIRECTORY | \ + ACE4_READ_NAMED_ATTRS | \ + ACE4_WRITE_NAMED_ATTRS | \ + ACE4_EXECUTE | \ + ACE4_DELETE_CHILD | \ + ACE4_READ_ATTRIBUTES | \ + ACE4_WRITE_ATTRIBUTES | \ + ACE4_DELETE | \ + ACE4_READ_ACL | \ + ACE4_WRITE_ACL | \ + ACE4_WRITE_OWNER | \ + ACE4_SYNCHRONIZE ) + +/* Special e_who identifiers: we use these pointer values in comparisons + instead of strcmp for efficiency. */ + +extern const char *nfs4ace_owner_who; +extern const char *nfs4ace_group_who; +extern const char *nfs4ace_everyone_who; + +static inline int +nfs4ace_is_owner(struct nfs4ace *ace) +{ + return (ace->e_flags & ACE4_SPECIAL_WHO) && + ace->u.e_who == nfs4ace_owner_who; +} + +static inline int +nfs4ace_is_group(struct nfs4ace *ace) +{ + return (ace->e_flags & ACE4_SPECIAL_WHO) && + ace->u.e_who == nfs4ace_group_who; +} + +static inline int +nfs4ace_is_everyone(struct nfs4ace *ace) +{ + return (ace->e_flags & ACE4_SPECIAL_WHO) && + ace->u.e_who == nfs4ace_everyone_who; +} + +static inline int +nfs4ace_is_unix_id(struct nfs4ace *ace) +{ + return !(ace->e_flags & ACE4_SPECIAL_WHO); +} + +static inline int +nfs4ace_is_inherit_only(struct nfs4ace *ace) +{ + return ace->e_flags & ACE4_INHERIT_ONLY_ACE; +} + +static inline int +nfs4ace_is_inheritable(struct nfs4ace *ace) +{ + return ace->e_flags & (ACE4_FILE_INHERIT_ACE | + ACE4_DIRECTORY_INHERIT_ACE); +} + +static inline void +nfs4ace_clear_inheritance_flags(struct nfs4ace *ace) +{ + ace->e_flags &= ~(ACE4_FILE_INHERIT_ACE | + ACE4_DIRECTORY_INHERIT_ACE | + ACE4_NO_PROPAGATE_INHERIT_ACE | + ACE4_INHERIT_ONLY_ACE); +} + +static inline int +nfs4ace_is_allow(struct nfs4ace *ace) +{ + return ace->e_type == ACE4_ACCESS_ALLOWED_ACE_TYPE; +} + +static inline int +nfs4ace_is_deny(struct nfs4ace *ace) +{ + return ace->e_type == ACE4_ACCESS_DENIED_ACE_TYPE; +} + +extern int nfs4ace_dup(struct nfs4ace *, struct nfs4ace *); +extern void nfs4ace_free(struct nfs4ace *); + +extern struct nfs4acl *nfs4acl_alloc(int count); +extern struct nfs4acl *nfs4acl_clone(struct nfs4acl *acl); +extern void __nfs4acl_free(struct nfs4acl *); + +static inline void +nfs4acl_free(struct nfs4acl *acl) +{ + kfree(acl); +} + +extern int nfs4acl_permission(struct inode *, const struct nfs4acl *, int, int); +extern int nfs4ace_is_same_who(struct nfs4ace *, struct nfs4ace *); +extern struct nfs4acl *nfs4acl_inherit(struct nfs4acl *, mode_t, int); +extern int nfs4acl_masks_to_mode(struct nfs4acl *); +extern void nfs4acl_chmod(struct nfs4acl *, mode_t); +extern int nfs4acl_apply_masks(struct nfs4acl **acl, int); + +#endif /* __NFS4ACL_H */ Index: linux-2.6.18-rc6/include/linux/nfs4acl_xattr.h =================================================================== --- /dev/null +++ linux-2.6.18-rc6/include/linux/nfs4acl_xattr.h @@ -0,0 +1,31 @@ +#ifndef __NFS4ACL_XATTR_H +#define __NFS4ACL_XATTR_H + +#include + +#define NFS4ACL_XATTR "system.nfs4acl" + +struct nfs4ace_xattr { + __be16 e_type; + __be16 e_flags; + __be32 e_mask; + __be32 e_id; + char e_who[0]; +}; + +struct nfs4acl_xattr { + unsigned char a_version; + unsigned char a_flags; + __be16 a_count; + __be32 a_owner_mask; + __be32 a_group_mask; + __be32 a_other_mask; +}; + +#define ACL4_XATTR_VERSION 0 +#define ACL4_XATTR_MAX_COUNT 1024 + +extern struct nfs4acl *nfs4acl_from_xattr(const void *, size_t); +extern void *nfs4acl_to_xattr(const struct nfs4acl *, size_t *); + +#endif /* __NFS4ACL_XATTR_H */ Index: linux-2.6.18-rc6/fs/nfs4acl_xattr.c =================================================================== --- /dev/null +++ linux-2.6.18-rc6/fs/nfs4acl_xattr.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2006 Andreas Gruenbacher + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); + +struct nfs4acl * +nfs4acl_from_xattr(const void *value, size_t size) +{ + const struct nfs4acl_xattr *xattr_acl = value; + const struct nfs4ace_xattr *xattr_ace = (void *)(xattr_acl + 1); + struct nfs4acl *acl; + struct nfs4ace *ace; + int count; + + if (size < sizeof(struct nfs4acl_xattr) || + xattr_acl->a_version != ACL4_XATTR_VERSION || + xattr_acl->a_flags != 0) + return ERR_PTR(-EINVAL); + + count = be16_to_cpu(xattr_acl->a_count); + if (count > ACL4_XATTR_MAX_COUNT) + return ERR_PTR(-EINVAL); + + acl = nfs4acl_alloc(count); + if (!acl) + return ERR_PTR(-ENOMEM); + + acl->a_owner_mask = be32_to_cpu(xattr_acl->a_owner_mask); + if (acl->a_owner_mask & ~ACE4_VALID_MASK) + goto fail_einval; + acl->a_group_mask = be32_to_cpu(xattr_acl->a_group_mask); + if (acl->a_group_mask & ~ACE4_VALID_MASK) + goto fail_einval; + acl->a_other_mask = be32_to_cpu(xattr_acl->a_other_mask); + if (acl->a_other_mask & ~ACE4_VALID_MASK) + goto fail_einval; + + nfs4acl_for_each_entry(ace, acl) { + const char *who = (void *)(xattr_ace + 1), *end; + ssize_t used = (void *)who - value; + + if (used > size) + goto fail_einval; + end = memchr(who, 0, size - used); + if (!end) + goto fail_einval; + + ace->e_type = be16_to_cpu(xattr_ace->e_type); + ace->e_flags = be16_to_cpu(xattr_ace->e_flags); + ace->e_mask = be32_to_cpu(xattr_ace->e_mask); + ace->u.e_id = be32_to_cpu(xattr_ace->e_id); + + if (ace->e_flags & ~ACE4_VALID_FLAGS) { + memset(ace, 0, sizeof(struct nfs4ace)); + goto fail_einval; + } + if (ace->e_type > ACE4_ACCESS_DENIED_ACE_TYPE || + (ace->e_mask & ~ACE4_VALID_MASK)) + goto fail_einval; + + if (who == end) { + if (ace->u.e_id == -1) + goto fail_einval; /* uid/gid needed */ + } else if (!strcmp(who, nfs4ace_owner_who)) { + ace->e_flags |= ACE4_SPECIAL_WHO; + ace->u.e_who = nfs4ace_owner_who; + } else if (!strcmp(who, nfs4ace_group_who)) { + ace->e_flags |= ACE4_SPECIAL_WHO; + ace->u.e_who = nfs4ace_group_who; + } else if (!strcmp(who, nfs4ace_everyone_who)) { + ace->e_flags |= ACE4_SPECIAL_WHO; + ace->u.e_who = nfs4ace_everyone_who; + } else + goto fail_einval; + + xattr_ace = (void *)who + ALIGN(end - who + 1, 4); + } + + return acl; + +fail_einval: + nfs4acl_free(acl); + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(nfs4acl_from_xattr); + +void * +nfs4acl_to_xattr(const struct nfs4acl *acl, size_t *xattr_size) +{ + struct nfs4acl_xattr *xattr_acl; + struct nfs4ace_xattr *xattr_ace; + size_t size = sizeof(struct nfs4acl_xattr); + struct nfs4ace *ace; + + nfs4acl_for_each_entry(ace, acl) { + size += sizeof(struct nfs4ace_xattr) + + (nfs4ace_is_unix_id(ace) ? 4 : + ALIGN(strlen(ace->u.e_who) + 1, 4)); + } + + xattr_acl = kmalloc(size, GFP_KERNEL); + if (!xattr_acl) + return ERR_PTR(-ENOMEM); + + xattr_acl->a_version = ACL4_XATTR_VERSION; + xattr_acl->a_flags = 0; + xattr_acl->a_count = cpu_to_be16(acl->a_count); + + xattr_acl->a_owner_mask = cpu_to_be32(acl->a_owner_mask); + xattr_acl->a_group_mask = cpu_to_be32(acl->a_group_mask); + xattr_acl->a_other_mask = cpu_to_be32(acl->a_other_mask); + + xattr_ace = (void *)(xattr_acl + 1); + nfs4acl_for_each_entry(ace, acl) { + xattr_ace->e_type = cpu_to_be16(ace->e_type); + xattr_ace->e_flags = cpu_to_be16(ace->e_flags & + ACE4_VALID_FLAGS); + xattr_ace->e_mask = cpu_to_be32(ace->e_mask); + if (nfs4ace_is_unix_id(ace)) { + xattr_ace->e_id = cpu_to_be32(ace->u.e_id); + memset(xattr_ace->e_who, 0, 4); + xattr_ace = (void *)xattr_ace->e_who + 4; + } else { + int sz = ALIGN(strlen(ace->u.e_who) + 1, 4); + + xattr_ace->e_id = cpu_to_be32(-1); + memset(xattr_ace->e_who + sz - 4, 0, 4); + strcpy(xattr_ace->e_who, ace->u.e_who); + xattr_ace = (void *)xattr_ace->e_who + sz; + } + } + + *xattr_size = size; + return xattr_acl; +} +EXPORT_SYMBOL_GPL(nfs4acl_to_xattr); Index: linux-2.6.18-rc6/fs/Kconfig =================================================================== --- linux-2.6.18-rc6.orig/fs/Kconfig +++ linux-2.6.18-rc6/fs/Kconfig @@ -126,6 +126,14 @@ config EXT3_FS_POSIX_ACL If you don't know what Access Control Lists are, say N +config EXT3_FS_NFS4ACL + bool "Native NFSv4 ACLs (EXPERIMENTAL)" + depends on EXT3_FS_XATTR && EXPERIMENTAL + select FS_NFS4ACL + help + Allow to use NFSv4 ACLs instead of POSIX ACLs. This may become + useful for filesystems exported over NFSv4 or Samba. + config EXT3_FS_SECURITY bool "Ext3 Security Labels" depends on EXT3_FS_XATTR @@ -322,6 +330,10 @@ config FS_POSIX_ACL bool default n +config FS_NFS4ACL + bool + default n + source "fs/xfs/Kconfig" config OCFS2_FS Index: linux-2.6.18-rc6/fs/Makefile =================================================================== --- linux-2.6.18-rc6.orig/fs/Makefile +++ linux-2.6.18-rc6/fs/Makefile @@ -36,6 +36,10 @@ obj-$(CONFIG_FS_MBCACHE) += mbcache.o obj-$(CONFIG_FS_POSIX_ACL) += posix_acl.o xattr_acl.o obj-$(CONFIG_NFS_COMMON) += nfs_common/ +obj-$(CONFIG_FS_NFS4ACL:y=m) += nfs4acl.o +nfs4acl-y := nfs4acl_base.o nfs4acl_xattr.o \ + nfs4acl_compat.o + obj-$(CONFIG_QUOTA) += dquot.o obj-$(CONFIG_QFMT_V1) += quota_v1.o obj-$(CONFIG_QFMT_V2) += quota_v2.o Index: linux-2.6.18-rc6/fs/ext3/file.c =================================================================== --- linux-2.6.18-rc6.orig/fs/ext3/file.c +++ linux-2.6.18-rc6/fs/ext3/file.c @@ -23,6 +23,7 @@ #include #include #include +#include "namei.h" #include "xattr.h" #include "acl.h" Index: linux-2.6.18-rc6/fs/ext3/inode.c =================================================================== --- linux-2.6.18-rc6.orig/fs/ext3/inode.c +++ linux-2.6.18-rc6/fs/ext3/inode.c @@ -38,6 +38,7 @@ #include #include "xattr.h" #include "acl.h" +#include "nfs4acl.h" static int ext3_writepage_trans_blocks(struct inode *inode); @@ -2964,8 +2965,12 @@ int ext3_setattr(struct dentry *dentry, if (inode->i_nlink) ext3_orphan_del(NULL, inode); - if (!rc && (ia_valid & ATTR_MODE)) - rc = ext3_acl_chmod(inode); + if (!rc && (ia_valid & ATTR_MODE)) { + if (test_opt(inode->i_sb, NFS4ACL)) + rc = ext3_nfs4acl_chmod(inode); + else + rc = ext3_acl_chmod(inode); + } err_out: ext3_std_error(inode->i_sb, error); Index: linux-2.6.18-rc6/fs/ext3/namei.c =================================================================== --- linux-2.6.18-rc6.orig/fs/ext3/namei.c +++ linux-2.6.18-rc6/fs/ext3/namei.c @@ -40,6 +40,7 @@ #include "namei.h" #include "xattr.h" #include "acl.h" +#include "nfs4acl.h" /* * define how far ahead to read directories while searching them. @@ -2361,6 +2362,16 @@ end_rename: return retval; } +int ext3_permission(struct inode *inode, int mask, struct nameidata *nd) +{ +#ifdef CONFIG_EXT3_FS_NFS4ACL + if (test_opt(inode->i_sb, NFS4ACL)) + return ext3_nfs4acl_permission(inode, mask); + else +#endif + return generic_permission(inode, mask, ext3_check_acl); +} + /* * directories can handle most operations... */ Index: linux-2.6.18-rc6/fs/ext3/super.c =================================================================== --- linux-2.6.18-rc6.orig/fs/ext3/super.c +++ linux-2.6.18-rc6/fs/ext3/super.c @@ -628,7 +628,7 @@ enum { Opt_bsd_df, Opt_minix_df, Opt_grpid, Opt_nogrpid, Opt_resgid, Opt_resuid, Opt_sb, Opt_err_cont, Opt_err_panic, Opt_err_ro, Opt_nouid32, Opt_nocheck, Opt_debug, Opt_oldalloc, Opt_orlov, - Opt_user_xattr, Opt_nouser_xattr, Opt_acl, Opt_noacl, + Opt_user_xattr, Opt_nouser_xattr, Opt_acl, Opt_acl_flavor, Opt_noacl, Opt_reservation, Opt_noreservation, Opt_noload, Opt_nobh, Opt_bh, Opt_commit, Opt_journal_update, Opt_journal_inum, Opt_journal_dev, Opt_abort, Opt_data_journal, Opt_data_ordered, Opt_data_writeback, @@ -660,6 +660,7 @@ static match_table_t tokens = { {Opt_user_xattr, "user_xattr"}, {Opt_nouser_xattr, "nouser_xattr"}, {Opt_acl, "acl"}, + {Opt_acl_flavor, "acl=%s"}, {Opt_noacl, "noacl"}, {Opt_reservation, "reservation"}, {Opt_noreservation, "noreservation"}, @@ -803,19 +804,39 @@ static int parse_options (char *options, printk("EXT3 (no)user_xattr options not supported\n"); break; #endif -#ifdef CONFIG_EXT3_FS_POSIX_ACL case Opt_acl: - set_opt(sbi->s_mount_opt, POSIX_ACL); + args[0].to = args[0].from; + /* fall through */ + case Opt_acl_flavor: +#ifdef CONFIG_EXT3_FS_POSIX_ACL + if (match_string(&args[0], "") || + match_string(&args[0], "posix")) { + set_opt(sbi->s_mount_opt, POSIX_ACL); + clear_opt(sbi->s_mount_opt, NFS4ACL); + } else +#endif +#ifdef CONFIG_EXT3_FS_NFS4ACL + if (match_string(&args[0], "nfs4")) { + clear_opt(sbi->s_mount_opt, POSIX_ACL); + set_opt(sbi->s_mount_opt, NFS4ACL); + clear_opt(sbi->s_mount_opt, NFS4ACL_MAX); + } else + if (match_string(&args[0], "nfs4+max")) { + clear_opt(sbi->s_mount_opt, POSIX_ACL); + set_opt(sbi->s_mount_opt, NFS4ACL); + set_opt(sbi->s_mount_opt, NFS4ACL_MAX); + } else +#endif + { + printk(KERN_ERR "EXT3-fs: unsupported acl " + "flavor\n"); + return 0; + } break; case Opt_noacl: clear_opt(sbi->s_mount_opt, POSIX_ACL); + clear_opt(sbi->s_mount_opt, NFS4ACL); break; -#else - case Opt_acl: - case Opt_noacl: - printk("EXT3 (no)acl options not supported\n"); - break; -#endif case Opt_reservation: set_opt(sbi->s_mount_opt, RESERVATION); break; @@ -1434,8 +1455,11 @@ static int ext3_fill_super (struct super NULL, 0)) goto failed_mount; - sb->s_flags = (sb->s_flags & ~MS_POSIXACL) | - ((sbi->s_mount_opt & EXT3_MOUNT_POSIX_ACL) ? MS_POSIXACL : 0); + sb->s_flags = (sb->s_flags & ~(MS_POSIXACL | MS_XPERM)); + if (sbi->s_mount_opt & EXT3_MOUNT_POSIX_ACL) + sb->s_flags |= MS_POSIXACL; + if (sbi->s_mount_opt & EXT3_MOUNT_NFS4ACL) + sb->s_flags |= MS_POSIXACL | MS_XPERM; if (le32_to_cpu(es->s_rev_level) == EXT3_GOOD_OLD_REV && (EXT3_HAS_COMPAT_FEATURE(sb, ~0U) || @@ -2250,8 +2274,12 @@ static int ext3_remount (struct super_bl if (sbi->s_mount_opt & EXT3_MOUNT_ABORT) ext3_abort(sb, __FUNCTION__, "Abort forced by user"); - sb->s_flags = (sb->s_flags & ~MS_POSIXACL) | - ((sbi->s_mount_opt & EXT3_MOUNT_POSIX_ACL) ? MS_POSIXACL : 0); + sb->s_flags = (sb->s_flags & ~(MS_POSIXACL | MS_XPERM)); + if (sbi->s_mount_opt & EXT3_MOUNT_POSIX_ACL) + sb->s_flags |= MS_POSIXACL; + if (sbi->s_mount_opt & EXT3_MOUNT_NFS4ACL) + sb->s_flags |= MS_POSIXACL | MS_XPERM; + es = sbi->s_es; Index: linux-2.6.18-rc6/fs/ext3/xattr.c =================================================================== --- linux-2.6.18-rc6.orig/fs/ext3/xattr.c +++ linux-2.6.18-rc6/fs/ext3/xattr.c @@ -112,6 +112,9 @@ static struct xattr_handler *ext3_xattr_ #ifdef CONFIG_EXT3_FS_SECURITY [EXT3_XATTR_INDEX_SECURITY] = &ext3_xattr_security_handler, #endif +#ifdef CONFIG_EXT3_FS_NFS4ACL + [EXT3_XATTR_INDEX_NFS4ACL] = &ext3_nfs4acl_xattr_handler, +#endif }; struct xattr_handler *ext3_xattr_handlers[] = { @@ -124,6 +127,12 @@ struct xattr_handler *ext3_xattr_handler #ifdef CONFIG_EXT3_FS_SECURITY &ext3_xattr_security_handler, #endif +#ifdef CONFIG_EXT3_FS_NFS4ACL + &ext3_nfs4acl_xattr_handler, +#ifdef NFS4ACL_DEBUG + &ext3_masked_nfs4acl_xattr_handler, +#endif +#endif NULL }; Index: linux-2.6.18-rc6/include/linux/ext3_fs.h =================================================================== --- linux-2.6.18-rc6.orig/include/linux/ext3_fs.h +++ linux-2.6.18-rc6/include/linux/ext3_fs.h @@ -371,6 +371,8 @@ struct ext3_inode { #define EXT3_MOUNT_QUOTA 0x80000 /* Some quota option set */ #define EXT3_MOUNT_USRQUOTA 0x100000 /* "old" user quota */ #define EXT3_MOUNT_GRPQUOTA 0x200000 /* "old" group quota */ +#define EXT3_MOUNT_NFS4ACL 0x400000 /* NFS version 4 ACLs */ +#define EXT3_MOUNT_NFS4ACL_MAX 0x800000 /* "write-through" behavior */ /* Compatibility, for having both ext2_fs.h and ext3_fs.h included at once */ #ifndef _LINUX_EXT2_FS_H Index: linux-2.6.18-rc6/fs/ext3/xattr.h =================================================================== --- linux-2.6.18-rc6.orig/fs/ext3/xattr.h +++ linux-2.6.18-rc6/fs/ext3/xattr.h @@ -21,6 +21,7 @@ #define EXT3_XATTR_INDEX_TRUSTED 4 #define EXT3_XATTR_INDEX_LUSTRE 5 #define EXT3_XATTR_INDEX_SECURITY 6 +#define EXT3_XATTR_INDEX_NFS4ACL 7 struct ext3_xattr_header { __le32 h_magic; /* magic number for identification */ @@ -63,6 +64,10 @@ extern struct xattr_handler ext3_xattr_t extern struct xattr_handler ext3_xattr_acl_access_handler; extern struct xattr_handler ext3_xattr_acl_default_handler; extern struct xattr_handler ext3_xattr_security_handler; +extern struct xattr_handler ext3_nfs4acl_xattr_handler; +#ifdef NFS4ACL_DEBUG +extern struct xattr_handler ext3_masked_nfs4acl_xattr_handler; +#endif extern ssize_t ext3_listxattr(struct dentry *, char *, size_t); Index: linux-2.6.18-rc6/fs/ext3/Makefile =================================================================== --- linux-2.6.18-rc6.orig/fs/ext3/Makefile +++ linux-2.6.18-rc6/fs/ext3/Makefile @@ -2,6 +2,8 @@ # Makefile for the linux ext3-filesystem routines. # +EXTRA_CFLAGS := -DNFS4ACL_DEBUG + obj-$(CONFIG_EXT3_FS) += ext3.o ext3-y := balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o \ @@ -10,3 +12,4 @@ ext3-y := balloc.o bitmap.o dir.o file.o ext3-$(CONFIG_EXT3_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o ext3-$(CONFIG_EXT3_FS_POSIX_ACL) += acl.o ext3-$(CONFIG_EXT3_FS_SECURITY) += xattr_security.o +ext3-$(CONFIG_EXT3_FS_NFS4ACL) += nfs4acl.o Index: linux-2.6.18-rc6/fs/ext3/ialloc.c =================================================================== --- linux-2.6.18-rc6.orig/fs/ext3/ialloc.c +++ linux-2.6.18-rc6/fs/ext3/ialloc.c @@ -28,6 +28,7 @@ #include "xattr.h" #include "acl.h" +#include "nfs4acl.h" /* * ialloc.c contains the inodes allocation and deallocation routines @@ -603,7 +604,10 @@ got: goto fail_drop; } - err = ext3_init_acl(handle, inode, dir); + if (test_opt(sb, NFS4ACL)) + err = ext3_nfs4acl_init(handle, inode, dir); + else + err = ext3_init_acl(handle, inode, dir); if (err) goto fail_free_drop; Index: linux-2.6.18-rc6/fs/ext3/nfs4acl.c =================================================================== --- /dev/null +++ linux-2.6.18-rc6/fs/ext3/nfs4acl.c @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2006 Andreas Gruenbacher + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include "xattr.h" + +static struct nfs4acl * +ext3_get_nfs4acl(struct inode *inode) +{ + const int name_index = EXT3_XATTR_INDEX_NFS4ACL; + void *value = NULL; + struct nfs4acl *acl; + int retval; + + retval = ext3_xattr_get(inode, name_index, "", NULL, 0); + if (retval > 0) { + value = kmalloc(retval, GFP_KERNEL); + if (!value) + return ERR_PTR(-ENOMEM); + retval = ext3_xattr_get(inode, name_index, "", value, retval); + } + if (retval > 0) { + acl = nfs4acl_from_xattr(value, retval); + if (acl == ERR_PTR(-EINVAL)) + acl = ERR_PTR(-EIO); + } else if (retval == -ENODATA || retval == -ENOSYS) + acl = NULL; + else + acl = ERR_PTR(retval); + kfree(value); + + return acl; +} + +static int +ext3_set_nfs4acl(handle_t *handle, struct inode *inode, struct nfs4acl *acl) +{ + const int name_index = EXT3_XATTR_INDEX_NFS4ACL; + size_t size; + void *value; + int retval; + + value = nfs4acl_to_xattr(acl, &size); + if (IS_ERR(value)) + return PTR_ERR(value); + if (handle) + retval = ext3_xattr_set_handle(handle, inode, name_index, "", + value, size, 0); + else + retval = ext3_xattr_set(inode, name_index, "", value, size, 0); + kfree(value); + return retval; +} + +int +ext3_nfs4acl_permission(struct inode *inode, int want) +{ + struct nfs4acl *acl = ext3_get_nfs4acl(inode); + int retval; + + BUG_ON(!test_opt(inode->i_sb, NFS4ACL)); + + if (!acl) + retval = generic_permission(inode, want, NULL); + else if (IS_ERR(acl)) + retval = PTR_ERR(acl); + else { + retval = nfs4acl_permission(inode, acl, want, + test_opt(inode->i_sb, NFS4ACL_MAX)); + nfs4acl_free(acl); + } + + return retval; +} + +int +ext3_nfs4acl_init(handle_t *handle, struct inode *inode, struct inode *dir) +{ + struct nfs4acl *dir_acl, *acl; + int retval = 0; + + BUG_ON(!test_opt(inode->i_sb, NFS4ACL)); + + dir_acl = ext3_get_nfs4acl(dir); + retval = PTR_ERR(dir_acl); + if (!dir_acl || IS_ERR(dir_acl)) + goto out; + acl = nfs4acl_inherit(dir_acl, inode->i_mode, + test_opt(inode->i_sb, NFS4ACL_MAX)); + nfs4acl_free(dir_acl); + + if (IS_ERR(acl)) + return PTR_ERR(acl); + else if (acl) { + retval = ext3_set_nfs4acl(handle, inode, acl); + inode->i_mode = (inode->i_mode & ~S_IRWXUGO) | + nfs4acl_masks_to_mode(acl); + nfs4acl_free(acl); + return retval; + } + +out: + if (!retval) + inode->i_mode &= ~current->fs->umask; + + return retval; +} + +int +ext3_nfs4acl_chmod(struct inode *inode) +{ + struct nfs4acl *acl; + int retval; + + BUG_ON(!test_opt(inode->i_sb, NFS4ACL)); + + acl = ext3_get_nfs4acl(inode); + if (!acl || IS_ERR(acl)) + return PTR_ERR(acl); + nfs4acl_chmod(acl, inode->i_mode); + retval = ext3_set_nfs4acl(NULL, inode, acl); + nfs4acl_free(acl); + + return retval; +} + +static size_t +ext3_xattr_list_nfs4acl(struct inode *inode, char *list, size_t list_len, + const char *name, size_t name_len) +{ + const size_t size = sizeof(NFS4ACL_XATTR); + + if (!test_opt(inode->i_sb, NFS4ACL)) + return 0; + if (list && size <= list_len) + memcpy(list, NFS4ACL_XATTR, size); + return size; +} + +static int +ext3_xattr_get_nfs4acl(struct inode *inode, const char *name, void *buffer, + size_t buffer_size) +{ + const int name_index = EXT3_XATTR_INDEX_NFS4ACL; + + if (!test_opt(inode->i_sb, NFS4ACL)) + return -EOPNOTSUPP; + if (strcmp(name, "") != 0) + return -EINVAL; + return ext3_xattr_get(inode, name_index, "", buffer, buffer_size); +} + +#ifdef NFS4ACL_DEBUG +static size_t +ext3_xattr_list_masked_nfs4acl(struct inode *inode, char *list, size_t list_len, + const char *name, size_t name_len) +{ + return 0; +} + +static int +ext3_xattr_get_masked_nfs4acl(struct inode *inode, const char *name, + void *buffer, size_t buffer_size) +{ + const int name_index = EXT3_XATTR_INDEX_NFS4ACL; + struct nfs4acl *acl; + void *xattr; + size_t size; + int error; + + if (!test_opt(inode->i_sb, NFS4ACL)) + return -EOPNOTSUPP; + if (strcmp(name, "") != 0) + return -EINVAL; + error = ext3_xattr_get(inode, name_index, "", NULL, 0); + if (error <= 0) + return error; + xattr = kmalloc(error, GFP_KERNEL); + if (!xattr) + return -ENOMEM; + error = ext3_xattr_get(inode, name_index, "", xattr, error); + if (error <= 0) + return error; + acl = nfs4acl_from_xattr(xattr, error); + kfree(xattr); + if (IS_ERR(acl)) + return PTR_ERR(acl); + error = nfs4acl_apply_masks(&acl, test_opt(inode->i_sb, NFS4ACL_MAX)); + if (error) { + nfs4acl_free(acl); + return error; + } + xattr = nfs4acl_to_xattr(acl, &size); + nfs4acl_free(acl); + if (IS_ERR(xattr)) + return PTR_ERR(xattr); + if (buffer) { + if (size > buffer_size) + return -ERANGE; + memcpy(buffer, xattr, size); + } + kfree(xattr); + return size; +} +#endif + +static int +ext3_set_mode(struct inode *inode, int mode) +{ + handle_t *handle = ext3_journal_start(inode, + EXT3_DATA_TRANS_BLOCKS(inode->i_sb)); + int error; + + error = PTR_ERR(handle); + if (!IS_ERR(handle)) { + inode->i_mode = mode; + error = ext3_mark_inode_dirty(handle, inode); + ext3_journal_stop(handle); + } + return error; +} + +static int +ext3_xattr_set_nfs4acl(struct inode *inode, const char *name, + const void *value, size_t size, int flags) +{ + const int name_index = EXT3_XATTR_INDEX_NFS4ACL; + struct nfs4acl *acl = NULL; + int retval; + + if (!test_opt(inode->i_sb, NFS4ACL)) + return -EOPNOTSUPP; + if (strcmp(name, "") != 0) + return -EINVAL; + if (value) { + acl = nfs4acl_from_xattr(value, size); + if (IS_ERR(acl)) + return PTR_ERR(acl); + + retval = ext3_set_mode(inode, (inode->i_mode & ~S_IRWXUGO) | + nfs4acl_masks_to_mode(acl)); + if (retval) + goto out; + } + retval = ext3_xattr_set(inode, name_index, "", value, size, flags); +out: + nfs4acl_free(acl); + return retval; +} + +struct xattr_handler ext3_nfs4acl_xattr_handler = { + .prefix = NFS4ACL_XATTR, + .list = ext3_xattr_list_nfs4acl, + .get = ext3_xattr_get_nfs4acl, + .set = ext3_xattr_set_nfs4acl, +}; + +#ifdef NFS4ACL_DEBUG +struct xattr_handler ext3_masked_nfs4acl_xattr_handler = { + .prefix = "system.masked-nfs4acl", + .list = ext3_xattr_list_masked_nfs4acl, + .get = ext3_xattr_get_masked_nfs4acl, + .set = ext3_xattr_set_nfs4acl, +}; +#endif Index: linux-2.6.18-rc6/fs/ext3/nfs4acl.h =================================================================== --- /dev/null +++ linux-2.6.18-rc6/fs/ext3/nfs4acl.h @@ -0,0 +1,26 @@ +#ifndef __FS_EXT3_NFS4ACL_H +#define __FS_EXT3_NFS4ACL_H + +#ifdef CONFIG_EXT3_FS_NFS4ACL + +extern int ext3_nfs4acl_permission(struct inode *, int); +extern int ext3_nfs4acl_init(handle_t *, struct inode *, struct inode *); +extern int ext3_nfs4acl_chmod(struct inode *); + +#else /* CONFIG_FS_EXT3_NFS4ACL */ + +static inline int +ext3_nfs4acl_init(handle_t *handle, struct inode *inode, struct inode *dir) +{ + return 0; +} + +static inline int +ext3_nfs4acl_chmod(struct inode *inode) +{ + return 0; +} + +#endif /* CONFIG_FS_EXT3_NFS4ACL */ + +#endif /* __FS_EXT3_NFS4ACL_H */ Index: linux-2.6.18-rc6/fs/ext3/acl.c =================================================================== --- linux-2.6.18-rc6.orig/fs/ext3/acl.c +++ linux-2.6.18-rc6/fs/ext3/acl.c @@ -282,7 +282,7 @@ ext3_set_acl(handle_t *handle, struct in return error; } -static int +int ext3_check_acl(struct inode *inode, int mask) { struct posix_acl *acl = ext3_get_acl(inode, ACL_TYPE_ACCESS); @@ -298,12 +298,6 @@ ext3_check_acl(struct inode *inode, int return -EAGAIN; } -int -ext3_permission(struct inode *inode, int mask, struct nameidata *nd) -{ - return generic_permission(inode, mask, ext3_check_acl); -} - /* * Initialize the ACLs of a new inode. Called from ext3_new_inode. * Index: linux-2.6.18-rc6/fs/ext3/acl.h =================================================================== --- linux-2.6.18-rc6.orig/fs/ext3/acl.h +++ linux-2.6.18-rc6/fs/ext3/acl.h @@ -58,13 +58,13 @@ static inline int ext3_acl_count(size_t #define EXT3_ACL_NOT_CACHED ((void *)-1) /* acl.c */ -extern int ext3_permission (struct inode *, int, struct nameidata *); +extern int ext3_check_acl(struct inode *, int); extern int ext3_acl_chmod (struct inode *); extern int ext3_init_acl (handle_t *, struct inode *, struct inode *); #else /* CONFIG_EXT3_FS_POSIX_ACL */ #include -#define ext3_permission NULL +#define ext3_check_acl NULL static inline int ext3_acl_chmod(struct inode *inode) Index: linux-2.6.18-rc6/fs/ext3/namei.h =================================================================== --- linux-2.6.18-rc6.orig/fs/ext3/namei.h +++ linux-2.6.18-rc6/fs/ext3/namei.h @@ -5,4 +5,5 @@ * */ +extern int ext3_permission (struct inode *, int, struct nameidata *); extern struct dentry *ext3_get_parent(struct dentry *child); Index: linux-2.6.18-rc6/fs/nfs4acl_base.c =================================================================== --- /dev/null +++ linux-2.6.18-rc6/fs/nfs4acl_base.c @@ -0,0 +1,532 @@ +/* + * Copyright (C) 2006 Andreas Gruenbacher + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include + +MODULE_LICENSE("GPL"); + +/** + * ACL entries that have ACE4_SPECIAL_WHO set in ace->e_flags use the + * pointer values of these constants in ace->u.e_who to avoid massive + * amounts of string comparisons. + */ + +const char *nfs4ace_owner_who = "OWNER@"; +const char *nfs4ace_group_who = "GROUP@"; +const char *nfs4ace_everyone_who = "EVERYONE@"; + +EXPORT_SYMBOL_GPL(nfs4ace_owner_who); +EXPORT_SYMBOL_GPL(nfs4ace_group_who); +EXPORT_SYMBOL_GPL(nfs4ace_everyone_who); + +/** + * nfs4acl_alloc - allocate an acl + * @count: number of entries + */ +struct nfs4acl * +nfs4acl_alloc(int count) +{ + size_t size = sizeof(struct nfs4acl) + count * sizeof(struct nfs4ace); + struct nfs4acl *acl = kmalloc(size, GFP_KERNEL); + + if (acl) { + memset(acl, 0, size); + acl->a_count = count; + } + return acl; +} + +#if 0 +/** + * nfs4acl_clone - create a copy of an acl + */ +struct nfs4acl * +nfs4acl_clone(struct nfs4acl *acl) +{ + struct nfs4acl *dup = nfs4acl_alloc(acl->a_count); + + if (acl) { + memcpy(dup, acl, sizeof(struct nfs4acl) + + sizeof(struct nfs4ace) * acl->a_count); + } + return acl; +} +EXPORT_SYMBOL(nfs4acl_clone); +#endif + +/** + * The POSIX permissions are supersets of the below mask flags. + * + * The ACE4_READ_ATTRIBUTES and ACE4_READ_ACL flags are always granted + * in POSIX. The ACE4_SYNCHRONIZE flag has no meaning under POSIX. We + * make sure that we do not mask them if they are set, so that users who + * rely on these flags won't get confused. + */ +#define ACE4_GENERIC_READ ( \ + ACE4_READ_DATA) +#define ACE4_GENERIC_WRITE ( \ + ACE4_WRITE_DATA | \ + ACE4_APPEND_DATA | \ + ACE4_DELETE_CHILD) +#define ACE4_GENERIC_EXEC ( \ + ACE4_EXECUTE) +#define ACE4_ALWAYS_ALLOWED ( \ + ACE4_SYNCHRONIZE | \ + ACE4_READ_ATTRIBUTES | \ + ACE4_READ_ACL ) + +static int +nfs4acl_mask_to_mode(unsigned int mask) +{ + int mode = 0; + + if (mask & ACE4_GENERIC_READ) + mode |= MAY_READ; + if (mask & ACE4_GENERIC_WRITE) + mode |= MAY_WRITE; + if (mask & ACE4_GENERIC_EXEC) + mode |= MAY_EXEC; + + return mode; +} + +/** + * nfs4acl_masks_to_mode - compute file mode permission bits from file masks + * + * Compute the file mode permission bits from the file masks in the acl. + */ +int +nfs4acl_masks_to_mode(struct nfs4acl *acl) +{ + return nfs4acl_mask_to_mode(acl->a_owner_mask) << 6 | + nfs4acl_mask_to_mode(acl->a_group_mask) << 3 | + nfs4acl_mask_to_mode(acl->a_other_mask); +} +EXPORT_SYMBOL_GPL(nfs4acl_masks_to_mode); + +static unsigned int +nfs4acl_mode_to_mask(mode_t mode) +{ + unsigned int mask = 0; + + if (mode & MAY_READ) + mask |= ACE4_ALWAYS_ALLOWED | ACE4_GENERIC_READ; + if (mode & MAY_WRITE) + mask |= ACE4_ALWAYS_ALLOWED | ACE4_GENERIC_WRITE; + if (mode & MAY_EXEC) + mask |= ACE4_ALWAYS_ALLOWED | ACE4_GENERIC_EXEC; + + return mask; +} + +/** + * nfs4acl_chmod - update the file masks to reflect the new mode + * @mode: file mode permission bits to apply to the @acl + * + * Converts the mask flags corresponding to the owner, group, and other file + * permissions and updates the file masks in @acl accordingly. + */ +void +nfs4acl_chmod(struct nfs4acl *acl, mode_t mode) +{ + acl->a_owner_mask = nfs4acl_mode_to_mask(mode >> 6); + acl->a_group_mask = nfs4acl_mode_to_mask(mode >> 3); + acl->a_other_mask = nfs4acl_mode_to_mask(mode); +} +EXPORT_SYMBOL_GPL(nfs4acl_chmod); + +/** + * nfs4acl_want_to_mask - convert permission want argument to a mask + * @want: @want argument of the permission inode operation + */ +static unsigned int +nfs4acl_want_to_mask(int want) +{ + unsigned int mask = 0; + + /** + * FIXME: This needs more changes in the vfs so that the vfs + * will ask the permission inode operations what it actually + * wants to do. Until then, we can only differentiate MAY_READ, + * MAY_APPEND (for regular files), MAY_WRITE, and MAY_EXEC. + */ + + if (want & MAY_READ) + mask |= ACE4_READ_DATA; + if (want & MAY_APPEND) + mask |= ACE4_APPEND_DATA; + else if (want & MAY_WRITE) + mask |= ACE4_WRITE_DATA; + if (want & MAY_EXEC) + mask |= ACE4_EXECUTE; + + return mask; +} + +/** + * __nfs4acl_permission - permission check algorithm without masking + * @inode: inode to check permissions for + * @acl: nfs4 acl if the inode + * @mask: requested mask flags + * @in_group_class: returns if the process matches a group class acl entry + * + * Checks if the current process is granted @mask flags in @acl. The + * @inode determines the file type, current owner and owning group. + * + * In addition to checking permissions, this function checks if the + * process matches a group class acl entry even after the result of the + * permission check has been determined: for non-owners, this property + * determines which file mask applies to the acl. + */ +static int +__nfs4acl_permission(struct inode *inode, const struct nfs4acl *acl, + unsigned int mask, int *in_group_class) +{ + struct nfs4ace *ace; + int retval = -EACCES; + + nfs4acl_for_each_entry(ace, acl) { + unsigned int ace_mask = ace->e_mask; + + if (nfs4ace_is_inherit_only(ace)) + continue; + + /** + * Check if the ACE matches the process. Remember when a + * group class ACE matches the process. + * + * For ACEs other than OWNER@ and EVERYONE@, we apply the + * file group mask: this would not strictly be necessary for + * access checking, but it saves us from allowing accesses + * that the ACL that nfs4acl_apply_masks() would deny: + * otherwise, ACLs like 'group@:rw::allow' with mode 0600 + * would allow processes that match the owner and the + * owning group rw access. This scenario cannot be expressed + * as an ACL. + */ + if (nfs4ace_is_owner(ace)) { + if (current->fsuid != inode->i_uid) + continue; + } else if (nfs4ace_is_group(ace)) { + if (!in_group_p(inode->i_gid)) + continue; + ace_mask &= acl->a_group_mask; + *in_group_class = 1; + } else if (nfs4ace_is_unix_id(ace)) { + if (ace->e_flags & ACE4_IDENTIFIER_GROUP) { + if (!in_group_p(ace->u.e_id)) + continue; + } else { + if (current->fsuid != ace->u.e_id) + continue; + } + ace_mask &= acl->a_group_mask; + *in_group_class = 1; + } else if (!nfs4ace_is_everyone(ace)) + continue; + + /* Check which mask flags the ACE allows or denies. */ + if (nfs4ace_is_allow(ace)) { + if (!S_ISDIR(inode->i_mode)) { + /* Everybody who is allowed ACE4_WRITE_DATA is + also allowed ACE4_APPEND_DATA. */ + if (ace_mask & ACE4_WRITE_DATA) + ace_mask |= ACE4_APPEND_DATA; + } + mask &= ~ace_mask; + if (mask == 0) { + retval = 0; + goto check_remaining_aces; + } + } else if (nfs4ace_is_deny(ace)) { + unsigned int ace_mask = ace->e_mask; + + if (!S_ISDIR(inode->i_mode)) { + /* Everybody who is denied ACE4_APPEND_DATA is + also denied ACE4_WRITE_DATA. */ + if (ace_mask & ACE4_APPEND_DATA) + ace_mask |= ACE4_WRITE_DATA; + } + if (mask & ace_mask) + goto check_remaining_aces; + } + } + return retval; + +check_remaining_aces: + /* Check if any of the remaining group class ACEs match the process. */ + for (ace++; ace != acl->a_entries + acl->a_count; ace++) { + if (nfs4ace_is_group(ace)) { + if (in_group_p(inode->i_gid)) + *in_group_class = 1; + } else if (nfs4ace_is_unix_id(ace)) { + if (ace->e_flags & ACE4_IDENTIFIER_GROUP) { + if (in_group_p(ace->u.e_id)) + *in_group_class = 1; + } else { + if (current->fsuid == ace->u.e_id) + *in_group_class = 1; + } + } + } + return retval; +} + +/** + * nfs4acl_permission - permission check algorithm with masking + * @inode: inode to check permissions for + * @acl: nfs4 acl if the inode + * @want: requested access (permission want argument) + * @write_through: assume that the masks "writes through" to the acl + * + * Checks if the current process is granted @mask flags in @acl. If + * @write_through is true, then the OWNER@ is always granted the owner + * mask, the GROUP@ is always granted the group mask, and EVERYONE@ is + * always granted the other mask. Otherwise, the OWNER@, GROUP@, and + * EVERYONE@ are only granted mask flags which they are granted both in + * the @acl and in the file mask that applies to the process (which + * depends on which file class the process is in). + */ +int +nfs4acl_permission(struct inode *inode, const struct nfs4acl *acl, int want, + int write_through) +{ + unsigned int mask = nfs4acl_want_to_mask(want); + int in_group_class = 0, error; + + error = __nfs4acl_permission(inode, acl, mask, &in_group_class); + if (write_through) { + if (current->fsuid == inode->i_uid || + in_group_p(inode->i_gid) || + !in_group_class) + error = 0; /* only check the mask */ + } + if (error) + goto capability_check; + if (current->fsuid == inode->i_uid) { + if (mask & ~acl->a_owner_mask) + goto capability_check; + } else if (in_group_p(inode->i_gid) || in_group_class) { + if (mask & ~acl->a_group_mask) + goto capability_check; + } else { + if (mask & ~acl->a_other_mask) + goto capability_check; + } + return 0; + +capability_check: + /* + * Read/write DACs are always overridable. + * Executable DACs are overridable if at least one exec bit is set. + */ + if (!(want & MAY_EXEC) || + (inode->i_mode & S_IXUGO) || S_ISDIR(inode->i_mode)) + if (capable(CAP_DAC_OVERRIDE)) + return 0; + + /* + * Searching includes executable on directories, else just read. + */ + if (want == MAY_READ || (S_ISDIR(inode->i_mode) && !(want & MAY_WRITE))) + if (capable(CAP_DAC_READ_SEARCH)) + return 0; + return -EACCES; + +} +EXPORT_SYMBOL_GPL(nfs4acl_permission); + +/** + * nfs4ace_is_same_who - do both acl entries refer to the same identifier? + */ +int +nfs4ace_is_same_who(struct nfs4ace *a, struct nfs4ace *b) +{ +#define WHO_FLAGS (ACE4_SPECIAL_WHO | ACE4_IDENTIFIER_GROUP) + if ((a->e_flags & WHO_FLAGS) != (b->e_flags & WHO_FLAGS)) + return 0; + if (a->e_flags & ACE4_SPECIAL_WHO) + return a->u.e_who == b->u.e_who; + else + return a->u.e_id == b->u.e_id; +#undef WHO_FLAGS +} + +/** + * nfs4acl_allowed_to_who - mask flags allowed to a specific who value + * + * Computes the mask values allowed to a specific who value, taking + * EVERYONE@ entries into account. + */ +static unsigned int +nfs4acl_allowed_to_who(struct nfs4acl *acl, struct nfs4ace *who) +{ + struct nfs4ace *ace; + unsigned int allowed = 0; + + nfs4acl_for_each_entry_reverse(ace, acl) { + if (nfs4ace_is_inherit_only(ace)) + continue; + if (nfs4ace_is_same_who(ace, who) || + nfs4ace_is_everyone(ace)) { + if (nfs4ace_is_allow(ace)) + allowed |= ace->e_mask; + else if (nfs4ace_is_deny(ace)) + allowed &= ~ace->e_mask; + } + } + return allowed; +} + +/** + * nfs4acl_compute_max_masks - compute upper bound masks + * + * Computes upper bound owner, group, and other masks so that none of + * the mask flags allowed by the acl are disabled (for any choice of the + * file owner or group membership). + */ +static void +nfs4acl_compute_max_masks(struct nfs4acl *acl) +{ + struct nfs4ace *ace; + + acl->a_owner_mask = 0; + acl->a_group_mask = 0; + acl->a_other_mask = 0; + + nfs4acl_for_each_entry_reverse(ace, acl) { + if (nfs4ace_is_inherit_only(ace)) + continue; + + if (nfs4ace_is_owner(ace)) { + if (nfs4ace_is_allow(ace)) + acl->a_owner_mask |= ace->e_mask; + else if (nfs4ace_is_deny(ace)) + acl->a_owner_mask &= ~ace->e_mask; + } else if (nfs4ace_is_everyone(ace)) { + if (nfs4ace_is_allow(ace)) { + struct nfs4ace who = { + .e_flags = ACE4_SPECIAL_WHO, + .u.e_who = nfs4ace_group_who, + }; + + acl->a_other_mask |= ace->e_mask; + acl->a_group_mask |= + nfs4acl_allowed_to_who(acl, &who); + acl->a_owner_mask |= ace->e_mask; + } else if (nfs4ace_is_deny(ace)) { + acl->a_other_mask &= ~ace->e_mask; + acl->a_group_mask &= ~ace->e_mask; + acl->a_owner_mask &= ~ace->e_mask; + } + } else { + if (nfs4ace_is_allow(ace)) { + unsigned int mask = + nfs4acl_allowed_to_who(acl, ace); + + acl->a_group_mask |= mask; + acl->a_owner_mask |= mask; + } + } + } +} + +/** + * nfs4acl_inherit - compute the acl a new file will inherit + * @dir_acl: acl of the containing direcory + * @mode: file type and create mode of the new file + * @write_through: assume that the mode "writes through" to the acl + * + * Given the containing directory's acl, this function will compute the + * acl that new files in that directory will inherit, or %NULL if + * @dir_acl does not contain acl entries inheritable by this file. + * + * Without @write_through, the file masks in the returned acl are set to + * the intersection of the create mode and the maximum permissions + * allowed to each file class. With @write_through, the file masks are + * set to the create mode. + */ +struct nfs4acl * +nfs4acl_inherit(struct nfs4acl *dir_acl, mode_t mode, int write_through) +{ + struct nfs4ace *dir_ace; + struct nfs4acl *acl; + struct nfs4ace *ace; + int count = 0; + + if (S_ISDIR(mode)) { + nfs4acl_for_each_entry(dir_ace, dir_acl) { + if (!nfs4ace_is_inheritable(dir_ace)) + continue; + count++; + } + if (!count) + return NULL; + acl = nfs4acl_alloc(count); + if (!acl) + return ERR_PTR(-ENOMEM); + ace = acl->a_entries; + nfs4acl_for_each_entry(dir_ace, dir_acl) { + if (!nfs4ace_is_inheritable(dir_ace)) + continue; + memcpy(ace, dir_ace, sizeof(struct nfs4ace)); + if (dir_ace->e_flags & ACE4_NO_PROPAGATE_INHERIT_ACE) + nfs4ace_clear_inheritance_flags(ace); + if ((dir_ace->e_flags & ACE4_FILE_INHERIT_ACE) && + !(dir_ace->e_flags & ACE4_DIRECTORY_INHERIT_ACE)) + ace->e_flags |= ACE4_INHERIT_ONLY_ACE; + ace++; + } + } else { + nfs4acl_for_each_entry(dir_ace, dir_acl) { + if (!(dir_ace->e_flags & ACE4_FILE_INHERIT_ACE)) + continue; + count++; + } + if (!count) + return NULL; + acl = nfs4acl_alloc(count); + if (!acl) + return ERR_PTR(-ENOMEM); + ace = acl->a_entries; + nfs4acl_for_each_entry(dir_ace, dir_acl) { + if (!(dir_ace->e_flags & ACE4_FILE_INHERIT_ACE)) + continue; + memcpy(ace, dir_ace, sizeof(struct nfs4ace)); + nfs4ace_clear_inheritance_flags(ace); + ace++; + } + } + + /* The maximum max flags that the owner, group, and other classes + are allowed. */ + if (write_through) { + acl->a_owner_mask = ACE4_VALID_MASK; + acl->a_group_mask = ACE4_VALID_MASK; + acl->a_other_mask = ACE4_VALID_MASK; + + mode &= ~current->fs->umask; + } else + nfs4acl_compute_max_masks(acl); + + /* Apply the create mode. */ + acl->a_owner_mask &= nfs4acl_mode_to_mask(mode >> 6); + acl->a_group_mask &= nfs4acl_mode_to_mask(mode >> 3); + acl->a_other_mask &= nfs4acl_mode_to_mask(mode); + + return acl; +} +EXPORT_SYMBOL_GPL(nfs4acl_inherit); Index: linux-2.6.18-rc6/fs/nfs4acl_compat.c =================================================================== --- /dev/null +++ linux-2.6.18-rc6/fs/nfs4acl_compat.c @@ -0,0 +1,743 @@ +/* + * Copyright (C) 2006 Andreas Gruenbacher + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include + +/** + * struct nfs4acl_alloc - remember how many entries are actually allocated + * @acl: acl with a_count <= @count + * @count: the actual number of entries allocated in @acl + * + * We pass around this structure while modifying an acl, so that we do + * not have to reallocate when we remove existing entries followed by + * adding new entries. + */ +struct nfs4acl_alloc { + struct nfs4acl *acl; + unsigned int count; +}; + +/** + * nfs4acl_delete_entry - delete an entry in an acl + * @x: acl and number of allocated entries + * @ace: an entry in @x->acl + * + * Updates @ace so that it points to the entry before the deleted entry + * on return. (When deleting the first entry, @ace will point to the + * (non-existant) entry before the first entry). This behavior is the + * expected behavior when deleting entries while forward iterating over + * an acl. + */ +static void +nfs4acl_delete_entry(struct nfs4acl_alloc *x, struct nfs4ace **ace) +{ + void *end = x->acl->a_entries + x->acl->a_count; + + memmove(*ace, *ace + 1, end - (void *)(*ace + 1)); + (*ace)--; + x->acl->a_count--; +} + +/** + * nfs4acl_insert_entry - insert an entry in an acl + * @x: acl and number of allocated entries + * @ace: entry before which the new entry shall be inserted + * + * Insert a new entry in @x->acl at position @ace, and zero-initialize + * it. This may require reallocating @x->acl. + */ +static int +nfs4acl_insert_entry(struct nfs4acl_alloc *x, struct nfs4ace **ace) +{ + if (x->count == x->acl->a_count) { + int n = *ace - x->acl->a_entries; + struct nfs4acl *acl2; + + acl2 = nfs4acl_alloc(x->acl->a_count + 1); + if (!acl2) + return -ENOMEM; + acl2->a_owner_mask = x->acl->a_owner_mask; + acl2->a_group_mask = x->acl->a_group_mask; + acl2->a_other_mask = x->acl->a_other_mask; + memcpy(acl2->a_entries, x->acl->a_entries, + n * sizeof(struct nfs4ace)); + memcpy(acl2->a_entries + n + 1, *ace, + (x->acl->a_count - n) * sizeof(struct nfs4ace)); + kfree(x->acl); + x->acl = acl2; + x->count = acl2->a_count; + *ace = acl2->a_entries + n; + } else { + void *end = x->acl->a_entries + x->acl->a_count; + + memmove(*ace + 1, *ace, end - (void *)*ace); + x->acl->a_count++; + } + memset(*ace, 0, sizeof(struct nfs4ace)); + return 0; +} + +/** + * nfs4ace_change_mask - change the mask in @ace to @mask + * @x: acl and number of allocated entries + * @ace: entry to modify + * @mask: new mask for @ace + * + * Set the effective mask of @ace to @mask. This will require splitting + * off a separate acl entry if @ace is inheritable. In that case, the + * effective- only acl entry is inserted after the inheritable acl + * entry, end the inheritable acl entry is set to inheritable-only. If + * @mode is 0, either set the original acl entry to inheritable-only if + * it was inheritable, or remove it otherwise. The returned @ace points + * to the modified or inserted effective-only acl entry if that entry + * exists, to the entry that has become inheritable-only, or else to the + * previous entry in the acl. This is the expected behavior when + * modifying masks while forward iterating over an acl. + */ +static int +nfs4ace_change_mask(struct nfs4acl_alloc *x, struct nfs4ace **ace, + unsigned int mask) +{ + if (mask && (*ace)->e_mask == mask) + return 0; + if (mask == 0) { + if (nfs4ace_is_inheritable(*ace)) + (*ace)->e_flags |= ACE4_INHERIT_ONLY_ACE; + else + nfs4acl_delete_entry(x, ace); + } else { + if (nfs4ace_is_inheritable(*ace)) { + if (nfs4acl_insert_entry(x, ace)) + return -ENOMEM; + memcpy(*ace, *ace + 1, sizeof(struct nfs4ace)); + (*ace)->e_flags |= ACE4_INHERIT_ONLY_ACE; + (*ace)++; + nfs4ace_clear_inheritance_flags(*ace); + } + (*ace)->e_mask = mask; + } + return 0; +} + +/** + * nfs4acl_move_everyone_aces_down - move everyone@ acl entries to the end + * @x: acl and number of allocated entries + * + * Move all everyone acl entries to the bottom of the acl so that only a + * single everyone@ allow acl entry remains at the end, and update the + * mask fields of all acl entries on the way. If everyone@ is not + * granted any permissions, no empty everyone@ acl entry is inserted. + * + * This transformation does not modify the permissions that the acl + * grants, but we need it to simplify successive transformations. + */ +static int +nfs4acl_move_everyone_aces_down(struct nfs4acl_alloc *x) +{ + struct nfs4ace *ace; + unsigned int allowed = 0, denied = 0; + + nfs4acl_for_each_entry(ace, x->acl) { + if (nfs4ace_is_inherit_only(ace)) + continue; + if (nfs4ace_is_everyone(ace)) { + if (nfs4ace_is_allow(ace)) + allowed |= (ace->e_mask & ~denied); + else if (nfs4ace_is_deny(ace)) + denied |= (ace->e_mask & ~allowed); + else + continue; + if (nfs4ace_change_mask(x, &ace, 0)) + return -ENOMEM; + } else { + if (nfs4ace_is_allow(ace)) { + if (nfs4ace_change_mask(x, &ace, allowed | + (ace->e_mask & ~denied))) + return -ENOMEM; + } else if (nfs4ace_is_deny(ace)) { + if (nfs4ace_change_mask(x, &ace, denied | + (ace->e_mask & ~allowed))) + return -ENOMEM; + } + } + } + if (allowed) { + if (nfs4acl_insert_entry(x, &ace)) + return -ENOMEM; + ace->e_type = ACE4_ACCESS_ALLOWED_ACE_TYPE; + ace->e_flags = ACE4_SPECIAL_WHO; + ace->e_mask = allowed; + ace->u.e_who = nfs4ace_everyone_who; + } + return 0; +} + +/** + * __nfs4acl_propagate_everyone - propagate everyone@ mask flags up for @who + * @x: acl and number of allocated entries + * @who: identifier to propagate mask flags for + * @allow: mask flags to propagate up + * + * Propagate mask flags from the trailing everyone@ allow acl entry up + * for the specified @who. + * + * The idea here is to precede the trailing EVERYONE@ ALLOW entry by an + * additional @who ALLOW entry, but with the following optimizations: + * (1) we don't bother setting any flags in the new @who ALLOW entry + * that has already been allowed or denied by a previous @who entry, (2) + * we merge the new @who entry with a previous @who entry if there is + * such a previous @who entry and there are no intervening DENY entries + * with mask flags that overlap the flags we care about. + */ +static int +__nfs4acl_propagate_everyone(struct nfs4acl_alloc *x, struct nfs4ace *who, + unsigned int allow) +{ + struct nfs4ace *allow_last = NULL, *ace; + + /* Remove the mask flags from allow that are already determined for + this who value, and figure out if there is an ALLOW entry for + this who value that is "reachable" from the trailing EVERYONE@ + ALLOW ACE. */ + nfs4acl_for_each_entry(ace, x->acl) { + if (nfs4ace_is_inherit_only(ace)) + continue; + if (nfs4ace_is_allow(ace)) { + if (nfs4ace_is_same_who(ace, who)) { + allow &= ~ace->e_mask; + allow_last = ace; + } + } else if (nfs4ace_is_deny(ace)) { + if (nfs4ace_is_same_who(ace, who)) + allow &= ~ace->e_mask; + if (allow & ace->e_mask) + allow_last = NULL; + } + } + + if (allow) { + if (allow_last) + return nfs4ace_change_mask(x, &allow_last, + allow_last->e_mask | allow); + else { + struct nfs4ace who_copy; + + ace = x->acl->a_entries + x->acl->a_count - 1; + memcpy(&who_copy, who, sizeof(struct nfs4ace)); + if (nfs4acl_insert_entry(x, &ace)) + return -ENOMEM; + memcpy(ace, &who_copy, sizeof(struct nfs4ace)); + ace->e_type = ACE4_ACCESS_ALLOWED_ACE_TYPE; + nfs4ace_clear_inheritance_flags(ace); + ace->e_mask = allow; + } + } + return 0; +} + +/** + * nfs4acl_propagate_everyone - propagate everyone@ mask flags up the acl + * @x: acl and number of allocated entries + * + * Make sure for owner@, group@, and all other users, groups, and + * special identifiers that they are allowed or denied all permissions + * that are granted be the trailing everyone@ acl entry. If they are + * not, try to add the missing permissions to existing allow acl entries + * for those users, or introduce additional acl entries if that is not + * possible. + * + * We do this so that no mask flags will get lost when finally applying + * the file masks to the acl entries: otherwise, with an other file mask + * that is more restrictive than the owner and/or group file mask, mask + * flags that were allowed to processes in the owner and group classes + * and that the other mask denies would be lost. For example, the + * following two acls show the problem when mode 0664 is applied to + * them: + * + * masking without propagation (wrong) + * =========================================================== + * joe:r::allow => joe:r::allow + * everyone@:rwx::allow => everyone@:r::allow + * ----------------------------------------------------------- + * joe:w::deny => joe:w::deny + * everyone@:rwx::allow everyone@:r::allow + * + * Note that the permissions of joe end up being more restrictive than + * what the acl would allow when first computing the allowed flags and + * then applying the respective mask. With propagation of permissions, + * we get: + * + * masking after propagation (correct) + * =========================================================== + * joe:r::allow => joe:rw::allow + * owner@:rw::allow + * group@:rw::allow + * everyone@:rwx::allow everyone@:r::allow + * ----------------------------------------------------------- + * joe:w::deny => owner@:x::deny + * joe:w::deny + * owner@:rw::allow + * owner@:rw::allow + * joe:r::allow + * everyone@:rwx::allow everyone@:r::allow + * + * The examples show the acls that would result from propagation with no + * masking performed. In fact, we do apply the respective mask to the + * acl entries before computing the propagation because this will save + * us from adding acl entries that would end up with empty mask fields + * after applying the masks. + * + * It is ensured that no more than one entry will be inserted for each + * who value, no matter how many entries each who value has already. + */ +static int +nfs4acl_propagate_everyone(struct nfs4acl_alloc *x, int write_through) +{ + struct nfs4ace who = { .e_flags = ACE4_SPECIAL_WHO }; + struct nfs4ace *ace; + unsigned int owner_allow, group_allow; + int error; + + if (!((x->acl->a_owner_mask | x->acl->a_group_mask) & + ~x->acl->a_other_mask)) + return 0; + if (!x->acl->a_count) + return 0; + ace = x->acl->a_entries + x->acl->a_count - 1; + if (nfs4ace_is_inherit_only(ace) || !nfs4ace_is_everyone(ace)) + return 0; + if (!(ace->e_mask & ~x->acl->a_other_mask)) { + /* None of the allowed permissions will get masked. */ + return 0; + } + owner_allow = ace->e_mask & x->acl->a_owner_mask; + group_allow = ace->e_mask & x->acl->a_group_mask; + + /* Propagate everyone@ permissions through to owner@. */ + if (owner_allow && !write_through && + (x->acl->a_owner_mask & ~x->acl->a_other_mask)) { + who.u.e_who = nfs4ace_owner_who; + error = __nfs4acl_propagate_everyone(x, &who, owner_allow); + if (error) + return -ENOMEM; + } + + if (group_allow && (x->acl->a_group_mask & ~x->acl->a_other_mask)) { + int n; + + if (!write_through) { + /* Propagate everyone@ permissions through to group@. */ + who.u.e_who = nfs4ace_group_who; + error = __nfs4acl_propagate_everyone(x, &who, + group_allow); + if (error) + return -ENOMEM; + } + + /* Start from the entry before the trailing EVERYONE@ ALLOW + entry. We will not hit EVERYONE@ entries in the loop. */ + for (n = x->acl->a_count - 2; n != -1; n--) { + ace = x->acl->a_entries + n; + + if (nfs4ace_is_inherit_only(ace) || + nfs4ace_is_owner(ace) || + nfs4ace_is_group(ace)) + continue; + if (nfs4ace_is_allow(ace) || nfs4ace_is_deny(ace)) { + /* Any inserted entry will end up below the + current entry. */ + error = __nfs4acl_propagate_everyone(x, ace, + group_allow); + if (error) + return -ENOMEM; + } + } + } + return 0; +} + +/** + * __nfs4acl_apply_masks - apply the masks to the acl entries + * @x: acl and number of allocated entries + * + * Apply the owner file mask to owner@ entries, the intersection of the + * group and other file masks to everyone@ entries, and the group file + * mask to all other entries. + */ +static int +__nfs4acl_apply_masks(struct nfs4acl_alloc *x) +{ + struct nfs4ace *ace; + + nfs4acl_for_each_entry(ace, x->acl) { + unsigned int mask; + + if (nfs4ace_is_inherit_only(ace) || !nfs4ace_is_allow(ace)) + continue; + if (nfs4ace_is_owner(ace)) + mask = x->acl->a_owner_mask; + else if (nfs4ace_is_everyone(ace)) + mask = x->acl->a_other_mask; + else + mask = x->acl->a_group_mask; + if (nfs4ace_change_mask(x, &ace, ace->e_mask & mask)) + return -ENOMEM; + } + return 0; +} + +/** + * nfs4acl_max_allowed - maximum mask flags that anybody is allowed + */ +static unsigned int +nfs4acl_max_allowed(struct nfs4acl *acl) +{ + struct nfs4ace *ace; + unsigned int allowed = 0; + + nfs4acl_for_each_entry_reverse(ace, acl) { + if (nfs4ace_is_inherit_only(ace)) + continue; + if (nfs4ace_is_allow(ace)) + allowed |= ace->e_mask; + else if (nfs4ace_is_deny(ace)) { + if (nfs4ace_is_everyone(ace)) + allowed &= ~ace->e_mask; + } + } + return allowed; +} + +/** + * nfs4acl_isolate_owner_class - limit the owner class to the owner file mask + * @x: acl and number of allocated entries + * + * Make sure the owner class (owner@) is granted no more than the owner + * mask by first checking which permissions anyone is granted, and then + * denying owner@ all permissions beyond that. + */ +static int +nfs4acl_isolate_owner_class(struct nfs4acl_alloc *x) +{ + struct nfs4ace *ace; + unsigned int allowed = 0; + + allowed = nfs4acl_max_allowed(x->acl); + if (allowed & ~x->acl->a_owner_mask) { + /* Figure out if we can update an existig OWNER@ DENY entry. */ + nfs4acl_for_each_entry(ace, x->acl) { + if (nfs4ace_is_inherit_only(ace)) + continue; + if (nfs4ace_is_deny(ace)) { + if (nfs4ace_is_owner(ace)) + break; + } else if (nfs4ace_is_allow(ace)) { + ace = x->acl->a_entries + x->acl->a_count; + break; + } + } + if (ace != x->acl->a_entries + x->acl->a_count) { + if (nfs4ace_change_mask(x, &ace, ace->e_mask | + (allowed & ~x->acl->a_owner_mask))) + return -ENOMEM; + } else { + /* Insert an owner@ deny entry at the front. */ + ace = x->acl->a_entries; + if (nfs4acl_insert_entry(x, &ace)) + return -ENOMEM; + ace->e_type = ACE4_ACCESS_DENIED_ACE_TYPE; + ace->e_flags = ACE4_SPECIAL_WHO; + ace->e_mask = allowed & ~x->acl->a_owner_mask; + ace->u.e_who = nfs4ace_owner_who; + } + } + return 0; +} + +/** + * __nfs4acl_isolate_who - isolate entry from EVERYONE@ ALLOW entry + * @x: acl and number of allocated entries + * @who: identifier to isolate + * @deny: mask flags this identifier should not be allowed + * + * Make sure that @who is not allowed any mask flags in @deny by checking + * which mask flags this identifier is allowed, and adding excess allowed + * mask flags to an existing DENY entry before the trailing EVERYONE@ ALLOW + * entry, or inserting such an entry. + */ +static int +__nfs4acl_isolate_who(struct nfs4acl_alloc *x, struct nfs4ace *who, + unsigned int deny) +{ + struct nfs4ace *ace; + unsigned int allowed = 0, n; + + /* Compute the mask flags granted to this who value. */ + nfs4acl_for_each_entry_reverse(ace, x->acl) { + if (nfs4ace_is_inherit_only(ace)) + continue; + if (nfs4ace_is_same_who(ace, who)) { + if (nfs4ace_is_allow(ace)) + allowed |= ace->e_mask; + else if (nfs4ace_is_deny(ace)) + allowed &= ~ace->e_mask; + deny &= ~ace->e_mask; + } + } + if (!deny) + return 0; + + /* Figure out if we can update an existig DENY entry. Start + from the entry before the trailing EVERYONE@ ALLOW entry. We + will not hit EVERYONE@ entries in the loop. */ + for (n = x->acl->a_count - 2; n != -1; n--) { + ace = x->acl->a_entries + n; + if (nfs4ace_is_inherit_only(ace)) + continue; + if (nfs4ace_is_deny(ace)) { + if (nfs4ace_is_same_who(ace, who)) + break; + } else if (nfs4ace_is_allow(ace) && + (ace->e_mask & deny)) { + n = -1; + break; + } + } + if (n != -1) { + if (nfs4ace_change_mask(x, &ace, ace->e_mask | deny)) + return -ENOMEM; + } else { + /* Insert a eny entry before the trailing EVERYONE@ DENY + entry. */ + struct nfs4ace who_copy; + + ace = x->acl->a_entries + x->acl->a_count - 1; + memcpy(&who_copy, who, sizeof(struct nfs4ace)); + if (nfs4acl_insert_entry(x, &ace)) + return -ENOMEM; + memcpy(ace, &who_copy, sizeof(struct nfs4ace)); + ace->e_type = ACE4_ACCESS_DENIED_ACE_TYPE; + nfs4ace_clear_inheritance_flags(ace); + ace->e_mask = deny; + } + return 0; +} + +/** + * nfs4acl_isolate_group_class - limit the group class to the group file mask + * @x: acl and number of allocated entries + * + * Make sure the group class (all entries except owner@ and everyone@) is + * granted no more than the group mask by inserting DENY entries for group + * class entries where necessary. + */ +static int +nfs4acl_isolate_group_class(struct nfs4acl_alloc *x) +{ + struct nfs4ace who = { + .e_flags = ACE4_SPECIAL_WHO, + .u.e_who = nfs4ace_group_who, + }; + struct nfs4ace *ace; + unsigned int deny; + + if (!x->acl->a_count) + return 0; + ace = x->acl->a_entries + x->acl->a_count - 1; + if (nfs4ace_is_inherit_only(ace) || !nfs4ace_is_everyone(ace)) + return 0; + deny = ace->e_mask & ~x->acl->a_group_mask; + + if (deny) { + unsigned int n; + + if (__nfs4acl_isolate_who(x, &who, deny)) + return -ENOMEM; + + /* Start from the entry before the trailing EVERYONE@ ALLOW + entry. We will not hit EVERYONE@ entries in the loop. */ + for (n = x->acl->a_count - 2; n != -1; n--) { + ace = x->acl->a_entries + n; + + if (nfs4ace_is_inherit_only(ace) || + nfs4ace_is_owner(ace) || + nfs4ace_is_group(ace)) + continue; + if (__nfs4acl_isolate_who(x, ace, deny)) + return -ENOMEM; + } + } + return 0; +} + +/** + * nfs4acl_write_through - grant the full masks to owner@, group@, everyone@ + * + * Make sure that owner, group@, and everyone@ are allowed the full mask + * permissions, and not only the permissions granted both by the acl and + * the masks. + */ +static int +nfs4acl_write_through(struct nfs4acl_alloc *x) +{ + struct nfs4ace *ace; + unsigned int allowed; + + /* Remove all owner@ and group@ ACEs: we re-insert them at the + top. */ + nfs4acl_for_each_entry(ace, x->acl) { + if (nfs4ace_is_inherit_only(ace)) + continue; + if ((nfs4ace_is_owner(ace) || nfs4ace_is_group(ace)) && + nfs4ace_change_mask(x, &ace, 0)) + return -ENOMEM; + } + + /* Insert the everyone@ allow entry at the end, or update the + existing entry. */ + allowed = x->acl->a_other_mask; + if (allowed) { + ace = x->acl->a_entries + x->acl->a_count - 1; + if (x->acl->a_count && nfs4ace_is_everyone(ace) && + !nfs4ace_is_inherit_only(ace)) { + if (nfs4ace_change_mask(x, &ace, allowed)) + return -ENOMEM; + } else { + ace = x->acl->a_entries + x->acl->a_count; + if (nfs4acl_insert_entry(x, &ace)) + return -ENOMEM; + ace->e_type = ACE4_ACCESS_ALLOWED_ACE_TYPE; + ace->e_flags = ACE4_SPECIAL_WHO; + ace->e_mask = allowed; + ace->u.e_who = nfs4ace_everyone_who; + } + } + + /* Compute the permissions that owner@ and group@ are already granted + though the everyone@ allow entry at the end. Note that the acl + contains no owner@ or group@ entries at this point. */ + allowed = 0; + nfs4acl_for_each_entry_reverse(ace, x->acl) { + if (nfs4ace_is_inherit_only(ace)) + continue; + if (nfs4ace_is_allow(ace)) { + if (nfs4ace_is_everyone(ace)) + allowed |= ace->e_mask; + } else if (nfs4ace_is_deny(ace)) + allowed &= ~ace->e_mask; + } + + /* Insert the appropriate group@ allow entry at the front. */ + if (x->acl->a_group_mask & ~allowed) { + ace = x->acl->a_entries; + if (nfs4acl_insert_entry(x, &ace)) + return -ENOMEM; + ace->e_type = ACE4_ACCESS_ALLOWED_ACE_TYPE; + ace->e_flags = ACE4_SPECIAL_WHO; + ace->e_mask = x->acl->a_group_mask /*& ~allowed*/; + ace->u.e_who = nfs4ace_group_who; + } + + /* Insert the appropriate owner@ allow entry at the front. */ + if (x->acl->a_owner_mask & ~allowed) { + ace = x->acl->a_entries; + if (nfs4acl_insert_entry(x, &ace)) + return -ENOMEM; + ace->e_type = ACE4_ACCESS_ALLOWED_ACE_TYPE; + ace->e_flags = ACE4_SPECIAL_WHO; + ace->e_mask = x->acl->a_owner_mask /*& ~allowed*/; + ace->u.e_who = nfs4ace_owner_who; + } + + /* Insert the appropriate owner@ deny entry at the front. */ + allowed = nfs4acl_max_allowed(x->acl); + if (allowed & ~x->acl->a_owner_mask) { + nfs4acl_for_each_entry(ace, x->acl) { + if (nfs4ace_is_inherit_only(ace)) + continue; + if (nfs4ace_is_allow(ace)) { + ace = x->acl->a_entries + x->acl->a_count; + break; + } + if (nfs4ace_is_deny(ace) && nfs4ace_is_owner(ace)) + break; + } + if (ace != x->acl->a_entries + x->acl->a_count) { + if (nfs4ace_change_mask(x, &ace, ace->e_mask | + (allowed & ~x->acl->a_owner_mask))) + return -ENOMEM; + } else { + ace = x->acl->a_entries; + if (nfs4acl_insert_entry(x, &ace)) + return -ENOMEM; + ace->e_type = ACE4_ACCESS_DENIED_ACE_TYPE; + ace->e_flags = ACE4_SPECIAL_WHO; + ace->e_mask = allowed & ~x->acl->a_owner_mask; + ace->u.e_who = nfs4ace_owner_who; + } + } + + return 0; +} + +/** + * nfs4acl_apply_masks - apply the masks to the acl + * + * Apply the masks so that the acl allows no more flags than the + * intersection between the flags that the original acl allows and the + * mask matching the process. + * + * With @write_through, change the acl so that the owner@, group@, and + * everyone@ are always granted the full owner, group, and other masks, + * respectively. + * + * In any case, the resulting acl will allow no more permissions to + * the other class (everyone@) than to the group class. This restriction + * allows us to avoid inserting a variable number of deny entries for + * each group class identifier before the final everyone@ allow entry. + * + * Note: this algorithm may push the number of entries in the acl above + * ACL4_XATTR_MAX_COUNT, so a read-modify-write cycle would fail. + */ +int +nfs4acl_apply_masks(struct nfs4acl **acl, int write_through) +{ + struct nfs4acl_alloc x = { + .acl = *acl, + .count = (*acl)->a_count, + }; + + if (nfs4acl_move_everyone_aces_down(&x) || + nfs4acl_propagate_everyone(&x, write_through) || + __nfs4acl_apply_masks(&x)) + goto out_enomem; + if (write_through) { + if (nfs4acl_write_through(&x) || + nfs4acl_isolate_group_class(&x)) + goto out_enomem; + } else { + if (nfs4acl_isolate_owner_class(&x) || + nfs4acl_isolate_group_class(&x)) + goto out_enomem; + } + *acl = x.acl; + return 0; + +out_enomem: + *acl = x.acl; + return -ENOMEM; +} +EXPORT_SYMBOL(nfs4acl_apply_masks); -- Andreas Gruenbacher SUSE Labs, SUSE LINUX Products GmbH / Novell Inc. - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/