diff mbox series

[v3,3/3] squashfs: implement readahead

Message ID 20220523065909.883444-4-hsinyi@chromium.org (mailing list archive)
State New
Headers show
Series Implement readahead for squashfs | expand

Commit Message

Hsin-Yi Wang May 23, 2022, 6:59 a.m. UTC
Implement readahead callback for squashfs. It will read datablocks
which cover pages in readahead request. For a few cases it will
not mark page as uptodate, including:
- file end is 0.
- zero filled blocks.
- current batch of pages isn't in the same datablock or not enough in a
  datablock.
- decompressor error.
Otherwise pages will be marked as uptodate. The unhandled pages will be
updated by readpage later.

Suggested-by: Matthew Wilcox <willy@infradead.org>
Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
Reported-by: Matthew Wilcox <willy@infradead.org>
Reported-by: Phillip Lougher <phillip@squashfs.org.uk>
Reported-by: Xiongwei Song <Xiongwei.Song@windriver.com>
---
v2->v3: Add checks on
- decompressed block size.
- fill zeros if the last page is not a full page.

v2: https://lore.kernel.org/lkml/20220517082650.2005840-4-hsinyi@chromium.org/
v1: https://lore.kernel.org/lkml/20220516105100.1412740-3-hsinyi@chromium.org/
---
 fs/squashfs/file.c | 91 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 90 insertions(+), 1 deletion(-)

Comments

Hsin-Yi Wang May 31, 2022, 9:51 a.m. UTC | #1
On Mon, May 23, 2022 at 3:00 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
>
> Implement readahead callback for squashfs. It will read datablocks
> which cover pages in readahead request. For a few cases it will
> not mark page as uptodate, including:
> - file end is 0.
> - zero filled blocks.
> - current batch of pages isn't in the same datablock or not enough in a
>   datablock.
> - decompressor error.
> Otherwise pages will be marked as uptodate. The unhandled pages will be
> updated by readpage later.
>
> Suggested-by: Matthew Wilcox <willy@infradead.org>
> Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
> Reported-by: Matthew Wilcox <willy@infradead.org>
> Reported-by: Phillip Lougher <phillip@squashfs.org.uk>
> Reported-by: Xiongwei Song <Xiongwei.Song@windriver.com>
> ---

Kindly ping on the thread. Conversations on v2:
https://patchwork.kernel.org/project/linux-mm/patch/20220517082650.2005840-4-hsinyi@chromium.org/#24869037
This version mainly addressed the error handling.

Thanks

