]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - fs/btrfs/inode.c
Merge tag 'vfio-ccw-20200206' of https://git.kernel.org/pub/scm/linux/kernel/git...
[linux.git] / fs / btrfs / inode.c
index 6d2bb58d277aa5e545f6076f91e0829961cd4337..5b3ec93ff911d7af07127fe3a1824024eedcf511 100644 (file)
@@ -2189,6 +2189,7 @@ int btrfs_set_extent_delalloc(struct inode *inode, u64 start, u64 end,
 /* see btrfs_writepage_start_hook for details on why this is required */
 struct btrfs_writepage_fixup {
        struct page *page;
+       struct inode *inode;
        struct btrfs_work work;
 };
 
@@ -2202,27 +2203,71 @@ static void btrfs_writepage_fixup_worker(struct btrfs_work *work)
        struct inode *inode;
        u64 page_start;
        u64 page_end;
-       int ret;
+       int ret = 0;
+       bool free_delalloc_space = true;
 
        fixup = container_of(work, struct btrfs_writepage_fixup, work);
        page = fixup->page;
+       inode = fixup->inode;
+       page_start = page_offset(page);
+       page_end = page_offset(page) + PAGE_SIZE - 1;
+
+       /*
+        * This is similar to page_mkwrite, we need to reserve the space before
+        * we take the page lock.
+        */
+       ret = btrfs_delalloc_reserve_space(inode, &data_reserved, page_start,
+                                          PAGE_SIZE);
 again:
        lock_page(page);
+
+       /*
+        * Before we queued this fixup, we took a reference on the page.
+        * page->mapping may go NULL, but it shouldn't be moved to a different
+        * address space.
+        */
        if (!page->mapping || !PageDirty(page) || !PageChecked(page)) {
-               ClearPageChecked(page);
+               /*
+                * Unfortunately this is a little tricky, either
+                *
+                * 1) We got here and our page had already been dealt with and
+                *    we reserved our space, thus ret == 0, so we need to just
+                *    drop our space reservation and bail.  This can happen the
+                *    first time we come into the fixup worker, or could happen
+                *    while waiting for the ordered extent.
+                * 2) Our page was already dealt with, but we happened to get an
+                *    ENOSPC above from the btrfs_delalloc_reserve_space.  In
+                *    this case we obviously don't have anything to release, but
+                *    because the page was already dealt with we don't want to
+                *    mark the page with an error, so make sure we're resetting
+                *    ret to 0.  This is why we have this check _before_ the ret
+                *    check, because we do not want to have a surprise ENOSPC
+                *    when the page was already properly dealt with.
+                */
+               if (!ret) {
+                       btrfs_delalloc_release_extents(BTRFS_I(inode),
+                                                      PAGE_SIZE);
+                       btrfs_delalloc_release_space(inode, data_reserved,
+                                                    page_start, PAGE_SIZE,
+                                                    true);
+               }
+               ret = 0;
                goto out_page;
        }
 
-       inode = page->mapping->host;
-       page_start = page_offset(page);
-       page_end = page_offset(page) + PAGE_SIZE - 1;
+       /*
+        * We can't mess with the page state unless it is locked, so now that
+        * it is locked bail if we failed to make our space reservation.
+        */
+       if (ret)
+               goto out_page;
 
        lock_extent_bits(&BTRFS_I(inode)->io_tree, page_start, page_end,
                         &cached_state);
 
        /* already ordered? We're done */
        if (PagePrivate2(page))
-               goto out;
+               goto out_reserved;
 
        ordered = btrfs_lookup_ordered_range(BTRFS_I(inode), page_start,
                                        PAGE_SIZE);
@@ -2235,39 +2280,49 @@ static void btrfs_writepage_fixup_worker(struct btrfs_work *work)
                goto again;
        }
 
-       ret = btrfs_delalloc_reserve_space(inode, &data_reserved, page_start,
-                                          PAGE_SIZE);
-       if (ret) {
-               mapping_set_error(page->mapping, ret);
-               end_extent_writepage(page, ret, page_start, page_end);
-               ClearPageChecked(page);
-               goto out;
-        }
-
        ret = btrfs_set_extent_delalloc(inode, page_start, page_end, 0,
                                        &cached_state);
-       if (ret) {
-               mapping_set_error(page->mapping, ret);
-               end_extent_writepage(page, ret, page_start, page_end);
-               ClearPageChecked(page);
+       if (ret)
                goto out_reserved;
-       }
 
-       ClearPageChecked(page);
-       set_page_dirty(page);
+       /*
+        * Everything went as planned, we're now the owner of a dirty page with
+        * delayed allocation bits set and space reserved for our COW
+        * destination.
+        *
+        * The page was dirty when we started, nothing should have cleaned it.
+        */
+       BUG_ON(!PageDirty(page));
+       free_delalloc_space = false;
 out_reserved:
        btrfs_delalloc_release_extents(BTRFS_I(inode), PAGE_SIZE);
-       if (ret)
+       if (free_delalloc_space)
                btrfs_delalloc_release_space(inode, data_reserved, page_start,
                                             PAGE_SIZE, true);
-out:
        unlock_extent_cached(&BTRFS_I(inode)->io_tree, page_start, page_end,
                             &cached_state);
 out_page:
+       if (ret) {
+               /*
+                * We hit ENOSPC or other errors.  Update the mapping and page
+                * to reflect the errors and clean the page.
+                */
+               mapping_set_error(page->mapping, ret);
+               end_extent_writepage(page, ret, page_start, page_end);
+               clear_page_dirty_for_io(page);
+               SetPageError(page);
+       }
+       ClearPageChecked(page);
        unlock_page(page);
        put_page(page);
        kfree(fixup);
        extent_changeset_free(data_reserved);
+       /*
+        * As a precaution, do a delayed iput in case it would be the last iput
+        * that could need flushing space. Recursing back to fixup worker would
+        * deadlock.
+        */
+       btrfs_add_delayed_iput(inode);
 }
 
 /*
@@ -2291,6 +2346,13 @@ int btrfs_writepage_cow_fixup(struct page *page, u64 start, u64 end)
        if (TestClearPagePrivate2(page))
                return 0;
 
+       /*
+        * PageChecked is set below when we create a fixup worker for this page,
+        * don't try to create another one if we're already PageChecked()
+        *
+        * The extent_io writepage code will redirty the page if we send back
+        * EAGAIN.
+        */
        if (PageChecked(page))
                return -EAGAIN;
 
@@ -2298,12 +2360,21 @@ int btrfs_writepage_cow_fixup(struct page *page, u64 start, u64 end)
        if (!fixup)
                return -EAGAIN;
 
+       /*
+        * We are already holding a reference to this inode from
+        * write_cache_pages.  We need to hold it because the space reservation
+        * takes place outside of the page lock, and we can't trust
+        * page->mapping outside of the page lock.
+        */
+       ihold(inode);
        SetPageChecked(page);
        get_page(page);
        btrfs_init_work(&fixup->work, btrfs_writepage_fixup_worker, NULL, NULL);
        fixup->page = page;
+       fixup->inode = inode;
        btrfs_queue_work(fs_info->fixup_workers, &fixup->work);
-       return -EBUSY;
+
+       return -EAGAIN;
 }
 
 static int insert_reserved_file_extent(struct btrfs_trans_handle *trans,