block.c 5.55 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 34 35 36 37
 *
 * 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>

#include "squashfs_fs.h"
#include "squashfs_fs_sb.h"
#include "squashfs.h"
38
#include "decompressor.h"
39
#include "page_actor.h"
Phillip Lougher's avatar
Phillip Lougher committed
40 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

/*
 * 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;
67 68 69 70 71 72 73 74

		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
75 76 77 78 79 80 81 82 83 84 85 86
	}

	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
87 88
 * generated a larger block - this does occasionally happen with compression
 * algorithms).
Phillip Lougher's avatar
Phillip Lougher committed
89
 */
90 91
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
92 93 94 95 96
{
	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;
97
	int bytes, compressed, b = 0, k = 0, avail, i;
Phillip Lougher's avatar
Phillip Lougher committed
98

99
	bh = kcalloc(((output->length + msblk->devblksize - 1)
100
		>> msblk->devblksize_log2) + 1, sizeof(*bh), GFP_KERNEL);
Phillip Lougher's avatar
Phillip Lougher committed
101 102 103 104 105 106 107 108 109 110 111 112 113 114
	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",
115
			index, compressed ? "" : "un", length, output->length);
Phillip Lougher's avatar
Phillip Lougher committed
116

117
		if (length < 0 || length > output->length ||
Phillip Lougher's avatar
Phillip Lougher committed
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
				(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;
		}
		ll_rw_block(READ, b, bh);
	} 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);

149
		if (length < 0 || length > output->length ||
Phillip Lougher's avatar
Phillip Lougher committed
150 151 152 153 154 155 156 157 158 159 160 161
					(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;
		}
		ll_rw_block(READ, b - 1, bh + 1);
	}

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

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

	kfree(bh);
	return length;

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

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