[RFC v4 00/31] Richacls

19 messages, 3 authors, 2015-06-25 · open the first message on its own page

[RFC v4 00/31] Richacls

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:56:49

Hello,

here's another update of the richacl patch queue.  The changes since the last
posting (https://lwn.net/Articles/641764/) include:

* The owner and other masks now determine the owner and other
  permissions rather than just limiting them. For example, a
  'chmod u=rw' will always grant the owner read and write access.
  The group mask still behaves as before and only limits the group
  class permissions.  Hence, the owner, group, and other masks
  now behave like the 'user::', 'group::', and 'mask::' entries in
  POSIX ACLs.

* ACL entries for users matching the current owner are treated similar to
  owner@ entries; they match the file owner. This differs from how such
  entries are treated in POSIX ACLs but is more consistent with the
  NFSv4 / Windows ACL model.

* Permissions implicitly granted to the owner ("ACo") are no longer added to
  owner@ entries automatically (they are still granted though). The idea
  behind adding them was to make the acl reflect the actual permissions
  more closely, but that didn't turn out to be helpful.

* Various smaller improvements and bug fixes.

The complete patch queue is available here:

  git://git.kernel.org/pub/scm/linux/kernel/git/agruen/linux-richacl.git \
        richacl-2015-06-24

I'm leaving out patches here which have been queued for 4.2 already.  Also,
the nfs patches need some more work before another round of reviews.

Thanks,
Andreas

Andreas Gruenbacher (29):
  vfs: Add IS_ACL() and IS_RICHACL() tests
  vfs: Add MAY_CREATE_FILE and MAY_CREATE_DIR permission flags
  vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD permission flags
  vfs: Make the inode passed to inode_change_ok non-const
  vfs: Add permission flags for setting file attributes
  richacl: In-memory representation and helper functions
  richacl: Permission mapping functions
  richacl: Compute maximum file masks from an acl
  richacl: Update the file masks in chmod()
  richacl: Permission check algorithm
  vfs: Cache base_acl objects in inodes
  vfs: Cache richacl in struct inode
  richacl: Check if an acl is equivalent to a file mode
  richacl: Create-time inheritance
  richacl: Automatic Inheritance
  richacl: xattr mapping functions
  vfs: Add richacl permission checking
  richacl: acl editing helper functions
  richacl: Move everyone@ aces down the acl
  richacl: Propagate everyone@ permissions to other aces
  richacl: Set the owner permissions to the owner mask
  richacl: Set the other permissions to the other mask
  richacl: Isolate the owner and group classes
  richacl: Apply the file masks to a richacl
  richacl: Create richacl from mode values
  nfsd: Keep list of acls to dispose of in compoundargs
  nfsd: Use richacls as internal acl representation
  nfsd: Add richacl support
  nfsd: Add support for the v4.1 dacl attribute

Aneesh Kumar K.V (2):
  ext4: Add richacl support
  ext4: Add richacl feature flag

 drivers/staging/lustre/lustre/llite/llite_lib.c |   2 +-
 fs/Kconfig                                      |   9 +
 fs/Makefile                                     |   3 +
 fs/attr.c                                       |  81 ++-
 fs/ext4/Kconfig                                 |  15 +
 fs/ext4/Makefile                                |   1 +
 fs/ext4/acl.c                                   |   6 +-
 fs/ext4/acl.h                                   |  12 +-
 fs/ext4/ext4.h                                  |   6 +-
 fs/ext4/file.c                                  |   6 +-
 fs/ext4/ialloc.c                                |   7 +-
 fs/ext4/inode.c                                 |  10 +-
 fs/ext4/namei.c                                 |  11 +-
 fs/ext4/richacl.c                               | 211 ++++++
 fs/ext4/richacl.h                               |  47 ++
 fs/ext4/super.c                                 |  41 +-
 fs/ext4/xattr.c                                 |   6 +
 fs/ext4/xattr.h                                 |   1 +
 fs/f2fs/acl.c                                   |   4 +-
 fs/inode.c                                      |  15 +-
 fs/namei.c                                      | 108 ++-
 fs/nfs_common/Makefile                          |   1 +
 fs/nfs_common/nfs4acl.c                         |  41 ++
 fs/nfsd/Kconfig                                 |   1 +
 fs/nfsd/acl.h                                   |  23 +-
 fs/nfsd/nfs4acl.c                               | 483 +++++++------
 fs/nfsd/nfs4proc.c                              |  19 +-
 fs/nfsd/nfs4xdr.c                               | 267 ++++---
 fs/nfsd/nfsd.h                                  |   6 +-
 fs/nfsd/xdr4.h                                  |  12 +-
 fs/posix_acl.c                                  |  26 +-
 fs/richacl_base.c                               | 539 ++++++++++++++
 fs/richacl_compat.c                             | 908 ++++++++++++++++++++++++
 fs/richacl_inode.c                              | 264 +++++++
 fs/richacl_xattr.c                              | 210 ++++++
 fs/xattr.c                                      |  34 +-
 include/linux/fs.h                              |  50 +-
 include/linux/nfs4.h                            |  24 +-
 include/linux/nfs4acl.h                         |   7 +
 include/linux/posix_acl.h                       |  12 +-
 include/linux/richacl.h                         | 342 +++++++++
 include/linux/richacl_compat.h                  |  40 ++
 include/linux/richacl_xattr.h                   |  52 ++
 include/uapi/linux/fs.h                         |   3 +-
 include/uapi/linux/nfs4.h                       |   3 +-
 include/uapi/linux/xattr.h                      |   2 +
 46 files changed, 3498 insertions(+), 473 deletions(-)
 create mode 100644 fs/ext4/richacl.c
 create mode 100644 fs/ext4/richacl.h
 create mode 100644 fs/nfs_common/nfs4acl.c
 create mode 100644 fs/richacl_base.c
 create mode 100644 fs/richacl_compat.c
 create mode 100644 fs/richacl_inode.c
 create mode 100644 fs/richacl_xattr.c
 create mode 100644 include/linux/nfs4acl.h
 create mode 100644 include/linux/richacl.h
 create mode 100644 include/linux/richacl_compat.h
 create mode 100644 include/linux/richacl_xattr.h

-- 
2.4.2

[RFC v4 01/31] vfs: Add IS_ACL() and IS_RICHACL() tests

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:56:50

The vfs does not apply the umask for file systems that support acls. The test
used for this used to be called IS_POSIXACL(). Switch to a new IS_ACL() test to
check for either posix acls or richacls instead. Add a new MS_RICHACL flag and
IS_RICHACL() test for richacls alone. The IS_POSIXACL() test is still needed
by file systems that specifically support POSIX ACLs, like nfsd.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/Kconfig              |  3 +++
 fs/namei.c              |  8 ++++----
 include/linux/fs.h      | 12 ++++++++++++
 include/uapi/linux/fs.h |  3 ++-
 4 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/fs/Kconfig b/fs/Kconfig
index 011f433..3e09c06 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -59,6 +59,9 @@ endif # BLOCK
 config FS_POSIX_ACL
 	def_bool n
 
+config FS_RICHACL
+	def_bool n
+
 config EXPORTFS
 	tristate
 
diff --git a/fs/namei.c b/fs/namei.c
index fe30d3b..f5715fb 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2703,7 +2703,7 @@ static int atomic_open(struct nameidata *nd, struct dentry *dentry,
 	}
 
 	mode = op->mode;
-	if ((open_flag & O_CREAT) && !IS_POSIXACL(dir))
+	if ((open_flag & O_CREAT) && !IS_ACL(dir))
 		mode &= ~current_umask();
 
 	excl = (open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT);
@@ -2887,7 +2887,7 @@ static int lookup_open(struct nameidata *nd, struct path *path,
 	/* Negative dentry, just create the file */
 	if (!dentry->d_inode && (op->open_flag & O_CREAT)) {
 		umode_t mode = op->mode;
-		if (!IS_POSIXACL(dir->d_inode))
+		if (!IS_ACL(dir->d_inode))
 			mode &= ~current_umask();
 		/*
 		 * This write is needed to ensure that a
@@ -3489,7 +3489,7 @@ retry:
 	if (IS_ERR(dentry))
 		return PTR_ERR(dentry);
 
-	if (!IS_POSIXACL(path.dentry->d_inode))
+	if (!IS_ACL(path.dentry->d_inode))
 		mode &= ~current_umask();
 	error = security_path_mknod(&path, dentry, mode, dev);
 	if (error)
@@ -3558,7 +3558,7 @@ retry:
 	if (IS_ERR(dentry))
 		return PTR_ERR(dentry);
 
-	if (!IS_POSIXACL(path.dentry->d_inode))
+	if (!IS_ACL(path.dentry->d_inode))
 		mode &= ~current_umask();
 	error = security_path_mkdir(&path, dentry, mode);
 	if (!error)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 35ec87e..2b8cabb 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1743,6 +1743,12 @@ struct super_operations {
 #define IS_IMMUTABLE(inode)	((inode)->i_flags & S_IMMUTABLE)
 #define IS_POSIXACL(inode)	__IS_FLG(inode, MS_POSIXACL)
 
+#ifdef CONFIG_FS_RICHACL
+#define IS_RICHACL(inode)	__IS_FLG(inode, MS_RICHACL)
+#else
+#define IS_RICHACL(inode)	0
+#endif
+
 #define IS_DEADDIR(inode)	((inode)->i_flags & S_DEAD)
 #define IS_NOCMTIME(inode)	((inode)->i_flags & S_NOCMTIME)
 #define IS_SWAPFILE(inode)	((inode)->i_flags & S_SWAPFILE)
@@ -1756,6 +1762,12 @@ struct super_operations {
 				 (inode)->i_rdev == WHITEOUT_DEV)
 
 /*
+ * IS_ACL() tells the VFS to not apply the umask
+ * and use check_acl for acl permission checks when defined.
+ */
+#define IS_ACL(inode)		__IS_FLG(inode, MS_POSIXACL | MS_RICHACL)
+
+/*
  * Inode state bits.  Protected by inode->i_lock
  *
  * Three bits determine the dirty state of the inode, I_DIRTY_SYNC,
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 9b964a5..6ac6bc9 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -81,7 +81,7 @@ struct inodes_stat_t {
 #define MS_VERBOSE	32768	/* War is peace. Verbosity is silence.
 				   MS_VERBOSE is deprecated. */
 #define MS_SILENT	32768
-#define MS_POSIXACL	(1<<16)	/* VFS does not apply the umask */
+#define MS_POSIXACL	(1<<16)	/* Supports POSIX ACLs */
 #define MS_UNBINDABLE	(1<<17)	/* change to unbindable */
 #define MS_PRIVATE	(1<<18)	/* change to private */
 #define MS_SLAVE	(1<<19)	/* change to slave */
@@ -91,6 +91,7 @@ struct inodes_stat_t {
 #define MS_I_VERSION	(1<<23) /* Update inode I_version field */
 #define MS_STRICTATIME	(1<<24) /* Always perform atime updates */
 #define MS_LAZYTIME	(1<<25) /* Update the on-disk [acm]times lazily */
+#define MS_RICHACL	(1<<26) /* Supports richacls */
 
 /* These sb flags are internal to the kernel */
 #define MS_NOSEC	(1<<28)
-- 
2.4.2

[RFC v4 05/31] vfs: Add permission flags for setting file attributes

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:56:54

Richacls support permissions that allow to take ownership of a file, change the
file permissions, and set the file timestamps.  Support that by introducing new
permission mask flags and by checking for those mask flags in
inode_change_ok().

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/attr.c          | 79 +++++++++++++++++++++++++++++++++++++++++++++---------
 include/linux/fs.h |  3 +++
 2 files changed, 70 insertions(+), 12 deletions(-)
diff --git a/fs/attr.c b/fs/attr.c
index 328be71..85483e0 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -17,6 +17,65 @@
 #include <linux/ima.h>
 
 /**
+ * inode_extended_permission  -  permissions beyond read/write/execute
+ *
+ * Check for permissions that only richacls can currently grant.
+ */
+static int inode_extended_permission(struct inode *inode, int mask)
+{
+	if (!IS_RICHACL(inode))
+		return -EPERM;
+	return inode_permission(inode, mask);
+}
+
+static bool inode_uid_change_ok(struct inode *inode, kuid_t ia_uid)
+{
+	if (uid_eq(current_fsuid(), inode->i_uid) &&
+	    uid_eq(ia_uid, inode->i_uid))
+		return true;
+	if (uid_eq(current_fsuid(), ia_uid) &&
+	    inode_extended_permission(inode, MAY_TAKE_OWNERSHIP) == 0)
+		return true;
+	if (capable_wrt_inode_uidgid(inode, CAP_CHOWN))
+		return true;
+	return false;
+}
+
+static bool inode_gid_change_ok(struct inode *inode, kgid_t ia_gid)
+{
+	int in_group = in_group_p(ia_gid);
+	if (uid_eq(current_fsuid(), inode->i_uid) &&
+	    (in_group || gid_eq(ia_gid, inode->i_gid)))
+		return true;
+	if (in_group && inode_extended_permission(inode, MAY_TAKE_OWNERSHIP) == 0)
+		return true;
+	if (capable_wrt_inode_uidgid(inode, CAP_CHOWN))
+		return true;
+	return false;
+}
+
+/**
+ * inode_owner_permitted_or_capable
+ *
+ * Check for permissions implicitly granted to the owner, like MAY_CHMOD or
+ * MAY_SET_TIMES.  Equivalent to inode_owner_or_capable for file systems
+ * without support for those permissions.
+ */
+static bool inode_owner_permitted_or_capable(struct inode *inode, int mask)
+{
+	struct user_namespace *ns;
+
+	if (uid_eq(current_fsuid(), inode->i_uid))
+		return true;
+	if (inode_extended_permission(inode, mask) == 0)
+		return true;
+	ns = current_user_ns();
+	if (ns_capable(ns, CAP_FOWNER) && kuid_has_mapping(ns, inode->i_uid))
+		return true;
+	return false;
+}
+
+/**
  * inode_change_ok - check if attribute changes to an inode are allowed
  * @inode:	inode to check
  * @attr:	attributes to change
@@ -47,22 +106,18 @@ int inode_change_ok(struct inode *inode, struct iattr *attr)
 		return 0;
 
 	/* Make sure a caller can chown. */
-	if ((ia_valid & ATTR_UID) &&
-	    (!uid_eq(current_fsuid(), inode->i_uid) ||
-	     !uid_eq(attr->ia_uid, inode->i_uid)) &&
-	    !capable_wrt_inode_uidgid(inode, CAP_CHOWN))
-		return -EPERM;
+	if (ia_valid & ATTR_UID)
+		if (!inode_uid_change_ok(inode, attr->ia_uid))
+			return -EPERM;
 
 	/* Make sure caller can chgrp. */
-	if ((ia_valid & ATTR_GID) &&
-	    (!uid_eq(current_fsuid(), inode->i_uid) ||
-	    (!in_group_p(attr->ia_gid) && !gid_eq(attr->ia_gid, inode->i_gid))) &&
-	    !capable_wrt_inode_uidgid(inode, CAP_CHOWN))
-		return -EPERM;
+	if (ia_valid & ATTR_GID)
+		if (!inode_gid_change_ok(inode, attr->ia_gid))
+			return -EPERM;
 
 	/* Make sure a caller can chmod. */
 	if (ia_valid & ATTR_MODE) {
-		if (!inode_owner_or_capable(inode))
+		if (!inode_owner_permitted_or_capable(inode, MAY_CHMOD))
 			return -EPERM;
 		/* Also check the setgid bit! */
 		if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid :
@@ -73,7 +128,7 @@ int inode_change_ok(struct inode *inode, struct iattr *attr)
 
 	/* Check for setting the inode time. */
 	if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) {
-		if (!inode_owner_or_capable(inode))
+		if (!inode_owner_permitted_or_capable(inode, MAY_SET_TIMES))
 			return -EPERM;
 	}
 
diff --git a/include/linux/fs.h b/include/linux/fs.h
index b98b211..a32f37a 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -84,6 +84,9 @@ typedef void (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
 #define MAY_CREATE_DIR		0x00000200
 #define MAY_DELETE_CHILD	0x00000400
 #define MAY_DELETE_SELF		0x00000800
+#define MAY_TAKE_OWNERSHIP	0x00001000
+#define MAY_CHMOD		0x00002000
+#define MAY_SET_TIMES		0x00004000
 
 /*
  * flags in file.f_mode.  Note that FMODE_READ and FMODE_WRITE must correspond
-- 
2.4.2

[RFC v4 06/31] richacl: In-memory representation and helper functions

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:56:55

A richacl consists of an NFSv4 acl and an owner, group, and other mask.
These three masks correspond to the owner, group, and other file
permission bits, but they contain NFSv4 permissions instead of POSIX
permissions.

Each entry in the NFSv4 acl applies to the file owner (OWNER@), the owning
group (GROUP@), everyone (EVERYONE@), or to a specific uid or gid.

As in the standard POSIX file permission model, each process is the
owner, group, or other file class.  A richacl grants a requested access
only if the NFSv4 acl in the richacl grants the access (according to the
NFSv4 permission check algorithm), and the file mask that applies to the
process includes the requested permissions.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/Makefile             |   2 +
 fs/richacl_base.c       |  58 +++++++++++
 include/linux/richacl.h | 258 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 318 insertions(+)
 create mode 100644 fs/richacl_base.c
 create mode 100644 include/linux/richacl.h
diff --git a/fs/Makefile b/fs/Makefile
index cb92fd4..654e716 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -47,6 +47,8 @@ obj-$(CONFIG_COREDUMP)		+= coredump.o
 obj-$(CONFIG_SYSCTL)		+= drop_caches.o
 
 obj-$(CONFIG_FHANDLE)		+= fhandle.o
+obj-$(CONFIG_FS_RICHACL)	+= richacl.o
+richacl-y			:= richacl_base.o
 
 obj-y				+= quota/
 
diff --git a/fs/richacl_base.c b/fs/richacl_base.c
new file mode 100644
index 0000000..164b902
--- /dev/null
+++ b/fs/richacl_base.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * 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 <linux/sched.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/richacl.h>
+
+MODULE_LICENSE("GPL");
+
+/**
+ * richacl_alloc  -  allocate a richacl
+ * @count:	number of entries
+ */
+struct richacl *
+richacl_alloc(int count, gfp_t gfp)
+{
+	size_t size = sizeof(struct richacl) + count * sizeof(struct richace);
+	struct richacl *acl = kzalloc(size, gfp);
+
+	if (acl) {
+		atomic_set(&acl->a_refcount, 1);
+		acl->a_count = count;
+	}
+	return acl;
+}
+EXPORT_SYMBOL_GPL(richacl_alloc);
+
+/**
+ * richacl_clone  -  create a copy of a richacl
+ */
+struct richacl *
+richacl_clone(const struct richacl *acl, gfp_t gfp)
+{
+	int count = acl->a_count;
+	size_t size = sizeof(struct richacl) + count * sizeof(struct richace);
+	struct richacl *dup = kmalloc(size, gfp);
+
+	if (dup) {
+		memcpy(dup, acl, size);
+		atomic_set(&dup->a_refcount, 1);
+	}
+	return dup;
+}
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
new file mode 100644
index 0000000..feebc2f
--- /dev/null
+++ b/include/linux/richacl.h
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * 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.
+ */
+
+#ifndef __RICHACL_H
+#define __RICHACL_H
+
+#define RICHACE_OWNER_SPECIAL_ID	0
+#define RICHACE_GROUP_SPECIAL_ID	1
+#define RICHACE_EVERYONE_SPECIAL_ID	2
+
+struct richace {
+	unsigned short	e_type;
+	unsigned short	e_flags;
+	unsigned int	e_mask;
+	union {
+		kuid_t		uid;
+		kgid_t		gid;
+		unsigned int	special;
+	} e_id;
+};
+
+struct richacl {
+	atomic_t	a_refcount;
+	unsigned int	a_owner_mask;
+	unsigned int	a_group_mask;
+	unsigned int	a_other_mask;
+	unsigned short	a_count;
+	unsigned short	a_flags;
+	struct richace	a_entries[0];
+};
+
+#define richacl_for_each_entry(_ace, _acl) \
+	for (_ace = (_acl)->a_entries; \
+	     _ace != (_acl)->a_entries + (_acl)->a_count; \
+	     _ace++)
+
+#define richacl_for_each_entry_reverse(_ace, _acl) \
+	for (_ace = (_acl)->a_entries + (_acl)->a_count - 1; \
+	     _ace != (_acl)->a_entries - 1; \
+	     _ace--)
+
+/* a_flag values */
+#define RICHACL_MASKED				0x80
+
+#define RICHACL_VALID_FLAGS (					\
+		RICHACL_MASKED)
+
+/* e_type values */
+#define RICHACE_ACCESS_ALLOWED_ACE_TYPE		0x0000
+#define RICHACE_ACCESS_DENIED_ACE_TYPE		0x0001
+
+/* e_flags bitflags */
+#define RICHACE_FILE_INHERIT_ACE		0x0001
+#define RICHACE_DIRECTORY_INHERIT_ACE		0x0002
+#define RICHACE_NO_PROPAGATE_INHERIT_ACE	0x0004
+#define RICHACE_INHERIT_ONLY_ACE		0x0008
+#define RICHACE_IDENTIFIER_GROUP		0x0040
+#define RICHACE_SPECIAL_WHO			0x4000
+
+#define RICHACE_VALID_FLAGS (					\
+	RICHACE_FILE_INHERIT_ACE |				\
+	RICHACE_DIRECTORY_INHERIT_ACE |				\
+	RICHACE_NO_PROPAGATE_INHERIT_ACE |			\
+	RICHACE_INHERIT_ONLY_ACE |				\
+	RICHACE_IDENTIFIER_GROUP |				\
+	RICHACE_SPECIAL_WHO)
+
+/* e_mask bitflags */
+#define RICHACE_READ_DATA			0x00000001
+#define RICHACE_LIST_DIRECTORY			0x00000001
+#define RICHACE_WRITE_DATA			0x00000002
+#define RICHACE_ADD_FILE			0x00000002
+#define RICHACE_APPEND_DATA			0x00000004
+#define RICHACE_ADD_SUBDIRECTORY		0x00000004
+#define RICHACE_READ_NAMED_ATTRS		0x00000008
+#define RICHACE_WRITE_NAMED_ATTRS		0x00000010
+#define RICHACE_EXECUTE				0x00000020
+#define RICHACE_DELETE_CHILD			0x00000040
+#define RICHACE_READ_ATTRIBUTES			0x00000080
+#define RICHACE_WRITE_ATTRIBUTES		0x00000100
+#define RICHACE_WRITE_RETENTION			0x00000200
+#define RICHACE_WRITE_RETENTION_HOLD		0x00000400
+#define RICHACE_DELETE				0x00010000
+#define RICHACE_READ_ACL			0x00020000
+#define RICHACE_WRITE_ACL			0x00040000
+#define RICHACE_WRITE_OWNER			0x00080000
+#define RICHACE_SYNCHRONIZE			0x00100000
+
+/* Valid RICHACE_* flags for directories and non-directories */
+#define RICHACE_VALID_MASK (					\
+	RICHACE_READ_DATA | RICHACE_LIST_DIRECTORY |		\
+	RICHACE_WRITE_DATA | RICHACE_ADD_FILE |			\
+	RICHACE_APPEND_DATA | RICHACE_ADD_SUBDIRECTORY |	\
+	RICHACE_READ_NAMED_ATTRS |				\
+	RICHACE_WRITE_NAMED_ATTRS |				\
+	RICHACE_EXECUTE |					\
+	RICHACE_DELETE_CHILD |					\
+	RICHACE_READ_ATTRIBUTES |				\
+	RICHACE_WRITE_ATTRIBUTES |				\
+	RICHACE_WRITE_RETENTION |				\
+	RICHACE_WRITE_RETENTION_HOLD |				\
+	RICHACE_DELETE |					\
+	RICHACE_READ_ACL |					\
+	RICHACE_WRITE_ACL |					\
+	RICHACE_WRITE_OWNER |					\
+	RICHACE_SYNCHRONIZE)
+
+/**
+ * richacl_get  -  grab another reference to a richacl handle
+ */
+static inline struct richacl *
+richacl_get(struct richacl *acl)
+{
+	if (acl)
+		atomic_inc(&acl->a_refcount);
+	return acl;
+}
+
+/**
+ * richacl_put  -  free a richacl handle
+ */
+static inline void
+richacl_put(struct richacl *acl)
+{
+	if (acl && atomic_dec_and_test(&acl->a_refcount))
+		kfree(acl);
+}
+
+/**
+ * richace_is_owner  -  check if @ace is an OWNER@ entry
+ */
+static inline bool
+richace_is_owner(const struct richace *ace)
+{
+	return (ace->e_flags & RICHACE_SPECIAL_WHO) &&
+	       ace->e_id.special == RICHACE_OWNER_SPECIAL_ID;
+}
+
+/**
+ * richace_is_group  -  check if @ace is a GROUP@ entry
+ */
+static inline bool
+richace_is_group(const struct richace *ace)
+{
+	return (ace->e_flags & RICHACE_SPECIAL_WHO) &&
+	       ace->e_id.special == RICHACE_GROUP_SPECIAL_ID;
+}
+
+/**
+ * richace_is_everyone  -  check if @ace is an EVERYONE@ entry
+ */
+static inline bool
+richace_is_everyone(const struct richace *ace)
+{
+	return (ace->e_flags & RICHACE_SPECIAL_WHO) &&
+	       ace->e_id.special == RICHACE_EVERYONE_SPECIAL_ID;
+}
+
+/**
+ * richace_is_unix_user  -  check if @ace applies to a specific user
+ */
+static inline bool
+richace_is_unix_user(const struct richace *ace)
+{
+	return !(ace->e_flags & RICHACE_SPECIAL_WHO) &&
+	       !(ace->e_flags & RICHACE_IDENTIFIER_GROUP);
+}
+
+/**
+ * richace_is_unix_group  -  check if @ace applies to a specific group
+ */
+static inline bool
+richace_is_unix_group(const struct richace *ace)
+{
+	return !(ace->e_flags & RICHACE_SPECIAL_WHO) &&
+	       (ace->e_flags & RICHACE_IDENTIFIER_GROUP);
+}
+
+/**
+ * richace_is_inherit_only  -  check if @ace is for inheritance only
+ *
+ * ACEs with the %RICHACE_INHERIT_ONLY_ACE flag set have no effect during
+ * permission checking.
+ */
+static inline bool
+richace_is_inherit_only(const struct richace *ace)
+{
+	return ace->e_flags & RICHACE_INHERIT_ONLY_ACE;
+}
+
+/**
+ * richace_is_inheritable  -  check if @ace is inheritable
+ */
+static inline bool
+richace_is_inheritable(const struct richace *ace)
+{
+	return ace->e_flags & (RICHACE_FILE_INHERIT_ACE |
+			       RICHACE_DIRECTORY_INHERIT_ACE);
+}
+
+/**
+ * richace_clear_inheritance_flags  - clear all inheritance flags in @ace
+ */
+static inline void
+richace_clear_inheritance_flags(struct richace *ace)
+{
+	ace->e_flags &= ~(RICHACE_FILE_INHERIT_ACE |
+			  RICHACE_DIRECTORY_INHERIT_ACE |
+			  RICHACE_NO_PROPAGATE_INHERIT_ACE |
+			  RICHACE_INHERIT_ONLY_ACE);
+}
+
+/**
+ * richace_is_allow  -  check if @ace is an %ALLOW type entry
+ */
+static inline bool
+richace_is_allow(const struct richace *ace)
+{
+	return ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+}
+
+/**
+ * richace_is_deny  -  check if @ace is a %DENY type entry
+ */
+static inline bool
+richace_is_deny(const struct richace *ace)
+{
+	return ace->e_type == RICHACE_ACCESS_DENIED_ACE_TYPE;
+}
+
+/**
+ * richace_is_same_identifier  -  are both identifiers the same?
+ */
+static inline bool
+richace_is_same_identifier(const struct richace *a, const struct richace *b)
+{
+	return !((a->e_flags ^ b->e_flags) &
+		 (RICHACE_SPECIAL_WHO | RICHACE_IDENTIFIER_GROUP)) &&
+	       !memcmp(&a->e_id, &b->e_id, sizeof(a->e_id));
+}
+
+extern struct richacl *richacl_alloc(int, gfp_t);
+extern struct richacl *richacl_clone(const struct richacl *, gfp_t);
+
+#endif /* __RICHACL_H */
-- 
2.4.2

[RFC v4 07/31] richacl: Permission mapping functions

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:56:56

We need to map from POSIX permissions to NFSv4 permissions when a
chmod() is done, from NFSv4 permissions to POSIX permissions when an acl
is set (which implicitly sets the file permission bits), and from the
MAY_READ/MAY_WRITE/MAY_EXEC/MAY_APPEND flags to NFSv4 permissions when
doing an access check in a richacl.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_base.c       | 113 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |  46 ++++++++++++++++++++
 2 files changed, 159 insertions(+)
diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 164b902..032c756 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -56,3 +56,116 @@ richacl_clone(const struct richacl *acl, gfp_t gfp)
 	}
 	return dup;
 }
+
+/**
+ * richacl_mask_to_mode  -  compute the file permission bits which correspond to @mask
+ * @mask:	%RICHACE_* permission mask
+ *
+ * See richacl_masks_to_mode().
+ */
+static int
+richacl_mask_to_mode(unsigned int mask)
+{
+	int mode = 0;
+
+	if (mask & RICHACE_POSIX_MODE_READ)
+		mode |= S_IROTH;
+	if (mask & RICHACE_POSIX_MODE_WRITE)
+		mode |= S_IWOTH;
+	if (mask & RICHACE_POSIX_MODE_EXEC)
+		mode |= S_IXOTH;
+
+	return mode;
+}
+
+/**
+ * richacl_masks_to_mode  -  compute the file permission bits from the file masks
+ *
+ * When setting a richacl, we set the file permission bits to indicate maximum
+ * permissions: for example, we set the Write permission when a mask contains
+ * RICHACE_APPEND_DATA even if it does not also contain RICHACE_WRITE_DATA.
+ *
+ * Permissions which are not in RICHACE_POSIX_MODE_READ, RICHACE_POSIX_MODE_WRITE, or
+ * RICHACE_POSIX_MODE_EXEC cannot be represented in the file permission bits.
+ * Such permissions can still be effective, but not for new files or after a
+ * chmod(); they must be explicitly enabled in the richacl.
+ */
+int
+richacl_masks_to_mode(const struct richacl *acl)
+{
+	return richacl_mask_to_mode(acl->a_owner_mask) << 6 |
+	       richacl_mask_to_mode(acl->a_group_mask) << 3 |
+	       richacl_mask_to_mode(acl->a_other_mask);
+}
+EXPORT_SYMBOL_GPL(richacl_masks_to_mode);
+
+/**
+ * richacl_mode_to_mask  - compute a file mask from the lowest three mode bits
+ *
+ * When the file permission bits of a file are set with chmod(), this specifies
+ * the maximum permissions that processes will get.  All permissions beyond
+ * that will be removed from the file masks, and become ineffective.
+ */
+unsigned int
+richacl_mode_to_mask(mode_t mode)
+{
+	unsigned int mask = 0;
+
+	if (mode & S_IROTH)
+		mask |= RICHACE_POSIX_MODE_READ;
+	if (mode & S_IWOTH)
+		mask |= RICHACE_POSIX_MODE_WRITE;
+	if (mode & S_IXOTH)
+		mask |= RICHACE_POSIX_MODE_EXEC;
+
+	return mask;
+}
+
+/**
+ * richacl_want_to_mask  - convert the iop->permission want argument to a mask
+ * @want:	@want argument of the permission inode operation
+ *
+ * When checking for append, @want is (MAY_WRITE | MAY_APPEND).
+ *
+ * Richacls use the iop->may_create and iop->may_delete hooks which are
+ * used for checking if creating and deleting files is allowed.  These hooks do
+ * not use richacl_want_to_mask(), so we do not have to deal with mapping
+ * MAY_WRITE to RICHACE_ADD_FILE, RICHACE_ADD_SUBDIRECTORY, and RICHACE_DELETE_CHILD
+ * here.
+ */
+unsigned int
+richacl_want_to_mask(unsigned int want)
+{
+	unsigned int mask = 0;
+
+	if (want & MAY_READ)
+		mask |= RICHACE_READ_DATA;
+	if (want & MAY_DELETE_SELF)
+		mask |= RICHACE_DELETE;
+	if (want & MAY_TAKE_OWNERSHIP)
+		mask |= RICHACE_WRITE_OWNER;
+	if (want & MAY_CHMOD)
+		mask |= RICHACE_WRITE_ACL;
+	if (want & MAY_SET_TIMES)
+		mask |= RICHACE_WRITE_ATTRIBUTES;
+	if (want & MAY_EXEC)
+		mask |= RICHACE_EXECUTE;
+	/*
+	 * differentiate MAY_WRITE from these request
+	 */
+	if (want & (MAY_APPEND |
+		    MAY_CREATE_FILE | MAY_CREATE_DIR |
+		    MAY_DELETE_CHILD)) {
+		if (want & MAY_APPEND)
+			mask |= RICHACE_APPEND_DATA;
+		if (want & MAY_CREATE_FILE)
+			mask |= RICHACE_ADD_FILE;
+		if (want & MAY_CREATE_DIR)
+			mask |= RICHACE_ADD_SUBDIRECTORY;
+		if (want & MAY_DELETE_CHILD)
+			mask |= RICHACE_DELETE_CHILD;
+	} else if (want & MAY_WRITE)
+		mask |= RICHACE_WRITE_DATA;
+	return mask;
+}
+EXPORT_SYMBOL_GPL(richacl_want_to_mask);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index feebc2f..7957bbf 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -118,6 +118,49 @@ struct richacl {
 	RICHACE_WRITE_OWNER |					\
 	RICHACE_SYNCHRONIZE)
 
+/*
+ * The POSIX permissions are supersets of the following NFSv4 permissions:
+ *
+ *  - MAY_READ maps to READ_DATA or LIST_DIRECTORY, depending on the type
+ *    of the file system object.
+ *
+ *  - MAY_WRITE maps to WRITE_DATA or RICHACE_APPEND_DATA for files, and to
+ *    ADD_FILE, RICHACE_ADD_SUBDIRECTORY, or RICHACE_DELETE_CHILD for directories.
+ *
+ *  - MAY_EXECUTE maps to RICHACE_EXECUTE.
+ *
+ *  (Some of these NFSv4 permissions have the same bit values.)
+ */
+#define RICHACE_POSIX_MODE_READ (			\
+		RICHACE_READ_DATA |		\
+		RICHACE_LIST_DIRECTORY)
+#define RICHACE_POSIX_MODE_WRITE (			\
+		RICHACE_WRITE_DATA |		\
+		RICHACE_ADD_FILE |			\
+		RICHACE_APPEND_DATA |		\
+		RICHACE_ADD_SUBDIRECTORY |		\
+		RICHACE_DELETE_CHILD)
+#define RICHACE_POSIX_MODE_EXEC RICHACE_EXECUTE
+#define RICHACE_POSIX_MODE_ALL (			\
+		RICHACE_POSIX_MODE_READ |		\
+		RICHACE_POSIX_MODE_WRITE |		\
+		RICHACE_POSIX_MODE_EXEC)
+/*
+ * These permissions are always allowed
+ * no matter what the acl says.
+ */
+#define RICHACE_POSIX_ALWAYS_ALLOWED (	\
+		RICHACE_SYNCHRONIZE |	\
+		RICHACE_READ_ATTRIBUTES |	\
+		RICHACE_READ_ACL)
+/*
+ * The owner is implicitly granted
+ * these permissions under POSIX.
+ */
+#define RICHACE_POSIX_OWNER_ALLOWED (		\
+		RICHACE_WRITE_ATTRIBUTES |		\
+		RICHACE_WRITE_OWNER |		\
+		RICHACE_WRITE_ACL)
 /**
  * richacl_get  -  grab another reference to a richacl handle
  */
@@ -254,5 +297,8 @@ richace_is_same_identifier(const struct richace *a, const struct richace *b)
 
 extern struct richacl *richacl_alloc(int, gfp_t);
 extern struct richacl *richacl_clone(const struct richacl *, gfp_t);
+extern int richacl_masks_to_mode(const struct richacl *);
+extern unsigned int richacl_mode_to_mask(mode_t);
+extern unsigned int richacl_want_to_mask(unsigned int);
 
 #endif /* __RICHACL_H */
-- 
2.4.2

[RFC v4 08/31] richacl: Compute maximum file masks from an acl

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:56:57

Compute upper bound owner, group, and other file masks with as few
permissions as possible without denying any permissions that the NFSv4
acl in a richacl grants.

This algorithm is used when a file inherits an acl at create time and
when an acl is set via a mechanism that does not provide file masks
(such as setting an acl via nfsd).  When user-space sets an acl via
setxattr, the extended attribute already includes the file masks.

Setting an acl also sets the file mode permission bits: they are
determined by the file masks; see richacl_masks_to_mode().

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Reviewed-by: J. Bruce Fields <redacted>
---
 fs/richacl_base.c       | 156 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |   1 +
 2 files changed, 157 insertions(+)
diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 032c756..fda0568 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -169,3 +169,159 @@ richacl_want_to_mask(unsigned int want)
 	return mask;
 }
 EXPORT_SYMBOL_GPL(richacl_want_to_mask);
