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);
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;
}