Fix a direct I/O locking issue revealed by the new mutex code.
Nathan Scott [Wed, 15 Mar 2006 04:14:45 +0000 (15:14 +1100)]
Affects only XFS (i.e. DIO_OWN_LOCKING case) - currently it is
not possible to get i_mutex locking correct when using DIO_OWN
direct I/O locking in a filesystem due to indeterminism in the
possible return code/lock/unlock combinations.  This can cause
a direct read to attempt a double i_mutex unlock inside XFS.

We're now ensuring __blockdev_direct_IO always exits with the
inode i_mutex (still) held for a direct reader.

Tested with the three different locking modes (via direct block
device access, ext3 and XFS) - both reading and writing; cannot
find any regressions resulting from this change, and it clearly
fixes the mutex_unlock warning originally reported here:
http://marc.theaimsgroup.com/?l=linux-kernel&m=114189068126253&w=2

Signed-off-by: Nathan Scott <nathans@sgi.com>
Acked-by: Christoph Hellwig <hch@lst.de>

fs/direct-io.c

index 848044a..27f3e78 100644 (file)
@@ -1155,15 +1155,16 @@ direct_io_worker(int rw, struct kiocb *iocb, struct inode *inode,
  * For writes, i_mutex is not held on entry; it is never taken.
  *
  * DIO_LOCKING (simple locking for regular files)
- * For writes we are called under i_mutex and return with i_mutex held, even though
- * it is internally dropped.
+ * For writes we are called under i_mutex and return with i_mutex held, even
+ * though it is internally dropped.
  * For reads, i_mutex is not held on entry, but it is taken and dropped before
  * returning.
  *
  * DIO_OWN_LOCKING (filesystem provides synchronisation and handling of
  *     uninitialised data, allowing parallel direct readers and writers)
  * For writes we are called without i_mutex, return without it, never touch it.
- * For reads, i_mutex is held on entry and will be released before returning.
+ * For reads we are called under i_mutex and return with i_mutex held, even
+ * though it may be internally dropped.
  *
  * Additional i_alloc_sem locking requirements described inline below.
  */
@@ -1182,7 +1183,8 @@ __blockdev_direct_IO(int rw, struct kiocb *iocb, struct inode *inode,
        ssize_t retval = -EINVAL;
        loff_t end = offset;
        struct dio *dio;
-       int reader_with_isem = (rw == READ && dio_lock_type == DIO_OWN_LOCKING);
+       int release_i_mutex = 0;
+       int acquire_i_mutex = 0;
 
        if (rw & WRITE)
                current->flags |= PF_SYNCWRITE;
@@ -1225,7 +1227,6 @@ __blockdev_direct_IO(int rw, struct kiocb *iocb, struct inode *inode,
         *      writers need to grab i_alloc_sem only (i_mutex is already held)
         * For regular files using DIO_OWN_LOCKING,
         *      neither readers nor writers take any locks here
-        *      (i_mutex is already held and release for writers here)
         */
        dio->lock_type = dio_lock_type;
        if (dio_lock_type != DIO_NO_LOCKING) {
@@ -1236,7 +1237,7 @@ __blockdev_direct_IO(int rw, struct kiocb *iocb, struct inode *inode,
                        mapping = iocb->ki_filp->f_mapping;
                        if (dio_lock_type != DIO_OWN_LOCKING) {
                                mutex_lock(&inode->i_mutex);
-                               reader_with_isem = 1;
+                               release_i_mutex = 1;
                        }
 
                        retval = filemap_write_and_wait_range(mapping, offset,
@@ -1248,7 +1249,7 @@ __blockdev_direct_IO(int rw, struct kiocb *iocb, struct inode *inode,
 
                        if (dio_lock_type == DIO_OWN_LOCKING) {
                                mutex_unlock(&inode->i_mutex);
-                               reader_with_isem = 0;
+                               acquire_i_mutex = 1;
                        }
                }
 
@@ -1269,11 +1270,13 @@ __blockdev_direct_IO(int rw, struct kiocb *iocb, struct inode *inode,
                                nr_segs, blkbits, get_blocks, end_io, dio);
 
        if (rw == READ && dio_lock_type == DIO_LOCKING)
-               reader_with_isem = 0;
+               release_i_mutex = 0;
 
 out:
-       if (reader_with_isem)
+       if (release_i_mutex)
                mutex_unlock(&inode->i_mutex);
+       else if (acquire_i_mutex)
+               mutex_lock(&inode->i_mutex);
        if (rw & WRITE)
                current->flags &= ~PF_SYNCWRITE;
        return retval;