ext4: fix SEEK_HOLE
[firefly-linux-kernel-4.4.55.git] / fs / ext4 / file.c
index 113837e7ba98d5cf866ee365a40a89d7fc781a71..8772bfc3415bb4d520610bc87a7a40f8f3b677ca 100644 (file)
@@ -209,15 +209,18 @@ static int ext4_dax_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
 {
        int result;
        handle_t *handle = NULL;
-       struct super_block *sb = file_inode(vma->vm_file)->i_sb;
+       struct inode *inode = file_inode(vma->vm_file);
+       struct super_block *sb = inode->i_sb;
        bool write = vmf->flags & FAULT_FLAG_WRITE;
 
        if (write) {
                sb_start_pagefault(sb);
                file_update_time(vma->vm_file);
+               down_read(&EXT4_I(inode)->i_mmap_sem);
                handle = ext4_journal_start_sb(sb, EXT4_HT_WRITE_PAGE,
                                                EXT4_DATA_TRANS_BLOCKS(sb));
-       }
+       } else
+               down_read(&EXT4_I(inode)->i_mmap_sem);
 
        if (IS_ERR(handle))
                result = VM_FAULT_SIGBUS;
@@ -228,8 +231,10 @@ static int ext4_dax_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
        if (write) {
                if (!IS_ERR(handle))
                        ext4_journal_stop(handle);
+               up_read(&EXT4_I(inode)->i_mmap_sem);
                sb_end_pagefault(sb);
-       }
+       } else
+               up_read(&EXT4_I(inode)->i_mmap_sem);
 
        return result;
 }
@@ -246,10 +251,12 @@ static int ext4_dax_pmd_fault(struct vm_area_struct *vma, unsigned long addr,
        if (write) {
                sb_start_pagefault(sb);
                file_update_time(vma->vm_file);
+               down_read(&EXT4_I(inode)->i_mmap_sem);
                handle = ext4_journal_start_sb(sb, EXT4_HT_WRITE_PAGE,
                                ext4_chunk_trans_blocks(inode,
                                                        PMD_SIZE / PAGE_SIZE));
-       }
+       } else
+               down_read(&EXT4_I(inode)->i_mmap_sem);
 
        if (IS_ERR(handle))
                result = VM_FAULT_SIGBUS;
@@ -260,30 +267,71 @@ static int ext4_dax_pmd_fault(struct vm_area_struct *vma, unsigned long addr,
        if (write) {
                if (!IS_ERR(handle))
                        ext4_journal_stop(handle);
+               up_read(&EXT4_I(inode)->i_mmap_sem);
                sb_end_pagefault(sb);
-       }
+       } else
+               up_read(&EXT4_I(inode)->i_mmap_sem);
 
        return result;
 }
 
 static int ext4_dax_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)
 {
-       return dax_mkwrite(vma, vmf, ext4_get_block_dax,
-                               ext4_end_io_unwritten);
+       int err;
+       struct inode *inode = file_inode(vma->vm_file);
+
+       sb_start_pagefault(inode->i_sb);
+       file_update_time(vma->vm_file);
+       down_read(&EXT4_I(inode)->i_mmap_sem);
+       err = __dax_mkwrite(vma, vmf, ext4_get_block_dax,
+                           ext4_end_io_unwritten);
+       up_read(&EXT4_I(inode)->i_mmap_sem);
+       sb_end_pagefault(inode->i_sb);
+
+       return err;
+}
+
+/*
+ * Handle write fault for VM_MIXEDMAP mappings. Similarly to ext4_dax_mkwrite()
+ * handler we check for races agaist truncate. Note that since we cycle through
+ * i_mmap_sem, we are sure that also any hole punching that began before we
+ * were called is finished by now and so if it included part of the file we
+ * are working on, our pte will get unmapped and the check for pte_same() in
+ * wp_pfn_shared() fails. Thus fault gets retried and things work out as
+ * desired.
+ */
+static int ext4_dax_pfn_mkwrite(struct vm_area_struct *vma,
+                               struct vm_fault *vmf)
+{
+       struct inode *inode = file_inode(vma->vm_file);
+       struct super_block *sb = inode->i_sb;
+       int ret = VM_FAULT_NOPAGE;
+       loff_t size;
+
+       sb_start_pagefault(sb);
+       file_update_time(vma->vm_file);
+       down_read(&EXT4_I(inode)->i_mmap_sem);
+       size = (i_size_read(inode) + PAGE_SIZE - 1) >> PAGE_SHIFT;
+       if (vmf->pgoff >= size)
+               ret = VM_FAULT_SIGBUS;
+       up_read(&EXT4_I(inode)->i_mmap_sem);
+       sb_end_pagefault(sb);
+
+       return ret;
 }
 
 static const struct vm_operations_struct ext4_dax_vm_ops = {
        .fault          = ext4_dax_fault,
        .pmd_fault      = ext4_dax_pmd_fault,
        .page_mkwrite   = ext4_dax_mkwrite,
-       .pfn_mkwrite    = dax_pfn_mkwrite,
+       .pfn_mkwrite    = ext4_dax_pfn_mkwrite,
 };
 #else
 #define ext4_dax_vm_ops        ext4_file_vm_ops
 #endif
 
 static const struct vm_operations_struct ext4_file_vm_ops = {
-       .fault          = filemap_fault,
+       .fault          = ext4_filemap_fault,
        .map_pages      = filemap_map_pages,
        .page_mkwrite   = ext4_page_mkwrite,
 };
@@ -415,47 +463,27 @@ static int ext4_find_unwritten_pgoff(struct inode *inode,
                num = min_t(pgoff_t, end - index, PAGEVEC_SIZE);
                nr_pages = pagevec_lookup(&pvec, inode->i_mapping, index,
                                          (pgoff_t)num);
-               if (nr_pages == 0) {
-                       if (whence == SEEK_DATA)
-                               break;
-
-                       BUG_ON(whence != SEEK_HOLE);
-                       /*
-                        * If this is the first time to go into the loop and
-                        * offset is not beyond the end offset, it will be a
-                        * hole at this offset
-                        */
-                       if (lastoff == startoff || lastoff < endoff)
-                               found = 1;
-                       break;
-               }
-
-               /*
-                * If this is the first time to go into the loop and
-                * offset is smaller than the first page offset, it will be a
-                * hole at this offset.
-                */
-               if (lastoff == startoff && whence == SEEK_HOLE &&
-                   lastoff < page_offset(pvec.pages[0])) {
-                       found = 1;
+               if (nr_pages == 0)
                        break;
-               }
 
                for (i = 0; i < nr_pages; i++) {
                        struct page *page = pvec.pages[i];
                        struct buffer_head *bh, *head;
 
                        /*
-                        * If the current offset is not beyond the end of given
-                        * range, it will be a hole.
+                        * If current offset is smaller than the page offset,
+                        * there is a hole at this offset.
                         */
-                       if (lastoff < endoff && whence == SEEK_HOLE &&
-                           page->index > end) {
+                       if (whence == SEEK_HOLE && lastoff < endoff &&
+                           lastoff < page_offset(pvec.pages[i])) {
                                found = 1;
                                *offset = lastoff;
                                goto out;
                        }
 
+                       if (page->index > end)
+                               goto out;
+
                        lock_page(page);
 
                        if (unlikely(page->mapping != inode->i_mapping)) {
@@ -495,20 +523,18 @@ static int ext4_find_unwritten_pgoff(struct inode *inode,
                        unlock_page(page);
                }
 
-               /*
-                * The no. of pages is less than our desired, that would be a
-                * hole in there.
-                */
-               if (nr_pages < num && whence == SEEK_HOLE) {
-                       found = 1;
-                       *offset = lastoff;
+               /* The no. of pages is less than our desired, we are done. */
+               if (nr_pages < num)
                        break;
-               }
 
                index = pvec.pages[i - 1]->index + 1;
                pagevec_release(&pvec);
        } while (index <= end);
 
+       if (whence == SEEK_HOLE && lastoff < endoff) {
+               found = 1;
+               *offset = lastoff;
+       }
 out:
        pagevec_release(&pvec);
        return found;