+
+/*
+ * Note: functions like richacl_allowed_to_who(), richacl_group_class_allowed(),
+ * and richacl_compute_max_masks() iterate through the entire acl in reverse
+ * order as an optimization.
+ *
+ * In the standard algorithm, aces are considered in forward order.  When a
+ * process matches an ace, the permissions in the ace are either allowed or
+ * denied depending on the ace type.  Once a permission has been allowed or
+ * denied, it is no longer considered in further aces.
+ *
+ * By iterating through the acl in reverse order, we can compute the same
+ * result without having to keep track of which permissions have been allowed
+ * and denied already.
+ */
+
+/**
+ * richacl_allowed_to_who  -  permissions allowed to a specific who value
+ *
+ * Compute the maximum mask values allowed to a specific who value, taking
+ * everyone@ aces into account.
+ */
+static unsigned int richacl_allowed_to_who(struct richacl *acl,
+					   struct richace *who)
+{
+	struct richace *ace;
+	unsigned int allowed = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_same_identifier(ace, who) ||
+		    richace_is_everyone(ace)) {
+			if (richace_is_allow(ace))
+				allowed |= ace->e_mask;
+			else if (richace_is_deny(ace))
+				allowed &= ~ace->e_mask;
+		}
+	}
+	return allowed;
+}
+
+/**
+ * richacl_group_class_allowed  -  maximum permissions the group class is allowed
+ *
+ * Compute the maximum mask values allowed to a process in the group class
+ * (i.e., a process which is not the owner but is in the owning group or
+ * matches a user or group acl entry).  This includes permissions granted or
+ * denied by everyone@ aces.
+ *
+ * See richacl_compute_max_masks().
+ */
+static unsigned int richacl_group_class_allowed(struct richacl *acl)
+{
+	struct richace *ace;
+	unsigned int everyone_allowed = 0, group_class_allowed = 0;
+	int had_group_ace = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace) ||
+		    richace_is_owner(ace))
+			continue;
+
+		if (richace_is_everyone(ace)) {
+			if (richace_is_allow(ace))
+				everyone_allowed |= ace->e_mask;
+			else if (richace_is_deny(ace))
+				everyone_allowed &= ~ace->e_mask;
+		} else {
+			group_class_allowed |=
+				richacl_allowed_to_who(acl, ace);
+
+			if (richace_is_group(ace))
+				had_group_ace = 1;
+		}
+	}
+	/*
+	 * If the acl doesn't contain any group@ aces, richacl_allowed_to_who()
+	 * wasn't called for the owning group.  We could make that call now, but
+	 * we already know the result (everyone_allowed).
+	 */
+	if (!had_group_ace)
+		group_class_allowed |= everyone_allowed;
+	return group_class_allowed;
+}
+
+/**
+ * richacl_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).
+ */
+void richacl_compute_max_masks(struct richacl *acl, kuid_t owner)
+{
+	unsigned int gmask = ~0;
+	struct richace *ace;
+
+	/*
+	 * @gmask contains all permissions which the group class is ever
+	 * allowed.  We use it to avoid adding permissions to the group mask
+	 * from everyone@ allow aces which the group class is always denied
+	 * through other aces.  For example, the following acl would otherwise
+	 * result in a group mask of rw:
+	 *
+	 *	group@:w::deny
+	 *	everyone@:rw::allow
+	 *
+	 * Avoid computing @gmask for acls which do not include any group class
+	 * deny aces: in such acls, the group class is never denied any
+	 * permissions from everyone@ allow aces, and the group class cannot
+	 * have fewer permissions than the other class.
+	 */
+
+restart:
+	acl->a_owner_mask = 0;
+	acl->a_group_mask = 0;
+	acl->a_other_mask = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+
+		if (richace_is_owner(ace) ||
+		    (richace_is_unix_user(ace) &&
+		     uid_eq(ace->e_id.uid, owner))) {
+			if (richace_is_allow(ace))
+				acl->a_owner_mask |= ace->e_mask;
+			else if (richace_is_deny(ace))
+				acl->a_owner_mask &= ~ace->e_mask;
+		} else if (richace_is_everyone(ace)) {
+			if (richace_is_allow(ace)) {
+				acl->a_owner_mask |= ace->e_mask;
+				acl->a_group_mask |= ace->e_mask & gmask;
+				acl->a_other_mask |= ace->e_mask;
+			} else if (richace_is_deny(ace)) {
+				acl->a_owner_mask &= ~ace->e_mask;
+				acl->a_group_mask &= ~ace->e_mask;
+				acl->a_other_mask &= ~ace->e_mask;
+			}
+		} else {
+			if (richace_is_allow(ace)) {
+				acl->a_owner_mask |= ace->e_mask & gmask;
+				acl->a_group_mask |= ace->e_mask & gmask;
+			} else if (richace_is_deny(ace) && gmask == ~0) {
+				gmask = richacl_group_class_allowed(acl);
+				if (likely(gmask != ~0))
+					/* should always be true */
+					goto restart;
+			}
+		}
+	}
+
+	acl->a_flags &= ~RICHACL_MASKED;
+}
+EXPORT_SYMBOL_GPL(richacl_compute_max_masks);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 7957bbf..3cea47a 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -300,5 +300,6 @@ extern struct richacl *richacl_clone(const struct richacl *, gfp_t);
 extern int richacl_masks_to_mode(const struct richacl *);
 extern unsigned int richacl_mode_to_mask(mode_t);
 extern unsigned int richacl_want_to_mask(unsigned int);
+extern void richacl_compute_max_masks(struct richacl *, kuid_t);
 
 #endif /* __RICHACL_H */
-- 
2.4.2

[RFC v4 11/31] vfs: Cache base_acl objects in inodes

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:57:00

POSIX ACLs and richacls are both objects allocated by kmalloc() with a
reference count which are freed by kfree_rcu().  An inode can either cache an
access and a default POSIX ACL, or a richacl (richacls do not have default
acls).  To allow an inode to cache either of the two kinds of acls, introduce a
new base_acl type and convert i_acl and i_default_acl to that type. In most
cases, the vfs then doesn't have to care which kind of acl an inode caches (if
any).

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 drivers/staging/lustre/lustre/llite/llite_lib.c |  2 +-
 fs/f2fs/acl.c                                   |  4 ++--
 fs/inode.c                                      |  4 ++--
 fs/posix_acl.c                                  | 18 +++++++++---------
 include/linux/fs.h                              | 25 ++++++++++++++++++++++---
 include/linux/posix_acl.h                       | 12 ++++--------
 include/linux/richacl.h                         |  2 +-
 7 files changed, 41 insertions(+), 26 deletions(-)
diff --git a/drivers/staging/lustre/lustre/llite/llite_lib.c b/drivers/staging/lustre/lustre/llite/llite_lib.c
index a27af78..f063b74 100644
--- a/drivers/staging/lustre/lustre/llite/llite_lib.c
+++ b/drivers/staging/lustre/lustre/llite/llite_lib.c
@@ -1142,7 +1142,7 @@ void ll_clear_inode(struct inode *inode)
 	}
 #ifdef CONFIG_FS_POSIX_ACL
 	else if (lli->lli_posix_acl) {
-		LASSERT(atomic_read(&lli->lli_posix_acl->a_refcount) == 1);
+		LASSERT(atomic_read(&lli->lli_posix_acl->a_base.ba_refcount) == 1);
 		LASSERT(lli->lli_remote_perms == NULL);
 		posix_acl_release(lli->lli_posix_acl);
 		lli->lli_posix_acl = NULL;
diff --git a/fs/f2fs/acl.c b/fs/f2fs/acl.c
index 4320ffa..f0cde7a 100644
--- a/fs/f2fs/acl.c
+++ b/fs/f2fs/acl.c
@@ -270,7 +270,7 @@ static struct posix_acl *f2fs_acl_clone(const struct posix_acl *acl,
 				sizeof(struct posix_acl_entry);
 		clone = kmemdup(acl, size, flags);
 		if (clone)
-			atomic_set(&clone->a_refcount, 1);
+			atomic_set(&clone->a_base.ba_refcount, 1);
 	}
 	return clone;
 }
@@ -282,7 +282,7 @@ static int f2fs_acl_create_masq(struct posix_acl *acl, umode_t *mode_p)
 	umode_t mode = *mode_p;
 	int not_equiv = 0;
 
-	/* assert(atomic_read(acl->a_refcount) == 1); */
+	/* assert(atomic_read(acl->a_base.ba_refcount) == 1); */
 
 	FOREACH_ACL_ENTRY(pa, acl, pe) {
 		switch(pa->e_tag) {
diff --git a/fs/inode.c b/fs/inode.c
index ea37cd1..ed7a6c0 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -233,9 +233,9 @@ void __destroy_inode(struct inode *inode)
 
 #ifdef CONFIG_FS_POSIX_ACL
 	if (inode->i_acl && inode->i_acl != ACL_NOT_CACHED)
-		posix_acl_release(inode->i_acl);
+		put_base_acl(inode->i_acl);
 	if (inode->i_default_acl && inode->i_default_acl != ACL_NOT_CACHED)
-		posix_acl_release(inode->i_default_acl);
+		put_base_acl(inode->i_default_acl);
 #endif
 	this_cpu_dec(nr_inodes);
 }
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index 84bb65b8..430d687 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -25,9 +25,9 @@ struct posix_acl **acl_by_type(struct inode *inode, int type)
 {
 	switch (type) {
 	case ACL_TYPE_ACCESS:
-		return &inode->i_acl;
+		return (struct posix_acl **)&inode->i_acl;
 	case ACL_TYPE_DEFAULT:
-		return &inode->i_default_acl;
+		return (struct posix_acl **)&inode->i_default_acl;
 	default:
 		BUG();
 	}
@@ -83,16 +83,16 @@ EXPORT_SYMBOL(forget_cached_acl);
 
 void forget_all_cached_acls(struct inode *inode)
 {
-	struct posix_acl *old_access, *old_default;
+	struct base_acl *old_access, *old_default;
 	spin_lock(&inode->i_lock);
 	old_access = inode->i_acl;
 	old_default = inode->i_default_acl;
 	inode->i_acl = inode->i_default_acl = ACL_NOT_CACHED;
 	spin_unlock(&inode->i_lock);
 	if (old_access != ACL_NOT_CACHED)
-		posix_acl_release(old_access);
+		put_base_acl(old_access);
 	if (old_default != ACL_NOT_CACHED)
-		posix_acl_release(old_default);
+		put_base_acl(old_default);
 }
 EXPORT_SYMBOL(forget_all_cached_acls);
 
@@ -129,7 +129,7 @@ EXPORT_SYMBOL(get_acl);
 void
 posix_acl_init(struct posix_acl *acl, int count)
 {
-	atomic_set(&acl->a_refcount, 1);
+	atomic_set(&acl->a_base.ba_refcount, 1);
 	acl->a_count = count;
 }
 EXPORT_SYMBOL(posix_acl_init);
@@ -162,7 +162,7 @@ posix_acl_clone(const struct posix_acl *acl, gfp_t flags)
 		           sizeof(struct posix_acl_entry);
 		clone = kmemdup(acl, size, flags);
 		if (clone)
-			atomic_set(&clone->a_refcount, 1);
+			atomic_set(&clone->a_base.ba_refcount, 1);
 	}
 	return clone;
 }
@@ -384,7 +384,7 @@ static int posix_acl_create_masq(struct posix_acl *acl, umode_t *mode_p)
 	umode_t mode = *mode_p;
 	int not_equiv = 0;
 
-	/* assert(atomic_read(acl->a_refcount) == 1); */
+	/* assert(atomic_read(acl->a_base.ba_refcount) == 1); */
 
 	FOREACH_ACL_ENTRY(pa, acl, pe) {
                 switch(pa->e_tag) {
@@ -439,7 +439,7 @@ static int __posix_acl_chmod_masq(struct posix_acl *acl, umode_t mode)
 	struct posix_acl_entry *group_obj = NULL, *mask_obj = NULL;
 	struct posix_acl_entry *pa, *pe;
 
-	/* assert(atomic_read(acl->a_refcount) == 1); */
+	/* assert(atomic_read(acl->a_base.ba_refcount) == 1); */
 
 	FOREACH_ACL_ENTRY(pa, acl, pe) {
 		switch(pa->e_tag) {
diff --git a/include/linux/fs.h b/include/linux/fs.h
index a32f37a..2f1152d 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -574,6 +574,12 @@ static inline void mapping_allow_writable(struct address_space *mapping)
 #define i_size_ordered_init(inode) do { } while (0)
 #endif
 
+struct base_acl {
+	union {
+		atomic_t ba_refcount;
+		struct rcu_head ba_rcu;
+	};
+};
 struct posix_acl;
 #define ACL_NOT_CACHED ((void *)(-1))
 
@@ -593,9 +599,9 @@ struct inode {
 	kgid_t			i_gid;
 	unsigned int		i_flags;
 
-#ifdef CONFIG_FS_POSIX_ACL
-	struct posix_acl	*i_acl;
-	struct posix_acl	*i_default_acl;
+#if defined(CONFIG_FS_POSIX_ACL)
+	struct base_acl *i_acl;
+	struct base_acl *i_default_acl;
 #endif
 
 	const struct inode_operations	*i_op;
@@ -3006,4 +3012,17 @@ static inline bool dir_relax(struct inode *inode)
 	return !IS_DEADDIR(inode);
 }
 
+static inline struct base_acl *get_base_acl(struct base_acl *acl)
+{
+	if (acl)
+		atomic_inc(&acl->ba_refcount);
+	return acl;
+}
+
+static inline void put_base_acl(struct base_acl *acl)
+{
+	if (acl && atomic_dec_and_test(&acl->ba_refcount))
+		kfree_rcu(acl, ba_rcu);
+}
+
 #endif /* _LINUX_FS_H */
diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h
index 3e96a6a..2c46441 100644
--- a/include/linux/posix_acl.h
+++ b/include/linux/posix_acl.h
@@ -43,10 +43,7 @@ struct posix_acl_entry {
 };
 
 struct posix_acl {
-	union {
-		atomic_t		a_refcount;
-		struct rcu_head		a_rcu;
-	};
+	struct base_acl		a_base;
 	unsigned int		a_count;
 	struct posix_acl_entry	a_entries[0];
 };
@@ -61,8 +58,7 @@ struct posix_acl {
 static inline struct posix_acl *
 posix_acl_dup(struct posix_acl *acl)
 {
-	if (acl)
-		atomic_inc(&acl->a_refcount);
+	get_base_acl(&acl->a_base);
 	return acl;
 }
 
@@ -72,8 +68,8 @@ posix_acl_dup(struct posix_acl *acl)
 static inline void
 posix_acl_release(struct posix_acl *acl)
 {
-	if (acl && atomic_dec_and_test(&acl->a_refcount))
-		kfree_rcu(acl, a_rcu);
+	BUILD_BUG_ON(offsetof(struct posix_acl, a_base) != 0);
+	put_base_acl(&acl->a_base);
 }
 
 
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index b828615..a397bae 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -168,7 +168,7 @@ static inline struct richacl *
 richacl_get(struct richacl *acl)
 {
 	if (acl)
-		atomic_inc(&acl->a_refcount);
+		atomic_inc(&acl->a_base.ba_refcount);
 	return acl;
 }
 
-- 
2.4.2

[RFC v4 14/31] richacl: Create-time inheritance

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:57:03

When a new file is created, it can inherit an acl from its parent
directory; this is similar to how default acls work in POSIX (draft)
ACLs.

As with POSIX ACLs, if a file inherits an acl from its parent directory,
the intersection between the create mode and the permissions granted by
the inherited acl determines the file masks and file permission bits,
and the umask is ignored.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_base.c       | 69 +++++++++++++++++++++++++++++++++++++++++++++++++
 fs/richacl_inode.c      | 62 ++++++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl.h |  2 ++
 3 files changed, 133 insertions(+)
diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 1ef3bf3..29d3197 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -460,3 +460,72 @@ richacl_equiv_mode(const struct richacl *acl, mode_t *mode_p)
 	return 0;
 }
 EXPORT_SYMBOL_GPL(richacl_equiv_mode);
+
+/**
+ * richacl_inherit  -  compute the inherited acl of a new file
+ * @dir_acl:	acl of the containing directory
+ * @isdir:	inherit by a directory or non-directory?
+ *
+ * A directory can have acl entries which files and/or directories created
+ * inside the directory will inherit.  This function computes the acl for such
+ * a new file.  If there is no inheritable acl, it will return %NULL.
+ */
+struct richacl *
+richacl_inherit(const struct richacl *dir_acl, int isdir)
+{
+	const struct richace *dir_ace;
+	struct richacl *acl = NULL;
+	struct richace *ace;
+	int count = 0;
+
+	if (isdir) {
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!richace_is_inheritable(dir_ace))
+				continue;
+			count++;
+		}
+		if (!count)
+			return NULL;
+		acl = richacl_alloc(count, GFP_KERNEL);
+		if (!acl)
+			return ERR_PTR(-ENOMEM);
+		ace = acl->a_entries;
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!richace_is_inheritable(dir_ace))
+				continue;
+			memcpy(ace, dir_ace, sizeof(struct richace));
+			if (dir_ace->e_flags & RICHACE_NO_PROPAGATE_INHERIT_ACE)
+				richace_clear_inheritance_flags(ace);
+			if ((dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE) &&
+			    !(dir_ace->e_flags & RICHACE_DIRECTORY_INHERIT_ACE))
+				ace->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+			ace++;
+		}
+	} else {
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
+				continue;
+			count++;
+		}
+		if (!count)
+			return NULL;
+		acl = richacl_alloc(count, GFP_KERNEL);
+		if (!acl)
+			return ERR_PTR(-ENOMEM);
+		ace = acl->a_entries;
+		richacl_for_each_entry(dir_ace, dir_acl) {
+			if (!(dir_ace->e_flags & RICHACE_FILE_INHERIT_ACE))
+				continue;
+			memcpy(ace, dir_ace, sizeof(struct richace));
+			richace_clear_inheritance_flags(ace);
+			/*
+			 * RICHACE_DELETE_CHILD is meaningless for
+			 * non-directories, so clear it.
+			 */
+			ace->e_mask &= ~RICHACE_DELETE_CHILD;
+			ace++;
+		}
+	}
+
+	return acl;
+}
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
index 4b06ab3..f233872 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -193,3 +193,65 @@ out:
 	return denied ? -EACCES : 0;
 }
 EXPORT_SYMBOL_GPL(richacl_permission);
