From: Andreas Gruenbacher <agruen@suse.de>
Subject: NFSv4 ACLs for ext3
Patch-mainline: Not yet
    
With the acl=nfs4 mount option, ext3 will use NFSv4 ACLs instead of
POSIX ACLs.  See http://www.suse.de/~agruen/nfs4acl/ for some
documentation and examples.

Signed-off-by: Andreas Gruenbacher <agruen@suse.de>

---
 fs/ext3/Kconfig           |    7 
 fs/ext3/Makefile          |    1 
 fs/ext3/file.c            |    4 
 fs/ext3/ialloc.c          |    6 
 fs/ext3/inode.c           |   73 ++++++++
 fs/ext3/namei.c           |   15 +
 fs/ext3/namei.h           |    1 
 fs/ext3/nfs4acl.c         |  378 ++++++++++++++++++++++++++++++++++++++++++++++
 fs/ext3/nfs4acl.h         |   36 ++++
 fs/ext3/super.c           |   61 +++++--
 fs/ext3/xattr.c           |    9 +
 fs/ext3/xattr.h           |    5 
 include/linux/ext3_fs.h   |    1 
 include/linux/ext3_fs_i.h |    3 
 14 files changed, 582 insertions(+), 18 deletions(-)

--- a/fs/ext3/Kconfig
+++ b/fs/ext3/Kconfig
@@ -97,6 +97,13 @@ config EXT3_FS_POSIX_ACL
 
 	  If you don't know what Access Control Lists are, say N
 
+config EXT3_FS_NFS4ACL
+	bool "Native NFSv4 ACLs (EXPERIMENTAL)"
+	depends on EXT3_FS_XATTR && EXPERIMENTAL
+	select FS_NFS4ACL
+	help
+	  Allow to use NFSv4 ACLs instead of POSIX ACLs.
+
 config EXT3_FS_SECURITY
 	bool "Ext3 Security Labels"
 	depends on EXT3_FS_XATTR
--- a/fs/ext3/Makefile
+++ b/fs/ext3/Makefile
@@ -10,3 +10,4 @@ ext3-y	:= balloc.o bitmap.o dir.o file.o
 ext3-$(CONFIG_EXT3_FS_XATTR)	 += xattr.o xattr_user.o xattr_trusted.o
 ext3-$(CONFIG_EXT3_FS_POSIX_ACL) += acl.o
 ext3-$(CONFIG_EXT3_FS_SECURITY)	 += xattr_security.o
+ext3-$(CONFIG_EXT3_FS_NFS4ACL)	 += nfs4acl.o
--- a/fs/ext3/file.c
+++ b/fs/ext3/file.c
@@ -24,8 +24,10 @@
 #include <linux/quotaops.h>
 #include <linux/ext3_fs.h>
 #include <linux/ext3_jbd.h>
+#include "namei.h"
 #include "xattr.h"
 #include "acl.h"
