From: Andreas Gruenbacher <agruen@suse.de>
Date: Fri, 11 Jun 2010 16:12:44 +0530
Subject: [PATCH 01/16] vfs: Hooks for more fine-grained directory permission checking
Patch-mainline: not yet

Add iop->may_create and iop->may_delete for overriding the POSIX file
permission checks when creating and deleting files.  File systems can
implement these hooks to support permission models which use different
rules for file creation and deletion.

When these hooks are not used, the vfs behavior remains unchanged.

Signed-off-by: Andreas Gruenbacher <agruen@suse.de>
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
---
 fs/namei.c         |   82 ++++++++++++++++++++++++++++++++++++++++++-----------
 include/linux/fs.h |    4 ++
 2 files changed, 69 insertions(+), 17 deletions(-)

--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1283,6 +1283,26 @@ static inline int check_sticky(struct in
 }
 
 /*
+ * Do the directory specific tests of inode_permission() and call the
+ * may_delete inode operation.  The may_delete inode operation must do the
+ * sticky check when needed.
+ */
+static int may_delete_iop(struct inode *dir, struct inode *inode, int replace)
+{
+	int error;
+
+	if (IS_RDONLY(dir))
+		return -EROFS;
+	if (IS_IMMUTABLE(dir))
+		return -EACCES;
+	error = dir->i_op->may_delete(dir, inode, replace);
+	if (!error)
+		error = security_inode_permission(dir, MAY_WRITE | MAY_EXEC);
+
+	return error;
+}
+
+/*
  *	Check whether we can remove a link victim from directory dir, check
  *  whether the type of victim is right.
  *  1. We can't do it if dir is read-only (done in permission())
@@ -1301,7 +1321,8 @@ static inline int check_sticky(struct in
  * 10. We don't allow removal of NFS sillyrenamed files; it's handled by
  *     nfs_async_unlink().
  */
-static int may_delete(struct inode *dir,struct dentry *victim,int isdir)
+static int may_delete(struct inode *dir, struct dentry *victim,
+		      int isdir, int replace)
 {
 	int error;
 
@@ -1310,14 +1331,19 @@ static int may_delete(struct inode *dir,
 
 	BUG_ON(victim->d_parent->d_inode != dir);
 	audit_inode_child(victim, dir);
-
-	error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+	if (dir->i_op->may_delete)
+		error = may_delete_iop(dir, victim->d_inode, replace);
+	else {
+		error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+		if (!error && check_sticky(dir, victim->d_inode))
+			error = -EPERM;
+	}
 	if (error)
 		return error;
 	if (IS_APPEND(dir))
 		return -EPERM;
-	if (check_sticky(dir, victim->d_inode)||IS_APPEND(victim->d_inode)||
-	    IS_IMMUTABLE(victim->d_inode) || IS_SWAPFILE(victim->d_inode))
+	if (IS_APPEND(victim->d_inode) || IS_IMMUTABLE(victim->d_inode) ||
+		IS_SWAPFILE(victim->d_inode))
 		return -EPERM;
 	if (isdir) {
 		if (!S_ISDIR(victim->d_inode->i_mode))
@@ -1333,6 +1359,25 @@ static int may_delete(struct inode *dir,
 	return 0;
 }
 
+/*
+ * Do the directory specific tests of inode_permission() and call the
+ * may_create inode operation.
+ */
+static int may_create_iop(struct inode *dir, int isdir)
+{
+	int error;
+
+	if (IS_RDONLY(dir))
+		return -EROFS;
+	if (IS_IMMUTABLE(dir))
+		return -EACCES;
+	error = dir->i_op->may_create(dir, isdir);
+	if (!error)
+		error = security_inode_permission(dir, MAY_WRITE | MAY_EXEC);
+
+	return error;
+}
+
 /*	Check whether we can create an object with dentry child in directory
  *  dir.
  *  1. We can't do it if child already exists (open has special treatment for
@@ -1341,13 +1386,16 @@ static int may_delete(struct inode *dir,
  *  3. We should have write and exec permissions on dir
  *  4. We can't do it if dir is immutable (done in permission())
  */
-static inline int may_create(struct inode *dir, struct dentry *child)
+static inline int may_create(struct inode *dir, struct dentry *child, int isdir)
 {
 	if (child->d_inode)
 		return -EEXIST;
 	if (IS_DEADDIR(dir))
 		return -ENOENT;
-	return inode_permission(dir, MAY_WRITE | MAY_EXEC);
+	if (dir->i_op->may_create)
+		return may_create_iop(dir, isdir);
+	else
+		return inode_permission(dir, MAY_WRITE | MAY_EXEC);
 }
 
 /*
@@ -1395,7 +1443,7 @@ void unlock_rename(struct dentry *p1, st
 int vfs_create(struct inode *dir, struct dentry *dentry, int mode,
 		struct nameidata *nd)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, 0);
 
 	if (error)
 		return error;
@@ -1957,7 +2005,7 @@ EXPORT_SYMBOL_GPL(lookup_create);
 
 int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, 0);
 
 	if (error)
 		return error;
@@ -2061,7 +2109,7 @@ SYSCALL_DEFINE3(mknod, const char __user
 
 int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, 1);
 
 	if (error)
 		return error;
@@ -2151,7 +2199,7 @@ void dentry_unhash(struct dentry *dentry
 
 int vfs_rmdir(struct inode *dir, struct dentry *dentry)
 {
-	int error = may_delete(dir, dentry, 1);
+	int error = may_delete(dir, dentry, 1, 0);
 
 	if (error)
 		return error;
@@ -2238,7 +2286,7 @@ SYSCALL_DEFINE1(rmdir, const char __user
 
 int vfs_unlink(struct inode *dir, struct dentry *dentry)
 {
-	int error = may_delete(dir, dentry, 0);
+	int error = may_delete(dir, dentry, 0, 0);
 
 	if (error)
 		return error;
@@ -2346,7 +2394,7 @@ SYSCALL_DEFINE1(unlink, const char __use
 
 int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
 {
-	int error = may_create(dir, dentry);
+	int error = may_create(dir, dentry, 0);
 
 	if (error)
 		return error;
@@ -2419,7 +2467,7 @@ int vfs_link(struct dentry *old_dentry,
 	if (!inode)
 		return -ENOENT;
 
-	error = may_create(dir, new_dentry);
+	error = may_create(dir, new_dentry, S_ISDIR(inode->i_mode));
 	if (error)
 		return error;
 
@@ -2630,14 +2678,14 @@ int vfs_rename(struct inode *old_dir, st
 	if (old_dentry->d_inode == new_dentry->d_inode)
  		return 0;
  
-	error = may_delete(old_dir, old_dentry, is_dir);
+	error = may_delete(old_dir, old_dentry, is_dir, 0);
 	if (error)
 		return error;
 
 	if (!new_dentry->d_inode)
-		error = may_create(new_dir, new_dentry);
+		error = may_create(new_dir, new_dentry, is_dir);
 	else
-		error = may_delete(new_dir, new_dentry, is_dir);
+		error = may_delete(new_dir, new_dentry, is_dir, 1);
 	if (error)
 		return error;
 
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1542,6 +1542,10 @@ struct inode_operations {
	void (*truncate_range)(struct inode *, loff_t, loff_t);
 	int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
 		      u64 len);
+	int (*may_create) (struct inode *, int);
+	int (*may_delete) (struct inode *, struct inode *, int);
+
+
 } ____cacheline_aligned;
 
 struct seq_file;
