Skip to content

Commit

Permalink
fs: fix page_mkwrite error cases in core code and btrfs
Browse files Browse the repository at this point in the history
page_mkwrite is called with neither the page lock nor the ptl held.  This
means a page can be concurrently truncated or invalidated out from
underneath it.  Callers are supposed to prevent truncate races themselves,
however previously the only thing they can do in case they hit one is to
raise a SIGBUS.  A sigbus is wrong for the case that the page has been
invalidated or truncated within i_size (eg.  hole punched).  Callers may
also have to perform memory allocations in this path, where again, SIGBUS
would be wrong.

The previous patch ("mm: page_mkwrite change prototype to match fault")
made it possible to properly specify errors.  Convert the generic buffer.c
code and btrfs to return sane error values (in the case of page removed
from pagecache, VM_FAULT_NOPAGE will cause the fault handler to exit
without doing anything, and the fault will be retried properly).

This fixes core code, and converts btrfs as a template/example.  All other
filesystems defining their own page_mkwrite should be fixed in a similar
manner.

Acked-by: Chris Mason <[email protected]>
Signed-off-by: Nick Piggin <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
Nick Piggin authored and torvalds committed Apr 1, 2009
1 parent c2ec175 commit 56a76f8
Show file tree
Hide file tree
Showing 2 changed files with 15 additions and 8 deletions.
11 changes: 7 additions & 4 deletions fs/btrfs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -4307,10 +4307,15 @@ int btrfs_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)
u64 page_end;

ret = btrfs_check_data_free_space(root, inode, PAGE_CACHE_SIZE);
if (ret)
if (ret) {
if (ret == -ENOMEM)
ret = VM_FAULT_OOM;
else /* -ENOSPC, -EIO, etc */
ret = VM_FAULT_SIGBUS;
goto out;
}

ret = -EINVAL;
ret = VM_FAULT_NOPAGE; /* make the VM retry the fault */
again:
lock_page(page);
size = i_size_read(inode);
Expand Down Expand Up @@ -4363,8 +4368,6 @@ int btrfs_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)
out_unlock:
unlock_page(page);
out:
if (ret)
ret = VM_FAULT_SIGBUS;
return ret;
}

Expand Down
12 changes: 8 additions & 4 deletions fs/buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -2320,7 +2320,7 @@ block_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf,
struct inode *inode = vma->vm_file->f_path.dentry->d_inode;
unsigned long end;
loff_t size;
int ret = -EINVAL;
int ret = VM_FAULT_NOPAGE; /* make the VM retry the fault */

lock_page(page);
size = i_size_read(inode);
Expand All @@ -2340,10 +2340,14 @@ block_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf,
if (!ret)
ret = block_commit_write(page, 0, end);

out_unlock:
if (ret)
ret = VM_FAULT_SIGBUS;
if (unlikely(ret)) {
if (ret == -ENOMEM)
ret = VM_FAULT_OOM;
else /* -ENOSPC, -EIO, etc */
ret = VM_FAULT_SIGBUS;
}

out_unlock:
unlock_page(page);
return ret;
}
Expand Down

0 comments on commit 56a76f8

Please sign in to comment.