[PATCH 5/5] VFS: introduce lock_and_check_dentry()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



A few callers operate on a dentry which they already have - unlike the
normal case where a lookup proceeds an operation.

For these callers lock_and_check_dentry() is provided where other
callers would use lookup_and_lock().  The call will fail if, after the
lock was gained, the child is no longer a child of the given parent.

When the operation completes dentry_unlock() must be called.  An
extra reference is taken when the lock_and_check_dentry() call succeeds
and will be dropped by dentry_unlock().

This patch changes ecryptfs to make use of this new interface.
cachefiles and smb/server can also benefit as will be seen in later
patches.

Note that lock_parent() in ecryptfs is changed to return with the lock
NOT held when an error occurs.

Signed-off-by: NeilBrown <neil@xxxxxxxxxx>
---
 fs/ecryptfs/inode.c   | 124 +++++++++++++++++++++++-------------------
 fs/namei.c            |  26 +++++++++
 include/linux/namei.h |   1 +
 3 files changed, 96 insertions(+), 55 deletions(-)

diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index 3e627bcbaff1..3173ba89bc20 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -26,16 +26,15 @@
 
 static int lock_parent(struct dentry *dentry,
 		       struct dentry **lower_dentry,
-		       struct inode **lower_dir)
+		       struct dentry **lower_dir_dentry)
 {
-	struct dentry *lower_dir_dentry;
 
-	lower_dir_dentry = ecryptfs_dentry_to_lower(dentry->d_parent);
-	*lower_dir = d_inode(lower_dir_dentry);
+	*lower_dir_dentry = ecryptfs_dentry_to_lower(dentry->d_parent);
 	*lower_dentry = ecryptfs_dentry_to_lower(dentry);
 
-	inode_lock_nested(*lower_dir, I_MUTEX_PARENT);
-	return (*lower_dentry)->d_parent == lower_dir_dentry ? 0 : -EINVAL;
+	if (!lock_and_check_dentry(*lower_dir_dentry, *lower_dentry))
+		return -EINVAL;
+	return 0;
 }
 
 static int ecryptfs_inode_test(struct inode *inode, void *lower_inode)