+#include "nfs4acl.h"
 
 /*
  * Called when an inode is released. Note that this is different
@@ -81,5 +83,7 @@ const struct inode_operations ext3_file_
 #endif
 	.check_acl	= ext3_check_acl,
 	.fiemap		= ext3_fiemap,
+	.may_create	= ext3_may_create,
+	.may_delete	= ext3_may_delete,
 };
 
--- a/fs/ext3/ialloc.c
+++ b/fs/ext3/ialloc.c
@@ -28,6 +28,7 @@
 
 #include "xattr.h"
 #include "acl.h"
+#include "nfs4acl.h"
 
 /*
  * ialloc.c contains the inodes allocation and deallocation routines
@@ -593,7 +594,10 @@ got:
 	if (err)
 		goto fail_drop;
 
-	err = ext3_init_acl(handle, inode, dir);
+	if (test_opt(sb, NFS4ACL))
+		err = ext3_nfs4acl_init(handle, inode, dir);
+	else
+		err = ext3_init_acl(handle, inode, dir);
 	if (err)
 		goto fail_free_drop;
 
--- a/fs/ext3/inode.c
+++ b/fs/ext3/inode.c
@@ -40,6 +40,7 @@
 #include <linux/namei.h>
 #include "xattr.h"
 #include "acl.h"
+#include "nfs4acl.h"
 
 static int ext3_writepage_trans_blocks(struct inode *inode);
 
@@ -2790,6 +2791,9 @@ struct inode *ext3_iget(struct super_blo
 		return inode;
 
 	ei = EXT3_I(inode);
+#ifdef CONFIG_EXT3_FS_NFS4ACL
+	ei->i_nfs4acl = EXT3_NFS4ACL_NOT_CACHED;
+#endif
 	ei->i_block_alloc_info = NULL;
 
 	ret = __ext3_get_inode_loc(inode, &iloc, 0);
@@ -3124,6 +3128,65 @@ int ext3_write_inode(struct inode *inode
 	return ext3_force_commit(inode->i_sb);
 }
 
+#ifdef CONFIG_EXT3_FS_NFS4ACL
+static int ext3_inode_change_ok(struct inode *inode, struct iattr *attr)
+{
+	unsigned int ia_valid = attr->ia_valid;
+
+	if (!test_opt(inode->i_sb, NFS4ACL))
+		return inode_change_ok(inode, attr);
+
+	/* If force is set do it anyway. */
+	if (ia_valid & ATTR_FORCE)
+		return 0;
+
+	/* Make sure a caller can chown. */
+	if ((ia_valid & ATTR_UID) &&
+	    (current_fsuid() != inode->i_uid ||
+	     attr->ia_uid != inode->i_uid) &&
+	    (current_fsuid() != attr->ia_uid ||
+	     ext3_nfs4acl_permission(inode, ACE4_WRITE_OWNER)) &&
+	    !capable(CAP_CHOWN))
+		goto error;
+
+	/* Make sure caller can chgrp. */
+	if ((ia_valid & ATTR_GID)) {
+		int in_group = in_group_p(attr->ia_gid);
+		if ((current_fsuid() != inode->i_uid ||
+		    (!in_group && attr->ia_gid != inode->i_gid)) &&
+		    (!in_group ||
+		     ext3_nfs4acl_permission(inode, ACE4_WRITE_OWNER)) &&
+		    !capable(CAP_CHOWN))
+			goto error;
+	}
+
+	/* Make sure a caller can chmod. */
+	if (ia_valid & ATTR_MODE) {
+		if (current_fsuid() != inode->i_uid &&
+		    ext3_nfs4acl_permission(inode, ACE4_WRITE_ACL) &&
+		    !capable(CAP_FOWNER))
+			goto error;
+		/* Also check the setgid bit! */
+		if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid :
+				inode->i_gid) && !capable(CAP_FSETID))
+			attr->ia_mode &= ~S_ISGID;
+	}
+
+	/* Check for setting the inode time. */
+	if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET)) {
+		if (current_fsuid() != inode->i_uid &&
+		    ext3_nfs4acl_permission(inode, ACE4_WRITE_ATTRIBUTES) &&
+		    !capable(CAP_FOWNER))
+			goto error;
+	}
+	return 0;
+error:
+	return -EPERM;
+}
+#else
+# define ext3_inode_change_ok inode_change_ok
+#endif
+
 /*
  * ext3_setattr()
  *
@@ -3147,7 +3210,7 @@ int ext3_setattr(struct dentry *dentry,
 	int error, rc = 0;
 	const unsigned int ia_valid = attr->ia_valid;
 
-	error = inode_change_ok(inode, attr);
+	error = ext3_inode_change_ok(inode, attr);
 	if (error)
 		return error;
 
@@ -3200,8 +3263,12 @@ int ext3_setattr(struct dentry *dentry,
 
 	rc = inode_setattr(inode, attr);
 
-	if (!rc && (ia_valid & ATTR_MODE))
-		rc = ext3_acl_chmod(inode);
+	if (!rc && (ia_valid & ATTR_MODE)) {
+		if (test_opt(inode->i_sb, NFS4ACL))
+			rc = ext3_nfs4acl_chmod(inode);
+		else
+			rc = ext3_acl_chmod(inode);
+	}
 
 err_out:
 	ext3_std_error(inode->i_sb, error);
--- a/fs/ext3/namei.c
+++ b/fs/ext3/namei.c
@@ -40,6 +40,7 @@
 #include "namei.h"
 #include "xattr.h"
 #include "acl.h"
+#include "nfs4acl.h"
 
 /*
  * define how far ahead to read directories while searching them.
@@ -2445,6 +2446,16 @@ end_rename:
 	return retval;
 }
 
+int ext3_permission(struct inode *inode, int mask)
+{
+#ifdef CONFIG_EXT3_FS_NFS4ACL
+	if (test_opt(inode->i_sb, NFS4ACL))
+		return ext3_nfs4acl_permission(inode, nfs4acl_want_to_mask(mask));
+	else
+#endif
+        return generic_permission(inode, mask, ext3_check_acl);
+}
+
 /*
  * directories can handle most operations...
  */
