Btrfs: fix rare chances for data loss when doing a fast fsync
[sfrench/cifs-2.6.git] / fs / btrfs / file.c
index 2be00e873e92a3183e142eebf010de3514475252..b92b7f05c3d5ff3d4caf8f89476152a0980cbbc2 100644 (file)
@@ -531,6 +531,14 @@ int btrfs_dirty_pages(struct inode *inode, struct page **pages,
 
        end_of_last_block = start_pos + num_bytes - 1;
 
+       /*
+        * The pages may have already been dirty, clear out old accounting so
+        * we can set things up properly
+        */
+       clear_extent_bit(&BTRFS_I(inode)->io_tree, start_pos, end_of_last_block,
+                        EXTENT_DIRTY | EXTENT_DELALLOC |
+                        EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG, 0, 0, cached);
+
        if (!btrfs_is_free_space_inode(BTRFS_I(inode))) {
                if (start_pos >= isize &&
                    !(BTRFS_I(inode)->flags & BTRFS_INODE_PREALLOC)) {
@@ -1500,18 +1508,27 @@ lock_and_cleanup_extent_if_need(struct btrfs_inode *inode, struct page **pages,
                }
                if (ordered)
                        btrfs_put_ordered_extent(ordered);
-               clear_extent_bit(&inode->io_tree, start_pos, last_pos,
-                                EXTENT_DIRTY | EXTENT_DELALLOC |
-                                EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG,
-                                0, 0, cached_state);
+
                *lockstart = start_pos;
                *lockend = last_pos;
                ret = 1;
        }
 
+       /*
+        * It's possible the pages are dirty right now, but we don't want
+        * to clean them yet because copy_from_user may catch a page fault
+        * and we might have to fall back to one page at a time.  If that
+        * happens, we'll unlock these pages and we'd have a window where
+        * reclaim could sneak in and drop the once-dirty page on the floor
+        * without writing it.
+        *
+        * We have the pages locked and the extent range locked, so there's
+        * no way someone can start IO on any dirty pages in this range.
+        *
+        * We'll call btrfs_dirty_pages() later on, and that will flip around
+        * delalloc bits and dirty the pages as required.
+        */
        for (i = 0; i < num_pages; i++) {
-               if (clear_page_dirty_for_io(pages[i]))
-                       account_page_redirty(pages[i]);
                set_page_extent_mapped(pages[i]);
                WARN_ON(!PageLocked(pages[i]));
        }
@@ -2061,14 +2078,47 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
                goto out;
 
        inode_lock(inode);
+
+       /*
+        * We take the dio_sem here because the tree log stuff can race with
+        * lockless dio writes and get an extent map logged for an extent we
+        * never waited on.  We need it this high up for lockdep reasons.
+        */
+       down_write(&BTRFS_I(inode)->dio_sem);
+
        atomic_inc(&root->log_batch);
 
+       /*
+        * Before we acquired the inode's lock, someone may have dirtied more
+        * pages in the target range. We need to make sure that writeback for
+        * any such pages does not start while we are logging the inode, because
+        * if it does, any of the following might happen when we are not doing a
+        * full inode sync:
+        *
+        * 1) We log an extent after its writeback finishes but before its
+        *    checksums are added to the csum tree, leading to -EIO errors
+        *    when attempting to read the extent after a log replay.
+        *
+        * 2) We can end up logging an extent before its writeback finishes.
+        *    Therefore after the log replay we will have a file extent item
+        *    pointing to an unwritten extent (and no data checksums as well).
+        *
+        * So trigger writeback for any eventual new dirty pages and then we
+        * wait for all ordered extents to complete below.
+        */
+       ret = start_ordered_ops(inode, start, end);
+       if (ret) {
+               inode_unlock(inode);
+               goto out;
+       }
+
        /*
         * We have to do this here to avoid the priority inversion of waiting on
         * IO of a lower priority task while holding a transaciton open.
         */
        ret = btrfs_wait_ordered_range(inode, start, len);
        if (ret) {
+               up_write(&BTRFS_I(inode)->dio_sem);
                inode_unlock(inode);
                goto out;
        }
@@ -2092,6 +2142,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
                 * checked called fsync.
                 */
                ret = filemap_check_wb_err(inode->i_mapping, file->f_wb_err);
+               up_write(&BTRFS_I(inode)->dio_sem);
                inode_unlock(inode);
                goto out;
        }
@@ -2110,6 +2161,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
        trans = btrfs_start_transaction(root, 0);
        if (IS_ERR(trans)) {
                ret = PTR_ERR(trans);
+               up_write(&BTRFS_I(inode)->dio_sem);
                inode_unlock(inode);
                goto out;
        }
@@ -2131,6 +2183,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
         * file again, but that will end up using the synchronization
         * inside btrfs_sync_log to keep things safe.
         */
+       up_write(&BTRFS_I(inode)->dio_sem);
        inode_unlock(inode);
 
        /*
@@ -2544,7 +2597,7 @@ static int btrfs_punch_hole(struct inode *inode, loff_t offset, loff_t len)
        }
 
        ret = btrfs_block_rsv_migrate(&fs_info->trans_block_rsv, rsv,
-                                     min_size, 0);
+                                     min_size, false);
        BUG_ON(ret);
        trans->block_rsv = rsv;
 
@@ -2594,7 +2647,7 @@ static int btrfs_punch_hole(struct inode *inode, loff_t offset, loff_t len)
                }
 
                ret = btrfs_block_rsv_migrate(&fs_info->trans_block_rsv,
-                                             rsv, min_size, 0);
+                                             rsv, min_size, false);
                BUG_ON(ret);    /* shouldn't happen */
                trans->block_rsv = rsv;