Skip to content

Commit 83e4fa9

Browse files
Hugh Dickinstorvalds
authored andcommitted
tmpfs: support fallocate FALLOC_FL_PUNCH_HOLE
tmpfs has supported hole-punching since 2.6.16, via madvise(,,MADV_REMOVE). But nowadays fallocate(,FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE,,) is the agreed way to punch holes. So add shmem_fallocate() to support that, and tweak shmem_truncate_range() to support partial pages at both the beginning and end of range (never needed for madvise, which demands rounded addr and rounds up length). Based-on-patch-by: Cong Wang <amwang@redhat.com> Signed-off-by: Hugh Dickins <hughd@google.com> Cc: Christoph Hellwig <hch@infradead.org> Cc: Cong Wang <amwang@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent ec9516f commit 83e4fa9

File tree

1 file changed

+57
-11
lines changed

1 file changed

+57
-11
lines changed

mm/shmem.c

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ static struct vfsmount *shm_mnt;
5353
#include <linux/blkdev.h>
5454
#include <linux/pagevec.h>
5555
#include <linux/percpu_counter.h>
56+
#include <linux/falloc.h>
5657
#include <linux/splice.h>
5758
#include <linux/security.h>
5859
#include <linux/swapops.h>
@@ -432,21 +433,23 @@ void shmem_truncate_range(struct inode *inode, loff_t lstart, loff_t lend)
432433
struct address_space *mapping = inode->i_mapping;
433434
struct shmem_inode_info *info = SHMEM_I(inode);
434435
pgoff_t start = (lstart + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
435-
unsigned partial = lstart & (PAGE_CACHE_SIZE - 1);
436-
pgoff_t end = (lend >> PAGE_CACHE_SHIFT);
436+
pgoff_t end = (lend + 1) >> PAGE_CACHE_SHIFT;
437+
unsigned int partial_start = lstart & (PAGE_CACHE_SIZE - 1);
438+
unsigned int partial_end = (lend + 1) & (PAGE_CACHE_SIZE - 1);
437439
struct pagevec pvec;
438440
pgoff_t indices[PAGEVEC_SIZE];
439441
long nr_swaps_freed = 0;
440442
pgoff_t index;
441443
int i;
442444

443-
BUG_ON((lend & (PAGE_CACHE_SIZE - 1)) != (PAGE_CACHE_SIZE - 1));
445+
if (lend == -1)
446+
end = -1; /* unsigned, so actually very big */
444447

445448
pagevec_init(&pvec, 0);
446449
index = start;
447-
while (index <= end) {
450+
while (index < end) {
448451
pvec.nr = shmem_find_get_pages_and_swap(mapping, index,
449-
min(end - index, (pgoff_t)PAGEVEC_SIZE - 1) + 1,
452+
min(end - index, (pgoff_t)PAGEVEC_SIZE),
450453
pvec.pages, indices);
451454
if (!pvec.nr)
452455
break;
@@ -455,7 +458,7 @@ void shmem_truncate_range(struct inode *inode, loff_t lstart, loff_t lend)
455458
struct page *page = pvec.pages[i];
456459

457460
index = indices[i];
458-
if (index > end)
461+
if (index >= end)
459462
break;
460463

461464
if (radix_tree_exceptional_entry(page)) {
@@ -479,30 +482,47 @@ void shmem_truncate_range(struct inode *inode, loff_t lstart, loff_t lend)
479482
index++;
480483
}
481484

482-
if (partial) {
485+
if (partial_start) {
483486
struct page *page = NULL;
484487
shmem_getpage(inode, start - 1, &page, SGP_READ, NULL);
485488
if (page) {
486-
zero_user_segment(page, partial, PAGE_CACHE_SIZE);
489+
unsigned int top = PAGE_CACHE_SIZE;
490+
if (start > end) {
491+
top = partial_end;
492+
partial_end = 0;
493+
}
494+
zero_user_segment(page, partial_start, top);
495+
set_page_dirty(page);
496+
unlock_page(page);
497+
page_cache_release(page);
498+
}
499+
}
500+
if (partial_end) {
501+
struct page *page = NULL;
502+
shmem_getpage(inode, end, &page, SGP_READ, NULL);
503+
if (page) {
504+
zero_user_segment(page, 0, partial_end);
487505
set_page_dirty(page);
488506
unlock_page(page);
489507
page_cache_release(page);
490508
}
491509
}
510+
if (start >= end)
511+
return;
492512

493513
index = start;
494514
for ( ; ; ) {
495515
cond_resched();
496516
pvec.nr = shmem_find_get_pages_and_swap(mapping, index,
497-
min(end - index, (pgoff_t)PAGEVEC_SIZE - 1) + 1,
517+
min(end - index, (pgoff_t)PAGEVEC_SIZE),
498518
pvec.pages, indices);
499519
if (!pvec.nr) {
500520
if (index == start)
501521
break;
502522
index = start;
503523
continue;
504524
}
505-
if (index == start && indices[0] > end) {
525+
if (index == start && indices[0] >= end) {
506526
shmem_deswap_pagevec(&pvec);
507527
pagevec_release(&pvec);
508528
break;
@@ -512,7 +532,7 @@ void shmem_truncate_range(struct inode *inode, loff_t lstart, loff_t lend)
512532
struct page *page = pvec.pages[i];
513533

514534
index = indices[i];
515-
if (index > end)
535+
if (index >= end)
516536
break;
517537

518538
if (radix_tree_exceptional_entry(page)) {
@@ -1578,6 +1598,31 @@ static ssize_t shmem_file_splice_read(struct file *in, loff_t *ppos,
15781598
return error;
15791599
}
15801600

1601+
static long shmem_fallocate(struct file *file, int mode, loff_t offset,
1602+
loff_t len)
1603+
{
1604+
struct inode *inode = file->f_path.dentry->d_inode;
1605+
int error = -EOPNOTSUPP;
1606+
1607+
mutex_lock(&inode->i_mutex);
1608+
1609+
if (mode & FALLOC_FL_PUNCH_HOLE) {
1610+
struct address_space *mapping = file->f_mapping;
1611+
loff_t unmap_start = round_up(offset, PAGE_SIZE);
1612+
loff_t unmap_end = round_down(offset + len, PAGE_SIZE) - 1;
1613+
1614+
if ((u64)unmap_end > (u64)unmap_start)
1615+
unmap_mapping_range(mapping, unmap_start,
1616+
1 + unmap_end - unmap_start, 0);
1617+
shmem_truncate_range(inode, offset, offset + len - 1);
1618+
/* No need to unmap again: hole-punching leaves COWed pages */
1619+
error = 0;
1620+
}
1621+
1622+
mutex_unlock(&inode->i_mutex);
1623+
return error;
1624+
}
1625+
15811626
static int shmem_statfs(struct dentry *dentry, struct kstatfs *buf)
15821627
{
15831628
struct shmem_sb_info *sbinfo = SHMEM_SB(dentry->d_sb);
@@ -2490,6 +2535,7 @@ static const struct file_operations shmem_file_operations = {
24902535
.fsync = noop_fsync,
24912536
.splice_read = shmem_file_splice_read,
24922537
.splice_write = generic_file_splice_write,
2538+
.fallocate = shmem_fallocate,
24932539
#endif
24942540
};
24952541

0 commit comments

Comments
 (0)