+
+/**
+ * richacl_inherit_inode  -  compute inherited acl and file mode
+ * @dir_acl:	acl of the containing directory
+ * @inode:	inode of the new file (create mode in i_mode)
+ *
+ * The file permission bits in inode->i_mode must be set to the create mode by
+ * the caller.
+ *
+ * If there is an inheritable acl, the maximum permissions that the acl grants
+ * will be computed and permissions not granted by the acl will be removed from
+ * inode->i_mode.  If there is no inheritable acl, the umask will be applied
+ * instead.
+ */
+static struct richacl *
+richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode)
+{
+	struct richacl *acl;
+	mode_t mask;
+
+	acl = richacl_inherit(dir_acl, S_ISDIR(inode->i_mode));
+	if (acl) {
+		mask = inode->i_mode;
+		if (richacl_equiv_mode(acl, &mask) == 0) {
+			richacl_put(acl);
+			acl = NULL;
+		} else {
+			richacl_compute_max_masks(acl, inode->i_uid);
+			/*
+			 * Ensure that the acl will not grant any permissions
+			 * beyond the create mode.
+			 */
+			acl->a_flags |= RICHACL_MASKED;
+			acl->a_owner_mask &= richacl_mode_to_mask(inode->i_mode >> 6);
+			acl->a_group_mask &= richacl_mode_to_mask(inode->i_mode >> 3);
+			acl->a_other_mask &= richacl_mode_to_mask(inode->i_mode);
+			mask = ~S_IRWXUGO | richacl_masks_to_mode(acl);
+		}
+	} else
+		mask = ~current_umask();
+
+	inode->i_mode &= mask;
+	return acl;
+}
+
+struct richacl *richacl_create(struct inode *inode, struct inode *dir)
+{
+	struct richacl *dir_acl, *acl = NULL;
+
+	if (S_ISLNK(inode->i_mode))
+		return NULL;
+	dir_acl = get_richacl(dir);
+	if (dir_acl) {
+		if (IS_ERR(dir_acl))
+			return dir_acl;
+		acl = richacl_inherit_inode(dir_acl, inode);
+		richacl_put(dir_acl);
+	} else
+		inode->i_mode &= ~current_umask();
+	return acl;
+}
+EXPORT_SYMBOL_GPL(richacl_create);
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 2357d6b..3fcefe3 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -308,8 +308,10 @@ extern unsigned int richacl_want_to_mask(unsigned int);
 extern void richacl_compute_max_masks(struct richacl *, kuid_t);
 extern struct richacl *richacl_chmod(struct richacl *, mode_t);
 extern int richacl_equiv_mode(const struct richacl *, mode_t *);
+extern struct richacl *richacl_inherit(const struct richacl *, int);
 
 /* richacl_inode.c */
 extern int richacl_permission(struct inode *, const struct richacl *, int);
+extern struct richacl *richacl_create(struct inode *, struct inode *);
 
 #endif /* __RICHACL_H */
-- 
2.4.2

[RFC v4 15/31] richacl: Automatic Inheritance

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:57:04

Automatic Inheritance (AI) allows changes to the acl of a directory to
propagate down to children.

This is mostly implemented in user space: when a process changes the
permissions of a directory and Automatic Inheritance is enabled for that
directory, the process must propagate those changes to all children,
recursively.

The kernel enables this by keeping track of which permissions have been
inherited at file create time.  In addition, it makes sure that permission
propagation is turned off when the permissions of a file are set explicitly
(for example, upon create or chmod).

Automatic Inheritance works as follows:

 - When the RICHACL_AUTO_INHERIT flag in the acl of a file is not set,
   the file is not affected by AI.

 - When the RICHACL_AUTO_INHERIT flag in the acl of a directory is set
   and a file or subdirectory is created in that directory, the
   inherited acl will have the RICHACL_AUTO_INHERIT flag set, and all
   inherited aces will have the RICHACE_INHERITED_ACE flag set.  This
   allows user space to distinguish between aces which have been
   inherited and aces which have been explicitly added.

 - When the RICHACL_PROTECTED acl flag in the acl of a file is set, AI
   will not modify the acl of the file.  This does not affect
   propagation of permissions from the file to its children (if the
   file is a directory).

Linux does not have a way of creating files without setting the file
permission bits, so all files created inside a directory with
RICHACL_AUTO_INHERIT set will have the RICHACL_PROTECTED flag set.  This
effectively disables Automatic Inheritance.

Protocols which support creating files without specifying permissions can
explicitly clear the RICHACL_PROTECTED flag after creating a file and
reset the file masks to "undo" applying the create mode; see
richacl_compute_max_masks().  They should set the RICHACL_DEFAULTED flag.
This is a workaround; a mechanism that would allow a process to indicate
to the kernel to ignore the create mode when there are inherited
permissions would fix this problem.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_base.c       | 10 +++++++++-
 fs/richacl_inode.c      |  7 +++++++
 include/linux/richacl.h | 25 +++++++++++++++++++++++--
 3 files changed, 39 insertions(+), 3 deletions(-)
diff --git a/fs/richacl_base.c b/fs/richacl_base.c
index 29d3197..4a31a2f 100644
--- a/fs/richacl_base.c
+++ b/fs/richacl_base.c
@@ -349,7 +349,8 @@ richacl_chmod(struct richacl *acl, mode_t mode)
 	if (acl->a_owner_mask == owner_mask &&
 	    acl->a_group_mask == group_mask &&
 	    acl->a_other_mask == other_mask &&
-	    (acl->a_flags & RICHACL_MASKED))
+	    (acl->a_flags & RICHACL_MASKED) &&
+	    (!richacl_is_auto_inherit(acl) || richacl_is_protected(acl)))
 		return acl;
 
 	clone = richacl_clone(acl, GFP_KERNEL);
@@ -361,6 +362,8 @@ richacl_chmod(struct richacl *acl, mode_t mode)
 	clone->a_owner_mask = owner_mask;
 	clone->a_group_mask = group_mask;
 	clone->a_other_mask = other_mask;
+	if (richacl_is_auto_inherit(clone))
+		clone->a_flags |= RICHACL_PROTECTED;
 
 	return clone;
 }
@@ -526,6 +529,11 @@ richacl_inherit(const struct richacl *dir_acl, int isdir)
 			ace++;
 		}
 	}
+	if (richacl_is_auto_inherit(dir_acl)) {
+		acl->a_flags = RICHACL_AUTO_INHERIT;
+		richacl_for_each_entry(ace, acl)
+			ace->e_flags |= RICHACE_INHERITED_ACE;
+	}
 
 	return acl;
 }
diff --git a/fs/richacl_inode.c b/fs/richacl_inode.c
index f233872..8277963 100644
--- a/fs/richacl_inode.c
+++ b/fs/richacl_inode.c
@@ -220,6 +220,13 @@ richacl_inherit_inode(const struct richacl *dir_acl, struct inode *inode)
 			richacl_put(acl);
 			acl = NULL;
 		} else {
+			/*
+			 * We need to set RICHACL_PROTECTED because we are
+			 * doing an implicit chmod
+			 */
+			if (richacl_is_auto_inherit(acl))
+				acl->a_flags |= RICHACL_PROTECTED;
+
 			richacl_compute_max_masks(acl, inode->i_uid);
 			/*
 			 * Ensure that the acl will not grant any permissions
diff --git a/include/linux/richacl.h b/include/linux/richacl.h
index 3fcefe3..4ccd6fb 100644
--- a/include/linux/richacl.h
+++ b/include/linux/richacl.h
@@ -53,9 +53,15 @@ struct richacl {
 	     _ace--)
 
 /* a_flag values */
+#define RICHACL_AUTO_INHERIT			0x01
+#define RICHACL_PROTECTED			0x02
+#define RICHACL_DEFAULTED			0x04
 #define RICHACL_MASKED				0x80
 
-#define RICHACL_VALID_FLAGS (					\
+#define RICHACL_VALID_FLAGS (			\
+		RICHACL_AUTO_INHERIT |		\
+		RICHACL_PROTECTED |		\
+		RICHACL_DEFAULTED |		\
 		RICHACL_MASKED)
 
 /* e_type values */