> v2->v3: Add checks on
> - decompressed block size.
> - fill zeros if the last page is not a full page.
>
> v2: https://lore.kernel.org/lkml/20220517082650.2005840-4-hsinyi@chromium.org/
> v1: https://lore.kernel.org/lkml/20220516105100.1412740-3-hsinyi@chromium.org/
> ---
>  fs/squashfs/file.c | 91 +++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 90 insertions(+), 1 deletion(-)
>
> diff --git a/fs/squashfs/file.c b/fs/squashfs/file.c
> index a8e495d8eb86..c311fc685fe4 100644
> --- a/fs/squashfs/file.c
> +++ b/fs/squashfs/file.c
> @@ -39,6 +39,7 @@
>  #include "squashfs_fs_sb.h"
>  #include "squashfs_fs_i.h"
>  #include "squashfs.h"
> +#include "page_actor.h"
>
>  /*
>   * Locate cache slot in range [offset, index] for specified inode.  If
> @@ -495,7 +496,95 @@ static int squashfs_read_folio(struct file *file, struct folio *folio)
>         return 0;
>  }
>
> +static void squashfs_readahead(struct readahead_control *ractl)
> +{
> +       struct inode *inode = ractl->mapping->host;
> +       struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
> +       size_t mask = (1UL << msblk->block_log) - 1;
> +       size_t shift = msblk->block_log - PAGE_SHIFT;
> +       loff_t start = readahead_pos(ractl) &~ mask;
> +       size_t len = readahead_length(ractl) + readahead_pos(ractl) - start;
> +       struct squashfs_page_actor *actor;
> +       unsigned int nr_pages = 0;
> +       struct page **pages;
> +       u64 block = 0;
> +       int bsize, res, i, index, bytes, expected;
> +       int file_end = i_size_read(inode) >> msblk->block_log;
> +       unsigned int max_pages = 1UL << shift;
> +       void *pageaddr;
> +
> +       readahead_expand(ractl, start, (len | mask) + 1);
> +
> +       if (file_end == 0)
> +               return;
> +
> +       pages = kmalloc_array(max_pages, sizeof(void *), GFP_KERNEL);
> +       if (!pages)
> +               return;
> +
> +       actor = squashfs_page_actor_init_special(pages, max_pages, 0);
> +       if (!actor)
> +               goto out;
> +
> +       for (;;) {
> +               nr_pages = __readahead_batch(ractl, pages, max_pages);
> +               if (!nr_pages)
> +                       break;
> +
> +               if (readahead_pos(ractl) >= i_size_read(inode) ||
> +                   nr_pages < max_pages)
> +                       goto skip_pages;
> +
> +               index = pages[0]->index >> shift;
> +               if ((pages[nr_pages - 1]->index >> shift) != index)
> +                       goto skip_pages;
> +
> +               expected = index == file_end ?
> +                          (i_size_read(inode) & (msblk->block_size - 1)) :
> +                           msblk->block_size;
> +
> +               bsize = read_blocklist(inode, index, &block);
> +               if (bsize == 0)
> +                       goto skip_pages;
> +
> +               res = squashfs_read_data(inode->i_sb, block, bsize, NULL,
> +                                        actor);
> +
> +               if (res == expected) {
> +                       /* Last page may have trailing bytes not filled */
> +                       bytes = res % PAGE_SIZE;
> +                       if (bytes) {
> +                               pageaddr = kmap_atomic(pages[nr_pages - 1]);
> +                               memset(pageaddr + bytes, 0, PAGE_SIZE - bytes);
> +                               kunmap_atomic(pageaddr);
> +                       }
> +
> +                       for (i = 0; i < nr_pages; i++)
> +                               SetPageUptodate(pages[i]);
> +               }
> +
> +               for (i = 0; i < nr_pages; i++) {
> +                       unlock_page(pages[i]);
> +                       put_page(pages[i]);
> +               }
> +       }
> +
> +       kfree(actor);
> +       kfree(pages);
> +       return;
> +
> +skip_pages:
> +       for (i = 0; i < nr_pages; i++) {
> +               unlock_page(pages[i]);
> +               put_page(pages[i]);
> +       }
> +
> +       kfree(actor);
> +out:
> +       kfree(pages);
> +}
>
>  const struct address_space_operations squashfs_aops = {
> -       .read_folio = squashfs_read_folio
> +       .read_folio = squashfs_read_folio,
> +       .readahead = squashfs_readahead
>  };
> --
> 2.36.1.124.g0e6072fb45-goog
>
Andrew Morton May 31, 2022, 8:47 p.m. UTC | #2
On Mon, 23 May 2022 14:59:13 +0800 Hsin-Yi Wang <hsinyi@chromium.org> wrote:

> Implement readahead callback for squashfs. It will read datablocks
> which cover pages in readahead request. For a few cases it will
> not mark page as uptodate, including:
> - file end is 0.
> - zero filled blocks.
> - current batch of pages isn't in the same datablock or not enough in a
>   datablock.
> - decompressor error.
> Otherwise pages will be marked as uptodate. The unhandled pages will be
> updated by readpage later.
> 
> ...
>

The choice of types seems somewhat confused.

> @@ -495,7 +496,95 @@ static int squashfs_read_folio(struct file *file, struct folio *folio)
>  	return 0;
>  }
>  
> +static void squashfs_readahead(struct readahead_control *ractl)
> +{
> +	struct inode *inode = ractl->mapping->host;
> +	struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
> +	size_t mask = (1UL << msblk->block_log) - 1;
> +	size_t shift = msblk->block_log - PAGE_SHIFT;

block_log is unsigned short.  Why size_t?

> +	loff_t start = readahead_pos(ractl) &~ mask;
> +	size_t len = readahead_length(ractl) + readahead_pos(ractl) - start;
> +	struct squashfs_page_actor *actor;
> +	unsigned int nr_pages = 0;

OK.

> +	struct page **pages;
> +	u64 block = 0;
> +	int bsize, res, i, index, bytes, expected;

`res' could be local to the inner loop.

`i' is used in situations where an unsigned type would be more
appropriate.  If it is made unsigned then `i' is no longer a suitable
identifier.  Doesn't matter much.

`index' is from page.index, which is pgoff_t.

`bytes' could be local to the innermost loop.

`expected' is inappropriately a signed type and could be local to the
inner loop.

> +	int file_end = i_size_read(inode) >> msblk->block_log;
> +	unsigned int max_pages = 1UL << shift;
> +	void *pageaddr;
> +
> +	readahead_expand(ractl, start, (len | mask) + 1);
> +
> +	if (file_end == 0)
> +		return;
> +
> +	pages = kmalloc_array(max_pages, sizeof(void *), GFP_KERNEL);
> +	if (!pages)
> +		return;
> +
> +	actor = squashfs_page_actor_init_special(pages, max_pages, 0);
> +	if (!actor)
> +		goto out;
> +
> +	for (;;) {
> +		nr_pages = __readahead_batch(ractl, pages, max_pages);
> +		if (!nr_pages)
> +			break;
> +
> +		if (readahead_pos(ractl) >= i_size_read(inode) ||
> +		    nr_pages < max_pages)
> +			goto skip_pages;
> +
> +		index = pages[0]->index >> shift;
> +		if ((pages[nr_pages - 1]->index >> shift) != index)
> +			goto skip_pages;
> +
> +		expected = index == file_end ?
> +			   (i_size_read(inode) & (msblk->block_size - 1)) :
> +			    msblk->block_size;
> +
> +		bsize = read_blocklist(inode, index, &block);
> +		if (bsize == 0)
> +			goto skip_pages;
> +
> +		res = squashfs_read_data(inode->i_sb, block, bsize, NULL,
> +					 actor);
> +
> +		if (res == expected) {
> +			/* Last page may have trailing bytes not filled */
> +			bytes = res % PAGE_SIZE;
> +			if (bytes) {
> +				pageaddr = kmap_atomic(pages[nr_pages - 1]);
> +				memset(pageaddr + bytes, 0, PAGE_SIZE - bytes);
> +				kunmap_atomic(pageaddr);
> +			}
> +
> +			for (i = 0; i < nr_pages; i++)
> +				SetPageUptodate(pages[i]);
> +		}

res == -EIO is unhandled?

> +		for (i = 0; i < nr_pages; i++) {
> +			unlock_page(pages[i]);
> +			put_page(pages[i]);
> +		}
> +	}
> +
> +	kfree(actor);
> +	kfree(pages);
> +	return;
> +
> +skip_pages:
> +	for (i = 0; i < nr_pages; i++) {
> +		unlock_page(pages[i]);
> +		put_page(pages[i]);
> +	}
> +
> +	kfree(actor);
> +out:
> +	kfree(pages);
> +}
>  
>  const struct address_space_operations squashfs_aops = {
> -	.read_folio = squashfs_read_folio
> +	.read_folio = squashfs_read_folio,
> +	.readahead = squashfs_readahead
>  };
Andrew Morton May 31, 2022, 8:48 p.m. UTC | #3
On Tue, 31 May 2022 17:51:11 +0800 Hsin-Yi Wang <hsinyi@chromium.org> wrote:

> On Mon, May 23, 2022 at 3:00 PM Hsin-Yi Wang <hsinyi@chromium.org> wrote:
> >
> > Implement readahead callback for squashfs. It will read datablocks
> > which cover pages in readahead request. For a few cases it will
> > not mark page as uptodate, including:
> > - file end is 0.
> > - zero filled blocks.
> > - current batch of pages isn't in the same datablock or not enough in a
> >   datablock.
> > - decompressor error.
> > Otherwise pages will be marked as uptodate. The unhandled pages will be
> > updated by readpage later.
> >
> > Suggested-by: Matthew Wilcox <willy@infradead.org>
> > Signed-off-by: Hsin-Yi Wang <hsinyi@chromium.org>
> > Reported-by: Matthew Wilcox <willy@infradead.org>
> > Reported-by: Phillip Lougher <phillip@squashfs.org.uk>
> > Reported-by: Xiongwei Song <Xiongwei.Song@windriver.com>
> > ---
> 
> Kindly ping on the thread. Conversations on v2:
> https://patchwork.kernel.org/project/linux-mm/patch/20220517082650.2005840-4-hsinyi@chromium.org/#24869037
> This version mainly addressed the error handling.

Yes, some reviewer input would be helpful please.
Matthew Wilcox May 31, 2022, 8:56 p.m. UTC | #4
On Tue, May 31, 2022 at 01:47:40PM -0700, Andrew Morton wrote:
> > +	for (;;) {
> > +		nr_pages = __readahead_batch(ractl, pages, max_pages);
> > +		if (!nr_pages)
> > +			break;
> > +
> > +		if (readahead_pos(ractl) >= i_size_read(inode) ||
> > +		    nr_pages < max_pages)
> > +			goto skip_pages;
> > +
> > +		index = pages[0]->index >> shift;
> > +		if ((pages[nr_pages - 1]->index >> shift) != index)
> > +			goto skip_pages;
> > +
> > +		expected = index == file_end ?
> > +			   (i_size_read(inode) & (msblk->block_size - 1)) :
> > +			    msblk->block_size;
> > +
> > +		bsize = read_blocklist(inode, index, &block);
> > +		if (bsize == 0)
> > +			goto skip_pages;
> > +
> > +		res = squashfs_read_data(inode->i_sb, block, bsize, NULL,
> > +					 actor);
> > +
> > +		if (res == expected) {
> > +			/* Last page may have trailing bytes not filled */
> > +			bytes = res % PAGE_SIZE;
> > +			if (bytes) {
> > +				pageaddr = kmap_atomic(pages[nr_pages - 1]);
> > +				memset(pageaddr + bytes, 0, PAGE_SIZE - bytes);
> > +				kunmap_atomic(pageaddr);
> > +			}
> > +
> > +			for (i = 0; i < nr_pages; i++)
> > +				SetPageUptodate(pages[i]);
> > +		}
> 
> res == -EIO is unhandled?

No it isn't ... this is readahead, which means there's nobody to care
about the error.  The pages are left !Uptodate, which means that they'll
be retried with a call to ->read_folio later.  At that point, somebody
actually wants the data in those pages, and they'll see the error.
Phillip Lougher June 1, 2022, 1:08 a.m. UTC | #5
On 31/05/2022 21:47, Andrew Morton wrote:
> On Mon, 23 May 2022 14:59:13 +0800 Hsin-Yi Wang <hsinyi@chromium.org> wrote:
> 
>> Implement readahead callback for squashfs. It will read datablocks
>> which cover pages in readahead request. For a few cases it will
>> not mark page as uptodate, including:
>> - file end is 0.
>> - zero filled blocks.
>> - current batch of pages isn't in the same datablock or not enough in a
>>    datablock.
>> - decompressor error.
>> Otherwise pages will be marked as uptodate. The unhandled pages will be
>> updated by readpage later.
>>
>> ...
>>
> 
> The choice of types seems somewhat confused.
> 
>> @@ -495,7 +496,95 @@ static int squashfs_read_folio(struct file *file, struct folio *folio)
>>   	return 0;
>>   }
>>   
>> +static void squashfs_readahead(struct readahead_control *ractl)
>> +{
>> +	struct inode *inode = ractl->mapping->host;
>> +	struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
>> +	size_t mask = (1UL << msblk->block_log) - 1;
>> +	size_t shift = msblk->block_log - PAGE_SHIFT;
> 
> block_log is unsigned short.  Why size_t?
> 
>> +	loff_t start = readahead_pos(ractl) &~ mask;
>> +	size_t len = readahead_length(ractl) + readahead_pos(ractl) - start;
>> +	struct squashfs_page_actor *actor;
>> +	unsigned int nr_pages = 0;
> 
> OK.
> 
>> +	struct page **pages;
>> +	u64 block = 0;
>> +	int bsize, res, i, index, bytes, expected;
> 
> `res' could be local to the inner loop.
> 
> `i' is used in situations where an unsigned type would be more
> appropriate.  If it is made unsigned then `i' is no longer a suitable
> identifier.  Doesn't matter much.
> 
> `index' is from page.index, which is pgoff_t.
> 
> `bytes' could be local to the innermost loop.
> 
> `expected' is inappropriately a signed type and could be local to the
> inner loop.
> 
>> +	int file_end = i_size_read(inode) >> msblk->block_log;
>> +	unsigned int max_pages = 1UL << shift;
>> +	void *pageaddr;
>> +

pageaddr could be made local to the innermost scope.

Apart from that the patch and updated error handling looks
good.

Phillip

>> +	readahead_expand(ractl, start, (len | mask) + 1);
>> +
>> +	if (file_end == 0)
>> +		return;
>> +
>> +	pages = kmalloc_array(max_pages, sizeof(void *), GFP_KERNEL);
>> +	if (!pages)
>> +		return;
>> +
>> +	actor = squashfs_page_actor_init_special(pages, max_pages, 0);
>> +	if (!actor)
>> +		goto out;
>> +
>> +	for (;;) {
>> +		nr_pages = __readahead_batch(ractl, pages, max_pages);
>> +		if (!nr_pages)
>> +			break;
>> +
>> +		if (readahead_pos(ractl) >= i_size_read(inode) ||
>> +		    nr_pages < max_pages)
>> +			goto skip_pages;
>> +
>> +		index = pages[0]->index >> shift;
>> +		if ((pages[nr_pages - 1]->index >> shift) != index)
>> +			goto skip_pages;
>> +
>> +		expected = index == file_end ?
>> +			   (i_size_read(inode) & (msblk->block_size - 1)) :
>> +			    msblk->block_size;
>> +
>> +		bsize = read_blocklist(inode, index, &block);
>> +		if (bsize == 0)
>> +			goto skip_pages;
>> +
>> +		res = squashfs_read_data(inode->i_sb, block, bsize, NULL,
>> +					 actor);
>> +
>> +		if (res == expected) {
>> +			/* Last page may have trailing bytes not filled */
>> +			bytes = res % PAGE_SIZE;
>> +			if (bytes) {
>> +				pageaddr = kmap_atomic(pages[nr_pages - 1]);
>> +				memset(pageaddr + bytes, 0, PAGE_SIZE - bytes);
>> +				kunmap_atomic(pageaddr);
>> +			}
>> +
>> +			for (i = 0; i < nr_pages; i++)
>> +				SetPageUptodate(pages[i]);
>> +		}
> 
> res == -EIO is unhandled?
> 
>> +		for (i = 0; i < nr_pages; i++) {
>> +			unlock_page(pages[i]);
>> +			put_page(pages[i]);
>> +		}
>> +	}
>> +
>> +	kfree(actor);
>> +	kfree(pages);
>> +	return;
>> +
>> +skip_pages:
>> +	for (i = 0; i < nr_pages; i++) {
>> +		unlock_page(pages[i]);
>> +		put_page(pages[i]);
>> +	}
>> +
>> +	kfree(actor);
>> +out:
>> +	kfree(pages);
>> +}
>>   
>>   const struct address_space_operations squashfs_aops = {
>> -	.read_folio = squashfs_read_folio
>> +	.read_folio = squashfs_read_folio,
>> +	.readahead = squashfs_readahead
>>   };
>
Hsin-Yi Wang June 1, 2022, 8:36 a.m. UTC | #6
On Wed, Jun 1, 2022 at 9:08 AM Phillip Lougher <phillip@squashfs.org.uk> wrote:
>
> On 31/05/2022 21:47, Andrew Morton wrote:
> > On Mon, 23 May 2022 14:59:13 +0800 Hsin-Yi Wang <hsinyi@chromium.org> wrote:
> >
> >> Implement readahead callback for squashfs. It will read datablocks
> >> which cover pages in readahead request. For a few cases it will
> >> not mark page as uptodate, including:
> >> - file end is 0.
> >> - zero filled blocks.
> >> - current batch of pages isn't in the same datablock or not enough in a
> >>    datablock.
> >> - decompressor error.
> >> Otherwise pages will be marked as uptodate. The unhandled pages will be
> >> updated by readpage later.
> >>
> >> ...
> >>
> >
> > The choice of types seems somewhat confused.
> >
> >> @@ -495,7 +496,95 @@ static int squashfs_read_folio(struct file *file, struct folio *folio)
> >>      return 0;
> >>   }
> >>
> >> +static void squashfs_readahead(struct readahead_control *ractl)
> >> +{
> >> +    struct inode *inode = ractl->mapping->host;
> >> +    struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
> >> +    size_t mask = (1UL << msblk->block_log) - 1;
> >> +    size_t shift = msblk->block_log - PAGE_SHIFT;
> >
> > block_log is unsigned short.  Why size_t?

Will update in the next version.

> >
> >> +    loff_t start = readahead_pos(ractl) &~ mask;
> >> +    size_t len = readahead_length(ractl) + readahead_pos(ractl) - start;
> >> +    struct squashfs_page_actor *actor;
> >> +    unsigned int nr_pages = 0;
> >
> > OK.
> >
> >> +    struct page **pages;
> >> +    u64 block = 0;
> >> +    int bsize, res, i, index, bytes, expected;
> >
> > `res' could be local to the inner loop.
> >
> > `i' is used in situations where an unsigned type would be more
> > appropriate.  If it is made unsigned then `i' is no longer a suitable
> > identifier.  Doesn't matter much.
> >
> > `index' is from page.index, which is pgoff_t.
> >
> > `bytes' could be local to the innermost loop.
> >
> > `expected' is inappropriately a signed type and could be local to the
> > inner loop.

Will update them in the next version.
> >
> >> +    int file_end = i_size_read(inode) >> msblk->block_log;
> >> +    unsigned int max_pages = 1UL << shift;
> >> +    void *pageaddr;
> >> +
>
> pageaddr could be made local to the innermost scope.
>
Will update them in the next version.

Thanks for your comments.

> Apart from that the patch and updated error handling looks
> good.
>
> Phillip
>
> >> +    readahead_expand(ractl, start, (len | mask) + 1);
> >> +
> >> +    if (file_end == 0)
> >> +            return;
> >> +
> >> +    pages = kmalloc_array(max_pages, sizeof(void *), GFP_KERNEL);
> >> +    if (!pages)
> >> +            return;
> >> +
> >> +    actor = squashfs_page_actor_init_special(pages, max_pages, 0);
> >> +    if (!actor)
> >> +            goto out;
> >> +
> >> +    for (;;) {
> >> +            nr_pages = __readahead_batch(ractl, pages, max_pages);
> >> +            if (!nr_pages)
> >> +                    break;
> >> +
> >> +            if (readahead_pos(ractl) >= i_size_read(inode) ||
> >> +                nr_pages < max_pages)
> >> +                    goto skip_pages;
> >> +
> >> +            index = pages[0]->index >> shift;
> >> +            if ((pages[nr_pages - 1]->index >> shift) != index)
> >> +                    goto skip_pages;
> >> +
> >> +            expected = index == file_end ?
> >> +                       (i_size_read(inode) & (msblk->block_size - 1)) :
> >> +                        msblk->block_size;
> >> +
> >> +            bsize = read_blocklist(inode, index, &block);
> >> +            if (bsize == 0)
> >> +                    goto skip_pages;
> >> +
> >> +            res = squashfs_read_data(inode->i_sb, block, bsize, NULL,
> >> +                                     actor);
> >> +
> >> +            if (res == expected) {
> >> +                    /* Last page may have trailing bytes not filled */
> >> +                    bytes = res % PAGE_SIZE;
> >> +                    if (bytes) {
> >> +                            pageaddr = kmap_atomic(pages[nr_pages - 1]);
> >> +                            memset(pageaddr + bytes, 0, PAGE_SIZE - bytes);
> >> +                            kunmap_atomic(pageaddr);
> >> +                    }
> >> +
> >> +                    for (i = 0; i < nr_pages; i++)
> >> +                            SetPageUptodate(pages[i]);
> >> +            }
> >
> > res == -EIO is unhandled?
> >
> >> +            for (i = 0; i < nr_pages; i++) {
> >> +                    unlock_page(pages[i]);
> >> +                    put_page(pages[i]);
> >> +            }
> >> +    }
> >> +
> >> +    kfree(actor);
> >> +    kfree(pages);
> >> +    return;
> >> +
> >> +skip_pages:
> >> +    for (i = 0; i < nr_pages; i++) {
> >> +            unlock_page(pages[i]);
> >> +            put_page(pages[i]);
> >> +    }
> >> +
> >> +    kfree(actor);
> >> +out:
> >> +    kfree(pages);
> >> +}
> >>
> >>   const struct address_space_operations squashfs_aops = {
> >> -    .read_folio = squashfs_read_folio
> >> +    .read_folio = squashfs_read_folio,
> >> +    .readahead = squashfs_readahead
> >>   };
> >
>
diff mbox series

Patch

diff --git a/fs/squashfs/file.c b/fs/squashfs/file.c
index a8e495d8eb86..c311fc685fe4 100644
--- a/fs/squashfs/file.c
+++ b/fs/squashfs/file.c
@@ -39,6 +39,7 @@ 
 #include "squashfs_fs_sb.h"
 #include "squashfs_fs_i.h"
 #include "squashfs.h"
+#include "page_actor.h"
 
 /*
  * Locate cache slot in range [offset, index] for specified inode.  If
@@ -495,7 +496,95 @@  static int squashfs_read_folio(struct file *file, struct folio *folio)
 	return 0;
 }
 
+static void squashfs_readahead(struct readahead_control *ractl)
+{
+	struct inode *inode = ractl->mapping->host;
+	struct squashfs_sb_info *msblk = inode->i_sb->s_fs_info;
+	size_t mask = (1UL << msblk->block_log) - 1;
+	size_t shift = msblk->block_log - PAGE_SHIFT;
+	loff_t start = readahead_pos(ractl) &~ mask;
+	size_t len = readahead_length(ractl) + readahead_pos(ractl) - start;
+	struct squashfs_page_actor *actor;
+	unsigned int nr_pages = 0;
+	struct page **pages;
+	u64 block = 0;
+	int bsize, res, i, index, bytes, expected;
+	int file_end = i_size_read(inode) >> msblk->block_log;
+	unsigned int max_pages = 1UL << shift;
+	void *pageaddr;
+
+	readahead_expand(ractl, start, (len | mask) + 1);
+
+	if (file_end == 0)
+		return;
+
+	pages = kmalloc_array(max_pages, sizeof(void *), GFP_KERNEL);
+	if (!pages)
+		return;
+
+	actor = squashfs_page_actor_init_special(pages, max_pages, 0);
+	if (!actor)
+		goto out;
+
+	for (;;) {
+		nr_pages = __readahead_batch(ractl, pages, max_pages);
+		if (!nr_pages)
+			break;
+
+		if (readahead_pos(ractl) >= i_size_read(inode) ||
+		    nr_pages < max_pages)
+			goto skip_pages;
+
+		index = pages[0]->index >> shift;
+		if ((pages[nr_pages - 1]->index >> shift) != index)
+			goto skip_pages;
+
+		expected = index == file_end ?
+			   (i_size_read(inode) & (msblk->block_size - 1)) :
+			    msblk->block_size;
+
+		bsize = read_blocklist(inode, index, &block);
+		if (bsize == 0)
+			goto skip_pages;
+
+		res = squashfs_read_data(inode->i_sb, block, bsize, NULL,
+					 actor);
+
+		if (res == expected) {
+			/* Last page may have trailing bytes not filled */
+			bytes = res % PAGE_SIZE;
+			if (bytes) {
+				pageaddr = kmap_atomic(pages[nr_pages - 1]);
+				memset(pageaddr + bytes, 0, PAGE_SIZE - bytes);
+				kunmap_atomic(pageaddr);
+			}
+
+			for (i = 0; i < nr_pages; i++)
+				SetPageUptodate(pages[i]);
+		}
+
+		for (i = 0; i < nr_pages; i++) {
+			unlock_page(pages[i]);
+			put_page(pages[i]);
+		}
+	}
+
+	kfree(actor);
+	kfree(pages);
+	return;
+
+skip_pages:
+	for (i = 0; i < nr_pages; i++) {
+		unlock_page(pages[i]);
+		put_page(pages[i]);
+	}
+
+	kfree(actor);
+out:
+	kfree(pages);
+}
 
 const struct address_space_operations squashfs_aops = {
-	.read_folio = squashfs_read_folio
+	.read_folio = squashfs_read_folio,
+	.readahead = squashfs_readahead
 };