]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - fs/ext3/inode.c
ext3: Add locking to ext3_do_update_inode
[mv-sheeva.git] / fs / ext3 / inode.c
index 5f51fed5c750870b8c575fdf7dd8e877fe986bfb..cd098a7b77fc04b7255fe5586248faa67dbbfb07 100644 (file)
@@ -172,10 +172,21 @@ static int try_to_extend_transaction(handle_t *handle, struct inode *inode)
  * so before we call here everything must be consistently dirtied against
  * this transaction.
  */
-static int ext3_journal_test_restart(handle_t *handle, struct inode *inode)
+static int truncate_restart_transaction(handle_t *handle, struct inode *inode)
 {
+       int ret;
+
        jbd_debug(2, "restarting handle %p\n", handle);
-       return ext3_journal_restart(handle, blocks_for_truncate(inode));
+       /*
+        * Drop truncate_mutex to avoid deadlock with ext3_get_blocks_handle
+        * At this moment, get_block can be called only for blocks inside
+        * i_size since page cache has been already dropped and writes are
+        * blocked by i_mutex. So we can safely drop the truncate_mutex.
+        */
+       mutex_unlock(&EXT3_I(inode)->truncate_mutex);
+       ret = ext3_journal_restart(handle, blocks_for_truncate(inode));
+       mutex_lock(&EXT3_I(inode)->truncate_mutex);
+       return ret;
 }
 
 /*
@@ -788,7 +799,7 @@ err_out:
 int ext3_get_blocks_handle(handle_t *handle, struct inode *inode,
                sector_t iblock, unsigned long maxblocks,
                struct buffer_head *bh_result,
-               int create, int extend_disksize)
+               int create)
 {
        int err = -EIO;
        int offsets[4];
@@ -911,13 +922,6 @@ int ext3_get_blocks_handle(handle_t *handle, struct inode *inode,
        if (!err)
                err = ext3_splice_branch(handle, inode, iblock,
                                        partial, indirect_blks, count);
-       /*
-        * i_disksize growing is protected by truncate_mutex.  Don't forget to
-        * protect it if you're about to implement concurrent
-        * ext3_get_block() -bzzz
-       */
-       if (!err && extend_disksize && inode->i_size > ei->i_disksize)
-               ei->i_disksize = inode->i_size;
        mutex_unlock(&ei->truncate_mutex);
        if (err)
                goto cleanup;
@@ -972,7 +976,7 @@ static int ext3_get_block(struct inode *inode, sector_t iblock,
        }
 
        ret = ext3_get_blocks_handle(handle, inode, iblock,
-                                       max_blocks, bh_result, create, 0);
+                                       max_blocks, bh_result, create);
        if (ret > 0) {
                bh_result->b_size = (ret << inode->i_blkbits);
                ret = 0;
@@ -1005,7 +1009,7 @@ struct buffer_head *ext3_getblk(handle_t *handle, struct inode *inode,
        dummy.b_blocknr = -1000;
        buffer_trace_init(&dummy.b_history);
        err = ext3_get_blocks_handle(handle, inode, block, 1,
-                                       &dummy, create, 1);
+                                       &dummy, create);
        /*
         * ext3_get_blocks_handle() returns number of blocks
         * mapped. 0 in case of a HOLE.
@@ -1193,15 +1197,16 @@ write_begin_failed:
                 * i_size_read because we hold i_mutex.
                 *
                 * Add inode to orphan list in case we crash before truncate
-                * finishes.
+                * finishes. Do this only if ext3_can_truncate() agrees so
+                * that orphan processing code is happy.
                 */
-               if (pos + len > inode->i_size)
+               if (pos + len > inode->i_size && ext3_can_truncate(inode))
                        ext3_orphan_add(handle, inode);
                ext3_journal_stop(handle);
                unlock_page(page);
                page_cache_release(page);
                if (pos + len > inode->i_size)
-                       vmtruncate(inode, inode->i_size);
+                       ext3_truncate(inode);
        }
        if (ret == -ENOSPC && ext3_should_retry_alloc(inode->i_sb, &retries))
                goto retry;
@@ -1287,7 +1292,7 @@ static int ext3_ordered_write_end(struct file *file,
         * There may be allocated blocks outside of i_size because
         * we failed to copy some data. Prepare for truncate.
         */
-       if (pos + len > inode->i_size)
+       if (pos + len > inode->i_size && ext3_can_truncate(inode))
                ext3_orphan_add(handle, inode);
        ret2 = ext3_journal_stop(handle);
        if (!ret)
@@ -1296,7 +1301,7 @@ static int ext3_ordered_write_end(struct file *file,
        page_cache_release(page);
 
        if (pos + len > inode->i_size)
-               vmtruncate(inode, inode->i_size);
+               ext3_truncate(inode);
        return ret ? ret : copied;
 }
 
@@ -1315,14 +1320,14 @@ static int ext3_writeback_write_end(struct file *file,
         * There may be allocated blocks outside of i_size because
         * we failed to copy some data. Prepare for truncate.
         */
-       if (pos + len > inode->i_size)
+       if (pos + len > inode->i_size && ext3_can_truncate(inode))
                ext3_orphan_add(handle, inode);
        ret = ext3_journal_stop(handle);
        unlock_page(page);
        page_cache_release(page);
 
        if (pos + len > inode->i_size)
-               vmtruncate(inode, inode->i_size);
+               ext3_truncate(inode);
        return ret ? ret : copied;
 }
 
@@ -1358,7 +1363,7 @@ static int ext3_journalled_write_end(struct file *file,
         * There may be allocated blocks outside of i_size because
         * we failed to copy some data. Prepare for truncate.
         */
-       if (pos + len > inode->i_size)
+       if (pos + len > inode->i_size && ext3_can_truncate(inode))
                ext3_orphan_add(handle, inode);
        EXT3_I(inode)->i_state |= EXT3_STATE_JDATA;
        if (inode->i_size > EXT3_I(inode)->i_disksize) {
@@ -1375,7 +1380,7 @@ static int ext3_journalled_write_end(struct file *file,
        page_cache_release(page);
 
        if (pos + len > inode->i_size)
-               vmtruncate(inode, inode->i_size);
+               ext3_truncate(inode);
        return ret ? ret : copied;
 }
 
@@ -2078,7 +2083,7 @@ static void ext3_clear_blocks(handle_t *handle, struct inode *inode,
                        ext3_journal_dirty_metadata(handle, bh);
                }
                ext3_mark_inode_dirty(handle, inode);
-               ext3_journal_test_restart(handle, inode);
+               truncate_restart_transaction(handle, inode);
                if (bh) {
                        BUFFER_TRACE(bh, "retaking write access");
                        ext3_journal_get_write_access(handle, bh);
@@ -2288,7 +2293,7 @@ static void ext3_free_branches(handle_t *handle, struct inode *inode,
                                return;
                        if (try_to_extend_transaction(handle, inode)) {
                                ext3_mark_inode_dirty(handle, inode);
-                               ext3_journal_test_restart(handle, inode);
+                               truncate_restart_transaction(handle, inode);
                        }
 
                        ext3_free_blocks(handle, inode, nr, 1);
@@ -2898,6 +2903,10 @@ static int ext3_do_update_inode(handle_t *handle,
        struct buffer_head *bh = iloc->bh;
        int err = 0, rc, block;
 
+again:
+       /* we can't allow multiple procs in here at once, its a bit racey */
+       lock_buffer(bh);
+
        /* For fields not not tracking in the in-memory inode,
         * initialise them to zero for new inodes. */
        if (ei->i_state & EXT3_STATE_NEW)
@@ -2957,16 +2966,20 @@ static int ext3_do_update_inode(handle_t *handle,
                               /* If this is the first large file
                                * created, add a flag to the superblock.
                                */
+                               unlock_buffer(bh);
                                err = ext3_journal_get_write_access(handle,
                                                EXT3_SB(sb)->s_sbh);
                                if (err)
                                        goto out_brelse;
+
                                ext3_update_dynamic_rev(sb);
                                EXT3_SET_RO_COMPAT_FEATURE(sb,
                                        EXT3_FEATURE_RO_COMPAT_LARGE_FILE);
                                handle->h_sync = 1;
                                err = ext3_journal_dirty_metadata(handle,
                                                EXT3_SB(sb)->s_sbh);
+                               /* get our lock and start over */
+                               goto again;
                        }
                }
        }
@@ -2989,6 +3002,7 @@ static int ext3_do_update_inode(handle_t *handle,
                raw_inode->i_extra_isize = cpu_to_le16(ei->i_extra_isize);
 
        BUFFER_TRACE(bh, "call ext3_journal_dirty_metadata");
+       unlock_buffer(bh);
        rc = ext3_journal_dirty_metadata(handle, bh);
        if (!err)
                err = rc;