@@ -138,28 +137,30 @@ static int ecryptfs_do_unlink(struct inode *dir, struct dentry *dentry,
 			      struct inode *inode)
 {
 	struct dentry *lower_dentry;
-	struct inode *lower_dir;
+	struct dentry *lower_dir_dentry;
 	int rc;
 
-	rc = lock_parent(dentry, &lower_dentry, &lower_dir);
-	dget(lower_dentry);	// don't even try to make the lower negative
+	rc = lock_parent(dentry, &lower_dentry, &lower_dir_dentry);
+	if (rc) {
+		printk(KERN_ERR "Error in vfs_unlink; rc = [%d]\n", rc);
+		return rc;
+	}
 	if (!rc) {
 		if (d_unhashed(lower_dentry))
 			rc = -EINVAL;
 		else
-			rc = vfs_unlink(&nop_mnt_idmap, lower_dir, lower_dentry,
-					NULL);
+			rc = vfs_unlink(&nop_mnt_idmap, d_inode(lower_dir_dentry),
+					lower_dentry, NULL);
 	}
 	if (rc) {
 		printk(KERN_ERR "Error in vfs_unlink; rc = [%d]\n", rc);
 		goto out_unlock;
 	}
-	fsstack_copy_attr_times(dir, lower_dir);
+	fsstack_copy_attr_times(dir, d_inode(lower_dir_dentry));
 	set_nlink(inode, ecryptfs_inode_to_lower(inode)->i_nlink);
 	inode_set_ctime_to_ts(inode, inode_get_ctime(dir));
 out_unlock:
-	dput(lower_dentry);
-	inode_unlock(lower_dir);
+	dentry_unlock(lower_dentry);
 	if (!rc)
 		d_drop(dentry);
 	return rc;
@@ -182,14 +183,18 @@ ecryptfs_do_create(struct inode *directory_inode,
 		   struct dentry *ecryptfs_dentry, umode_t mode)
 {
 	int rc;
-	struct dentry *lower_dentry;
+	struct dentry *lower_dentry, *lower_dir_dentry;
 	struct inode *lower_dir;
 	struct inode *inode;
 
-	rc = lock_parent(ecryptfs_dentry, &lower_dentry, &lower_dir);
-	if (!rc)
-		rc = vfs_create(&nop_mnt_idmap, lower_dir,
-				lower_dentry, mode, true);
+	rc = lock_parent(ecryptfs_dentry, &lower_dentry, &lower_dir_dentry);
+	if (rc) {
+		printk(KERN_ERR "%s: Failure to create dentry in lower fs; "
+		       "rc = [%d]\n", __func__, rc);
+		return ERR_PTR(rc);
+	}
+	lower_dir = d_inode(lower_dir_dentry);
+	rc = vfs_create(&nop_mnt_idmap, lower_dir, lower_dentry, mode, true);
 	if (rc) {
 		printk(KERN_ERR "%s: Failure to create dentry in lower fs; "
 		       "rc = [%d]\n", __func__, rc);
@@ -205,7 +210,7 @@ ecryptfs_do_create(struct inode *directory_inode,
 	fsstack_copy_attr_times(directory_inode, lower_dir);
 	fsstack_copy_inode_size(directory_inode, lower_dir);
 out_lock:
-	inode_unlock(lower_dir);
+	dentry_unlock(lower_dentry);
 	return inode;
 }
 
@@ -436,16 +441,19 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
 {
 	struct dentry *lower_old_dentry;
 	struct dentry *lower_new_dentry;
+	struct dentry *lower_dir_dentry;
 	struct inode *lower_dir;
 	u64 file_size_save;
 	int rc;
 
 	file_size_save = i_size_read(d_inode(old_dentry));
 	lower_old_dentry = ecryptfs_dentry_to_lower(old_dentry);
-	rc = lock_parent(new_dentry, &lower_new_dentry, &lower_dir);
-	if (!rc)
-		rc = vfs_link(lower_old_dentry, &nop_mnt_idmap, lower_dir,
-			      lower_new_dentry, NULL);
+	rc = lock_parent(new_dentry, &lower_new_dentry, &lower_dir_dentry);
+	if (rc)
+		return rc;
+	lower_dir = d_inode(lower_dir_dentry);
+	rc = vfs_link(lower_old_dentry, &nop_mnt_idmap, lower_dir,
+		      lower_new_dentry, NULL);
 	if (rc || d_really_is_negative(lower_new_dentry))
 		goto out_lock;
 	rc = ecryptfs_interpose(lower_new_dentry, new_dentry, dir->i_sb);
@@ -457,7 +465,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
 		  ecryptfs_inode_to_lower(d_inode(old_dentry))->i_nlink);
 	i_size_write(d_inode(new_dentry), file_size_save);
 out_lock:
-	inode_unlock(lower_dir);
+	dentry_unlock(lower_new_dentry);
 	return rc;
 }
 
@@ -472,14 +480,16 @@ static int ecryptfs_symlink(struct mnt_idmap *idmap,
 {
 	int rc;
 	struct dentry *lower_dentry;
+	struct dentry *lower_dir_dentry;
 	struct inode *lower_dir;
 	char *encoded_symname;
 	size_t encoded_symlen;
 	struct ecryptfs_mount_crypt_stat *mount_crypt_stat = NULL;
 
-	rc = lock_parent(dentry, &lower_dentry, &lower_dir);
+	rc = lock_parent(dentry, &lower_dentry, &lower_dir_dentry);
 	if (rc)
-		goto out_lock;
+		goto out;
+	lower_dir = d_inode(lower_dir_dentry);
 	mount_crypt_stat = &ecryptfs_superblock_to_private(
 		dir->i_sb)->mount_crypt_stat;
 	rc = ecryptfs_encrypt_and_encode_filename(&encoded_symname,
@@ -499,7 +509,8 @@ static int ecryptfs_symlink(struct mnt_idmap *idmap,
 	fsstack_copy_attr_times(dir, lower_dir);
 	fsstack_copy_inode_size(dir, lower_dir);
 out_lock:
-	inode_unlock(lower_dir);
+	dentry_unlock(lower_dentry);
+out:
 	if (d_really_is_negative(dentry))
 		d_drop(dentry);
 	return rc;
@@ -509,30 +520,30 @@ static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 				     struct dentry *dentry, umode_t mode)
 {
 	int rc;
-	struct dentry *lower_dentry;
+	struct dentry *lower_dentry, *lower_dir_dentry;
 	struct inode *lower_dir;
 
-	rc = lock_parent(dentry, &lower_dentry, &lower_dir);
+	rc = lock_parent(dentry, &lower_dentry, &lower_dir_dentry);
 	if (rc)
 		goto out;
-
+	lower_dir = d_inode(lower_dir_dentry);
 	lower_dentry = vfs_mkdir(&nop_mnt_idmap, lower_dir,
 				 lower_dentry, mode);
 	rc = PTR_ERR(lower_dentry);
 	if (IS_ERR(lower_dentry))
-		goto out_unlocked;
+		goto out;
 	rc = 0;
 	if (d_unhashed(lower_dentry))
-		goto out;
+		goto out_unlock;
 	rc = ecryptfs_interpose(lower_dentry, dentry, dir->i_sb);
 	if (rc)
-		goto out;
+		goto out_unlock;
 	fsstack_copy_attr_times(dir, lower_dir);
 	fsstack_copy_inode_size(dir, lower_dir);
 	set_nlink(dir, lower_dir->i_nlink);
+out_unlock:
+	dentry_unlock(lower_dentry);
 out:
-	inode_unlock(lower_dir);
-out_unlocked:
 	if (d_really_is_negative(dentry))
 		d_drop(dentry);
 	return ERR_PTR(rc);
@@ -540,25 +551,25 @@ static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 
 static int ecryptfs_rmdir(struct inode *dir, struct dentry *dentry)
 {
-	struct dentry *lower_dentry;
+	struct dentry *lower_dentry, *lower_dir_dentry;
 	struct inode *lower_dir;
 	int rc;
 
-	rc = lock_parent(dentry, &lower_dentry, &lower_dir);
-	dget(lower_dentry);	// don't even try to make the lower negative
-	if (!rc) {
-		if (d_unhashed(lower_dentry))
-			rc = -EINVAL;
-		else
-			rc = vfs_rmdir(&nop_mnt_idmap, lower_dir, lower_dentry);
-	}
+	rc = lock_parent(dentry, &lower_dentry, &lower_dir_dentry);
+	if (rc)
+		return rc;
+	lower_dir = d_inode(lower_dir_dentry);
+	if (d_unhashed(lower_dentry))
+		rc = -EINVAL;
+	else
+		rc = vfs_rmdir(&nop_mnt_idmap, lower_dir, lower_dentry);
+
 	if (!rc) {
 		clear_nlink(d_inode(dentry));
 		fsstack_copy_attr_times(dir, lower_dir);
 		set_nlink(dir, lower_dir->i_nlink);
 	}
-	dput(lower_dentry);
-	inode_unlock(lower_dir);
+	dentry_unlock(lower_dentry);
 	if (!rc)
 		d_drop(dentry);
 	return rc;
@@ -569,22 +580,25 @@ ecryptfs_mknod(struct mnt_idmap *idmap, struct inode *dir,
 	       struct dentry *dentry, umode_t mode, dev_t dev)
 {
 	int rc;
-	struct dentry *lower_dentry;
+	struct dentry *lower_dentry, *lower_dir_dentry;
 	struct inode *lower_dir;
 
-	rc = lock_parent(dentry, &lower_dentry, &lower_dir);
-	if (!rc)
-		rc = vfs_mknod(&nop_mnt_idmap, lower_dir,
-			       lower_dentry, mode, dev);
-	if (rc || d_really_is_negative(lower_dentry))
+	rc = lock_parent(dentry, &lower_dentry, &lower_dir_dentry);
+	if (rc)
 		goto out;
+	lower_dir = d_inode(lower_dir_dentry);
+	rc = vfs_mknod(&nop_mnt_idmap, lower_dir,
+		       lower_dentry, mode, dev);
+	if (rc || d_really_is_negative(lower_dentry))
+		goto out_unlock;
 	rc = ecryptfs_interpose(lower_dentry, dentry, dir->i_sb);
 	if (rc)
-		goto out;
+		goto out_unlock;
 	fsstack_copy_attr_times(dir, lower_dir);
 	fsstack_copy_inode_size(dir, lower_dir);
+out_unlock:
+	dentry_unlock(lower_dentry);
 out:
-	inode_unlock(lower_dir);
 	if (d_really_is_negative(dentry))
 		d_drop(dentry);
 	return rc;
diff --git a/fs/namei.c b/fs/namei.c
index 39868ee40f03..65f1d50c5a5b 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1850,6 +1850,32 @@ struct dentry *lookup_and_lock_killable(struct mnt_idmap *idmap,
 }
 EXPORT_SYMBOL(lookup_and_lock_killable);
 
+/**
+ * lock_and_check_dentry: lock a dentry in given parent prior to dir ops
+ * @child: the dentry to lock
+ * @parent: the dentry of the assumed parent
+ *
+ * The child is locked - currently by taking i_rwsem on the parent - to
+ * prepare for create/remove operations.  If the given parent is no longer
+ * the parent of the dentry after the lock is gained, the lock is released
+ * and the call failed (returns %false).
+ *
+ * A reference is taken to the child on success.  The lock and references
+ * must both be dropped by dentry_unlock() after the operation completes.
+ */
+bool lock_and_check_dentry(struct dentry *child, struct dentry *parent)
+{
+	inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
+	if (child->d_parent == parent) {
+		/* get the child to balance with dentry_unlock which puts it. */
+		dget(child);
+		return true;
+	}
+	inode_unlock(d_inode(parent));
+	return false;
+}
+EXPORT_SYMBOL(lock_and_check_dentry);
+
 void dentry_unlock_dir_locked(struct dentry *dentry)
 {
 	d_lookup_done(dentry);
diff --git a/include/linux/namei.h b/include/linux/namei.h
index a51f3caad106..67c82caa4676 100644
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -106,6 +106,7 @@ int lookup_and_lock_rename(struct renamedata *rd, int lookup_flags);
 int lookup_and_lock_rename_noperm(struct renamedata *rd, int lookup_flags);
 int lookup_and_lock_rename_hashed(struct renamedata *rd, int lookup_flags);
 void dentry_unlock_rename(struct renamedata *rd);
+bool lock_and_check_dentry(struct dentry *child, struct dentry *parent);
 
 /**
  * mode_strip_umask - handle vfs umask stripping
-- 
2.49.0





[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [NTFS 3]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [NTFS 3]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux