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); |
| 52 | memcpy(actor_addr + actor_offset, |
| 53 | page_address(bvec->bv_page) + bvec->bv_offset + offset, |
| 54 | bytes_to_copy); |
| 55 | |
| 56 | actor_offset += bytes_to_copy; |
| 57 | copied_bytes += bytes_to_copy; |
| 58 | offset += bytes_to_copy; |
| 59 | |
| 60 | if (actor_offset >= PAGE_SIZE) { |
| 61 | actor_addr = squashfs_next_page(actor); |
| 62 | if (!actor_addr) |
| 63 | break; |
| 64 | actor_offset = 0; |
| 65 | } |
| 66 | if (offset >= bvec->bv_len) { |
| 67 | if (!bio_next_segment(bio, &iter_all)) |
| 68 | break; |
| 69 | offset = 0; |
Phillip Lougher | 3689456 | 2011-01-25 15:07:34 -0800 | [diff] [blame] | 70 | } |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 71 | } |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 72 | squashfs_finish_page(actor); |
| 73 | return copied_bytes; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 74 | } |
| 75 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 76 | static int squashfs_bio_read(struct super_block *sb, u64 index, int length, |
| 77 | struct bio **biop, int *block_offset) |
| 78 | { |
| 79 | struct squashfs_sb_info *msblk = sb->s_fs_info; |
| 80 | const u64 read_start = round_down(index, msblk->devblksize); |
| 81 | const sector_t block = read_start >> msblk->devblksize_log2; |
| 82 | const u64 read_end = round_up(index + length, msblk->devblksize); |
| 83 | const sector_t block_end = read_end >> msblk->devblksize_log2; |
| 84 | int offset = read_start - round_down(index, PAGE_SIZE); |
| 85 | int total_len = (block_end - block) << msblk->devblksize_log2; |
| 86 | const int page_count = DIV_ROUND_UP(total_len + offset, PAGE_SIZE); |
| 87 | int error, i; |
| 88 | struct bio *bio; |
| 89 | |
Christoph Hellwig | a8affc0 | 2021-03-11 12:01:37 +0100 | [diff] [blame^] | 90 | if (page_count <= BIO_MAX_VECS) |
Phillip Lougher | f26044c | 2020-08-20 17:42:21 -0700 | [diff] [blame] | 91 | bio = bio_alloc(GFP_NOIO, page_count); |
| 92 | else |
| 93 | bio = bio_kmalloc(GFP_NOIO, page_count); |
| 94 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 95 | if (!bio) |
| 96 | return -ENOMEM; |
| 97 | |
| 98 | bio_set_dev(bio, sb->s_bdev); |
| 99 | bio->bi_opf = READ; |
| 100 | bio->bi_iter.bi_sector = block * (msblk->devblksize >> SECTOR_SHIFT); |
| 101 | |
| 102 | for (i = 0; i < page_count; ++i) { |
| 103 | unsigned int len = |
| 104 | min_t(unsigned int, PAGE_SIZE - offset, total_len); |
| 105 | struct page *page = alloc_page(GFP_NOIO); |
| 106 | |
| 107 | if (!page) { |
| 108 | error = -ENOMEM; |
| 109 | goto out_free_bio; |
| 110 | } |
| 111 | if (!bio_add_page(bio, page, len, offset)) { |
| 112 | error = -EIO; |
| 113 | goto out_free_bio; |
| 114 | } |
| 115 | offset = 0; |
| 116 | total_len -= len; |
| 117 | } |
| 118 | |
| 119 | error = submit_bio_wait(bio); |
| 120 | if (error) |
| 121 | goto out_free_bio; |
| 122 | |
| 123 | *biop = bio; |
| 124 | *block_offset = index & ((1 << msblk->devblksize_log2) - 1); |
| 125 | return 0; |
| 126 | |
| 127 | out_free_bio: |
| 128 | bio_free_pages(bio); |
| 129 | bio_put(bio); |
| 130 | return error; |
| 131 | } |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 132 | |
| 133 | /* |
| 134 | * Read and decompress a metadata block or datablock. Length is non-zero |
| 135 | * if a datablock is being read (the size is stored elsewhere in the |
| 136 | * filesystem), otherwise the length is obtained from the first two bytes of |
| 137 | * the metadata block. A bit in the length field indicates if the block |
| 138 | * is stored uncompressed in the filesystem (usually because compression |
Phillip Lougher | ec9267b | 2012-03-06 01:18:49 +0000 | [diff] [blame] | 139 | * generated a larger block - this does occasionally happen with compression |
| 140 | * algorithms). |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 141 | */ |
Phillip Lougher | 846b730 | 2013-11-18 02:59:12 +0000 | [diff] [blame] | 142 | int squashfs_read_data(struct super_block *sb, u64 index, int length, |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 143 | u64 *next_index, struct squashfs_page_actor *output) |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 144 | { |
| 145 | struct squashfs_sb_info *msblk = sb->s_fs_info; |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 146 | struct bio *bio = NULL; |
| 147 | int compressed; |
| 148 | int res; |
| 149 | int offset; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 150 | |
| 151 | if (length) { |
| 152 | /* |
| 153 | * Datablock. |
| 154 | */ |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 155 | compressed = SQUASHFS_COMPRESSED_BLOCK(length); |
| 156 | length = SQUASHFS_COMPRESSED_SIZE_BLOCK(length); |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 157 | TRACE("Block @ 0x%llx, %scompressed size %d, src size %d\n", |
Phillip Lougher | 846b730 | 2013-11-18 02:59:12 +0000 | [diff] [blame] | 158 | index, compressed ? "" : "un", length, output->length); |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 159 | } else { |
| 160 | /* |
| 161 | * Metadata block. |
| 162 | */ |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 163 | const u8 *data; |
| 164 | struct bvec_iter_all iter_all = {}; |
| 165 | struct bio_vec *bvec = bvec_init_iter_all(&iter_all); |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 166 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 167 | if (index + 2 > msblk->bytes_used) { |
| 168 | res = -EIO; |
| 169 | goto out; |
| 170 | } |
| 171 | res = squashfs_bio_read(sb, index, 2, &bio, &offset); |
| 172 | if (res) |
| 173 | goto out; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 174 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 175 | if (WARN_ON_ONCE(!bio_next_segment(bio, &iter_all))) { |
| 176 | res = -EIO; |
| 177 | goto out_free_bio; |
| 178 | } |
| 179 | /* Extract the length of the metadata block */ |
| 180 | data = page_address(bvec->bv_page) + bvec->bv_offset; |
| 181 | length = data[offset]; |
Phillip Lougher | 2910c59 | 2020-07-23 21:15:40 -0700 | [diff] [blame] | 182 | if (offset < bvec->bv_len - 1) { |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 183 | length |= data[offset + 1] << 8; |
| 184 | } else { |
| 185 | if (WARN_ON_ONCE(!bio_next_segment(bio, &iter_all))) { |
| 186 | res = -EIO; |
| 187 | goto out_free_bio; |
| 188 | } |
| 189 | data = page_address(bvec->bv_page) + bvec->bv_offset; |
| 190 | length |= data[0] << 8; |
| 191 | } |
| 192 | bio_free_pages(bio); |
| 193 | bio_put(bio); |
| 194 | |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 195 | compressed = SQUASHFS_COMPRESSED(length); |
| 196 | length = SQUASHFS_COMPRESSED_SIZE(length); |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 197 | index += 2; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 198 | |
Phillip Lougher | e812cbb | 2021-02-09 13:41:50 -0800 | [diff] [blame] | 199 | TRACE("Block @ 0x%llx, %scompressed size %d\n", index - 2, |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 200 | compressed ? "" : "un", length); |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 201 | } |
Phillip Lougher | e812cbb | 2021-02-09 13:41:50 -0800 | [diff] [blame] | 202 | if (length < 0 || length > output->length || |
| 203 | (index + length) > msblk->bytes_used) { |
| 204 | res = -EIO; |
| 205 | goto out; |
| 206 | } |
| 207 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 208 | if (next_index) |
| 209 | *next_index = index + length; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 210 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 211 | res = squashfs_bio_read(sb, index, length, &bio, &offset); |
| 212 | if (res) |
| 213 | goto out; |
Phillip Lougher | 9508c6b | 2013-11-13 02:56:26 +0000 | [diff] [blame] | 214 | |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 215 | if (compressed) { |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 216 | if (!msblk->stream) { |
| 217 | res = -EIO; |
| 218 | goto out_free_bio; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 219 | } |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 220 | res = squashfs_decompress(msblk, bio, offset, length, output); |
| 221 | } else { |
| 222 | res = copy_bio_to_actor(bio, output, offset, length); |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 223 | } |
| 224 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 225 | out_free_bio: |
| 226 | bio_free_pages(bio); |
| 227 | bio_put(bio); |
| 228 | out: |
| 229 | if (res < 0) |
| 230 | ERROR("Failed to read block 0x%llx: %d\n", index, res); |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 231 | |
Philippe Liard | 93e72b3 | 2020-06-01 21:45:23 -0700 | [diff] [blame] | 232 | return res; |
Phillip Lougher | e2780ab1 | 2009-01-05 08:46:27 +0000 | [diff] [blame] | 233 | } |