@@ -68,6 +74,7 @@ struct richacl {
 #define RICHACE_NO_PROPAGATE_INHERIT_ACE	0x0004
 #define RICHACE_INHERIT_ONLY_ACE		0x0008
 #define RICHACE_IDENTIFIER_GROUP		0x0040
+#define RICHACE_INHERITED_ACE			0x0080
 #define RICHACE_SPECIAL_WHO			0x4000
 
 #define RICHACE_VALID_FLAGS (					\
@@ -76,6 +83,7 @@ struct richacl {
 	RICHACE_NO_PROPAGATE_INHERIT_ACE |			\
 	RICHACE_INHERIT_ONLY_ACE |				\
 	RICHACE_IDENTIFIER_GROUP |				\
+	RICHACE_INHERITED_ACE |					\
 	RICHACE_SPECIAL_WHO)
 
 /* e_mask bitflags */
@@ -187,6 +195,18 @@ extern void set_cached_richacl(struct inode *, struct richacl *);
 extern void forget_cached_richacl(struct inode *);
 extern struct richacl *get_richacl(struct inode *);
 
+static inline int
+richacl_is_auto_inherit(const struct richacl *acl)
+{
+	return acl->a_flags & RICHACL_AUTO_INHERIT;
+}
+
+static inline int
+richacl_is_protected(const struct richacl *acl)
+{
+	return acl->a_flags & RICHACL_PROTECTED;
+}
+
 /**
  * richace_is_owner  -  check if @ace is an OWNER@ entry
  */
@@ -268,7 +288,8 @@ richace_clear_inheritance_flags(struct richace *ace)
 	ace->e_flags &= ~(RICHACE_FILE_INHERIT_ACE |
 			  RICHACE_DIRECTORY_INHERIT_ACE |
 			  RICHACE_NO_PROPAGATE_INHERIT_ACE |
-			  RICHACE_INHERIT_ONLY_ACE);
+			  RICHACE_INHERIT_ONLY_ACE |
+			  RICHACE_INHERITED_ACE);
 }
 
 /**
-- 
2.4.2

[RFC v4 17/31] vfs: Add richacl permission checking

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:57:06

Hook the richacl permission checking function into the vfs.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/namei.c     | 51 +++++++++++++++++++++++++++++++++++++++++++++++++--
 fs/posix_acl.c |  6 +++---
 2 files changed, 52 insertions(+), 5 deletions(-)
diff --git a/fs/namei.c b/fs/namei.c
index 8f91790..e0e7ceb 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -35,6 +35,7 @@
 #include <linux/fs_struct.h>
 #include <linux/posix_acl.h>
 #include <linux/hash.h>
+#include <linux/richacl.h>
 #include <asm/uaccess.h>
 
 #include "internal.h"
@@ -255,7 +256,40 @@ void putname(struct filename *name)
 		__putname(name);
 }
 
-static int check_acl(struct inode *inode, int mask)
+static int check_richacl(struct inode *inode, int mask)
+{
+#ifdef CONFIG_FS_RICHACL
+	struct richacl *acl;
+
+	if (mask & MAY_NOT_BLOCK) {
+		acl = get_cached_richacl_rcu(inode);
+		if (!acl)
+			goto no_acl;
+		/* no ->get_richacl() calls in RCU mode... */
+		if (acl == ACL_NOT_CACHED)
+			return -ECHILD;
+		return richacl_permission(inode, acl, mask & ~MAY_NOT_BLOCK);
+	}
+
+	acl = get_richacl(inode);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	if (acl) {
+		int error = richacl_permission(inode, acl, mask);
+		richacl_put(acl);
+		return error;
+	}
+no_acl:
+#endif
+	if (mask & (MAY_DELETE_SELF | MAY_TAKE_OWNERSHIP |
+		    MAY_CHMOD | MAY_SET_TIMES)) {
+		/* File permission bits cannot grant this. */
+		return -EACCES;
+	}
+	return -EAGAIN;
+}
+
+static int check_posix_acl(struct inode *inode, int mask)
 {
 #ifdef CONFIG_FS_POSIX_ACL
 	struct posix_acl *acl;
@@ -290,11 +324,24 @@ static int acl_permission_check(struct inode *inode, int mask)
 {
 	unsigned int mode = inode->i_mode;
 
+	/*
+	 * With POSIX ACLs, the (mode & S_IRWXU) bits exactly match the owner
+	 * permissions, and we can skip checking posix acls for the owner.
+	 * With richacls, the owner may be granted fewer permissions than the
+	 * mode bits seem to suggest (for example, append but not write), and
+	 * we always need to check the richacl.
+	 */
+
+	if (IS_RICHACL(inode)) {
+		int error = check_richacl(inode, mask);
+		if (error != -EAGAIN)
+			return error;
+	}
 	if (likely(uid_eq(current_fsuid(), inode->i_uid)))
 		mode >>= 6;
 	else {
 		if (IS_POSIXACL(inode) && (mode & S_IRWXG)) {
-			int error = check_acl(inode, mask);
+			int error = check_posix_acl(inode, mask);
 			if (error != -EAGAIN)
 				return error;
 		}
diff --git a/fs/posix_acl.c b/fs/posix_acl.c
index 8fa9bc5..1d8cb0c 100644
--- a/fs/posix_acl.c
+++ b/fs/posix_acl.c
@@ -100,13 +100,13 @@ struct posix_acl *get_acl(struct inode *inode, int type)
 {
 	struct posix_acl *acl;
 
+	if (!IS_POSIXACL(inode))
+		return NULL;
+
 	acl = get_cached_acl(inode, type);
 	if (acl != ACL_NOT_CACHED)
 		return acl;
 
-	if (!IS_POSIXACL(inode))
-		return NULL;
-
 	/*
 	 * A filesystem can force a ACL callback by just never filling the
 	 * ACL cache. But normally you'd fill the cache either at inode
-- 
2.4.2

[RFC v4 23/31] richacl: Set the owner permissions to the owner mask

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:57:12

Change the acl so that owner@ is granted the permissions set in the owner mask.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/richacl_compat.c | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)
diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index c6db72a..882c2f1 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -405,3 +405,45 @@ richacl_propagate_everyone(struct richacl_alloc *alloc)
 	}
 	return 0;
 }
+
+/**
+ * richacl_set_owner_permissions  -  set the owner permissions to the owner mask
+ *
+ * Change the acl so that owner@ is granted the permissions set in the owner
+ * mask.  This leaves at most one efective owner@ allow entry at the beginning
+ * of the acl.
+ */
+static int
+richacl_set_owner_permissions(struct richacl_alloc *alloc)
+{
+	unsigned int x = RICHACE_POSIX_ALWAYS_ALLOWED;
+	unsigned int owner_mask = alloc->acl->a_owner_mask & ~x;
+	unsigned int denied = 0;
+	struct richace *ace;
+
+	if (!owner_mask)
+		return 0;
+
+	richacl_for_each_entry(ace, alloc->acl) {
+		if (richace_is_owner(ace)) {
+			if (!richace_is_inherit_only(ace))
+				richace_change_mask(alloc, &ace, 0);
+		} else {
+			if (richace_is_deny(ace))
+				denied |= ace->e_mask;
+		}
+	}
+
+	if (owner_mask & (denied |
+			  ~alloc->acl->a_other_mask |
+			  ~alloc->acl->a_group_mask)) {
+		ace = alloc->acl->a_entries;
+		if (richacl_insert_entry(alloc, &ace))
+			return -1;
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = RICHACE_SPECIAL_WHO;
+		ace->e_mask = owner_mask;
+		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
+	}
+	return 0;
+}
-- 
2.4.2

[RFC v4 04/31] vfs: Make the inode passed to inode_change_ok non-const

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:57:38

We will need to call iop->permission and iop->get_acl from
inode_change_ok() for additional permission checks, and both take a
non-const inode.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/attr.c          | 2 +-
 include/linux/fs.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/fs/attr.c b/fs/attr.c
index 6530ced..328be71 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -28,7 +28,7 @@
  * Should be called as the first thing in ->setattr implementations,
  * possibly after taking additional locks.
  */
-int inode_change_ok(const struct inode *inode, struct iattr *attr)
+int inode_change_ok(struct inode *inode, struct iattr *attr)
 {
 	unsigned int ia_valid = attr->ia_valid;
 
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 96a9954..b98b211 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2820,7 +2820,7 @@ extern int buffer_migrate_page(struct address_space *,
 #define buffer_migrate_page NULL
 #endif
 
-extern int inode_change_ok(const struct inode *, struct iattr *);
+extern int inode_change_ok(struct inode *, struct iattr *);
 extern int inode_newsize_ok(const struct inode *, loff_t offset);
 extern void setattr_copy(struct inode *inode, const struct iattr *attr);
 
-- 
2.4.2

[RFC v4 19/31] ext4: Add richacl feature flag

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:58:02

From: "Aneesh Kumar K.V" <redacted>

This feature flag selects richacl instead of posix acl support on the file
system. In addition, the "acl" mount option is needed for enabling either of
the two kinds of acls.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/ext4/ext4.h  |  6 ++++--
 fs/ext4/super.c | 41 ++++++++++++++++++++++++++++++++---------
 2 files changed, 36 insertions(+), 11 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 9a83f14..e164526 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -996,7 +996,7 @@ struct ext4_inode_info {
 #define EXT4_MOUNT_UPDATE_JOURNAL	0x01000	/* Update the journal format */
 #define EXT4_MOUNT_NO_UID32		0x02000  /* Disable 32-bit UIDs */
 #define EXT4_MOUNT_XATTR_USER		0x04000	/* Extended user attributes */
-#define EXT4_MOUNT_POSIX_ACL		0x08000	/* POSIX Access Control Lists */
+#define EXT4_MOUNT_ACL			0x08000	/* Access Control Lists */
 #define EXT4_MOUNT_NO_AUTO_DA_ALLOC	0x10000	/* No auto delalloc mapping */
 #define EXT4_MOUNT_BARRIER		0x20000 /* Use block barriers */
 #define EXT4_MOUNT_QUOTA		0x80000 /* Some quota option set */
@@ -1593,6 +1593,7 @@ static inline int ext4_encrypted_inode(struct inode *inode)
 #define EXT4_FEATURE_INCOMPAT_LARGEDIR		0x4000 /* >2GB or 3-lvl htree */
 #define EXT4_FEATURE_INCOMPAT_INLINE_DATA	0x8000 /* data in inode */
 #define EXT4_FEATURE_INCOMPAT_ENCRYPT		0x10000
+#define EXT4_FEATURE_INCOMPAT_RICHACL		0x20000
 
 #define EXT2_FEATURE_COMPAT_SUPP	EXT4_FEATURE_COMPAT_EXT_ATTR
 #define EXT2_FEATURE_INCOMPAT_SUPP	(EXT4_FEATURE_INCOMPAT_FILETYPE| \
@@ -1618,7 +1619,8 @@ static inline int ext4_encrypted_inode(struct inode *inode)
 					 EXT4_FEATURE_INCOMPAT_FLEX_BG| \
 					 EXT4_FEATURE_INCOMPAT_MMP | \
 					 EXT4_FEATURE_INCOMPAT_INLINE_DATA | \
-					 EXT4_FEATURE_INCOMPAT_ENCRYPT)
+					 EXT4_FEATURE_INCOMPAT_ENCRYPT | \
+					 EXT4_FEATURE_INCOMPAT_RICHACL)
 #define EXT4_FEATURE_RO_COMPAT_SUPP	(EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER| \
 					 EXT4_FEATURE_RO_COMPAT_LARGE_FILE| \
 					 EXT4_FEATURE_RO_COMPAT_GDT_CSUM| \
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index ca9d4a2..e601996 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1231,6 +1231,27 @@ static ext4_fsblk_t get_sb_block(void **data)
 	return sb_block;
 }
 
+static int enable_acl(struct super_block *sb)
+{
+	sb->s_flags &= ~(MS_POSIXACL | MS_RICHACL);
+	if (test_opt(sb, ACL)) {
+		if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_RICHACL)) {
+#ifdef CONFIG_EXT4_FS_RICHACL
+			sb->s_flags |= MS_RICHACL;
+#else
+			return -EOPNOTSUPP;
+#endif
+		} else {
+#ifdef CONFIG_EXT4_FS_POSIX_ACL
+			sb->s_flags |= MS_POSIXACL;
+#else
+			return -EOPNOTSUPP;
+#endif
+		}
+	}
+	return 0;
+}
+
 #define DEFAULT_JOURNAL_IOPRIO (IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 3))
 static char deprecated_msg[] = "Mount option \"%s\" will be removed by %s\n"
 	"Contact linux-ext4@vger.kernel.org if you think we should keep it.\n";
@@ -1377,9 +1398,9 @@ static const struct mount_opts {
 	 MOPT_NO_EXT2 | MOPT_DATAJ},
 	{Opt_user_xattr, EXT4_MOUNT_XATTR_USER, MOPT_SET},
 	{Opt_nouser_xattr, EXT4_MOUNT_XATTR_USER, MOPT_CLEAR},
-#ifdef CONFIG_EXT4_FS_POSIX_ACL
-	{Opt_acl, EXT4_MOUNT_POSIX_ACL, MOPT_SET},
-	{Opt_noacl, EXT4_MOUNT_POSIX_ACL, MOPT_CLEAR},
+#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL)
+	{Opt_acl, EXT4_MOUNT_ACL, MOPT_SET},
+	{Opt_noacl, EXT4_MOUNT_ACL, MOPT_CLEAR},
 #else
 	{Opt_acl, 0, MOPT_NOSUPPORT},
 	{Opt_noacl, 0, MOPT_NOSUPPORT},
@@ -3544,8 +3565,8 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
 		set_opt(sb, NO_UID32);
 	/* xattr user namespace & acls are now defaulted on */
 	set_opt(sb, XATTR_USER);
-#ifdef CONFIG_EXT4_FS_POSIX_ACL
-	set_opt(sb, POSIX_ACL);
+#if defined(CONFIG_EXT4_FS_POSIX_ACL) || defined(CONFIG_EXT4_FS_RICHACL)
+	set_opt(sb, ACL);
 #endif
 	/* don't forget to enable journal_csum when metadata_csum is enabled. */
 	if (ext4_has_metadata_csum(sb))
@@ -3626,8 +3647,9 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
 			clear_opt(sb, DELALLOC);
 	}
 
-	sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
-		(test_opt(sb, POSIX_ACL) ? MS_POSIXACL : 0);
+	err = enable_acl(sb);
+	if (err)
+		goto failed_mount;
 
 	if (le32_to_cpu(es->s_rev_level) == EXT4_GOOD_OLD_REV &&
 	    (EXT4_HAS_COMPAT_FEATURE(sb, ~0U) ||
@@ -4933,8 +4955,9 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data)
 	if (sbi->s_mount_flags & EXT4_MF_FS_ABORTED)
 		ext4_abort(sb, "Abort forced by user");
 
-	sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
-		(test_opt(sb, POSIX_ACL) ? MS_POSIXACL : 0);
+	err = enable_acl(sb);
+	if (err)
+		goto restore_opts;
 
 	es = sbi->s_es;
 
-- 
2.4.2

[RFC v4 20/31] richacl: acl editing helper functions

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:58:03

The file masks in richacls make chmod and creating new files more
efficient than having to apply file permission bits to the acl directly.
They also allow us to regain permissions from an acl even after a
restrictive chmod, because the permissions in the acl itself are not
being destroyed.  In POSIX ACLs, the mask entry has a similar function.

Protocols like nfsv4 do not understand file masks.  For those protocols,
we need to compute nfs4 acls which represent the effective permissions
granted by a richacl: we need to "apply" the file masks to the acl.

This is the first in a series of richacl transformation patches; it
implements basic richacl editing functions.  The following patches
implement algorithms for transforming a richacl so that it can be
evaluated as a plain nfs4 acl, with identical permission check results.

Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
---
 fs/Makefile                    |   3 +-
 fs/richacl_compat.c            | 161 +++++++++++++++++++++++++++++++++++++++++
 include/linux/richacl_compat.h |  40 ++++++++++
 3 files changed, 203 insertions(+), 1 deletion(-)
 create mode 100644 fs/richacl_compat.c
 create mode 100644 include/linux/richacl_compat.h
diff --git a/fs/Makefile b/fs/Makefile
index e0cdaab..1baae4e 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -48,7 +48,8 @@ obj-$(CONFIG_SYSCTL)		+= drop_caches.o
 
 obj-$(CONFIG_FHANDLE)		+= fhandle.o
 obj-$(CONFIG_FS_RICHACL)	+= richacl.o
-richacl-y			:= richacl_base.o richacl_inode.o richacl_xattr.o
+richacl-y			:= richacl_base.o richacl_inode.o \
+				   richacl_xattr.o richacl_compat.o
 
 obj-y				+= quota/
 
diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
new file mode 100644
index 0000000..1c62c3c
--- /dev/null
+++ b/fs/richacl_compat.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2006, 2010  Novell, Inc.
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruen@kernel.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/richacl_compat.h>
+
+/**
+ * richacl_prepare  -  allocate richacl being constructed
+ *
+ * Allocate a richacl which can hold @count entries but which is initially
+ * empty.
+ */
+struct richacl *richacl_prepare(struct richacl_alloc *alloc, unsigned int count)
+{
+	alloc->acl = richacl_alloc(count, GFP_KERNEL);
+	if (!alloc->acl)
+		return NULL;
+	alloc->acl->a_count = 0;
+	alloc->count = count;
+	return alloc->acl;
+}
+EXPORT_SYMBOL_GPL(richacl_prepare);
+
+/**
+ * richacl_delete_entry  -  delete an entry in an acl
+ * @alloc:	acl and number of allocated entries
+ * @ace:	an entry in @alloc->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.
+ */
+void
+richacl_delete_entry(struct richacl_alloc *alloc, struct richace **ace)
+{
+	void *end = alloc->acl->a_entries + alloc->acl->a_count;
+
+	memmove(*ace, *ace + 1, end - (void *)(*ace + 1));
+	(*ace)--;
+	alloc->acl->a_count--;
+}
+EXPORT_SYMBOL_GPL(richacl_delete_entry);
+
+/**
+ * richacl_insert_entry  -  insert an entry in an acl
+ * @alloc:	acl and number of allocated entries
+ * @ace:	entry before which the new entry shall be inserted
+ *
+ * Insert a new entry in @alloc->acl at position @ace and zero-initialize
+ * it.  This may require reallocating @alloc->acl.
+ */
+int
+richacl_insert_entry(struct richacl_alloc *alloc, struct richace **ace)
+{
+	if (alloc->count == alloc->acl->a_count) {
+		int n = *ace - alloc->acl->a_entries;
+		struct richacl *acl2;
+
+		acl2 = richacl_alloc(alloc->acl->a_count + 1, GFP_KERNEL);
+		if (!acl2)
+			return -1;
+		acl2->a_flags = alloc->acl->a_flags;
+		acl2->a_owner_mask = alloc->acl->a_owner_mask;
+		acl2->a_group_mask = alloc->acl->a_group_mask;
+		acl2->a_other_mask = alloc->acl->a_other_mask;
+		memcpy(acl2->a_entries, alloc->acl->a_entries,
+		       n * sizeof(struct richace));
+		memcpy(acl2->a_entries + n + 1, *ace,
+		       (alloc->acl->a_count - n) * sizeof(struct richace));
+		kfree(alloc->acl);
+		alloc->acl = acl2;
+		alloc->count = acl2->a_count;
+		*ace = acl2->a_entries + n;
+	} else {
+		void *end = alloc->acl->a_entries + alloc->acl->a_count;
+
+		memmove(*ace + 1, *ace, end - (void *)*ace);
+		alloc->acl->a_count++;
+	}
+	memset(*ace, 0, sizeof(struct richace));
+	return 0;
+}
+EXPORT_SYMBOL_GPL(richacl_insert_entry);
+
+/**
+ * richacl_append_entry  -  append an entry to an acl
+ * @alloc:		acl and number of allocated entries
+ *
+ * This may require reallocating @alloc->acl.
+ */
+struct richace *richacl_append_entry(struct richacl_alloc *alloc)
+{
+	struct richacl *acl = alloc->acl;
+	struct richace *ace = acl->a_entries + acl->a_count;
+
+	if (alloc->count > alloc->acl->a_count) {
+		acl->a_count++;
+		return ace;
+	}
+	return richacl_insert_entry(alloc, &ace) ? NULL : ace;
+}
+EXPORT_SYMBOL_GPL(richacl_append_entry);
+
+/**
+ * richace_change_mask  -  set the mask of @ace to @mask
+ * @alloc:	acl and number of allocated entries
+ * @ace:	entry to modify
+ * @mask:	new mask for @ace
+ *
+ * If @ace is inheritable, a inherit-only ace is inserted before @ace which
+ * includes the inheritable permissions of @ace and the inheritance flags of
+ * @ace are cleared before changing the mask.
+ *
+ * If @mask is 0, the original ace is turned into an inherit-only entry if
+ * there are any inheritable permissions, and removed 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.
+ */
+static int
+richace_change_mask(struct richacl_alloc *alloc, struct richace **ace,
+			   unsigned int mask)
+{
+	if (mask && (*ace)->e_mask == mask)
+		(*ace)->e_flags &= ~RICHACE_INHERIT_ONLY_ACE;
+	else if (mask & ~RICHACE_POSIX_ALWAYS_ALLOWED) {
+		if (richace_is_inheritable(*ace)) {
+			if (richacl_insert_entry(alloc, ace))
+				return -1;
+			memcpy(*ace, *ace + 1, sizeof(struct richace));
+			(*ace)->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+			(*ace)++;
+			richace_clear_inheritance_flags(*ace);
+		}
+		(*ace)->e_mask = mask;
+	} else {
+		if (richace_is_inheritable(*ace))
+			(*ace)->e_flags |= RICHACE_INHERIT_ONLY_ACE;
+		else
+			richacl_delete_entry(alloc, ace);
+	}
+	return 0;
+}
diff --git a/include/linux/richacl_compat.h b/include/linux/richacl_compat.h
new file mode 100644
index 0000000..a9ff630
--- /dev/null
+++ b/include/linux/richacl_compat.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015  Red Hat, Inc.
+ * Written by Andreas Gruenbacher <agruenba@redhat.com>
+ *
+ * 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.
+ */
+
+#ifndef __RICHACL_COMPAT_H
+#define __RICHACL_COMPAT_H
+
+#include <linux/richacl.h>
+
+/**
+ * struct richacl_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 richacl_alloc {
+	struct richacl *acl;
+	unsigned int count;
+};
+
+struct richacl *richacl_prepare(struct richacl_alloc *, unsigned int);
+struct richace *richacl_append_entry(struct richacl_alloc *);
+int richacl_insert_entry(struct richacl_alloc *, struct richace **);
+void richacl_delete_entry(struct richacl_alloc *, struct richace **);
+
+#endif  /* __RICHACL_COMPAT_H */
-- 
2.4.2

[RFC v4 25/31] richacl: Isolate the owner and group classes

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:58:09

When applying the file masks to an acl, we need to ensure that no
process gets more permissions than allowed by its file mask.

This may require inserting an owner@ deny ace to ensure this if the
owner mask contains fewer permissions than the group or other mask.  For
example, when applying mode 0466 to the following acl:

   everyone@:rw::allow

A deny ace needs to be inserted so that the owner won't get elevated
write access:

   owner@:w::deny
   everyone@:rw::allow

Likewise, we may need to insert group class deny aces if the group mask
contains fewer permissions than the other mask.  For example, when
applying mode 0646 to the following acl:

   owner@:rw::allow
   everyone@:rw::allow

A deny ace needs to be inserted so that the owning group won't get
elevated write access:

   owner@:rw::allow
   group@:w::deny
   everyone@:rw::allow

Signed-off-by: Andreas Gruenbacher <agruen@kernel.org>
---
 fs/richacl_compat.c | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 228 insertions(+)
diff --git a/fs/richacl_compat.c b/fs/richacl_compat.c
index b699cd3..ae10fa6 100644
--- a/fs/richacl_compat.c
+++ b/fs/richacl_compat.c
@@ -480,3 +480,231 @@ richacl_set_other_permissions(struct richacl_alloc *alloc)
 		richace_change_mask(alloc, &ace, other_mask);
 	return 0;
 }
+
+/**
+ * richacl_max_allowed  -  maximum permissions that anybody is allowed
+ */
+static unsigned int
+richacl_max_allowed(struct richacl *acl)
+{
+	struct richace *ace;
+	unsigned int allowed = 0;
+
+	richacl_for_each_entry_reverse(ace, acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_allow(ace))
+			allowed |= ace->e_mask;
+		else if (richace_is_deny(ace)) {
+			if (richace_is_everyone(ace))
+				allowed &= ~ace->e_mask;
+		}
+	}
+	return allowed;
+}
+
+/**
+ * richacl_isolate_owner_class  -  limit the owner class to the owner file mask
+ * @alloc:	acl and number of allocated entries
+ *
+ * POSIX requires that after a chmod, the owner class is granted no more
+ * permissions than the owner file permission bits.  For richacls, this
+ * means that the owner class must not be granted any permissions that the
+ * owner mask does not include.
+ *
+ * When we apply file masks to an acl which grant more permissions to the group
+ * or other class than to the owner class, we may end up in a situation where
+ * the owner is granted additional permissions from other aces.  For example,
+ * given this acl:
+ *
+ *    everyone:rwx::allow
+ *
+ * when file masks corresponding to mode 0466 are applied, after
+ * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
+ *
+ *    owner@:r::allow
+ *    everyone@:rw::allow
+ *
+ * This acl still grants the owner rw access through the everyone@ allow ace.
+ * To fix this, we must deny the owner w access:
+ *
+ *    owner@:w::deny
+ *    owner@:r::allow
+ *    everyone@:rw::allow
+ */
+static int
+richacl_isolate_owner_class(struct richacl_alloc *alloc)
+{
+	struct richace *ace;
+	unsigned int allowed = 0;
+
+	allowed = richacl_max_allowed(alloc->acl);
+	if (allowed & ~alloc->acl->a_owner_mask) {
+		/*
+		 * Figure out if we can update an existig OWNER@ DENY entry.
+		 */
+		richacl_for_each_entry(ace, alloc->acl) {
+			if (richace_is_inherit_only(ace))
+				continue;
+			if (richace_is_deny(ace)) {
+				if (richace_is_owner(ace))
+					break;
+			} else if (richace_is_allow(ace)) {
+				ace = alloc->acl->a_entries + alloc->acl->a_count;
+				break;
+			}
+		}
+		if (ace != alloc->acl->a_entries + alloc->acl->a_count) {
+			if (richace_change_mask(alloc, &ace, ace->e_mask |
+					(allowed & ~alloc->acl->a_owner_mask)))
+				return -1;
+		} else {
+			/* Insert an owner@ deny entry at the front. */
+			ace = alloc->acl->a_entries;
+			if (richacl_insert_entry(alloc, &ace))
+				return -1;
+			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+			ace->e_flags = RICHACE_SPECIAL_WHO;
+			ace->e_mask = allowed & ~alloc->acl->a_owner_mask;
+			ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
+		}
+	}
+	return 0;
+}
+
+/**
+ * __richacl_isolate_who  -  isolate entry from everyone@ allow entry
+ * @alloc:	acl and number of allocated entries
+ * @who:	identifier to isolate
+ * @deny:	permissions this identifier should not be allowed
+ *
+ * See richacl_isolate_group_class().
+ */
+static int
+__richacl_isolate_who(struct richacl_alloc *alloc, struct richace *who,
+		      unsigned int deny)
+{
+	struct richace *ace;
+	unsigned int n;
+	/*
+	 * Compute the permissions already denied to @who.
+	 */
+	richacl_for_each_entry(ace, alloc->acl) {
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_same_identifier(ace, who) &&
+		    richace_is_deny(ace))
+			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 = alloc->acl->a_count - 2; n != -1; n--) {
+		ace = alloc->acl->a_entries + n;
+		if (richace_is_inherit_only(ace))
+			continue;
+		if (richace_is_deny(ace)) {
+			if (richace_is_same_identifier(ace, who))
+				break;
+		} else if (richace_is_allow(ace) &&
+			   (ace->e_mask & deny)) {
+			n = -1;
+			break;
+		}
+	}
+	if (n != -1) {
+		if (richace_change_mask(alloc, &ace, ace->e_mask | deny))
+			return -1;
+	} else {
+		/*
+		 * Insert a new entry before the trailing everyone@ deny entry.
+		 */
+		struct richace who_copy;
+
+		ace = alloc->acl->a_entries + alloc->acl->a_count - 1;
+		memcpy(&who_copy, who, sizeof(struct richace));
+		if (richacl_insert_entry(alloc, &ace))
+			return -1;
+		memcpy(ace, &who_copy, sizeof(struct richace));
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		richace_clear_inheritance_flags(ace);
+		ace->e_mask = deny;
+	}
+	return 0;
+}
+
+/**
+ * richacl_isolate_group_class  -  limit the group class to the group file mask
+ * @alloc:	acl and number of allocated entries
+ *
+ * POSIX requires that after a chmod, the group class is granted no more
+ * permissions than the group file permission bits.  For richacls, this
+ * means that the group class must not be granted any permissions that the
+ * group mask does not include.
+ *
+ * When we apply file masks to an acl which grant more permissions to the other
+ * class than to the group class, we may end up in a situation where processes
+ * in the group class are granted additional permission from other aces.  For
+ * example, given this acl:
+ *
+ *    joe:rwx::allow
+ *    everyone:rwx::allow
+ *
+ * when file masks corresponding to mode 0646 are applied, after
+ * richacl_propagate_everyone() and __richacl_apply_masks(), we end up with:
+ *
+ *    joe:r::allow
+ *    owner@:rw::allow
+ *    group@:r::allow
+ *    everyone@:rw::allow
+ *
+ * This acl still grants joe and group@ rw access through the everyone@ allow
+ * ace.  To fix this, we must deny w access to group class aces before the
+ * everyone@ allow ace at the end of the acl:
+ *
+ *    joe:r::allow
+ *    owner@:rw::allow
+ *    group@:r::allow
+ *    joe:w::deny
+ *    group@:w::deny
+ *    everyone@:rw::allow
+ */
+static int
+richacl_isolate_group_class(struct richacl_alloc *alloc)
+{
+	struct richace who = {
+		.e_flags = RICHACE_SPECIAL_WHO,
+		.e_id.special = RICHACE_GROUP_SPECIAL_ID,
+	};
+	unsigned int deny;
+
+	deny = alloc->acl->a_other_mask & ~alloc->acl->a_group_mask;
+
+	if (deny) {
+		unsigned int n;
+
+		if (__richacl_isolate_who(alloc, &who, deny))
+			return -1;
+		/*
+		 * Start from the entry before the trailing everyone@ allow
+		 * entry.  We will not hit everyone@ entries in the loop.
+		 */
+		for (n = alloc->acl->a_count - 1; n != -1; n--) {
+			struct richace *ace = alloc->acl->a_entries + n;
+
+			if (richace_is_inherit_only(ace) ||
+			    richace_is_owner(ace) ||
+			    richace_is_group(ace) ||
+			    richace_is_everyone(ace))
+				continue;
+			if (__richacl_isolate_who(alloc, ace, deny))
+				return -1;
+		}
+	}
+	return 0;
+}
-- 
2.4.2

