Unionfs ODF: Fix a lockdep warning on nfsd_readdir of exported unionfs mount
authorRachita Kothiyal <rachita@dewey.fsl.cs.sunysb.edu>
Sun, 18 May 2008 07:37:20 +0000 (03:37 -0400)
committerRachita Kothiyal <rachita@dewey.fsl.cs.sunysb.edu>
Sun, 18 May 2008 07:37:20 +0000 (03:37 -0400)
nfsd_readdir calls vfs_llseek and vfs_readdir. Following is the locking
order seen by lockdep:
vfs_llseek: sb -> dentry -> inode
vfs_readdir: inode -> sb -> dentry
Lockdep reports this as a possible circular locking order bug.

To fix this, we define our own unionfs_file_llseek_locked function,
which is essentially the vfs generic_file_llseek function, but with
the inode locking moved into the caller. This now changes the locking
order in vfs_llseek call to be: inode -> sb -> dentry, and satisfies
lockdep too.

Signed-off-by: Rachita Kothiyal <rachita@fsl.cs.sunysb.edu>
fs/unionfs/dirfops.c

index 8e578af511d0fd082282ed2536877d9dbb6bc041..27b55ca7a0540f34269730a19c08d67542058cb2 100644 (file)
@@ -137,11 +137,48 @@ out:
        return err;
 }
 
+/*
+ * Heavily borrowed from the vfs generic_file_llseek.  This version expects
+ * an already locked inode.  This was done to fix a lockdep warning arising
+ * from the following locking order in nfs_readdir on an exported unionfs
+ * mount:
+ * nfs_readdir
+ *     --> vfs_llseek(): sb -> dentry -> inode
+ *     --> vfs_readdir(): inode -> sb -> dentry
+ *
+ * A better fix would be to have this as a vfs function.
+ */
+static loff_t unionfs_file_llseek_locked(struct file *file, loff_t offset,
+                                        int origin)
+{
+       long long retval;
+       struct inode *inode = file->f_mapping->host;
+
+       switch (origin) {
+       case SEEK_END:
+               offset += inode->i_size;
+               break;
+       case SEEK_CUR:
+               offset += file->f_pos;
+       }
+       retval = -EINVAL;
+       if (offset >= 0 && offset <= inode->i_sb->s_maxbytes) {
+               if (offset != file->f_pos) {
+                       file->f_pos = offset;
+                       file->f_version = 0;
+               }
+               retval = offset;
+       }
+       return retval;
+}
+
 static loff_t unionfs_dir_llseek(struct file *file, loff_t offset, int origin)
 {
        struct dentry *dentry = file->f_path.dentry;
+       struct inode *inode = dentry->d_inode;
        loff_t err;
 
+       mutex_lock(&inode->i_mutex);
        unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
        unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
 
@@ -150,9 +187,10 @@ static loff_t unionfs_dir_llseek(struct file *file, loff_t offset, int origin)
        if (unlikely(err))
                goto out;
 
-       err = generic_file_llseek(file, offset, origin);
+       err = unionfs_file_llseek_locked(file, offset, origin);
 out:
        unionfs_read_unlock(dentry->d_sb);
+       mutex_unlock(&inode->i_mutex);
        return err;
 }