Commit 3c840525 authored by Andrew Morton's avatar Andrew Morton Committed by Christoph Hellwig

[PATCH] tmpfs: shmem_getpage beyond eof

Patch from Hugh Dickins

The last set of tmpfs patches left shmem_getpage with an inadequate
next_index test to guard against races with truncation.  Now remove
that check and settle the issue with checks against i_size within
shmem_swp_alloc, which needs to know whether reading or writing.
parent e7e27221
...@@ -51,6 +51,12 @@ ...@@ -51,6 +51,12 @@
/* Keep swapped page count in private field of indirect struct page */ /* Keep swapped page count in private field of indirect struct page */
#define nr_swapped private #define nr_swapped private
/* Flag end-of-file treatment to shmem_getpage and shmem_swp_alloc */
enum sgp_type {
SGP_READ, /* don't exceed i_size */
SGP_WRITE, /* may exceed i_size */
};
static inline struct page *shmem_dir_alloc(unsigned int gfp_mask) static inline struct page *shmem_dir_alloc(unsigned int gfp_mask)
{ {
/* /*
...@@ -200,8 +206,6 @@ static swp_entry_t *shmem_swp_entry(struct shmem_inode_info *info, unsigned long ...@@ -200,8 +206,6 @@ static swp_entry_t *shmem_swp_entry(struct shmem_inode_info *info, unsigned long
struct page **dir; struct page **dir;
struct page *subdir; struct page *subdir;
if (index >= info->next_index)
return NULL;
if (index < SHMEM_NR_DIRECT) if (index < SHMEM_NR_DIRECT)
return info->i_direct+index; return info->i_direct+index;
if (!info->i_indirect) { if (!info->i_indirect) {
...@@ -274,20 +278,20 @@ static void shmem_swp_set(struct shmem_inode_info *info, swp_entry_t *entry, uns ...@@ -274,20 +278,20 @@ static void shmem_swp_set(struct shmem_inode_info *info, swp_entry_t *entry, uns
* *
* @info: info structure for the inode * @info: info structure for the inode
* @index: index of the page to find * @index: index of the page to find
* @sgp: check and recheck i_size?
*/ */
static swp_entry_t *shmem_swp_alloc(struct shmem_inode_info *info, unsigned long index) static swp_entry_t *shmem_swp_alloc(struct shmem_inode_info *info, unsigned long index, enum sgp_type sgp)
{ {
struct inode *inode = &info->vfs_inode; struct inode *inode = &info->vfs_inode;
struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
struct page *page = NULL; struct page *page = NULL;
swp_entry_t *entry; swp_entry_t *entry;
while (!(entry = shmem_swp_entry(info, index, &page))) { if (sgp != SGP_WRITE &&
if (index >= info->next_index) { ((loff_t) index << PAGE_CACHE_SHIFT) >= inode->i_size)
entry = ERR_PTR(-EFAULT); return ERR_PTR(-EINVAL);
break;
}
while (!(entry = shmem_swp_entry(info, index, &page))) {
/* /*
* Test free_blocks against 1 not 0, since we have 1 data * Test free_blocks against 1 not 0, since we have 1 data
* page (and perhaps indirect index pages) yet to allocate: * page (and perhaps indirect index pages) yet to allocate:
...@@ -314,12 +318,21 @@ static swp_entry_t *shmem_swp_alloc(struct shmem_inode_info *info, unsigned long ...@@ -314,12 +318,21 @@ static swp_entry_t *shmem_swp_alloc(struct shmem_inode_info *info, unsigned long
shmem_free_block(inode); shmem_free_block(inode);
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
} }
if (sgp != SGP_WRITE &&
((loff_t) index << PAGE_CACHE_SHIFT) >= inode->i_size) {
entry = ERR_PTR(-EINVAL);
break;
}
if (info->next_index <= index)
info->next_index = index + 1;
} }
if (page) { if (page) {
/* another task gave its page, or truncated the file */ /* another task gave its page, or truncated the file */
shmem_free_block(inode); shmem_free_block(inode);
shmem_dir_free(page); shmem_dir_free(page);
} }
if (info->next_index <= index && !IS_ERR(entry))
info->next_index = index + 1;
return entry; return entry;
} }
...@@ -672,6 +685,7 @@ static int shmem_writepage(struct page *page) ...@@ -672,6 +685,7 @@ static int shmem_writepage(struct page *page)
spin_lock(&info->lock); spin_lock(&info->lock);
shmem_recalc_inode(inode); shmem_recalc_inode(inode);
BUG_ON(index >= info->next_index);
entry = shmem_swp_entry(info, index, NULL); entry = shmem_swp_entry(info, index, NULL);
BUG_ON(!entry); BUG_ON(!entry);
BUG_ON(entry->val); BUG_ON(entry->val);
...@@ -710,7 +724,7 @@ static int shmem_vm_writeback(struct page *page, struct writeback_control *wbc) ...@@ -710,7 +724,7 @@ static int shmem_vm_writeback(struct page *page, struct writeback_control *wbc)
* vm. If we swap it in we mark it dirty since we also free the swap * vm. If we swap it in we mark it dirty since we also free the swap
* entry since a page cannot live in both the swap and page cache * entry since a page cannot live in both the swap and page cache
*/ */
static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **pagep) static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **pagep, enum sgp_type sgp)
{ {
struct address_space *mapping = inode->i_mapping; struct address_space *mapping = inode->i_mapping;
struct shmem_inode_info *info = SHMEM_I(inode); struct shmem_inode_info *info = SHMEM_I(inode);
...@@ -722,18 +736,6 @@ static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **p ...@@ -722,18 +736,6 @@ static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **p
if (idx >= SHMEM_MAX_INDEX) if (idx >= SHMEM_MAX_INDEX)
return -EFBIG; return -EFBIG;
/*
* When writing, i_sem is held against truncation and other
* writing, so next_index will remain as set here; but when
* reading, idx must always be checked against next_index
* after sleeping, lest truncation occurred meanwhile.
*/
spin_lock(&info->lock);
if (info->next_index <= idx)
info->next_index = idx + 1;
spin_unlock(&info->lock);
repeat: repeat:
page = find_lock_page(mapping, idx); page = find_lock_page(mapping, idx);
if (page) { if (page) {
...@@ -744,7 +746,7 @@ static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **p ...@@ -744,7 +746,7 @@ static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **p
spin_lock(&info->lock); spin_lock(&info->lock);
shmem_recalc_inode(inode); shmem_recalc_inode(inode);
entry = shmem_swp_alloc(info, idx); entry = shmem_swp_alloc(info, idx, sgp);
if (IS_ERR(entry)) { if (IS_ERR(entry)) {
spin_unlock(&info->lock); spin_unlock(&info->lock);
return PTR_ERR(entry); return PTR_ERR(entry);
...@@ -761,7 +763,7 @@ static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **p ...@@ -761,7 +763,7 @@ static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **p
page = read_swap_cache_async(swap); page = read_swap_cache_async(swap);
if (!page) { if (!page) {
spin_lock(&info->lock); spin_lock(&info->lock);
entry = shmem_swp_alloc(info, idx); entry = shmem_swp_alloc(info, idx, sgp);
if (IS_ERR(entry)) if (IS_ERR(entry))
error = PTR_ERR(entry); error = PTR_ERR(entry);
else { else {
...@@ -830,7 +832,7 @@ static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **p ...@@ -830,7 +832,7 @@ static int shmem_getpage(struct inode *inode, unsigned long idx, struct page **p
} }
spin_lock(&info->lock); spin_lock(&info->lock);
entry = shmem_swp_alloc(info, idx); entry = shmem_swp_alloc(info, idx, sgp);
if (IS_ERR(entry)) if (IS_ERR(entry))
error = PTR_ERR(entry); error = PTR_ERR(entry);
else { else {
...@@ -881,7 +883,7 @@ static struct page *shmem_holdpage(struct inode *inode, unsigned long idx) ...@@ -881,7 +883,7 @@ static struct page *shmem_holdpage(struct inode *inode, unsigned long idx)
} }
spin_unlock(&info->lock); spin_unlock(&info->lock);
if (swap.val) { if (swap.val) {
(void) shmem_getpage(inode, idx, &page); (void) shmem_getpage(inode, idx, &page, SGP_READ);
} }
return page; return page;
} }
...@@ -897,10 +899,7 @@ struct page *shmem_nopage(struct vm_area_struct *vma, unsigned long address, int ...@@ -897,10 +899,7 @@ struct page *shmem_nopage(struct vm_area_struct *vma, unsigned long address, int
idx += vma->vm_pgoff; idx += vma->vm_pgoff;
idx >>= PAGE_CACHE_SHIFT - PAGE_SHIFT; idx >>= PAGE_CACHE_SHIFT - PAGE_SHIFT;
if (((loff_t) idx << PAGE_CACHE_SHIFT) >= inode->i_size) error = shmem_getpage(inode, idx, &page, SGP_READ);
return NOPAGE_SIGBUS;
error = shmem_getpage(inode, idx, &page);
if (error) if (error)
return (error == -ENOMEM)? NOPAGE_OOM: NOPAGE_SIGBUS; return (error == -ENOMEM)? NOPAGE_OOM: NOPAGE_SIGBUS;
...@@ -1105,7 +1104,7 @@ shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos) ...@@ -1105,7 +1104,7 @@ shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
* what would it guard against? - so no deadlock here. * what would it guard against? - so no deadlock here.
*/ */
status = shmem_getpage(inode, index, &page); status = shmem_getpage(inode, index, &page, SGP_WRITE);
if (status) if (status)
break; break;
...@@ -1170,9 +1169,9 @@ static void do_shmem_file_read(struct file *filp, loff_t *ppos, read_descriptor_ ...@@ -1170,9 +1169,9 @@ static void do_shmem_file_read(struct file *filp, loff_t *ppos, read_descriptor_
break; break;
} }
desc->error = shmem_getpage(inode, index, &page); desc->error = shmem_getpage(inode, index, &page, SGP_READ);
if (desc->error) { if (desc->error) {
if (desc->error == -EFAULT) if (desc->error == -EINVAL)
desc->error = 0; desc->error = 0;
break; break;
} }
...@@ -1419,7 +1418,7 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s ...@@ -1419,7 +1418,7 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
iput(inode); iput(inode);
return -ENOMEM; return -ENOMEM;
} }
error = shmem_getpage(inode, 0, &page); error = shmem_getpage(inode, 0, &page, SGP_WRITE);
if (error) { if (error) {
vm_unacct_memory(VM_ACCT(1)); vm_unacct_memory(VM_ACCT(1));
iput(inode); iput(inode);
...@@ -1455,7 +1454,7 @@ static int shmem_follow_link_inline(struct dentry *dentry, struct nameidata *nd) ...@@ -1455,7 +1454,7 @@ static int shmem_follow_link_inline(struct dentry *dentry, struct nameidata *nd)
static int shmem_readlink(struct dentry *dentry, char *buffer, int buflen) static int shmem_readlink(struct dentry *dentry, char *buffer, int buflen)
{ {
struct page *page; struct page *page;
int res = shmem_getpage(dentry->d_inode, 0, &page); int res = shmem_getpage(dentry->d_inode, 0, &page, SGP_READ);
if (res) if (res)
return res; return res;
res = vfs_readlink(dentry, buffer, buflen, kmap(page)); res = vfs_readlink(dentry, buffer, buflen, kmap(page));
...@@ -1467,7 +1466,7 @@ static int shmem_readlink(struct dentry *dentry, char *buffer, int buflen) ...@@ -1467,7 +1466,7 @@ static int shmem_readlink(struct dentry *dentry, char *buffer, int buflen)
static int shmem_follow_link(struct dentry *dentry, struct nameidata *nd) static int shmem_follow_link(struct dentry *dentry, struct nameidata *nd)
{ {
struct page *page; struct page *page;
int res = shmem_getpage(dentry->d_inode, 0, &page); int res = shmem_getpage(dentry->d_inode, 0, &page, SGP_READ);
if (res) if (res)
return res; return res;
res = vfs_follow_link(nd, kmap(page)); res = vfs_follow_link(nd, kmap(page));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment