Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

squashfs: extend "page actor" to handle missing pages

Patch series "Squashfs: handle missing pages decompressing into page
cache".

This patchset enables Squashfs to handle missing pages when directly
decompressing datablocks into the page cache.

Previously if the full set of pages needed was not available, Squashfs
would have to fall back to using an intermediate buffer (the older
method), which is slower, involving a memcopy, and it introduces
contention on a shared buffer.

The first patch extends the "page actor" code to handle missing pages.

The second patch updates Squashfs_readpage_block() to use the new
functionality, and removes the code that falls back to using an
intermediate buffer.

This patchset is independent of the readahead work, and it is standalone.
It can be merged on its own.

But the readahead patch for efficiency also needs this patch-set.


This patch (of 2):

This patch extends the "page actor" code to handle missing pages.

Previously if the full set of pages needed to decompress a Squashfs
datablock was unavailable, this would cause decompression to fail on the
missing pages.

In this case direct decompression into the page cache could not be
achieved and the code would fall back to using the older intermediate
buffer method.

With this patch, direct decompression into the page cache can be achieved
with missing pages.

For "multi-shot" decompressors (zlib, xz, zstd), the page actor will
allocate a temporary buffer which is passed to the decompressor, and then
freed by the page actor.

For "single shot" decompressors (lz4, lzo) which decompress into a
contiguous "bounce buffer", and which is then copied into the page cache,
it would be pointless to allocate a temporary buffer, memcpy into it, and
then free it. For these decompressors -ENOMEM is returned, which
signifies that the memcpy for that page should be skipped.

This also happens if the data block is uncompressed.

Link: https://lkml.kernel.org/r/20220611032133.5743-1-phillip@squashfs.org.uk
Link: https://lkml.kernel.org/r/20220611032133.5743-2-phillip@squashfs.org.uk
Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: Hsin-Yi Wang <hsinyi@chromium.org>
Cc: Xiongwei Song <Xiongwei.Song@windriver.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Phillip Lougher and committed by
akpm
f268eedd 0aed4724

+126 -31
+7 -3
fs/squashfs/block.c
··· 34 34 struct squashfs_page_actor *actor, 35 35 int offset, int req_length) 36 36 { 37 - void *actor_addr = squashfs_first_page(actor); 37 + void *actor_addr; 38 38 struct bvec_iter_all iter_all = {}; 39 39 struct bio_vec *bvec = bvec_init_iter_all(&iter_all); 40 40 int copied_bytes = 0; 41 41 int actor_offset = 0; 42 + 43 + squashfs_actor_nobuff(actor); 44 + actor_addr = squashfs_first_page(actor); 42 45 43 46 if (WARN_ON_ONCE(!bio_next_segment(bio, &iter_all))) 44 47 return 0; ··· 52 49 53 50 bytes_to_copy = min_t(int, bytes_to_copy, 54 51 req_length - copied_bytes); 55 - memcpy(actor_addr + actor_offset, bvec_virt(bvec) + offset, 56 - bytes_to_copy); 52 + if (!IS_ERR(actor_addr)) 53 + memcpy(actor_addr + actor_offset, bvec_virt(bvec) + 54 + offset, bytes_to_copy); 57 55 58 56 actor_offset += bytes_to_copy; 59 57 copied_bytes += bytes_to_copy;
+1
fs/squashfs/decompressor.h
··· 20 20 struct bio *, int, int, struct squashfs_page_actor *); 21 21 int id; 22 22 char *name; 23 + int alloc_buffer; 23 24 int supported; 24 25 }; 25 26
+11 -10
fs/squashfs/file_direct.c
··· 47 47 if (page == NULL) 48 48 return res; 49 49 50 - /* 51 - * Create a "page actor" which will kmap and kunmap the 52 - * page cache pages appropriately within the decompressor 53 - */ 54 - actor = squashfs_page_actor_init_special(page, pages, 0); 55 - if (actor == NULL) 56 - goto out; 57 - 58 50 /* Try to grab all the pages covered by the Squashfs block */ 59 51 for (missing_pages = 0, i = 0, n = start_index; i < pages; i++, n++) { 60 52 page[i] = (n == target_page->index) ? target_page : ··· 81 89 goto out; 82 90 } 83 91 92 + /* 93 + * Create a "page actor" which will kmap and kunmap the 94 + * page cache pages appropriately within the decompressor 95 + */ 96 + actor = squashfs_page_actor_init_special(msblk, page, pages, 0); 97 + if (actor == NULL) 98 + goto out; 99 + 84 100 /* Decompress directly into the page cache buffers */ 85 101 res = squashfs_read_data(inode->i_sb, block, bsize, NULL, actor); 102 + 103 + kfree(actor); 104 + 86 105 if (res < 0) 87 106 goto mark_errored; 88 107 ··· 119 116 put_page(page[i]); 120 117 } 121 118 122 - kfree(actor); 123 119 kfree(page); 124 120 125 121 return 0; ··· 137 135 } 138 136 139 137 out: 140 - kfree(actor); 141 138 kfree(page); 142 139 return res; 143 140 }
+5 -2
fs/squashfs/lz4_wrapper.c
··· 119 119 buff = stream->output; 120 120 while (data) { 121 121 if (bytes <= PAGE_SIZE) { 122 - memcpy(data, buff, bytes); 122 + if (!IS_ERR(data)) 123 + memcpy(data, buff, bytes); 123 124 break; 124 125 } 125 - memcpy(data, buff, PAGE_SIZE); 126 + if (!IS_ERR(data)) 127 + memcpy(data, buff, PAGE_SIZE); 126 128 buff += PAGE_SIZE; 127 129 bytes -= PAGE_SIZE; 128 130 data = squashfs_next_page(output); ··· 141 139 .decompress = lz4_uncompress, 142 140 .id = LZ4_COMPRESSION, 143 141 .name = "lz4", 142 + .alloc_buffer = 0, 144 143 .supported = 1 145 144 };
+5 -2
fs/squashfs/lzo_wrapper.c
··· 93 93 buff = stream->output; 94 94 while (data) { 95 95 if (bytes <= PAGE_SIZE) { 96 - memcpy(data, buff, bytes); 96 + if (!IS_ERR(data)) 97 + memcpy(data, buff, bytes); 97 98 break; 98 99 } else { 99 - memcpy(data, buff, PAGE_SIZE); 100 + if (!IS_ERR(data)) 101 + memcpy(data, buff, PAGE_SIZE); 100 102 buff += PAGE_SIZE; 101 103 bytes -= PAGE_SIZE; 102 104 data = squashfs_next_page(output); ··· 118 116 .decompress = lzo_uncompress, 119 117 .id = LZO_COMPRESSION, 120 118 .name = "lzo", 119 + .alloc_buffer = 0, 121 120 .supported = 1 122 121 };
+47 -8
fs/squashfs/page_actor.c
··· 7 7 #include <linux/kernel.h> 8 8 #include <linux/slab.h> 9 9 #include <linux/pagemap.h> 10 + #include "squashfs_fs_sb.h" 11 + #include "decompressor.h" 10 12 #include "page_actor.h" 11 13 12 14 /* ··· 59 57 } 60 58 61 59 /* Implementation of page_actor for decompressing directly into page cache. */ 60 + static void *handle_next_page(struct squashfs_page_actor *actor) 61 + { 62 + int max_pages = (actor->length + PAGE_SIZE - 1) >> PAGE_SHIFT; 63 + 64 + if (actor->returned_pages == max_pages) 65 + return NULL; 66 + 67 + if ((actor->next_page == actor->pages) || 68 + (actor->next_index != actor->page[actor->next_page]->index)) { 69 + if (actor->alloc_buffer) { 70 + void *tmp_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); 71 + 72 + if (tmp_buffer) { 73 + actor->tmp_buffer = tmp_buffer; 74 + actor->next_index++; 75 + actor->returned_pages++; 76 + return tmp_buffer; 77 + } 78 + } 79 + 80 + actor->next_index++; 81 + actor->returned_pages++; 82 + return ERR_PTR(-ENOMEM); 83 + } 84 + 85 + actor->next_index++; 86 + actor->returned_pages++; 87 + return actor->pageaddr = kmap_local_page(actor->page[actor->next_page++]); 88 + } 89 + 62 90 static void *direct_first_page(struct squashfs_page_actor *actor) 63 91 { 64 - actor->next_page = 1; 65 - return actor->pageaddr = kmap_atomic(actor->page[0]); 92 + return handle_next_page(actor); 66 93 } 67 94 68 95 static void *direct_next_page(struct squashfs_page_actor *actor) 69 96 { 70 97 if (actor->pageaddr) 71 - kunmap_atomic(actor->pageaddr); 98 + kunmap_local(actor->pageaddr); 72 99 73 - return actor->pageaddr = actor->next_page == actor->pages ? NULL : 74 - kmap_atomic(actor->page[actor->next_page++]); 100 + kfree(actor->tmp_buffer); 101 + actor->pageaddr = actor->tmp_buffer = NULL; 102 + 103 + return handle_next_page(actor); 75 104 } 76 105 77 106 static void direct_finish_page(struct squashfs_page_actor *actor) 78 107 { 79 108 if (actor->pageaddr) 80 - kunmap_atomic(actor->pageaddr); 109 + kunmap_local(actor->pageaddr); 110 + 111 + kfree(actor->tmp_buffer); 81 112 } 82 113 83 - struct squashfs_page_actor *squashfs_page_actor_init_special(struct page **page, 84 - int pages, int length) 114 + struct squashfs_page_actor *squashfs_page_actor_init_special(struct squashfs_sb_info *msblk, 115 + struct page **page, int pages, int length) 85 116 { 86 117 struct squashfs_page_actor *actor = kmalloc(sizeof(*actor), GFP_KERNEL); 87 118 ··· 125 90 actor->page = page; 126 91 actor->pages = pages; 127 92 actor->next_page = 0; 93 + actor->returned_pages = 0; 94 + actor->next_index = page[0]->index & ~((1 << (msblk->block_log - PAGE_SHIFT)) - 1); 128 95 actor->pageaddr = NULL; 96 + actor->tmp_buffer = NULL; 97 + actor->alloc_buffer = msblk->decompressor->alloc_buffer; 129 98 actor->squashfs_first_page = direct_first_page; 130 99 actor->squashfs_next_page = direct_next_page; 131 100 actor->squashfs_finish_page = direct_finish_page;
+18 -3
fs/squashfs/page_actor.h
··· 45 45 { 46 46 /* empty */ 47 47 } 48 + 49 + static inline void squashfs_actor_nobuff(struct squashfs_page_actor *actor) 50 + { 51 + /* empty */ 52 + } 48 53 #else 49 54 struct squashfs_page_actor { 50 55 union { ··· 57 52 struct page **page; 58 53 }; 59 54 void *pageaddr; 55 + void *tmp_buffer; 60 56 void *(*squashfs_first_page)(struct squashfs_page_actor *); 61 57 void *(*squashfs_next_page)(struct squashfs_page_actor *); 62 58 void (*squashfs_finish_page)(struct squashfs_page_actor *); 63 59 int pages; 64 60 int length; 65 61 int next_page; 62 + int alloc_buffer; 63 + int returned_pages; 64 + pgoff_t next_index; 66 65 }; 67 66 68 - extern struct squashfs_page_actor *squashfs_page_actor_init(void **, int, int); 69 - extern struct squashfs_page_actor *squashfs_page_actor_init_special(struct page 70 - **, int, int); 67 + extern struct squashfs_page_actor *squashfs_page_actor_init(void **buffer, 68 + int pages, int length); 69 + extern struct squashfs_page_actor *squashfs_page_actor_init_special( 70 + struct squashfs_sb_info *msblk, 71 + struct page **page, int pages, int length); 71 72 static inline void *squashfs_first_page(struct squashfs_page_actor *actor) 72 73 { 73 74 return actor->squashfs_first_page(actor); ··· 85 74 static inline void squashfs_finish_page(struct squashfs_page_actor *actor) 86 75 { 87 76 actor->squashfs_finish_page(actor); 77 + } 78 + static inline void squashfs_actor_nobuff(struct squashfs_page_actor *actor) 79 + { 80 + actor->alloc_buffer = 0; 88 81 } 89 82 #endif 90 83 #endif
+10 -1
fs/squashfs/xz_wrapper.c
··· 131 131 stream->buf.out_pos = 0; 132 132 stream->buf.out_size = PAGE_SIZE; 133 133 stream->buf.out = squashfs_first_page(output); 134 + if (IS_ERR(stream->buf.out)) { 135 + error = PTR_ERR(stream->buf.out); 136 + goto finish; 137 + } 134 138 135 139 for (;;) { 136 140 enum xz_ret xz_err; ··· 160 156 161 157 if (stream->buf.out_pos == stream->buf.out_size) { 162 158 stream->buf.out = squashfs_next_page(output); 163 - if (stream->buf.out != NULL) { 159 + if (IS_ERR(stream->buf.out)) { 160 + error = PTR_ERR(stream->buf.out); 161 + break; 162 + } else if (stream->buf.out != NULL) { 164 163 stream->buf.out_pos = 0; 165 164 total += PAGE_SIZE; 166 165 } ··· 178 171 } 179 172 } 180 173 174 + finish: 181 175 squashfs_finish_page(output); 182 176 183 177 return error ? error : total + stream->buf.out_pos; ··· 191 183 .decompress = squashfs_xz_uncompress, 192 184 .id = XZ_COMPRESSION, 193 185 .name = "xz", 186 + .alloc_buffer = 1, 194 187 .supported = 1 195 188 };
+11 -1
fs/squashfs/zlib_wrapper.c
··· 62 62 stream->next_out = squashfs_first_page(output); 63 63 stream->avail_in = 0; 64 64 65 + if (IS_ERR(stream->next_out)) { 66 + error = PTR_ERR(stream->next_out); 67 + goto finish; 68 + } 69 + 65 70 for (;;) { 66 71 int zlib_err; 67 72 ··· 90 85 91 86 if (stream->avail_out == 0) { 92 87 stream->next_out = squashfs_next_page(output); 93 - if (stream->next_out != NULL) 88 + if (IS_ERR(stream->next_out)) { 89 + error = PTR_ERR(stream->next_out); 90 + break; 91 + } else if (stream->next_out != NULL) 94 92 stream->avail_out = PAGE_SIZE; 95 93 } 96 94 ··· 115 107 } 116 108 } 117 109 110 + finish: 118 111 squashfs_finish_page(output); 119 112 120 113 if (!error) ··· 131 122 .decompress = zlib_uncompress, 132 123 .id = ZLIB_COMPRESSION, 133 124 .name = "zlib", 125 + .alloc_buffer = 1, 134 126 .supported = 1 135 127 }; 136 128
+11 -1
fs/squashfs/zstd_wrapper.c
··· 80 80 81 81 out_buf.size = PAGE_SIZE; 82 82 out_buf.dst = squashfs_first_page(output); 83 + if (IS_ERR(out_buf.dst)) { 84 + error = PTR_ERR(out_buf.dst); 85 + goto finish; 86 + } 83 87 84 88 for (;;) { 85 89 size_t zstd_err; ··· 108 104 109 105 if (out_buf.pos == out_buf.size) { 110 106 out_buf.dst = squashfs_next_page(output); 111 - if (out_buf.dst == NULL) { 107 + if (IS_ERR(out_buf.dst)) { 108 + error = PTR_ERR(out_buf.dst); 109 + break; 110 + } else if (out_buf.dst == NULL) { 112 111 /* Shouldn't run out of pages 113 112 * before stream is done. 114 113 */ ··· 136 129 } 137 130 } 138 131 132 + finish: 133 + 139 134 squashfs_finish_page(output); 140 135 141 136 return error ? error : total_out; ··· 149 140 .decompress = zstd_uncompress, 150 141 .id = ZSTD_COMPRESSION, 151 142 .name = "zstd", 143 + .alloc_buffer = 1, 152 144 .supported = 1 153 145 };