Thomas Gleixner | 68252eb | 2019-05-20 19:08:00 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 2 | /* |
| 3 | * Squashfs - a compressed read only filesystem for Linux |
| 4 | * |
| 5 | * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008 |
Phillip Lougher | d7f2ff6 | 2011-05-26 10:39:56 +0100 | [diff] [blame] | 6 | * Phillip Lougher <phillip@squashfs.org.uk> |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 7 | * |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 8 | * block.c |
| 9 | */ |
| 10 | |
| 11 | /* |
| 12 | * This file implements the low-level routines to read and decompress |
| 13 | * datablocks and metadata blocks. |
| 14 | */ |
| 15 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 16 | #include <linux/blkdev.h> |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 17 | #include <linux/fs.h> |
| 18 | #include <linux/vfs.h> |
| 19 | #include <linux/slab.h> |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 20 | #include <linux/string.h> |
| 21 | #include <linux/buffer_head.h> |
Christoph Hellwig | 2f8b544 | 2016-11-01 07:40:13 -0600 | [diff] [blame] | 22 | #include <linux/bio.h> |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 23 | |
| 24 | #include "squashfs_fs.h" |
| 25 | #include "squashfs_fs_sb.h" |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 26 | #include "squashfs.h" |
Phillip Lougher | 4c0f0bb | 2009-10-06 04:04:15 +0100 | [diff] [blame] | 27 | #include "decompressor.h" |
Phillip Lougher | 846b730 | 2013-11-18 02:59:12 +0000 | [diff] [blame] | 28 | #include "page_actor.h" |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 29 | |
| 30 | /* |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 31 | * Returns the amount of bytes copied to the page actor. |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 32 | */ |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 33 | static int copy_bio_to_actor(struct bio *bio, |
| 34 | struct squashfs_page_actor *actor, |
| 35 | int offset, int req_length) |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 36 | { |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 37 | void *actor_addr = squashfs_first_page(actor); |
| 38 | struct bvec_iter_all iter_all = {}; |
| 39 | struct bio_vec *bvec = bvec_init_iter_all(&iter_all); |
| 40 | int copied_bytes = 0; |
| 41 | int actor_offset = 0; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 42 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 43 | if (WARN_ON_ONCE(!bio_next_segment(bio, &iter_all))) |
| 44 | return 0; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 45 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 46 | while (copied_bytes < req_length) { |
| 47 | int bytes_to_copy = min_t(int, bvec->bv_len - offset, |
| 48 | PAGE_SIZE - actor_offset); |
Phillip Lougher | 3689456 | 2011-01-25 15:07:34 -0800 | [diff] [blame] | 49 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 50 | bytes_to_copy = min_t(int, bytes_to_copy, |
| 51 | req_length - copied_bytes); |
Christoph Hellwig | fbc27241 | 2021-08-04 11:56:25 +0200 | [diff] [blame] | 52 | memcpy(actor_addr + actor_offset, bvec_virt(bvec) + offset, |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 53 | bytes_to_copy); |
| 54 | |
| 55 | actor_offset += bytes_to_copy; |
| 56 | copied_bytes += bytes_to_copy; |
| 57 | offset += bytes_to_copy; |
| 58 | |
| 59 | if (actor_offset >= PAGE_SIZE) { |
| 60 | actor_addr = squashfs_next_page(actor); |
| 61 | if (!actor_addr) |
| 62 | break; |
| 63 | actor_offset = 0; |
| 64 | } |
| 65 | if (offset >= bvec->bv_len) { |
| 66 | if (!bio_next_segment(bio, &iter_all)) |
| 67 | break; |
| 68 | offset = 0; |
Phillip Lougher | 3689456 | 2011-01-25 15:07:34 -0800 | [diff] [blame] | 69 | } |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 70 | } |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 71 | squashfs_finish_page(actor); |
| 72 | return copied_bytes; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 73 | } |
| 74 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 75 | static int squashfs_bio_read(struct super_block *sb, u64 index, int length, |
| 76 | struct bio **biop, int *block_offset) |
| 77 | { |
| 78 | struct squashfs_sb_info *msblk = sb->s_fs_info; |
| 79 | const u64 read_start = round_down(index, msblk->devblksize); |
| 80 | const sector_t block = read_start >> msblk->devblksize_log2; |
| 81 | const u64 read_end = round_up(index + length, msblk->devblksize); |
| 82 | const sector_t block_end = read_end >> msblk->devblksize_log2; |
| 83 | int offset = read_start - round_down(index, PAGE_SIZE); |
| 84 | int total_len = (block_end - block) << msblk->devblksize_log2; |
| 85 | const int page_count = DIV_ROUND_UP(total_len + offset, PAGE_SIZE); |
| 86 | int error, i; |
| 87 | struct bio *bio; |
| 88 | |
Christoph Hellwig | a8affc0 | 2021-03-11 12:01:37 +0100 | [diff] [blame] | 89 | if (page_count <= BIO_MAX_VECS) |
Phillip Lougher | f26044c | 2020-08-20 17:42:21 -0700 | [diff] [blame] | 90 | bio = bio_alloc(GFP_NOIO, page_count); |
| 91 | else |
| 92 | bio = bio_kmalloc(GFP_NOIO, page_count); |
| 93 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 94 | if (!bio) |
| 95 | return -ENOMEM; |
| 96 | |
| 97 | bio_set_dev(bio, sb->s_bdev); |
| 98 | bio->bi_opf = READ; |
| 99 | bio->bi_iter.bi_sector = block * (msblk->devblksize >> SECTOR_SHIFT); |
| 100 | |
| 101 | for (i = 0; i < page_count; ++i) { |
| 102 | unsigned int len = |
| 103 | min_t(unsigned int, PAGE_SIZE - offset, total_len); |
| 104 | struct page *page = alloc_page(GFP_NOIO); |
| 105 | |
| 106 | if (!page) { |
| 107 | error = -ENOMEM; |
| 108 | goto out_free_bio; |
| 109 | } |
| 110 | if (!bio_add_page(bio, page, len, offset)) { |
| 111 | error = -EIO; |
| 112 | goto out_free_bio; |
| 113 | } |
| 114 | offset = 0; |
| 115 | total_len -= len; |
| 116 | } |
| 117 | |
| 118 | error = submit_bio_wait(bio); |
| 119 | if (error) |
| 120 | goto out_free_bio; |
| 121 | |
| 122 | *biop = bio; |
| 123 | *block_offset = index & ((1 << msblk->devblksize_log2) - 1); |
| 124 | return 0; |
| 125 | |
| 126 | out_free_bio: |
| 127 | bio_free_pages(bio); |
| 128 | bio_put(bio); |
| 129 | return error; |
| 130 | } |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 131 | |
| 132 | /* |
| 133 | * Read and decompress a metadata block or datablock. Length is non-zero |
| 134 | * if a datablock is being read (the size is stored elsewhere in the |
| 135 | * filesystem), otherwise the length is obtained from the first two bytes of |
| 136 | * the metadata block. A bit in the length field indicates if the block |
| 137 | * is stored uncompressed in the filesystem (usually because compression |
Phillip Lougher | ec9267b | 2012-03-06 01:18:49 +0000 | [diff] [blame] | 138 | * generated a larger block - this does occasionally happen with compression |
| 139 | * algorithms). |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 140 | */ |
Phillip Lougher | 846b730 | 2013-11-18 02:59:12 +0000 | [diff] [blame] | 141 | int squashfs_read_data(struct super_block *sb, u64 index, int length, |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 142 | u64 *next_index, struct squashfs_page_actor *output) |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 143 | { |
| 144 | struct squashfs_sb_info *msblk = sb->s_fs_info; |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 145 | struct bio *bio = NULL; |
| 146 | int compressed; |
| 147 | int res; |
| 148 | int offset; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 149 | |
| 150 | if (length) { |
| 151 | /* |
| 152 | * Datablock. |
| 153 | */ |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 154 | compressed = SQUASHFS_COMPRESSED_BLOCK(length); |
| 155 | length = SQUASHFS_COMPRESSED_SIZE_BLOCK(length); |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 156 | TRACE("Block @ 0x%llx, %scompressed size %d, src size %d\n", |
Phillip Lougher | 846b730 | 2013-11-18 02:59:12 +0000 | [diff] [blame] | 157 | index, compressed ? "" : "un", length, output->length); |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 158 | } else { |
| 159 | /* |
| 160 | * Metadata block. |
| 161 | */ |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 162 | const u8 *data; |
| 163 | struct bvec_iter_all iter_all = {}; |
| 164 | struct bio_vec *bvec = bvec_init_iter_all(&iter_all); |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 165 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 166 | if (index + 2 > msblk->bytes_used) { |
| 167 | res = -EIO; |
| 168 | goto out; |
| 169 | } |
| 170 | res = squashfs_bio_read(sb, index, 2, &bio, &offset); |
| 171 | if (res) |
| 172 | goto out; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 173 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 174 | if (WARN_ON_ONCE(!bio_next_segment(bio, &iter_all))) { |
| 175 | res = -EIO; |
| 176 | goto out_free_bio; |
| 177 | } |
| 178 | /* Extract the length of the metadata block */ |
Christoph Hellwig | fbc27241 | 2021-08-04 11:56:25 +0200 | [diff] [blame] | 179 | data = bvec_virt(bvec); |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 180 | length = data[offset]; |
Phillip Lougher | 2910c59 | 2020-07-23 21:15:40 -0700 | [diff] [blame] | 181 | if (offset < bvec->bv_len - 1) { |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 182 | length |= data[offset + 1] << 8; |
| 183 | } else { |
| 184 | if (WARN_ON_ONCE(!bio_next_segment(bio, &iter_all))) { |
| 185 | res = -EIO; |
| 186 | goto out_free_bio; |
| 187 | } |
Christoph Hellwig | fbc27241 | 2021-08-04 11:56:25 +0200 | [diff] [blame] | 188 | data = bvec_virt(bvec); |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 189 | length |= data[0] << 8; |
| 190 | } |
| 191 | bio_free_pages(bio); |
| 192 | bio_put(bio); |
| 193 | |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 194 | compressed = SQUASHFS_COMPRESSED(length); |
| 195 | length = SQUASHFS_COMPRESSED_SIZE(length); |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 196 | index += 2; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 197 | |
Phillip Lougher | e812cbb | 2021-02-09 13:41:50 -0800 | [diff] [blame] | 198 | TRACE("Block @ 0x%llx, %scompressed size %d\n", index - 2, |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 199 | compressed ? "" : "un", length); |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 200 | } |
Phillip Lougher | e812cbb | 2021-02-09 13:41:50 -0800 | [diff] [blame] | 201 | if (length < 0 || length > output->length || |
| 202 | (index + length) > msblk->bytes_used) { |
| 203 | res = -EIO; |
| 204 | goto out; |
| 205 | } |
| 206 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 207 | if (next_index) |
| 208 | *next_index = index + length; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 209 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 210 | res = squashfs_bio_read(sb, index, length, &bio, &offset); |
| 211 | if (res) |
| 212 | goto out; |
Phillip Lougher | 9508c6b | 2013-11-13 02:56:26 +0000 | [diff] [blame] | 213 | |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 214 | if (compressed) { |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 215 | if (!msblk->stream) { |
| 216 | res = -EIO; |
| 217 | goto out_free_bio; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 218 | } |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 219 | res = squashfs_decompress(msblk, bio, offset, length, output); |
| 220 | } else { |
| 221 | res = copy_bio_to_actor(bio, output, offset, length); |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 222 | } |
| 223 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 224 | out_free_bio: |
| 225 | bio_free_pages(bio); |
| 226 | bio_put(bio); |
| 227 | out: |
Vincent Whitchurch | 10dde05 | 2021-06-28 19:33:55 -0700 | [diff] [blame] | 228 | if (res < 0) { |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 229 | ERROR("Failed to read block 0x%llx: %d\n", index, res); |
Vincent Whitchurch | 10dde05 | 2021-06-28 19:33:55 -0700 | [diff] [blame] | 230 | if (msblk->panic_on_errors) |
| 231 | panic("squashfs read failed"); |
| 232 | } |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 233 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 234 | return res; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 235 | } |