[RFC v4 29/31] nfsd: Use richacls as internal acl representation

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:58:14

When converting from NFSv4 ACLs to POSIX ACLs, nfsd so far was using struct
nfs4_acl as its internal representation. This representation is a subset of
richacls, so get rid of struct nfs4_acl. Richacls even have a more compact
in-memory representation, so a few more ACL entries can easily be supported.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/Kconfig              |   6 +
 fs/nfs_common/Makefile  |   1 +
 fs/nfs_common/nfs4acl.c |  41 ++++++
 fs/nfsd/Kconfig         |   1 +
 fs/nfsd/acl.h           |  24 ++--
 fs/nfsd/nfs4acl.c       | 368 ++++++++++++++++++++++--------------------------
 fs/nfsd/nfs4proc.c      |  15 +-
 fs/nfsd/nfs4xdr.c       |  62 +++-----
 fs/nfsd/xdr4.h          |   6 +-
 include/linux/nfs4.h    |  23 ---
 include/linux/nfs4acl.h |   7 +
 11 files changed, 268 insertions(+), 286 deletions(-)
 create mode 100644 fs/nfs_common/nfs4acl.c
 create mode 100644 include/linux/nfs4acl.h
diff --git a/fs/Kconfig b/fs/Kconfig
index 3e09c06..dd3f2d6 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -268,6 +268,12 @@ config NFS_COMMON
 	depends on NFSD || NFS_FS || LOCKD
 	default y
 
+config NFS_RICHACL
+	bool
+	depends on NFSD_V4 || NFS_V4
+	select FS_RICHACL
+	default y
+
 source "net/sunrpc/Kconfig"
 source "fs/ceph/Kconfig"
 source "fs/cifs/Kconfig"
diff --git a/fs/nfs_common/Makefile b/fs/nfs_common/Makefile
index d153ca3..e055139 100644
--- a/fs/nfs_common/Makefile
+++ b/fs/nfs_common/Makefile
@@ -4,5 +4,6 @@
 
 obj-$(CONFIG_NFS_ACL_SUPPORT) += nfs_acl.o
 nfs_acl-objs := nfsacl.o
+obj-$(CONFIG_NFS_RICHACL) += nfs4acl.o
 
 obj-$(CONFIG_GRACE_PERIOD) += grace.o
diff --git a/fs/nfs_common/nfs4acl.c b/fs/nfs_common/nfs4acl.c
new file mode 100644
index 0000000..6a1f6b8
--- /dev/null
+++ b/fs/nfs_common/nfs4acl.c
@@ -0,0 +1,41 @@
+#include <linux/fs.h>
+#include <linux/richacl.h>
+#include <linux/nfs4acl.h>
+
+static struct special_id {
+	char *who;
+	int   len;
+} special_who_map[] = {
+	[RICHACE_OWNER_SPECIAL_ID] =
+		{ .who = "OWNER@", .len = sizeof("OWNER@") - 1 },
+	[RICHACE_GROUP_SPECIAL_ID] =
+		{ .who = "GROUP@", .len = sizeof("GROUP@") - 1 },
+	[RICHACE_EVERYONE_SPECIAL_ID] =
+		{ .who = "EVERYONE@", .len = sizeof("EVERYONE@") - 1 }
+};
+
+int nfs4acl_who_to_special_id(const char *who, u32 len)
+{
+	int n;
+
+	for (n = 0; n < ARRAY_SIZE(special_who_map); n++) {
+		if (len == special_who_map[n].len &&
+		    !memcmp(who, special_who_map[n].who, len))
+			return n;
+	}
+	return -1;
+}
+EXPORT_SYMBOL(nfs4acl_who_to_special_id);
+
+bool nfs4acl_special_id_to_who(unsigned int special_who,
+			       const char **who, unsigned int *len)
+{
+	struct special_id *special = &special_who_map[special_who];
+
+	if (special_who > ARRAY_SIZE(special_who_map) || !special->len)
+		return false;
+	*who = special->who;
+	*len = special->len;
+	return true;
+}
+EXPORT_SYMBOL(nfs4acl_special_id_to_who);
diff --git a/fs/nfsd/Kconfig b/fs/nfsd/Kconfig
index a0b77fc..811379a 100644
--- a/fs/nfsd/Kconfig
+++ b/fs/nfsd/Kconfig
@@ -70,6 +70,7 @@ config NFSD_V4
 	depends on NFSD && PROC_FS
 	select NFSD_V3
 	select FS_POSIX_ACL
+	select FS_RICHACL
 	select SUNRPC_GSS
 	select CRYPTO
 	select GRACE_PERIOD
diff --git a/fs/nfsd/acl.h b/fs/nfsd/acl.h
index 4cd7c69..1c5deb5 100644
--- a/fs/nfsd/acl.h
+++ b/fs/nfsd/acl.h
@@ -35,25 +35,27 @@
 #ifndef LINUX_NFS4_ACL_H
 #define LINUX_NFS4_ACL_H
 
-struct nfs4_acl;
+struct richacl;
+struct richace;
 struct svc_fh;
 struct svc_rqst;
 
 /*
  * Maximum ACL we'll accept from a client; chosen (somewhat
  * arbitrarily) so that kmalloc'ing the ACL shouldn't require a
- * high-order allocation.  This allows 204 ACEs on x86_64:
+ * high-order allocation.  This allows 339 ACEs on x86_64:
  */
-#define NFS4_ACL_MAX ((PAGE_SIZE - sizeof(struct nfs4_acl)) \
-			/ sizeof(struct nfs4_ace))
+#define NFSD4_ACL_MAX ((PAGE_SIZE - sizeof(struct richacl)) \
+			/ sizeof(struct richace))
 
-int nfs4_acl_bytes(int entries);
-int nfs4_acl_get_whotype(char *, u32);
-__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who);
+__be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
+			    char *who, u32 len);
+__be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
+			    struct richace *ace);
 
-int nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
-		struct nfs4_acl **acl);
-__be32 nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
-		struct nfs4_acl *acl);
+int nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
+		  struct richacl **acl);
+__be32 nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
+		     struct richacl *acl);
 
 #endif /* LINUX_NFS4_ACL_H */
diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c
index 7e10e2a..692abc3 100644
--- a/fs/nfsd/nfs4acl.c
+++ b/fs/nfsd/nfs4acl.c
@@ -36,45 +36,49 @@
 
 #include <linux/slab.h>
 #include <linux/nfs_fs.h>
+#include <linux/richacl_compat.h>
+#include <linux/nfs4acl.h>
 #include "nfsfh.h"
 #include "nfsd.h"
+#include "idmap.h"
 #include "acl.h"
 #include "vfs.h"
 
-#define NFS4_ACL_TYPE_DEFAULT	0x01
-#define NFS4_ACL_DIR		0x02
-#define NFS4_ACL_OWNER		0x04
+#define FLAG_DEFAULT_ACL	0x01
+#define FLAG_DIRECTORY		0x02
+#define FLAG_OWNER		0x04
 
 /* mode bit translations: */
-#define NFS4_READ_MODE (NFS4_ACE_READ_DATA)
-#define NFS4_WRITE_MODE (NFS4_ACE_WRITE_DATA | 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)
+#define RICHACE_READ_MODE (RICHACE_READ_DATA)
+#define RICHACE_WRITE_MODE (RICHACE_WRITE_DATA | RICHACE_APPEND_DATA)
+#define RICHACE_EXECUTE_MODE RICHACE_EXECUTE
+#define RICHACE_ANYONE_MODE (RICHACE_READ_ATTRIBUTES | RICHACE_READ_ACL | RICHACE_SYNCHRONIZE)
+#define RICHACE_OWNER_MODE (RICHACE_WRITE_ATTRIBUTES | RICHACE_WRITE_ACL)
 
 /* flags used to simulate posix default ACLs */
-#define NFS4_INHERITANCE_FLAGS (NFS4_ACE_FILE_INHERIT_ACE \
-		| NFS4_ACE_DIRECTORY_INHERIT_ACE)
+#define RICHACE_INHERITANCE_FLAGS (RICHACE_FILE_INHERIT_ACE \
+		| RICHACE_DIRECTORY_INHERIT_ACE)
 
-#define NFS4_SUPPORTED_FLAGS (NFS4_INHERITANCE_FLAGS \
-		| NFS4_ACE_INHERIT_ONLY_ACE \
-		| NFS4_ACE_IDENTIFIER_GROUP)
+#define RICHACE_SUPPORTED_FLAGS (RICHACE_INHERITANCE_FLAGS \
+		| RICHACE_INHERIT_ONLY_ACE \
+		| RICHACE_IDENTIFIER_GROUP \
+		| RICHACE_SPECIAL_WHO)
 
 static u32
 mask_from_posix(unsigned short perm, unsigned int flags)
 {
-	int mask = NFS4_ANYONE_MODE;
+	int mask = RICHACE_ANYONE_MODE;
 
-	if (flags & NFS4_ACL_OWNER)
-		mask |= NFS4_OWNER_MODE;
+	if (flags & FLAG_OWNER)
+		mask |= RICHACE_OWNER_MODE;
 	if (perm & ACL_READ)
-		mask |= NFS4_READ_MODE;
+		mask |= RICHACE_READ_MODE;
 	if (perm & ACL_WRITE)
-		mask |= NFS4_WRITE_MODE;
-	if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
-		mask |= NFS4_ACE_DELETE_CHILD;
+		mask |= RICHACE_WRITE_MODE;
+	if ((perm & ACL_WRITE) && (flags & FLAG_DIRECTORY))
+		mask |= RICHACE_DELETE_CHILD;
 	if (perm & ACL_EXECUTE)
-		mask |= NFS4_EXECUTE_MODE;
+		mask |= RICHACE_EXECUTE_MODE;
 	return mask;
 }
 
@@ -84,13 +88,13 @@ deny_mask_from_posix(unsigned short perm, u32 flags)
 	u32 mask = 0;
 
 	if (perm & ACL_READ)
-		mask |= NFS4_READ_MODE;
+		mask |= RICHACE_READ_MODE;
 	if (perm & ACL_WRITE)
-		mask |= NFS4_WRITE_MODE;
-	if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
-		mask |= NFS4_ACE_DELETE_CHILD;
+		mask |= RICHACE_WRITE_MODE;
+	if ((perm & ACL_WRITE) && (flags & FLAG_DIRECTORY))
+		mask |= RICHACE_DELETE_CHILD;
 	if (perm & ACL_EXECUTE)
-		mask |= NFS4_EXECUTE_MODE;
+		mask |= RICHACE_EXECUTE_MODE;
 	return mask;
 }
 
@@ -106,32 +110,32 @@ deny_mask_from_posix(unsigned short perm, u32 flags)
 static void
 low_mode_from_nfs4(u32 perm, unsigned short *mode, unsigned int flags)
 {
-	u32 write_mode = NFS4_WRITE_MODE;
+	u32 write_mode = RICHACE_WRITE_MODE;
 
-	if (flags & NFS4_ACL_DIR)
-		write_mode |= NFS4_ACE_DELETE_CHILD;
+	if (flags & FLAG_DIRECTORY)
+		write_mode |= RICHACE_DELETE_CHILD;
 	*mode = 0;
-	if ((perm & NFS4_READ_MODE) == NFS4_READ_MODE)
+	if ((perm & RICHACE_READ_MODE) == RICHACE_READ_MODE)
 		*mode |= ACL_READ;
 	if ((perm & write_mode) == write_mode)
 		*mode |= ACL_WRITE;
-	if ((perm & NFS4_EXECUTE_MODE) == NFS4_EXECUTE_MODE)
+	if ((perm & RICHACE_EXECUTE_MODE) == RICHACE_EXECUTE_MODE)
 		*mode |= ACL_EXECUTE;
 }
 
-static short ace2type(struct nfs4_ace *);
-static void _posix_to_nfsv4_one(struct posix_acl *, struct nfs4_acl *,
+static short ace2type(struct richace *);
+static void _posix_to_richacl_one(struct posix_acl *, struct richacl_alloc *,
 				unsigned int);
 
 int
-nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
-		struct nfs4_acl **acl)
+nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry, struct richacl **acl)
 {
 	struct inode *inode = d_inode(dentry);
 	int error = 0;
 	struct posix_acl *pacl = NULL, *dpacl = NULL;
+	struct richacl_alloc alloc;
 	unsigned int flags = 0;
-	int size = 0;
+	int count;
 
 	pacl = get_acl(inode, ACL_TYPE_ACCESS);
 	if (!pacl)
@@ -141,10 +145,10 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
 		return PTR_ERR(pacl);
 
 	/* allocate for worst case: one (deny, allow) pair each: */
-	size += 2 * pacl->a_count;
+	count = 2 * pacl->a_count;
 
 	if (S_ISDIR(inode->i_mode)) {
-		flags = NFS4_ACL_DIR;
+		flags = FLAG_DIRECTORY;
 		dpacl = get_acl(inode, ACL_TYPE_DEFAULT);
 		if (IS_ERR(dpacl)) {
 			error = PTR_ERR(dpacl);
@@ -152,20 +156,20 @@ nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
 		}
 
 		if (dpacl)
-			size += 2 * dpacl->a_count;
+			count += 2 * dpacl->a_count;
 	}
 
-	*acl = kmalloc(nfs4_acl_bytes(size), GFP_KERNEL);
-	if (*acl == NULL) {
+	if (!richacl_prepare(&alloc, count)) {
 		error = -ENOMEM;
 		goto out;
 	}
-	(*acl)->naces = 0;
 
-	_posix_to_nfsv4_one(pacl, *acl, flags & ~NFS4_ACL_TYPE_DEFAULT);
+	_posix_to_richacl_one(pacl, &alloc, flags);
 
 	if (dpacl)
-		_posix_to_nfsv4_one(dpacl, *acl, flags | NFS4_ACL_TYPE_DEFAULT);
+		_posix_to_richacl_one(dpacl, &alloc, flags | FLAG_DEFAULT_ACL);
+
+	*acl = alloc.acl;
 
 out:
 	posix_acl_release(dpacl);
@@ -228,21 +232,20 @@ summarize_posix_acl(struct posix_acl *acl, struct posix_acl_summary *pas)
 
 /* We assume the acl has been verified with posix_acl_valid. */
 static void
-_posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
-						unsigned int flags)
+_posix_to_richacl_one(struct posix_acl *pacl, struct richacl_alloc *alloc,
+		unsigned int flags)
 {
 	struct posix_acl_entry *pa, *group_owner_entry;
-	struct nfs4_ace *ace;
+	struct richace *ace;
 	struct posix_acl_summary pas;
 	unsigned short deny;
-	int eflag = ((flags & NFS4_ACL_TYPE_DEFAULT) ?
-		NFS4_INHERITANCE_FLAGS | NFS4_ACE_INHERIT_ONLY_ACE : 0);
+	int e_flags = ((flags & FLAG_DEFAULT_ACL) ?
+		RICHACE_INHERITANCE_FLAGS | RICHACE_INHERIT_ONLY_ACE : 0);
 
 	BUG_ON(pacl->a_count < 3);
 	summarize_posix_acl(pacl, &pas);
 
 	pa = pacl->a_entries;
-	ace = acl->aces + acl->naces;
 
 	/* We could deny everything not granted by the owner: */
 	deny = ~pas.owner;
@@ -252,42 +255,35 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 	 */
 	deny &= pas.users | pas.group | pas.groups | pas.other;
 	if (deny) {
-		ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-		ace->flag = eflag;
-		ace->access_mask = deny_mask_from_posix(deny, flags);
-		ace->whotype = NFS4_ACL_WHO_OWNER;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+		ace->e_mask = deny_mask_from_posix(deny, flags);
+		ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
 	}
 
-	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-	ace->flag = eflag;
-	ace->access_mask = mask_from_posix(pa->e_perm, flags | NFS4_ACL_OWNER);
-	ace->whotype = NFS4_ACL_WHO_OWNER;
-	ace++;
-	acl->naces++;
+	ace = richacl_append_entry(alloc);
+	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+	ace->e_mask = mask_from_posix(pa->e_perm, flags | FLAG_OWNER);
+	ace->e_id.special = RICHACE_OWNER_SPECIAL_ID;
 	pa++;
 
 	while (pa->e_tag == ACL_USER) {
 		deny = ~(pa->e_perm & pas.mask);
 		deny &= pas.groups | pas.group | pas.other;
 		if (deny) {
-			ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-			ace->flag = eflag;
-			ace->access_mask = deny_mask_from_posix(deny, flags);
-			ace->whotype = NFS4_ACL_WHO_NAMED;
-			ace->who_uid = pa->e_uid;
-			ace++;
-			acl->naces++;
+			ace = richacl_append_entry(alloc);
+			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+			ace->e_flags = e_flags;
+			ace->e_mask = deny_mask_from_posix(deny, flags);
+			ace->e_id.uid = pa->e_uid;
 		}
-		ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-		ace->flag = eflag;
-		ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
-						   flags);
-		ace->whotype = NFS4_ACL_WHO_NAMED;
-		ace->who_uid = pa->e_uid;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = e_flags;
+		ace->e_mask = mask_from_posix(pa->e_perm & pas.mask, flags);
+		ace->e_id.uid = pa->e_uid;
 		pa++;
 	}
 
@@ -298,23 +294,19 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 
 	group_owner_entry = pa;
 
-	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-	ace->flag = eflag;
-	ace->access_mask = mask_from_posix(pas.group, flags);
-	ace->whotype = NFS4_ACL_WHO_GROUP;
-	ace++;
-	acl->naces++;
+	ace = richacl_append_entry(alloc);
+	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+	ace->e_mask = mask_from_posix(pas.group, flags);
+	ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
 	pa++;
 
 	while (pa->e_tag == ACL_GROUP) {
-		ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-		ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
-		ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
-						   flags);
-		ace->whotype = NFS4_ACL_WHO_NAMED;
-		ace->who_gid = pa->e_gid;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+		ace->e_flags = e_flags | RICHACE_IDENTIFIER_GROUP;
+		ace->e_mask = mask_from_posix(pa->e_perm & pas.mask, flags);
+		ace->e_id.gid = pa->e_gid;
 		pa++;
 	}
 
