Skip to content

Commit

Permalink
tmpfs: support SEEK_DATA and SEEK_HOLE
Browse files Browse the repository at this point in the history
It's quite easy for tmpfs to scan the radix_tree to support llseek's new
SEEK_DATA and SEEK_HOLE options: so add them while the minutiae are still
on my mind (in particular, the !PageUptodate-ness of pages fallocated but
still unwritten).

But I don't know who actually uses SEEK_DATA or SEEK_HOLE, and whether it
would be of any use to them on tmpfs.  This code adds 92 lines and 752
bytes on x86_64 - is that bloat or worthwhile?

[[email protected]: fix warning with CONFIG_TMPFS=n]
Signed-off-by: Hugh Dickins <[email protected]>
Cc: Christoph Hellwig <[email protected]>
Cc: Josef Bacik <[email protected]>
Cc: Andi Kleen <[email protected]>
Cc: Andreas Dilger <[email protected]>
Cc: Dave Chinner <[email protected]>
Cc: Marco Stornelli <[email protected]>
Cc: Jeff liu <[email protected]>
Cc: Chris Mason <[email protected]>
Cc: Sunil Mushran <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
Hugh Dickins authored and torvalds committed May 29, 2012
1 parent 1aac140 commit 4fb5ef0
Showing 1 changed file with 93 additions and 1 deletion.
94 changes: 93 additions & 1 deletion mm/shmem.c
Original file line number Diff line number Diff line change
Expand Up @@ -1674,6 +1674,98 @@ static ssize_t shmem_file_splice_read(struct file *in, loff_t *ppos,
return error;
}

/*
* llseek SEEK_DATA or SEEK_HOLE through the radix_tree.
*/
static pgoff_t shmem_seek_hole_data(struct address_space *mapping,
pgoff_t index, pgoff_t end, int origin)
{
struct page *page;
struct pagevec pvec;
pgoff_t indices[PAGEVEC_SIZE];
bool done = false;
int i;

pagevec_init(&pvec, 0);
pvec.nr = 1; /* start small: we may be there already */
while (!done) {
pvec.nr = shmem_find_get_pages_and_swap(mapping, index,
pvec.nr, pvec.pages, indices);
if (!pvec.nr) {
if (origin == SEEK_DATA)
index = end;
break;
}
for (i = 0; i < pvec.nr; i++, index++) {
if (index < indices[i]) {
if (origin == SEEK_HOLE) {
done = true;
break;
}
index = indices[i];
}
page = pvec.pages[i];
if (page && !radix_tree_exceptional_entry(page)) {
if (!PageUptodate(page))
page = NULL;
}
if (index >= end ||
(page && origin == SEEK_DATA) ||
(!page && origin == SEEK_HOLE)) {
done = true;
break;
}
}
shmem_deswap_pagevec(&pvec);
pagevec_release(&pvec);
pvec.nr = PAGEVEC_SIZE;
cond_resched();
}
return index;
}

static loff_t shmem_file_llseek(struct file *file, loff_t offset, int origin)
{
struct address_space *mapping;
struct inode *inode;
pgoff_t start, end;
loff_t new_offset;

if (origin != SEEK_DATA && origin != SEEK_HOLE)
return generic_file_llseek_size(file, offset, origin,
MAX_LFS_FILESIZE);
mapping = file->f_mapping;
inode = mapping->host;
mutex_lock(&inode->i_mutex);
/* We're holding i_mutex so we can access i_size directly */

if (offset < 0)
offset = -EINVAL;
else if (offset >= inode->i_size)
offset = -ENXIO;
else {
start = offset >> PAGE_CACHE_SHIFT;
end = (inode->i_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
new_offset = shmem_seek_hole_data(mapping, start, end, origin);
new_offset <<= PAGE_CACHE_SHIFT;
if (new_offset > offset) {
if (new_offset < inode->i_size)
offset = new_offset;
else if (origin == SEEK_DATA)
offset = -ENXIO;
else
offset = inode->i_size;
}
}

if (offset >= 0 && offset != file->f_pos) {
file->f_pos = offset;
file->f_version = 0;
}
mutex_unlock(&inode->i_mutex);
return offset;
}

static long shmem_fallocate(struct file *file, int mode, loff_t offset,
loff_t len)
{
Expand Down Expand Up @@ -2679,7 +2771,7 @@ static const struct address_space_operations shmem_aops = {
static const struct file_operations shmem_file_operations = {
.mmap = shmem_mmap,
#ifdef CONFIG_TMPFS
.llseek = generic_file_llseek,
.llseek = shmem_file_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = shmem_file_aio_read,
Expand Down

0 comments on commit 4fb5ef0

Please sign in to comment.