@@ -2466,6 +2477,8 @@ const struct inode_operations ext3_dir_i
 	.removexattr	= generic_removexattr,
 #endif
 	.check_acl	= ext3_check_acl,
+	.may_create	= ext3_may_create,
+	.may_delete	= ext3_may_delete,
 };
 
 const struct inode_operations ext3_special_inode_operations = {
@@ -2477,4 +2490,6 @@ const struct inode_operations ext3_speci
 	.removexattr	= generic_removexattr,
 #endif
 	.check_acl	= ext3_check_acl,
+	.may_create	= ext3_may_create,
+	.may_delete	= ext3_may_delete,
 };
--- a/fs/ext3/namei.h
+++ b/fs/ext3/namei.h
@@ -5,4 +5,5 @@
  *
 */
 
+extern int ext3_permission (struct inode *, int);
 extern struct dentry *ext3_get_parent(struct dentry *child);
--- /dev/null
+++ b/fs/ext3/nfs4acl.c
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2006 Andreas Gruenbacher <a.gruenbacher@computer.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/kernel.h>
+#include <linux/fs.h>
+#include <linux/fs_struct.h>
+#include <linux/ext3_jbd.h>
+#include <linux/ext3_fs.h>
+#include <linux/nfs4acl_xattr.h>
+#include "namei.h"
+#include "xattr.h"
+#include "nfs4acl.h"
+
+static inline struct nfs4acl *
+ext3_iget_nfs4acl(struct inode *inode)
+{
+	struct nfs4acl *acl = EXT3_NFS4ACL_NOT_CACHED;
+	struct ext3_inode_info *ei = EXT3_I(inode);
+
+	spin_lock(&inode->i_lock);
+	if (ei->i_nfs4acl != EXT3_NFS4ACL_NOT_CACHED)
+		acl = nfs4acl_get(ei->i_nfs4acl);
+	spin_unlock(&inode->i_lock);
+
+	return acl;
+}
+
+static inline void
+ext3_iset_nfs4acl(struct inode *inode, struct nfs4acl *acl)
+{
+	struct ext3_inode_info *ei = EXT3_I(inode);
+
+	spin_lock(&inode->i_lock);
+	if (ei->i_nfs4acl != EXT3_NFS4ACL_NOT_CACHED)
+		nfs4acl_put(ei->i_nfs4acl);
+	ei->i_nfs4acl = nfs4acl_get(acl);
+	spin_unlock(&inode->i_lock);
+}
+
+static struct nfs4acl *
+ext3_get_nfs4acl(struct inode *inode)
+{
+	const int name_index = EXT3_XATTR_INDEX_NFS4ACL;
+	void *value = NULL;
+	struct nfs4acl *acl;
+	int retval;
+
+	if (!test_opt(inode->i_sb, NFS4ACL))
+		return NULL;
+
+	acl = ext3_iget_nfs4acl(inode);
+	if (acl != EXT3_NFS4ACL_NOT_CACHED)
+		return acl;
+	retval = ext3_xattr_get(inode, name_index, "", NULL, 0);
+	if (retval > 0) {
+		value = kmalloc(retval, GFP_KERNEL);
+		if (!value)
+			return ERR_PTR(-ENOMEM);
+		retval = ext3_xattr_get(inode, name_index, "", value, retval);
+	}
+	if (retval > 0) {
+		acl = nfs4acl_from_xattr(value, retval);
+		if (acl == ERR_PTR(-EINVAL))
+			acl = ERR_PTR(-EIO);
+	} else if (retval == -ENODATA || retval == -ENOSYS)
+		acl = NULL;
+	else
+		acl = ERR_PTR(retval);
+	kfree(value);
+
+	if (!IS_ERR(acl))
+		ext3_iset_nfs4acl(inode, acl);
+
+	return acl;
+}
+
+static int
+ext3_set_nfs4acl(handle_t *handle, struct inode *inode, struct nfs4acl *acl)
+{
+	const int name_index = EXT3_XATTR_INDEX_NFS4ACL;
+	size_t size = 0;
+	void *value = NULL;
+	int retval;
+
+	if (acl) {
+		size = nfs4acl_xattr_size(acl);
+		value = kmalloc(size, GFP_KERNEL);
+		if (!value)
+			return -ENOMEM;
+		nfs4acl_to_xattr(acl, value);
+	}
+	if (handle)
+		retval = ext3_xattr_set_handle(handle, inode, name_index, "",
+					       value, size, 0);
+	else
+		retval = ext3_xattr_set(inode, name_index, "", value, size, 0);
+	if (value)
+		kfree(value);
+	if (!retval)
+		ext3_iset_nfs4acl(inode, acl);
+
+	return retval;
+}
+
+int
+ext3_nfs4acl_permission(struct inode *inode, unsigned int mask)
+{
+	struct nfs4acl *acl;
+	int retval;
+
+	BUG_ON(!test_opt(inode->i_sb, NFS4ACL));
+
+	acl = ext3_get_nfs4acl(inode);
+	if (!acl)
+		retval = nfs4acl_generic_permission(inode, mask);
+	else if (IS_ERR(acl))
+		retval = PTR_ERR(acl);
+	else {
+		retval = nfs4acl_permission(inode, acl, mask);
+		nfs4acl_put(acl);
+	}
+
+	return retval;
+}
+
+int ext3_may_create(struct inode *dir, int isdir)
+{
+	int error;
+
+	if (test_opt(dir->i_sb, NFS4ACL)) {
+		unsigned int mask = (isdir ? ACE4_ADD_SUBDIRECTORY : ACE4_ADD_FILE) |
+				    ACE4_EXECUTE;
+
+		error = ext3_nfs4acl_permission(dir, mask);
+	} else
+		error = ext3_permission(dir,  MAY_WRITE | MAY_EXEC);
+
+	return error;
+}
+
+static int check_sticky(struct inode *dir, struct inode *inode)
+{
+	if (!(dir->i_mode & S_ISVTX))
+		return 0;
+	if (inode->i_uid == current_fsuid())
+		return 0;
+	if (dir->i_uid == current_fsuid())
+		return 0;
+	return !capable(CAP_FOWNER);
+}
+
+int ext3_may_delete(struct inode *dir, struct inode *inode)
+{
+	int error;
+
+	if (test_opt(inode->i_sb, NFS4ACL)) {
+		error = ext3_nfs4acl_permission(dir, ACE4_DELETE_CHILD | ACE4_EXECUTE);
+		if (!error && check_sticky(dir, inode))
+			error = -EPERM;
+		if (error && !ext3_nfs4acl_permission(inode, ACE4_DELETE))
+			error = 0;
+	} else {
+		error = ext3_permission(dir, MAY_WRITE | MAY_EXEC);
+		if (!error && check_sticky(dir, inode))
+			error = -EPERM;
+	}
+
+	return error;
+}
+
+int
+ext3_nfs4acl_init(handle_t *handle, struct inode *inode, struct inode *dir)
+{
+	struct nfs4acl *dir_acl = NULL, *acl;
+	int retval;
+
+	if (!S_ISLNK(inode->i_mode))
+		dir_acl = ext3_get_nfs4acl(dir);
+	if (!dir_acl || IS_ERR(dir_acl)) {
+		inode->i_mode &= ~current->fs->umask;
+		return PTR_ERR(dir_acl);
+	}
+	acl = nfs4acl_inherit(dir_acl, inode->i_mode);
+	nfs4acl_put(dir_acl);
+
+	retval = PTR_ERR(acl);
+	if (acl && !IS_ERR(acl)) {
+		retval = ext3_set_nfs4acl(handle, inode, acl);
+		inode->i_mode = (inode->i_mode & ~S_IRWXUGO) |
+				nfs4acl_masks_to_mode(acl);
+		nfs4acl_put(acl);
+	}
+	return retval;
+}
+
+int
+ext3_nfs4acl_chmod(struct inode *inode)
+{
+	struct nfs4acl *acl;
+	int retval;
+
+	if (S_ISLNK(inode->i_mode))
+		return -EOPNOTSUPP;
+	acl = ext3_get_nfs4acl(inode);
+	if (!acl || IS_ERR(acl))
+		return PTR_ERR(acl);
+	acl = nfs4acl_chmod(acl, inode->i_mode);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	retval = ext3_set_nfs4acl(NULL, inode, acl);
+	nfs4acl_put(acl);
+
+	return retval;
+}
+
+static size_t
+ext3_xattr_list_nfs4acl(struct dentry *dentry, char *list, size_t list_len,
+			const char *name, size_t name_len, int handler_flags)
+{
+	struct inode *inode = dentry->d_inode;
+	const size_t size = sizeof(NFS4ACL_XATTR);
+
+	if (!test_opt(inode->i_sb, NFS4ACL))
+		return 0;
+	if (list && size <= list_len)
+		memcpy(list, NFS4ACL_XATTR, size);
+	return size;
+}
+
+static int
+ext3_xattr_get_nfs4acl(struct dentry *dentry, const char *name, void *buffer,
+		       size_t buffer_size, int handler_flags)
+{
+	struct inode *inode = dentry->d_inode;
+	struct nfs4acl *acl;
+	size_t size;
+
+	if (!test_opt(inode->i_sb, NFS4ACL))
+		return -EOPNOTSUPP;
+	if (strcmp(name, "") != 0)
+		return -EINVAL;
+
+	acl = ext3_get_nfs4acl(inode);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	if (acl == NULL)
+		return -ENODATA;
+	size = nfs4acl_xattr_size(acl);
+	if (buffer) {
+		if (size > buffer_size)
+			return -ERANGE;
+		nfs4acl_to_xattr(acl, buffer);
+	}
+	nfs4acl_put(acl);
+
+	return size;
+}
+
+#ifdef NFS4ACL_DEBUG
+static size_t
+ext3_xattr_list_masked_nfs4acl(struct dentry *dentry, char *list,
+			       size_t list_len, const char *name,
+			       size_t name_len, int handler_flags)
+{
+	return 0;
+}
+
+static int
+ext3_xattr_get_masked_nfs4acl(struct dentry *dentry, const char *name,
+			      void *buffer, size_t buffer_size,
+			      int handler_flags)
+{
+	struct inode *inode = dentry->d_inode;
+	const int name_index = EXT3_XATTR_INDEX_NFS4ACL;
+	struct nfs4acl *acl;
+	void *xattr;
+	size_t size;
+	int retval;
+
+	if (!test_opt(inode->i_sb, NFS4ACL))
+		return -EOPNOTSUPP;
+	if (strcmp(name, "") != 0)
+		return -EINVAL;
+	retval = ext3_xattr_get(inode, name_index, "", NULL, 0);
+	if (retval <= 0)
+		return retval;
+	xattr = kmalloc(retval, GFP_KERNEL);
+	if (!xattr)
+		return -ENOMEM;
+	retval = ext3_xattr_get(inode, name_index, "", xattr, retval);
+	if (retval <= 0)
+		return retval;
+	acl = nfs4acl_from_xattr(xattr, retval);
+	kfree(xattr);
+	if (IS_ERR(acl))
+		return PTR_ERR(acl);
+	retval = nfs4acl_apply_masks(&acl);
+	if (retval) {
+		nfs4acl_put(acl);
+		return retval;
+	}
+	size = nfs4acl_xattr_size(acl);
+	if (buffer) {
+		if (size > buffer_size)
+			return -ERANGE;
+		nfs4acl_to_xattr(acl, buffer);
+	}
+	nfs4acl_put(acl);
+	return size;
+}
+#endif
+
+static int
+ext3_xattr_set_nfs4acl(struct dentry *dentry, const char *name,
+		       const void *value, size_t size, int flags,
+		       int handler_flags)
+{
+	struct inode *inode = dentry->d_inode;
+	handle_t *handle;
+	struct nfs4acl *acl = NULL;
+	int retval, retries = 0;
+
+	if (S_ISLNK(inode->i_mode) || !test_opt(inode->i_sb, NFS4ACL))
+		return -EOPNOTSUPP;
+	if (strcmp(name, "") != 0)
+		return -EINVAL;
+	if (current_fsuid() != inode->i_uid &&
+	    ext3_nfs4acl_permission(inode, ACE4_WRITE_ACL) &&
+	    !capable(CAP_FOWNER))
+		return -EPERM;
+	if (value) {
+		acl = nfs4acl_from_xattr(value, size);
+		if (IS_ERR(acl))
+			return PTR_ERR(acl);
+
+		inode->i_mode &= ~S_IRWXUGO;
+		inode->i_mode |= nfs4acl_masks_to_mode(acl);
+	}
+
+retry:
+	handle = ext3_journal_start(inode, EXT3_DATA_TRANS_BLOCKS(inode->i_sb));
+	if (IS_ERR(handle))
+		return PTR_ERR(handle);
+	ext3_mark_inode_dirty(handle, inode);
+	retval = ext3_set_nfs4acl(handle, inode, acl);
+	ext3_journal_stop(handle);
+	if (retval == ENOSPC && ext3_should_retry_alloc(inode->i_sb, &retries))
+		goto retry;
+	nfs4acl_put(acl);
+	return retval;
+}
+
+struct xattr_handler ext3_nfs4acl_xattr_handler = {
+	.prefix	= NFS4ACL_XATTR,
+	.list	= ext3_xattr_list_nfs4acl,
+	.get	= ext3_xattr_get_nfs4acl,
+	.set	= ext3_xattr_set_nfs4acl,
+};
+
+#ifdef NFS4ACL_DEBUG
+struct xattr_handler ext3_masked_nfs4acl_xattr_handler = {
+	.prefix	= "system.masked-nfs4acl",
+	.list	= ext3_xattr_list_masked_nfs4acl,
+	.get	= ext3_xattr_get_masked_nfs4acl,
+	.set	= ext3_xattr_set_nfs4acl,
+};
+#endif
--- /dev/null
+++ b/fs/ext3/nfs4acl.h
@@ -0,0 +1,36 @@
+#ifndef __FS_EXT3_NFS4ACL_H
+#define __FS_EXT3_NFS4ACL_H
+
+#ifdef CONFIG_EXT3_FS_NFS4ACL
+
+#include <linux/nfs4acl.h>
+
+/* Value for i_nfs4acl if NFS4ACL has not been cached */
+#define EXT3_NFS4ACL_NOT_CACHED ((void *)-1)
+
+extern int ext3_nfs4acl_permission(struct inode *, unsigned int);
+extern int ext3_may_create(struct inode *, int);
+extern int ext3_may_delete(struct inode *, struct inode *);
+extern int ext3_nfs4acl_init(handle_t *, struct inode *, struct inode *);
+extern int ext3_nfs4acl_chmod(struct inode *);
+
+#else  /* CONFIG_FS_EXT3_NFS4ACL */
+
+#define ext3_may_create NULL
+#define ext3_may_delete NULL
+
+static inline int
+ext3_nfs4acl_init(handle_t *handle, struct inode *inode, struct inode *dir)
+{
+	return 0;
+}
+
+static inline int
+ext3_nfs4acl_chmod(struct inode *inode)
+{
+	return 0;
+}
+
+#endif  /* CONFIG_FS_EXT3_NFS4ACL */
+
+#endif  /* __FS_EXT3_NFS4ACL_H */
--- a/fs/ext3/super.c
+++ b/fs/ext3/super.c
@@ -36,12 +36,14 @@
 #include <linux/namei.h>
 #include <linux/quotaops.h>
 #include <linux/seq_file.h>
