|
@@ -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)
|
|
|
{
|
|
@@ -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,
|