block.c 5.58 KB
Newer Older
Phillip Lougher's avatar
Phillip Lougher committed
1 2 3 4
/*
 * Squashfs - a compressed read only filesystem for Linux
 *
 * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
5
 * Phillip Lougher <phillip@squashfs.org.uk>
Phillip Lougher's avatar
Phillip Lougher committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2,
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * block.c
 */

/*
 * This file implements the low-level routines to read and decompress
 * datablocks and metadata blocks.
 */

#include <linux/fs.h>
#include <linux/vfs.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/buffer_head.h>
34
#include <linux/bio.h>
Phillip Lougher's avatar
Phillip Lougher committed
35 36 37 38

#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs.h"
39
#include "decompressor.h"
40
#include "page_actor.h"
Phillip Lougher's avatar
Phillip Lougher committed
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

/*
 * Read the metadata block length, this is stored in the first two
 * bytes of the metadata block.
 */
static struct buffer_head *get_block_length(struct super_block *sb,
			u64 *cur_index, int *offset, int *length)
{
	struct squashfs_sb_info *msblk = sb->s_fs_info;
	struct buffer_head *bh;

	bh = sb_bread(sb, *cur_index);
	if (bh == NULL)
		return NULL;

	if (msblk->devblksize - *offset == 1) {
		*length = (unsigned char) bh->b_data[*offset];
		put_bh(bh);
		bh = sb_bread(sb, ++(*cur_index));
		if (bh == NULL)
			return NULL;
		*length |= (unsigned char) bh->b_data[0] << 8;
		*offset = 1;
	} else {
		*length = (unsigned char) bh->b_data[*offset] |
			(unsigned char) bh->b_data[*offset + 1] << 8;
		*offset += 2;
68 69 70 71 72 73 74 75

		if (*offset == msblk->devblksize) {
			put_bh(bh);
			bh = sb_bread(sb, ++(*cur_index));
			if (bh == NULL)
				return NULL;
			*offset = 0;
		}
Phillip Lougher's avatar
Phillip Lougher committed
76 77 78 79 80 81 82 83 84 85 86 87
	}

	return bh;
}


/*
 * Read and decompress a metadata block or datablock.  Length is non-zero
 * if a datablock is being read (the size is stored elsewhere in the
 * filesystem), otherwise the length is obtained from the first two bytes of
 * the metadata block.  A bit in the length field indicates if the block
 * is stored uncompressed in the filesystem (usually because compression
88 89
 * generated a larger block - this does occasionally happen with compression
 * algorithms).
Phillip Lougher's avatar
Phillip Lougher committed
90
 */
91 92
int squashfs_read_data(struct super_block *sb, u64 index, int length,
		u64 *next_index, struct squashfs_page_actor *output)
Phillip Lougher's avatar
Phillip Lougher committed
93 94 95 96 97
{
	struct squashfs_sb_info *msblk = sb->s_fs_info;
	struct buffer_head **bh;
	int offset = index & ((1 << msblk->devblksize_log2) - 1);
	u64 cur_index = index >> msblk->devblksize_log2;
98
	int bytes, compressed, b = 0, k = 0, avail, i;
Phillip Lougher's avatar
Phillip Lougher committed
99

100
	bh = kcalloc(((output->length + msblk->devblksize - 1)
101
		>> msblk->devblksize_log2) + 1, sizeof(*bh), GFP_KERNEL);
Phillip Lougher's avatar
Phillip Lougher committed
102 103 104 105 106 107 108 109 110 111 112 113 114 115
	if (bh == NULL)
		return -ENOMEM;

	if (length) {
		/*
		 * Datablock.
		 */
		bytes = -offset;
		compressed = SQUASHFS_COMPRESSED_BLOCK(length);
		length = SQUASHFS_COMPRESSED_SIZE_BLOCK(length);
		if (next_index)
			*next_index = index + length;

		TRACE("Block @ 0x%llx, %scompressed size %d, src size %d\n",
116
			index, compressed ? "" : "un", length, output->length);
Phillip Lougher's avatar
Phillip Lougher committed
117

118
		if (length < 0 || length > output->length ||
Phillip Lougher's avatar
Phillip Lougher committed
119 120 121 122 123 124 125 126 127
				(index + length) > msblk->bytes_used)
			goto read_failure;

		for (b = 0; bytes < length; b++, cur_index++) {
			bh[b] = sb_getblk(sb, cur_index);
			if (bh[b] == NULL)
				goto block_release;
			bytes += msblk->devblksize;
		}
128
		ll_rw_block(REQ_OP_READ, 0, b, bh);
Phillip Lougher's avatar
Phillip Lougher committed
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
	} else {
		/*
		 * Metadata block.
		 */
		if ((index + 2) > msblk->bytes_used)
			goto read_failure;

		bh[0] = get_block_length(sb, &cur_index, &offset, &length);
		if (bh[0] == NULL)
			goto read_failure;
		b = 1;

		bytes = msblk->devblksize - offset;
		compressed = SQUASHFS_COMPRESSED(length);
		length = SQUASHFS_COMPRESSED_SIZE(length);
		if (next_index)
			*next_index = index + length + 2;

		TRACE("Block @ 0x%llx, %scompressed size %d\n", index,
				compressed ? "" : "un", length);

150
		if (length < 0 || length > output->length ||
Phillip Lougher's avatar
Phillip Lougher committed
151 152 153 154 155 156 157 158 159
					(index + length) > msblk->bytes_used)
			goto block_release;

		for (; bytes < length; b++) {
			bh[b] = sb_getblk(sb, ++cur_index);
			if (bh[b] == NULL)
				goto block_release;
			bytes += msblk->devblksize;
		}
160
		ll_rw_block(REQ_OP_READ, 0, b - 1, bh + 1);
Phillip Lougher's avatar
Phillip Lougher committed
161 162
	}

163 164 165 166 167 168
	for (i = 0; i < b; i++) {
		wait_on_buffer(bh[i]);
		if (!buffer_uptodate(bh[i]))
			goto block_release;
	}

Phillip Lougher's avatar
Phillip Lougher committed
169
	if (compressed) {
170 171
		if (!msblk->stream)
			goto read_failure;
172 173
		length = squashfs_decompress(msblk, bh, b, offset, length,
			output);
174 175
		if (length < 0)
			goto read_failure;
Phillip Lougher's avatar
Phillip Lougher committed
176 177 178 179
	} else {
		/*
		 * Block is uncompressed.
		 */
180
		int in, pg_offset = 0;
181
		void *data = squashfs_first_page(output);
Phillip Lougher's avatar
Phillip Lougher committed
182 183 184 185 186

		for (bytes = length; k < b; k++) {
			in = min(bytes, msblk->devblksize - offset);
			bytes -= in;
			while (in) {
187
				if (pg_offset == PAGE_SIZE) {
188
					data = squashfs_next_page(output);
Phillip Lougher's avatar
Phillip Lougher committed
189 190
					pg_offset = 0;
				}
191
				avail = min_t(int, in, PAGE_SIZE -
Phillip Lougher's avatar
Phillip Lougher committed
192
						pg_offset);
193 194
				memcpy(data + pg_offset, bh[k]->b_data + offset,
						avail);
Phillip Lougher's avatar
Phillip Lougher committed
195 196 197 198 199 200 201
				in -= avail;
				pg_offset += avail;
				offset += avail;
			}
			offset = 0;
			put_bh(bh[k]);
		}
202
		squashfs_finish_page(output);
Phillip Lougher's avatar
Phillip Lougher committed
203 204 205 206 207 208 209 210 211 212
	}

	kfree(bh);
	return length;

block_release:
	for (; k < b; k++)
		put_bh(bh[k]);

read_failure:
213 214
	ERROR("squashfs_read_data failed to read block 0x%llx\n",
					(unsigned long long) index);
Phillip Lougher's avatar
Phillip Lougher committed
215 216 217
	kfree(bh);
	return -EIO;
}