+#include <linux/nfs4acl.h>
 #include <linux/log2.h>
 
 #include <asm/uaccess.h>
 
 #include "xattr.h"
 #include "acl.h"
+#include "nfs4acl.h"
 #include "namei.h"
 
 #ifdef CONFIG_EXT3_DEFAULTS_TO_ORDERED
@@ -476,6 +478,9 @@ static struct inode *ext3_alloc_inode(st
 	ei = kmem_cache_alloc(ext3_inode_cachep, GFP_NOFS);
 	if (!ei)
 		return NULL;
+#ifdef CONFIG_EXT3_FS_NFS4ACL
+	ei->i_nfs4acl = EXT3_NFS4ACL_NOT_CACHED;
+#endif
 	ei->i_block_alloc_info = NULL;
 	ei->vfs_inode.i_version = 1;
 	atomic_set(&ei->i_datasync_tid, 0);
@@ -529,6 +534,13 @@ static void ext3_clear_inode(struct inod
 {
 	struct ext3_block_alloc_info *rsv = EXT3_I(inode)->i_block_alloc_info;
 
+#ifdef CONFIG_EXT3_FS_NFS4ACL
+	if (EXT3_I(inode)->i_nfs4acl &&
+			EXT3_I(inode)->i_nfs4acl != EXT3_NFS4ACL_NOT_CACHED) {
+		nfs4acl_put(EXT3_I(inode)->i_nfs4acl);
+		EXT3_I(inode)->i_nfs4acl = EXT3_NFS4ACL_NOT_CACHED;
+	}
+#endif
 	dquot_drop(inode);
 	ext3_discard_reservation(inode);
 	EXT3_I(inode)->i_block_alloc_info = NULL;
@@ -803,7 +815,7 @@ enum {
 	Opt_bsd_df, Opt_minix_df, Opt_grpid, Opt_nogrpid,
 	Opt_resgid, Opt_resuid, Opt_sb, Opt_err_cont, Opt_err_panic, Opt_err_ro,
 	Opt_nouid32, Opt_nocheck, Opt_debug, Opt_oldalloc, Opt_orlov,
-	Opt_user_xattr, Opt_nouser_xattr, Opt_acl, Opt_noacl,
+	Opt_user_xattr, Opt_nouser_xattr, Opt_acl, Opt_acl_flavor, Opt_noacl,
 	Opt_reservation, Opt_noreservation, Opt_noload, Opt_nobh, Opt_bh,
 	Opt_commit, Opt_journal_update, Opt_journal_inum, Opt_journal_dev,
 	Opt_abort, Opt_data_journal, Opt_data_ordered, Opt_data_writeback,
@@ -836,6 +848,7 @@ static const match_table_t tokens = {
 	{Opt_user_xattr, "user_xattr"},
 	{Opt_nouser_xattr, "nouser_xattr"},
 	{Opt_acl, "acl"},
+	{Opt_acl_flavor, "acl=%s"},
 	{Opt_noacl, "noacl"},
 	{Opt_reservation, "reservation"},
 	{Opt_noreservation, "noreservation"},
@@ -1040,20 +1053,33 @@ static int parse_options (char *options,
 				"(no)user_xattr options not supported");
 			break;
 #endif
-#ifdef CONFIG_EXT3_FS_POSIX_ACL
 		case Opt_acl:
-			set_opt(sbi->s_mount_opt, POSIX_ACL);
+			args[0].to = args[0].from;
+			/* fall through */
+		case Opt_acl_flavor:
+#ifdef CONFIG_EXT3_FS_POSIX_ACL
+			if (match_string(&args[0], "") ||
+			    match_string(&args[0], "posix")) {
+				set_opt(sbi->s_mount_opt, POSIX_ACL);
+				clear_opt(sbi->s_mount_opt, NFS4ACL);
+			} else
+#endif
+#ifdef CONFIG_EXT3_FS_NFS4ACL
+			if (match_string(&args[0], "nfs4")) {
+				clear_opt(sbi->s_mount_opt, POSIX_ACL);
+				set_opt(sbi->s_mount_opt, NFS4ACL);
+			} else
+#endif
+			{
+				ext3_msg(sb, KERN_ERR,
+					 "unsupported acl flavor");
+				return 0;
+			}
 			break;
 		case Opt_noacl:
 			clear_opt(sbi->s_mount_opt, POSIX_ACL);
+			clear_opt(sbi->s_mount_opt, NFS4ACL);
 			break;
-#else
-		case Opt_acl:
-		case Opt_noacl:
-			ext3_msg(sb, KERN_INFO,
-				"(no)acl options not supported");
-			break;
-#endif
 		case Opt_reservation:
 			set_opt(sbi->s_mount_opt, RESERVATION);
 			break;
@@ -1698,8 +1724,11 @@ static int ext3_fill_super (struct super
 			    NULL, 0))
 		goto failed_mount;
 
-	sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
-		(test_opt(sb, POSIX_ACL) ? MS_POSIXACL : 0);
+	sb->s_flags = (sb->s_flags & ~MS_POSIXACL);
+	if (test_opt(sb, POSIX_ACL))
+		sb->s_flags |= MS_POSIXACL;
+	if (test_opt(sb, NFS4ACL))
+		sb->s_flags |= MS_POSIXACL | MS_WITHAPPEND;
 
 	if (le32_to_cpu(es->s_rev_level) == EXT3_GOOD_OLD_REV &&
 	    (EXT3_HAS_COMPAT_FEATURE(sb, ~0U) ||
@@ -2576,8 +2605,12 @@ static int ext3_remount (struct super_bl
 	if (test_opt(sb, ABORT))
 		ext3_abort(sb, __func__, "Abort forced by user");
 
-	sb->s_flags = (sb->s_flags & ~MS_POSIXACL) |
-		(test_opt(sb, POSIX_ACL) ? MS_POSIXACL : 0);
+	sb->s_flags = (sb->s_flags & ~MS_POSIXACL);
+	if (test_opt(sb, POSIX_ACL))
+		sb->s_flags |= MS_POSIXACL;
+	if (test_opt(sb, NFS4ACL))
+		sb->s_flags |= MS_POSIXACL;
+
 
 	es = sbi->s_es;
 
--- a/fs/ext3/xattr.c
+++ b/fs/ext3/xattr.c
@@ -114,6 +114,9 @@ static struct xattr_handler *ext3_xattr_
 #ifdef CONFIG_EXT3_FS_SECURITY
 	[EXT3_XATTR_INDEX_SECURITY]	     = &ext3_xattr_security_handler,
 #endif
+#ifdef CONFIG_EXT3_FS_NFS4ACL
+	[EXT3_XATTR_INDEX_NFS4ACL]	     = &ext3_nfs4acl_xattr_handler,
+#endif
 };
 
 struct xattr_handler *ext3_xattr_handlers[] = {
@@ -126,6 +129,12 @@ struct xattr_handler *ext3_xattr_handler
 #ifdef CONFIG_EXT3_FS_SECURITY
 	&ext3_xattr_security_handler,
 #endif
+#ifdef CONFIG_EXT3_FS_NFS4ACL
+	&ext3_nfs4acl_xattr_handler,
+#ifdef NFS4ACL_DEBUG
+	&ext3_masked_nfs4acl_xattr_handler,
+#endif
+#endif
 	NULL
 };
 
--- a/fs/ext3/xattr.h
+++ b/fs/ext3/xattr.h
@@ -21,6 +21,7 @@
 #define EXT3_XATTR_INDEX_TRUSTED		4
 #define	EXT3_XATTR_INDEX_LUSTRE			5
 #define EXT3_XATTR_INDEX_SECURITY	        6
+#define EXT3_XATTR_INDEX_NFS4ACL		7
 
 struct ext3_xattr_header {
 	__le32	h_magic;	/* magic number for identification */
@@ -63,6 +64,10 @@ extern struct xattr_handler ext3_xattr_t
 extern struct xattr_handler ext3_xattr_acl_access_handler;
 extern struct xattr_handler ext3_xattr_acl_default_handler;
 extern struct xattr_handler ext3_xattr_security_handler;
+extern struct xattr_handler ext3_nfs4acl_xattr_handler;
+#ifdef NFS4ACL_DEBUG
+extern struct xattr_handler ext3_masked_nfs4acl_xattr_handler;
+#endif
 
 extern ssize_t ext3_listxattr(struct dentry *, char *, size_t);
 
--- a/include/linux/ext3_fs.h
+++ b/include/linux/ext3_fs.h
@@ -406,6 +406,7 @@ struct ext3_inode {
 #define EXT3_MOUNT_GRPQUOTA		0x200000 /* "old" group quota */
 #define EXT3_MOUNT_DATA_ERR_ABORT	0x400000 /* Abort on file data write
 						  * error in ordered mode */
+#define EXT3_MOUNT_NFS4ACL		0x800000 /* NFS version 4 ACLs */
 
 /* Compatibility, for having both ext2_fs.h and ext3_fs.h included at once */
 #ifndef _LINUX_EXT2_FS_H
--- a/include/linux/ext3_fs_i.h
+++ b/include/linux/ext3_fs_i.h
@@ -103,6 +103,9 @@ struct ext3_inode_info {
 	 */
 	struct rw_semaphore xattr_sem;
 #endif
+#ifdef CONFIG_EXT3_FS_NFS4ACL
+	struct nfs4acl		*i_nfs4acl;
+#endif
 
 	struct list_head i_orphan;	/* unlinked but open inodes */
 