@@ -324,12 +316,11 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 
 	deny = ~pas.group & pas.other;
 	if (deny) {
-		ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-		ace->flag = eflag;
-		ace->access_mask = deny_mask_from_posix(deny, flags);
-		ace->whotype = NFS4_ACL_WHO_GROUP;
-		ace++;
-		acl->naces++;
+		ace = richacl_append_entry(alloc);
+		ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+		ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+		ace->e_mask = deny_mask_from_posix(deny, flags);
+		ace->e_id.special = RICHACE_GROUP_SPECIAL_ID;
 	}
 	pa++;
 
@@ -337,24 +328,22 @@ _posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
 		deny = ~(pa->e_perm & pas.mask);
 		deny &= pas.other;
 		if (deny) {
-			ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
-			ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
-			ace->access_mask = deny_mask_from_posix(deny, flags);
-			ace->whotype = NFS4_ACL_WHO_NAMED;
-			ace->who_gid = pa->e_gid;
-			ace++;
-			acl->naces++;
+			ace = richacl_append_entry(alloc);
+			ace->e_type = RICHACE_ACCESS_DENIED_ACE_TYPE;
+			ace->e_flags = e_flags | RICHACE_IDENTIFIER_GROUP;
+			ace->e_mask = deny_mask_from_posix(deny, flags);
+			ace->e_id.gid = pa->e_gid;
 		}
 		pa++;
 	}
 
 	if (pa->e_tag == ACL_MASK)
 		pa++;
-	ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
-	ace->flag = eflag;
-	ace->access_mask = mask_from_posix(pa->e_perm, flags);
-	ace->whotype = NFS4_ACL_WHO_EVERYONE;
-	acl->naces++;
+	ace = richacl_append_entry(alloc);
+	ace->e_type = RICHACE_ACCESS_ALLOWED_ACE_TYPE;
+	ace->e_flags = e_flags | RICHACE_SPECIAL_WHO;
+	ace->e_mask = mask_from_posix(pa->e_perm, flags);
+	ace->e_id.special = RICHACE_EVERYONE_SPECIAL_ID;
 }
 
 static bool
@@ -500,7 +489,7 @@ posix_state_to_acl(struct posix_acl_state *state, unsigned int flags)
 	 * and effective cases: when there are no inheritable ACEs,
 	 * calls ->set_acl with a NULL ACL structure.
 	 */
-	if (state->empty && (flags & NFS4_ACL_TYPE_DEFAULT))
+	if (state->empty && (flags & FLAG_DEFAULT_ACL))
 		return NULL;
 
 	/*
@@ -619,24 +608,24 @@ static void allow_bits_array(struct posix_ace_state_array *a, u32 mask)
 }
 
 static void process_one_v4_ace(struct posix_acl_state *state,
-				struct nfs4_ace *ace)
+				struct richace *ace)
 {
-	u32 mask = ace->access_mask;
+	u32 mask = ace->e_mask;
 	int i;
 
 	state->empty = 0;
 
 	switch (ace2type(ace)) {
 	case ACL_USER_OBJ:
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->owner, mask);
 		} else {
 			deny_bits(&state->owner, mask);
 		}
 		break;
 	case ACL_USER:
-		i = find_uid(state, ace->who_uid);
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		i = find_uid(state, ace->e_id.uid);
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->users->aces[i].perms, mask);
 		} else {
 			deny_bits(&state->users->aces[i].perms, mask);
@@ -645,7 +634,7 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 		}
 		break;
 	case ACL_GROUP_OBJ:
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->group, mask);
 		} else {
 			deny_bits(&state->group, mask);
@@ -657,8 +646,8 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 		}
 		break;
 	case ACL_GROUP:
-		i = find_gid(state, ace->who_gid);
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		i = find_gid(state, ace->e_id.gid);
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->groups->aces[i].perms, mask);
 		} else {
 			deny_bits(&state->groups->aces[i].perms, mask);
@@ -671,7 +660,7 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 		}
 		break;
 	case ACL_OTHER:
-		if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
+		if (ace->e_type == RICHACE_ACCESS_ALLOWED_ACE_TYPE) {
 			allow_bits(&state->owner, mask);
 			allow_bits(&state->group, mask);
 			allow_bits(&state->other, mask);
@@ -689,32 +678,32 @@ static void process_one_v4_ace(struct posix_acl_state *state,
 	}
 }
 
-static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
+static int nfs4_richacl_to_posix(struct richacl *acl,
 		struct posix_acl **pacl, struct posix_acl **dpacl,
 		unsigned int flags)
 {
 	struct posix_acl_state effective_acl_state, default_acl_state;
-	struct nfs4_ace *ace;
+	struct richace *ace;
 	int ret;
 
-	ret = init_state(&effective_acl_state, acl->naces);
+	ret = init_state(&effective_acl_state, acl->a_count);
 	if (ret)
 		return ret;
-	ret = init_state(&default_acl_state, acl->naces);
+	ret = init_state(&default_acl_state, acl->a_count);
 	if (ret)
 		goto out_estate;
 	ret = -EINVAL;
-	for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) {
-		if (ace->type != NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE &&
-		    ace->type != NFS4_ACE_ACCESS_DENIED_ACE_TYPE)
+	richacl_for_each_entry(ace, acl) {
+		if (ace->e_type != RICHACE_ACCESS_ALLOWED_ACE_TYPE &&
+		    ace->e_type != RICHACE_ACCESS_DENIED_ACE_TYPE)
 			goto out_dstate;
-		if (ace->flag & ~NFS4_SUPPORTED_FLAGS)
+		if (ace->e_flags & ~RICHACE_SUPPORTED_FLAGS)
 			goto out_dstate;
-		if ((ace->flag & NFS4_INHERITANCE_FLAGS) == 0) {
+		if ((ace->e_flags & RICHACE_INHERITANCE_FLAGS) == 0) {
 			process_one_v4_ace(&effective_acl_state, ace);
 			continue;
 		}
-		if (!(flags & NFS4_ACL_DIR))
+		if (!(flags & FLAG_DIRECTORY))
 			goto out_dstate;
 		/*
 		 * Note that when only one of FILE_INHERIT or DIRECTORY_INHERIT
@@ -723,7 +712,7 @@ static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
 		 */
 		process_one_v4_ace(&default_acl_state, ace);
 
-		if (!(ace->flag & NFS4_ACE_INHERIT_ONLY_ACE))
+		if (!(ace->e_flags & RICHACE_INHERIT_ONLY_ACE))
 			process_one_v4_ace(&effective_acl_state, ace);
 	}
 	*pacl = posix_state_to_acl(&effective_acl_state, flags);
@@ -733,7 +722,7 @@ static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
 		goto out_dstate;
 	}
 	*dpacl = posix_state_to_acl(&default_acl_state,
-						flags | NFS4_ACL_TYPE_DEFAULT);
+						flags | FLAG_DEFAULT_ACL);
 	if (IS_ERR(*dpacl)) {
 		ret = PTR_ERR(*dpacl);
 		*dpacl = NULL;
@@ -752,8 +741,7 @@ out_estate:
 }
 
 __be32
-nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
-		struct nfs4_acl *acl)
+nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
 {
 	__be32 error;
 	int host_error;
@@ -774,9 +762,9 @@ nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		return nfserr_attrnotsupp;
 
 	if (S_ISDIR(inode->i_mode))
-		flags = NFS4_ACL_DIR;
+		flags = FLAG_DIRECTORY;
 
-	host_error = nfs4_acl_nfsv4_to_posix(acl, &pacl, &dpacl, flags);
+	host_error = nfs4_richacl_to_posix(acl, &pacl, &dpacl, flags);
 	if (host_error == -EINVAL)
 		return nfserr_attrnotsupp;
 	if (host_error < 0)
@@ -803,82 +791,62 @@ out_nfserr:
 
 
 static short
-ace2type(struct nfs4_ace *ace)
+ace2type(struct richace *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;
+	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
+		switch(ace->e_id.special) {
+			case RICHACE_OWNER_SPECIAL_ID:
+				return ACL_USER_OBJ;
+			case RICHACE_GROUP_SPECIAL_ID:
+				return ACL_GROUP_OBJ;
+			case RICHACE_EVERYONE_SPECIAL_ID:
+				return ACL_OTHER;
+			default:
+				BUG();
+		}
 	}
-	BUG();
-	return -1;
+	return ace->e_flags & RICHACE_IDENTIFIER_GROUP ? ACL_GROUP : ACL_USER;
 }
 
-/*
- * return the size of the struct nfs4_acl required to represent an acl
- * with @entries entries.
- */
-int nfs4_acl_bytes(int entries)
+__be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
+			    char *who, u32 len)
 {
-	return sizeof(struct nfs4_acl) + entries * sizeof(struct nfs4_ace);
-}
-
-static struct {
-	char *string;
-	int   stringlen;
-	int type;
-} s2t_map[] = {
-	{
-		.string    = "OWNER@",
-		.stringlen = sizeof("OWNER@") - 1,
-		.type      = NFS4_ACL_WHO_OWNER,
-	},
-	{
-		.string    = "GROUP@",
-		.stringlen = sizeof("GROUP@") - 1,
-		.type      = NFS4_ACL_WHO_GROUP,
-	},
-	{
-		.string    = "EVERYONE@",
-		.stringlen = sizeof("EVERYONE@") - 1,
-		.type      = NFS4_ACL_WHO_EVERYONE,
-	},
-};
-
-int
-nfs4_acl_get_whotype(char *p, u32 len)
-{
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
-		if (s2t_map[i].stringlen == len &&
-				0 == memcmp(s2t_map[i].string, p, len))
-			return s2t_map[i].type;
+	int special_id;
+
+	special_id = nfs4acl_who_to_special_id(who, len);
+	if (special_id >= 0) {
+		ace->e_flags |= RICHACE_SPECIAL_WHO;
+		ace->e_flags &= ~RICHACE_IDENTIFIER_GROUP;
+		ace->e_id.special = special_id;
+		return nfs_ok;
 	}
-	return NFS4_ACL_WHO_NAMED;
+	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
+		return nfsd_map_name_to_gid(rqstp, who, len, &ace->e_id.gid);
+	else
+		return nfsd_map_name_to_uid(rqstp, who, len, &ace->e_id.uid);
 }
 
-__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who)
+__be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
+			    struct richace *ace)
 {
-	__be32 *p;
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
-		if (s2t_map[i].type != who)
-			continue;
-		p = xdr_reserve_space(xdr, s2t_map[i].stringlen + 4);
+	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
+		unsigned int special_id = ace->e_id.special;
+		const char *who;
+		unsigned int len;
+		__be32 *p;
+
+		if (!nfs4acl_special_id_to_who(special_id, &who, &len)) {
+			WARN_ON_ONCE(1);
+			return nfserr_serverfault;
+		}
+		p = xdr_reserve_space(xdr, len + 4);
 		if (!p)
 			return nfserr_resource;
-		p = xdr_encode_opaque(p, s2t_map[i].string,
-					s2t_map[i].stringlen);
+		p = xdr_encode_opaque(p, who, len);
 		return 0;
 	}
-	WARN_ON_ONCE(1);
-	return nfserr_serverfault;
+	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
+		return nfsd4_encode_group(xdr, rqstp, ace->e_id.gid);
+	else
+		return nfsd4_encode_user(xdr, rqstp, ace->e_id.uid);
 }
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 864e200..2f67c3f 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -159,12 +159,12 @@ is_create_with_attrs(struct nfsd4_open *open)
  * in the returned attr bitmap.
  */
 static void
-do_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
-		struct nfs4_acl *acl, u32 *bmval)
+do_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl,
+	   u32 *bmval)
 {
 	__be32 status;
 
-	status = nfsd4_set_nfs4_acl(rqstp, fhp, acl);
+	status = nfsd4_set_acl(rqstp, fhp, acl);
 	if (status)
 		/*
 		 * We should probably fail the whole open at this point,
@@ -299,7 +299,7 @@ do_open_lookup(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, stru
 		goto out;
 
 	if (is_create_with_attrs(open) && open->op_acl != NULL)
-		do_set_nfs4_acl(rqstp, *resfh, open->op_acl, open->op_bmval);
+		do_set_acl(rqstp, *resfh, open->op_acl, open->op_bmval);
 
 	nfsd4_set_open_owner_reply_cache(cstate, open, *resfh);
 	accmode = NFSD_MAY_NOP;
@@ -674,8 +674,7 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		nfsd4_security_inode_setsecctx(&resfh, &create->cr_label, create->cr_bmval);
 
 	if (create->cr_acl != NULL)
-		do_set_nfs4_acl(rqstp, &resfh, create->cr_acl,
-				create->cr_bmval);
+		do_set_acl(rqstp, &resfh, create->cr_acl, create->cr_bmval);
 
 	fh_unlock(&cstate->current_fh);
 	set_change_info(&create->cr_cinfo, &cstate->current_fh);
@@ -942,8 +941,8 @@ nfsd4_setattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 		goto out;
 
 	if (setattr->sa_acl != NULL)
-		status = nfsd4_set_nfs4_acl(rqstp, &cstate->current_fh,
-					    setattr->sa_acl);
+		status = nfsd4_set_acl(rqstp, &cstate->current_fh,
+				       setattr->sa_acl);
 	if (status)
 		goto out;
 	if (setattr->sa_label.len)
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 277d9d8..4753c03 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -302,7 +302,7 @@ nfsd4_decode_bitmap(struct nfsd4_compoundargs *argp, u32 *bmval)
 
 static __be32
 nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
-		   struct iattr *iattr, struct nfs4_acl **acl,
+		   struct iattr *iattr, struct richacl **acl,
 		   struct xdr_netobj *label)
 {
 	int expected_len, len = 0;
@@ -325,38 +325,31 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 	}
 	if (bmval[0] & FATTR4_WORD0_ACL) {
 		u32 nace;
-		struct nfs4_ace *ace;
+		struct richace *ace;
 
 		READ_BUF(4); len += 4;
 		nace = be32_to_cpup(p++);
 
-		if (nace > NFS4_ACL_MAX)
+		if (nace > NFSD4_ACL_MAX)
 			return nfserr_fbig;
 
-		*acl = svcxdr_tmpalloc(argp, nfs4_acl_bytes(nace));
+		*acl = svcxdr_alloc_richacl(argp, nace);
 		if (*acl == NULL)
 			return nfserr_jukebox;
 
-		(*acl)->naces = nace;
-		for (ace = (*acl)->aces; ace < (*acl)->aces + nace; ace++) {
+		richacl_for_each_entry(ace, *acl) {
 			READ_BUF(16); len += 16;
-			ace->type = be32_to_cpup(p++);
-			ace->flag = be32_to_cpup(p++);
-			ace->access_mask = be32_to_cpup(p++);
+			ace->e_type = be32_to_cpup(p++);
+			ace->e_flags = be32_to_cpup(p++);
+			ace->e_mask = be32_to_cpup(p++);
+			if (ace->e_flags & RICHACE_SPECIAL_WHO)
+				return nfserr_inval;
 			dummy32 = be32_to_cpup(p++);
 			READ_BUF(dummy32);
 			len += XDR_QUADLEN(dummy32) << 2;
 			READMEM(buf, dummy32);
-			ace->whotype = nfs4_acl_get_whotype(buf, dummy32);
-			status = nfs_ok;
-			if (ace->whotype != NFS4_ACL_WHO_NAMED)
-				;
-			else if (ace->flag & NFS4_ACE_IDENTIFIER_GROUP)
-				status = nfsd_map_name_to_gid(argp->rqstp,
-						buf, dummy32, &ace->who_gid);
-			else
-				status = nfsd_map_name_to_uid(argp->rqstp,
-						buf, dummy32, &ace->who_uid);
+			status = nfsd4_decode_ace_who(ace, argp->rqstp,
+						      buf, dummy32);
 			if (status)
 				return status;
 		}
@@ -2146,18 +2139,6 @@ static u32 nfs4_file_type(umode_t mode)
 	};
 }
 
-static inline __be32
-nfsd4_encode_aclname(struct xdr_stream *xdr, struct svc_rqst *rqstp,
-		     struct nfs4_ace *ace)
-{
-	if (ace->whotype != NFS4_ACL_WHO_NAMED)
-		return nfs4_acl_write_who(xdr, ace->whotype);
-	else if (ace->flag & NFS4_ACE_IDENTIFIER_GROUP)
-		return nfsd4_encode_group(xdr, rqstp, ace->who_gid);
-	else
-		return nfsd4_encode_user(xdr, rqstp, ace->who_uid);
-}
-
 #define WORD0_ABSENT_FS_ATTRS (FATTR4_WORD0_FS_LOCATIONS | FATTR4_WORD0_FSID | \
 			      FATTR4_WORD0_RDATTR_ERROR)
 #define WORD1_ABSENT_FS_ATTRS FATTR4_WORD1_MOUNTED_ON_FILEID
@@ -2246,7 +2227,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 	u32 rdattr_err = 0;
 	__be32 status;
 	int err;
-	struct nfs4_acl *acl = NULL;
+	struct richacl *acl = NULL;
 	void *context = NULL;
 	int contextlen;
 	bool contextsupport = false;
@@ -2467,7 +2448,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		*p++ = cpu_to_be32(rdattr_err);
 	}
 	if (bmval0 & FATTR4_WORD0_ACL) {
-		struct nfs4_ace *ace;
+		struct richace *ace;
 
 		if (acl == NULL) {
 			p = xdr_reserve_space(xdr, 4);
@@ -2480,17 +2461,16 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		p = xdr_reserve_space(xdr, 4);
 		if (!p)
 			goto out_resource;
-		*p++ = cpu_to_be32(acl->naces);
+		*p++ = cpu_to_be32(acl->a_count);
 
-		for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) {
+		richacl_for_each_entry(ace, acl) {
 			p = xdr_reserve_space(xdr, 4*3);
 			if (!p)
 				goto out_resource;
-			*p++ = cpu_to_be32(ace->type);
-			*p++ = cpu_to_be32(ace->flag);
-			*p++ = cpu_to_be32(ace->access_mask &
-							NFS4_ACE_MASK_ALL);
-			status = nfsd4_encode_aclname(xdr, rqstp, ace);
+			*p++ = cpu_to_be32(ace->e_type);
+			*p++ = cpu_to_be32(ace->e_flags & ~RICHACE_SPECIAL_WHO);
+			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
+			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
 			if (status)
 				goto out;
 		}
@@ -2753,7 +2733,7 @@ out:
 	if (context)
 		security_release_secctx(context, contextlen);
 #endif /* CONFIG_NFSD_V4_SECURITY_LABEL */
-	kfree(acl);
+	richacl_put(acl);
 	if (tempfh) {
 		fh_put(tempfh);
 		kfree(tempfh);
diff --git a/fs/nfsd/xdr4.h b/fs/nfsd/xdr4.h
index db47af2..52d70d7 100644
--- a/fs/nfsd/xdr4.h
+++ b/fs/nfsd/xdr4.h
@@ -118,7 +118,7 @@ struct nfsd4_create {
 	u32		cr_bmval[3];        /* request */
 	struct iattr	cr_iattr;           /* request */
 	struct nfsd4_change_info  cr_cinfo; /* response */
-	struct nfs4_acl *cr_acl;
+	struct richacl *cr_acl;
 	struct xdr_netobj cr_label;
 };
 #define cr_datalen	u.link.datalen
@@ -248,7 +248,7 @@ struct nfsd4_open {
 	struct nfs4_file *op_file;          /* used during processing */
 	struct nfs4_ol_stateid *op_stp;	    /* used during processing */
 	struct nfs4_clnt_odstate *op_odstate; /* used during processing */
-	struct nfs4_acl *op_acl;
+	struct richacl *op_acl;
 	struct xdr_netobj op_label;
 };
 
@@ -331,7 +331,7 @@ struct nfsd4_setattr {
 	stateid_t	sa_stateid;         /* request */
 	u32		sa_bmval[3];        /* request */
 	struct iattr	sa_iattr;           /* request */
-	struct nfs4_acl *sa_acl;
+	struct richacl *sa_acl;
 	struct xdr_netobj sa_label;
 };
 
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index 32201c2..095854c 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -16,29 +16,6 @@
 #include <linux/uidgid.h>
 #include <uapi/linux/nfs4.h>
 
-enum nfs4_acl_whotype {
-	NFS4_ACL_WHO_NAMED = 0,
-	NFS4_ACL_WHO_OWNER,
-	NFS4_ACL_WHO_GROUP,
-	NFS4_ACL_WHO_EVERYONE,
-};
-
-struct nfs4_ace {
-	uint32_t	type;
-	uint32_t	flag;
-	uint32_t	access_mask;
-	int		whotype;
-	union {
-		kuid_t	who_uid;
-		kgid_t	who_gid;
-	};
-};
-
-struct nfs4_acl {
-	uint32_t	naces;
-	struct nfs4_ace	aces[0];
-};
-
 #define NFS4_MAXLABELLEN	2048
 
 struct nfs4_label {
diff --git a/include/linux/nfs4acl.h b/include/linux/nfs4acl.h
new file mode 100644
index 0000000..db9f9a6
--- /dev/null
+++ b/include/linux/nfs4acl.h
@@ -0,0 +1,7 @@
+#ifndef __LINUX_NFS4ACL_H
+#define __LINUX_NFS4ACL_H
+
+int nfs4acl_who_to_special_id(const char *, u32);
+bool nfs4acl_special_id_to_who(unsigned int, const char **, unsigned int *);
+
+#endif
-- 
2.4.2

[RFC v4 30/31] nfsd: Add richacl support

From: Andreas Gruenbacher <hidden>
Date: 2015-06-24 21:58:14

On file systems with richacls enabled, get and set richacls directly instead of
converting from / to posix acls.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
---
 fs/nfsd/acl.h      |   3 +-
 fs/nfsd/nfs4acl.c  | 123 +++++++++++++++++++++++++++++++++++++----------------
 fs/nfsd/nfs4proc.c |   2 +-
 fs/nfsd/nfs4xdr.c  |  34 +++++++++++----
 4 files changed, 115 insertions(+), 47 deletions(-)
diff --git a/fs/nfsd/acl.h b/fs/nfsd/acl.h
index 1c5deb5..d73c664 100644
--- a/fs/nfsd/acl.h
+++ b/fs/nfsd/acl.h
@@ -53,8 +53,7 @@ __be32 nfsd4_decode_ace_who(struct richace *ace, struct svc_rqst *rqstp,
 __be32 nfsd4_encode_ace_who(struct xdr_stream *xdr, struct svc_rqst *rqstp,
 			    struct richace *ace);
 
-int nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry,
-		  struct richacl **acl);
+struct richacl *nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry);
 __be32 nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp,
 		     struct richacl *acl);
 
diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c
index 692abc3..211eb54 100644
--- a/fs/nfsd/nfs4acl.c
+++ b/fs/nfsd/nfs4acl.c
@@ -38,6 +38,8 @@
 #include <linux/nfs_fs.h>
 #include <linux/richacl_compat.h>
 #include <linux/nfs4acl.h>
+#include <linux/xattr.h>
+#include <linux/richacl_xattr.h>
 #include "nfsfh.h"
 #include "nfsd.h"
 #include "idmap.h"
@@ -127,31 +129,28 @@ static short ace2type(struct richace *);
 static void _posix_to_richacl_one(struct posix_acl *, struct richacl_alloc *,
 				unsigned int);
 
-int
-nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry, struct richacl **acl)
+static struct richacl *
+nfsd4_get_posix_acl(struct svc_rqst *rqstp, struct dentry *dentry)
 {
 	struct inode *inode = d_inode(dentry);
-	int error = 0;
 	struct posix_acl *pacl = NULL, *dpacl = NULL;
 	struct richacl_alloc alloc;
 	unsigned int flags = 0;
 	int count;
 
 	pacl = get_acl(inode, ACL_TYPE_ACCESS);
-	if (!pacl)
-		pacl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
-
-	if (IS_ERR(pacl))
-		return PTR_ERR(pacl);
+	if (IS_ERR_OR_NULL(pacl))
+		return (void *)pacl;
 
-	/* allocate for worst case: one (deny, allow) pair each: */
+	/* Allocate for worst case: one (deny, allow) pair each.  The resulting
+	   acl will be released shortly and won't be cached. */
 	count = 2 * pacl->a_count;
 
 	if (S_ISDIR(inode->i_mode)) {
 		flags = FLAG_DIRECTORY;
 		dpacl = get_acl(inode, ACL_TYPE_DEFAULT);
 		if (IS_ERR(dpacl)) {
-			error = PTR_ERR(dpacl);
+			alloc.acl = (void *)dpacl;
 			goto rel_pacl;
 		}
 
@@ -160,7 +159,7 @@ nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry, struct richacl **ac
 	}
 
 	if (!richacl_prepare(&alloc, count)) {
-		error = -ENOMEM;
+		alloc.acl = ERR_PTR(-ENOMEM);
 		goto out;
 	}
 
@@ -169,13 +168,37 @@ nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry, struct richacl **ac
 	if (dpacl)
 		_posix_to_richacl_one(dpacl, &alloc, flags | FLAG_DEFAULT_ACL);
 
-	*acl = alloc.acl;
-
 out:
 	posix_acl_release(dpacl);
 rel_pacl:
 	posix_acl_release(pacl);
-	return error;
+	return alloc.acl;
+}
+
+struct richacl *
+nfsd4_get_acl(struct svc_rqst *rqstp, struct dentry *dentry)
+{
+	struct inode *inode = d_inode(dentry);
+	struct richacl *acl;
+	int error;
+
+	if (IS_RICHACL(inode))
+		acl = get_richacl(inode);
+	else
+		acl = nfsd4_get_posix_acl(rqstp, dentry);
+	if (IS_ERR(acl))
+		return acl;
+	else if (acl == NULL) {
+		acl = richacl_from_mode(inode->i_mode);
+		if (acl == NULL)
+			acl = ERR_PTR(-ENOMEM);
+	}
+	error = richacl_apply_masks(&acl, inode->i_uid);
+	if (error) {
+		richacl_put(acl);
+		acl = ERR_PTR(error);
+	}
+	return acl;
 }
 
 struct posix_acl_summary {
@@ -740,56 +763,84 @@ out_estate:
 	return ret;
 }
 
-__be32
-nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
+static int
+nfsd4_set_posix_acl(struct svc_rqst *rqstp, struct dentry *dentry, struct richacl *acl)
 {
-	__be32 error;
 	int host_error;
-	struct dentry *dentry;
-	struct inode *inode;
+	struct inode *inode = d_inode(dentry);
 	struct posix_acl *pacl = NULL, *dpacl = NULL;
 	unsigned int flags = 0;
 
-	/* Get inode */
-	error = fh_verify(rqstp, fhp, 0, NFSD_MAY_SATTR);
-	if (error)
-		return error;
-
-	dentry = fhp->fh_dentry;
-	inode = d_inode(dentry);
-
 	if (!inode->i_op->set_acl || !IS_POSIXACL(inode))
-		return nfserr_attrnotsupp;
+		return -EOPNOTSUPP;
 
 	if (S_ISDIR(inode->i_mode))
 		flags = FLAG_DIRECTORY;
 
 	host_error = nfs4_richacl_to_posix(acl, &pacl, &dpacl, flags);
 	if (host_error == -EINVAL)
-		return nfserr_attrnotsupp;
+		return -EOPNOTSUPP;
 	if (host_error < 0)
-		goto out_nfserr;
+		return host_error;
 
 	host_error = inode->i_op->set_acl(inode, pacl, ACL_TYPE_ACCESS);
 	if (host_error < 0)
 		goto out_release;
 
-	if (S_ISDIR(inode->i_mode)) {
-		host_error = inode->i_op->set_acl(inode, dpacl,
-						  ACL_TYPE_DEFAULT);
-	}
+	if (S_ISDIR(inode->i_mode))
+		host_error = inode->i_op->set_acl(inode, dpacl, ACL_TYPE_DEFAULT);
 
 out_release:
 	posix_acl_release(pacl);
 	posix_acl_release(dpacl);
-out_nfserr:
+	return host_error;
+}
+
+static int
+nfsd4_set_richacl(struct svc_rqst *rqstp, struct dentry *dentry, struct richacl *acl)
+{
+	int host_error;
+	struct inode *inode = d_inode(dentry);
+	size_t size = richacl_xattr_size(acl);
+	char *buffer;
+
+	if (!inode->i_op->setxattr || !IS_RICHACL(inode))
+		return -EOPNOTSUPP;
+
+	richacl_compute_max_masks(acl, inode->i_uid);
+
+	buffer = kmalloc(size, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+	richacl_to_xattr(&init_user_ns, acl, buffer, size);
+	host_error = inode->i_op->setxattr(dentry, RICHACL_XATTR, buffer, size, 0);
+	kfree(buffer);
+	return host_error;
+}
+
+__be32
+nfsd4_set_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, struct richacl *acl)
+{
+	struct dentry *dentry;
+	int host_error;
+	__be32 error;
+
+	error = fh_verify(rqstp, fhp, 0, NFSD_MAY_SATTR);
+	if (error)
+		return error;
+	dentry = fhp->fh_dentry;
+
+	if (IS_RICHACL(d_inode(dentry)))
+		host_error = nfsd4_set_richacl(rqstp, dentry, acl);
+	else
+		host_error = nfsd4_set_posix_acl(rqstp, dentry, acl);
+
 	if (host_error == -EOPNOTSUPP)
 		return nfserr_attrnotsupp;
 	else
 		return nfserrno(host_error);
 }
 
-
 static short
 ace2type(struct richace *ace)
 {
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index 2f67c3f..a245528 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -110,7 +110,7 @@ check_attr_support(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
 	 * in current environment or not.
 	 */
 	if (bmval[0] & FATTR4_WORD0_ACL) {
-		if (!IS_POSIXACL(d_inode(dentry)))
+		if (!IS_ACL(d_inode(dentry)))
 			return nfserr_attrnotsupp;
 	}
 
diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
index 4753c03..853e0d0 100644
--- a/fs/nfsd/nfs4xdr.c
+++ b/fs/nfsd/nfs4xdr.c
@@ -339,11 +339,24 @@ nfsd4_decode_fattr(struct nfsd4_compoundargs *argp, u32 *bmval,
 
 		richacl_for_each_entry(ace, *acl) {
 			READ_BUF(16); len += 16;
-			ace->e_type = be32_to_cpup(p++);
-			ace->e_flags = be32_to_cpup(p++);
-			ace->e_mask = be32_to_cpup(p++);
-			if (ace->e_flags & RICHACE_SPECIAL_WHO)
+
+			dummy32 = be32_to_cpup(p++);
+			if (dummy32 > RICHACE_ACCESS_DENIED_ACE_TYPE)
+				return nfserr_inval;
+			ace->e_type = dummy32;
+
+			dummy32 = be32_to_cpup(p++);
+			if (dummy32 & (~RICHACE_VALID_FLAGS |
+				       RICHACE_INHERITED_ACE |
+				       RICHACE_SPECIAL_WHO))
 				return nfserr_inval;
+			ace->e_flags = dummy32;
+
+			dummy32 = be32_to_cpup(p++);
+			if (dummy32 & ~NFS4_ACE_MASK_ALL)
+				return nfserr_inval;
+			ace->e_mask = dummy32;
+
 			dummy32 = be32_to_cpup(p++);
 			READ_BUF(dummy32);
 			len += XDR_QUADLEN(dummy32) << 2;
@@ -2274,7 +2287,11 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		fhp = tempfh;
 	}
 	if (bmval0 & FATTR4_WORD0_ACL) {
-		err = nfsd4_get_nfs4_acl(rqstp, dentry, &acl);
+		acl = nfsd4_get_acl(rqstp, dentry);
+		if (IS_ERR(acl)) {
+			err = PTR_ERR(acl);
+			acl = NULL;
+		}
 		if (err == -EOPNOTSUPP)
 			bmval0 &= ~FATTR4_WORD0_ACL;
 		else if (err == -EINVAL) {
@@ -2333,7 +2350,7 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 		u32 word1 = nfsd_suppattrs1(minorversion);
 		u32 word2 = nfsd_suppattrs2(minorversion);
 
-		if (!IS_POSIXACL(d_inode(dentry)))
+		if (!IS_ACL(d_inode(dentry)))
 			word0 &= ~FATTR4_WORD0_ACL;
 		if (!contextsupport)
 			word2 &= ~FATTR4_WORD2_SECURITY_LABEL;
@@ -2468,7 +2485,8 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
 			if (!p)
 				goto out_resource;
 			*p++ = cpu_to_be32(ace->e_type);
-			*p++ = cpu_to_be32(ace->e_flags & ~RICHACE_SPECIAL_WHO);
+			*p++ = cpu_to_be32(ace->e_flags &
+				~(RICHACE_SPECIAL_WHO | RICHACE_INHERITED_ACE));
 			*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
 			status = nfsd4_encode_ace_who(xdr, rqstp, ace);
 			if (status)
@@ -2480,7 +2498,7 @@ out_acl:
 		p = xdr_reserve_space(xdr, 4);
 		if (!p)
 			goto out_resource;
-		*p++ = cpu_to_be32(IS_POSIXACL(d_inode(dentry)) ?
+		*p++ = cpu_to_be32(IS_ACL(d_inode(dentry)) ?
 			ACL4_SUPPORT_ALLOW_ACL|ACL4_SUPPORT_DENY_ACL : 0);
 	}
 	if (bmval0 & FATTR4_WORD0_CANSETTIME) {
-- 
2.4.2

Re: [RFC v4 06/31] richacl: In-memory representation and helper functions

From: "Stefan (metze) Metzmacher" <metze@samba.org>
Date: 2015-06-25 19:58:45

Hi Andreas,
+#define RICHACE_OWNER_SPECIAL_ID	0
+#define RICHACE_GROUP_SPECIAL_ID	1
+#define RICHACE_EVERYONE_SPECIAL_ID	2
+
+struct richace {
+	unsigned short	e_type;
+	unsigned short	e_flags;
+	unsigned int	e_mask;
+	union {
+		kuid_t		uid;
+		kgid_t		gid;
+		unsigned int	special;
+	} e_id;
+};
+
+struct richacl {
+	atomic_t	a_refcount;
+	unsigned int	a_owner_mask;
+	unsigned int	a_group_mask;
+	unsigned int	a_other_mask;
+	unsigned short	a_count;
+	unsigned short	a_flags;
+	struct richace	a_entries[0];
+};
Is that also the on disk representation?

I'm wondering if the size of an ace should be dynamic,
which might make it possible to support other ace types
in future. E.g. supporting other identities like 128-bit values
to make it easier to map Windows SIDS.

Even without 128-bit ids, it would be very useful to mark an
ace so that it applies to a uid or gid at the same time.
This would reduce the size of the ace list when Samba uses
IDMAP_TYPE_BOTH, which means a SID is mapped to a unix id, which
is user (uid) and group (gid) at the same time. This feature is required
in order to support SID-Histories on accounts.
Currently Samba needs to add two aces (one uid and one gid)
in order to represent one Windows ace.

I haven't looked at the claims based acls on Windows, but it would be
good if the new infrastructure is dynamic enough to support something
like that in a future version.

Thanks very much on your persistent to bring richacls forward!

metze

Re: [RFC v4 06/31] richacl: In-memory representation and helper functions

From: Andreas Grünbacher <hidden>
Date: 2015-06-25 21:06:21

Hi,

2015-06-25 21:58 GMT+02:00 Stefan (metze) Metzmacher [off-list ref]:
Is that also the on disk representation?
that's not the xattr representation, no.
I'm wondering if the size of an ace should be dynamic,
which might make it possible to support other ace types
in future. E.g. supporting other identities like 128-bit values
to make it easier to map Windows SIDS.
I'm working on additionally supporting unmapped user@domain and
group@domain identifier strings; we have to deal with that case in the
nfs client; that may be useful for Samba as well.
Even without 128-bit ids, it would be very useful to mark an
ace so that it applies to a uid or gid at the same time.
This would reduce the size of the ace list when Samba uses
IDMAP_TYPE_BOTH, which means a SID is mapped to a unix id, which
is user (uid) and group (gid) at the same time. This feature is required
in order to support SID-Histories on accounts.
Currently Samba needs to add two aces (one uid and one gid)
in order to represent one Windows ace.
It's not clear to me if supporting this would be a good idea right now.
The kernel would have to treat each such entry like two separate entries
internally. How would we map a combined user-space "uid + gid"
number to a kernel uid and gid since it could map to two distinct
numbers there?
I haven't looked at the claims based acls on Windows, but it would be
good if the new infrastructure is dynamic enough to support something
like that in a future version.
I don't know, I have yet to see a use case that isn't totally crazy.

Thanks,
Andreas
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help