| /* |
| * Copyright (c) 2013 |
| * Phillip Lougher <phillip@squashfs.org.uk> |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2. See |
| * the COPYING file in the top-level directory. |
| */ |
| |
| #include <linux/fs.h> |
| #include <linux/vfs.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include <linux/pagemap.h> |
| #include <linux/mutex.h> |
| #include <linux/mm_inline.h> |
| |
| #include "squashfs_fs.h" |
| #include "squashfs_fs_sb.h" |
| #include "squashfs_fs_i.h" |
| #include "squashfs.h" |
| #include "page_actor.h" |
| |
| static void release_actor_pages(struct page **page, int pages, int error) |
| { |
| int i; |
| |
| for (i = 0; i < pages; i++) { |
| if (!page[i]) |
| continue; |
| flush_dcache_page(page[i]); |
| if (!error) |
| SetPageUptodate(page[i]); |
| else { |
| SetPageError(page[i]); |
| zero_user_segment(page[i], 0, PAGE_SIZE); |
| } |
| unlock_page(page[i]); |
| put_page(page[i]); |
| } |
| kfree(page); |
| } |
| |
| /* |
| * Create a "page actor" which will kmap and kunmap the |
| * page cache pages appropriately within the decompressor |
| */ |
| static struct squashfs_page_actor *actor_from_page_cache( |
| unsigned int actor_pages, struct page *target_page, |
| struct list_head *rpages, unsigned int *nr_pages, int start_index, |
| struct address_space *mapping) |
| { |
| struct page **page; |
| struct squashfs_page_actor *actor; |
| int i, n; |
| gfp_t gfp = mapping_gfp_constraint(mapping, GFP_KERNEL); |
| |
| page = kmalloc_array(actor_pages, sizeof(void *), GFP_KERNEL); |
| if (!page) |
| return NULL; |
| |
| for (i = 0, n = start_index; i < actor_pages; i++, n++) { |
| if (target_page == NULL && rpages && !list_empty(rpages)) { |
| struct page *cur_page = lru_to_page(rpages); |
| |
| if (cur_page->index < start_index + actor_pages) { |
| list_del(&cur_page->lru); |
| --(*nr_pages); |
| if (add_to_page_cache_lru(cur_page, mapping, |
| cur_page->index, gfp)) |
| put_page(cur_page); |
| else |
| target_page = cur_page; |
| } else |
| rpages = NULL; |
| } |
| |
| if (target_page && target_page->index == n) { |
| page[i] = target_page; |
| target_page = NULL; |
| } else { |
| page[i] = grab_cache_page_nowait(mapping, n); |
| if (page[i] == NULL) |
| continue; |
| } |
| |
| if (PageUptodate(page[i])) { |
| unlock_page(page[i]); |
| put_page(page[i]); |
| page[i] = NULL; |
| } |
| } |
| |
| actor = squashfs_page_actor_init(page, actor_pages, 0, |
| release_actor_pages); |
| if (!actor) { |
| release_actor_pages(page, actor_pages, -ENOMEM); |
| kfree(page); |
| return NULL; |
| } |
| return actor; |
| } |
| |
| int squashfs_readpages_block(struct page *target_page, |
| struct list_head *readahead_pages, |
| unsigned int *nr_pages, |
| struct address_space *mapping, |
| int page_index, u64 block, int bsize) |
| |
| { |
| struct squashfs_page_actor *actor; |
| struct inode *inode = mapping->host; |
| struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info; |
| int start_index, end_index, file_end, actor_pages, res; |
| int mask = (1 << (msblk->block_log - PAGE_SHIFT)) - 1; |
| |
| /* |
| * If readpage() is called on an uncompressed datablock, we can just |
| * read the pages instead of fetching the whole block. |
| * This greatly improves the performance when a process keep doing |
| * random reads because we only fetch the necessary data. |
| * The readahead algorithm will take care of doing speculative reads |
| * if necessary. |
| * We can't read more than 1 block even if readahead provides use more |
| * pages because we don't know yet if the next block is compressed or |
| * not. |
| */ |
| if (bsize && !SQUASHFS_COMPRESSED_BLOCK(bsize)) { |
| u64 block_end = block + msblk->block_size; |
| |
| block += (page_index & mask) * PAGE_SIZE; |
| actor_pages = (block_end - block) / PAGE_SIZE; |
| if (*nr_pages < actor_pages) |
| actor_pages = *nr_pages; |
| start_index = page_index; |
| bsize = min_t(int, bsize, (PAGE_SIZE * actor_pages) |
| | SQUASHFS_COMPRESSED_BIT_BLOCK); |
| } else { |
| file_end = (i_size_read(inode) - 1) >> PAGE_SHIFT; |
| start_index = page_index & ~mask; |
| end_index = start_index | mask; |
| if (end_index > file_end) |
| end_index = file_end; |
| actor_pages = end_index - start_index + 1; |
| } |
| |
| actor = actor_from_page_cache(actor_pages, target_page, |
| readahead_pages, nr_pages, start_index, |
| mapping); |
| if (!actor) |
| return -ENOMEM; |
| |
| res = squashfs_read_data_async(inode->i_sb, block, bsize, NULL, |
| actor); |
| return res < 0 ? res : 0; |
| } |