/*
 * Read a squashfs filesystem.  This is a highly compressed read only
 * filesystem.
 *
 * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
 * 2012, 2013, 2014, 2019, 2021, 2022
 * Phillip Lougher <phillip@squashfs.org.uk>
 *
 * 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * read_fs.c
 */

#define TRUE 1
#define FALSE 0
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <limits.h>
#include <dirent.h>
#include <stdlib.h>
#include <regex.h>

#include "squashfs_fs.h"
#include "squashfs_swap.h"
#include "compressor.h"
#include "mksquashfs.h"
#include "xattr.h"
#include "mksquashfs_error.h"

int read_block(int fd, long long start, long long *next, int expected,
								void *block)
{
	unsigned short c_byte;
	int res, compressed;
	int outlen = expected ? expected : SQUASHFS_METADATA_SIZE;
	
	/* Read block size */
	res = read_fs_bytes(fd, start, 2, &c_byte);
	if(res == 0)
		return 0;

	SQUASHFS_INSWAP_SHORTS(&c_byte, 1);
	compressed = SQUASHFS_COMPRESSED(c_byte);
	c_byte = SQUASHFS_COMPRESSED_SIZE(c_byte);

	/*
	 * The block size should not be larger than
	 * the uncompressed size (or max uncompressed size if
	 * expected is 0)
	 */
	if (c_byte > outlen)
		return 0;

	if(compressed) {
		char buffer[c_byte];
		int error;

		res = read_fs_bytes(fd, start + 2, c_byte, buffer);
		if(res == 0)
			return 0;

		res = compressor_uncompress(comp, block, buffer, c_byte,
			outlen, &error);
		if(res == -1) {
			ERROR("%s uncompress failed with error code %d\n",
				comp->name, error);
			return 0;
		}
	} else {
		res = read_fs_bytes(fd, start + 2, c_byte, block);
		if(res == 0)
			return 0;
		res = c_byte;
	}

	if(next)
		*next = start + 2 + c_byte;

	/*
	 * if expected, then check the (uncompressed) return data
	 * is of the expected size
	 */
	if(expected && expected != res)
		return 0;
	else
		return res;
}


#define NO_BYTES(SIZE) \
	(bytes - (cur_ptr - inode_table) < (SIZE))

#define NO_INODE_BYTES(INODE) NO_BYTES(sizeof(struct INODE))

unsigned char *scan_inode_table(int fd, long long start, long long end,
	long long root_inode_start, int root_inode_offset,
	struct squashfs_super_block *sBlk, union squashfs_inode_header
	*dir_inode, long long *root_inode_block, unsigned int
	*root_inode_size, long long *uncompressed_file, long long
	*uncompressed_directory, unsigned int *file_count, unsigned int *sym_count,
	unsigned int *dev_count, unsigned int *dir_count, unsigned int *fifo_count,
	unsigned int *sock_count, unsigned int *id_table)
{
	unsigned char *cur_ptr;
	unsigned char *inode_table = NULL;
	int byte, files = 0;
	unsigned int directory_start_block;
	struct squashfs_base_inode_header base;
	long long alloc_size, bytes = 0, size = 0;

	TRACE("scan_inode_table: start 0x%llx, end 0x%llx, root_inode_start "
		"0x%llx\n", start, end, root_inode_start);

	/*
	 * Use the size of the compressed inode table as an initial
	 * memory allocation value, and the reallocation value, if
	 * this is too small.
	 *
	 * With a 50% compression ratio, this should require 2 alloc calls
	 * With a 25% compression ratio, this should require 4 alloc calls
	 * With a 12.5% compression ratio, this should require 8 alloc calls
	 *
	 * Always round to a multiple of SQUASHFS_METADATA_SIZE
	 */
	alloc_size = ((end - start) + SQUASHFS_METADATA_SIZE) & ~(SQUASHFS_METADATA_SIZE - 1);

	/* Rogue value used to check if it was found */
	*root_inode_block = -1LL;
	while(start < end) {
		if(start == root_inode_start) {
			TRACE("scan_inode_table: read compressed block 0x%llx "
				"containing root inode\n", start);
			*root_inode_block = bytes;
		}
		if(size - bytes < SQUASHFS_METADATA_SIZE) {
			inode_table = realloc(inode_table, size += alloc_size);
			if(inode_table == NULL)
				MEM_ERROR();
		}
		TRACE("scan_inode_table: reading block 0x%llx\n", start);
		byte = read_block(fd, start, &start, 0, inode_table + bytes);
		if(byte == 0)
			goto corrupted;

		bytes += byte;

		/* If this is not the last metadata block in the inode table
		 * then it should be SQUASHFS_METADATA_SIZE in size.
		 * Note, we can't use expected in read_block() above for this
		 * because we don't know if this is the last block until
		 * after reading.
		 */
		if(start != end && byte != SQUASHFS_METADATA_SIZE)
			goto corrupted;
	}

	/*
	 * We expect to have found the metadata block containing the
	 * root inode in the above inode_table metadata block scan.  If it
	 * hasn't been found then the filesystem is corrupted
	 */
	if(*root_inode_block == -1LL)
		goto corrupted;

	/*
	 * The number of bytes available after the root inode metadata block
	 * should be at least the root inode offset + the size of a
	 * regular directory inode, if not the filesystem is corrupted
	 *
	 *	+-----------------------+-----------------------+
	 *	| 			|        directory	|
	 *	|			|          inode	|
	 *	+-----------------------+-----------------------+
	 *	^			^			^
	 *	*root_inode_block	root_inode_offset	bytes
	 */
	if((bytes - *root_inode_block) < (root_inode_offset +
			sizeof(struct squashfs_dir_inode_header)))
		goto corrupted;

	/*
	 * Read the last inode in the inode table, which is the root directory
	 * inode, and get the directory start block.  This is used when
	 * calculating the uncompressed directory size.  The directory
	 * bytes in the last block will be counted as normal.
	 *
	 * Note, the previous check ensures the following calculation won't
	 * underflow, and we won't access beyond the buffer
	 */
	*root_inode_size = bytes - (*root_inode_block + root_inode_offset);
	bytes = *root_inode_block + root_inode_offset;
	SQUASHFS_SWAP_DIR_INODE_HEADER(inode_table + bytes, &dir_inode->dir);
	
	if(dir_inode->base.inode_type == SQUASHFS_DIR_TYPE) {
		directory_start_block = dir_inode->dir.start_block;
		if(*root_inode_size < sizeof(struct squashfs_dir_inode_header))
			/* corrupted filesystem */
			goto corrupted;
	} else if(dir_inode->base.inode_type == SQUASHFS_LDIR_TYPE) {
		if(*root_inode_size < sizeof(struct squashfs_ldir_inode_header))
			/* corrupted filesystem */
			goto corrupted;
		SQUASHFS_SWAP_LDIR_INODE_HEADER(inode_table + bytes,
			&dir_inode->ldir);
		directory_start_block = dir_inode->ldir.start_block;
	} else
		/* bad type, corrupted filesystem */
		goto corrupted;

	if(dir_inode->base.uid >= sBlk->no_ids) {
		ERROR("File system corrupted - uid index in inode too large (uid: %d)\n", dir_inode->base.uid);
		goto corrupted2;
	}

	if(dir_inode->base.guid >= sBlk->no_ids) {
		ERROR("File system corrupted - gid index in inode too large (gid: %d)\n", dir_inode->base.guid);
		goto corrupted2;
	}

	get_uid(id_table[dir_inode->base.uid]);
	get_guid(id_table[dir_inode->base.guid]);

	/* allocate fragment to file mapping table */
	file_mapping = calloc(sBlk->fragments, sizeof(struct append_file *));
	if(file_mapping == NULL)
		MEM_ERROR();

	for(cur_ptr = inode_table; cur_ptr < inode_table + bytes; files ++) {
		/*
		 * There should always be enough bytes to read the base
		 * inode header
		 */
		if(NO_INODE_BYTES(squashfs_base_inode_header))
			/* corrupted filesystem */
			goto corrupted;

		SQUASHFS_SWAP_BASE_INODE_HEADER(cur_ptr, &base);

		TRACE("scan_inode_table: processing inode @ byte position "
			"0x%x, type 0x%x\n",
			(unsigned int) (cur_ptr - inode_table),
			base.inode_type);

		if(base.uid >= sBlk->no_ids) {
			ERROR("File system corrupted - uid index in inode too large (uid: %d)\n", base.uid);
			goto corrupted2;
		}

		if(base.guid >= sBlk->no_ids) {
			ERROR("File system corrupted - gid index in inode too large (gid: %d)\n", base.guid);
			goto corrupted2;
		}

		get_uid(id_table[base.uid]);
		get_guid(id_table[base.guid]);

		switch(base.inode_type) {
		case SQUASHFS_FILE_TYPE: {
			struct squashfs_reg_inode_header inode;
			int frag_bytes, blocks, i;
			long long start, file_bytes = 0;
			unsigned int *block_list;

			/*
			 * There should always be enough bytes to read an
			 * inode of the expected type
			 */
			if(NO_INODE_BYTES(squashfs_reg_inode_header))
				/* corrupted filesystem */
				goto corrupted;

			SQUASHFS_SWAP_REG_INODE_HEADER(cur_ptr, &inode);

			frag_bytes = inode.fragment == SQUASHFS_INVALID_FRAG ?
				0 : inode.file_size % sBlk->block_size;
			blocks = inode.fragment == SQUASHFS_INVALID_FRAG ?
				(inode.file_size + sBlk->block_size - 1) >>
				sBlk->block_log : inode.file_size >>
				sBlk->block_log;
			start = inode.start_block;

			TRACE("scan_inode_table: regular file, file_size %d, "
				"blocks %d\n", inode.file_size, blocks);

			cur_ptr += sizeof(inode);

			if(NO_BYTES(blocks * sizeof(unsigned int)))
				/* corrupted filesystem */
				goto corrupted;

			block_list = malloc(blocks * sizeof(unsigned int));
			if(block_list == NULL)
				MEM_ERROR();

			SQUASHFS_SWAP_INTS(cur_ptr, block_list, blocks);

			*uncompressed_file += inode.file_size;
			(*file_count) ++;

			for(i = 0; i < blocks; i++)
				file_bytes +=
					SQUASHFS_COMPRESSED_SIZE_BLOCK
								(block_list[i]);

			if(inode.fragment != SQUASHFS_INVALID_FRAG &&
					inode.fragment >= sBlk->fragments) {
				free(block_list);
				goto corrupted;
			}

			add_file(start, inode.file_size, file_bytes,
				block_list, blocks, inode.fragment,
				inode.offset, frag_bytes);
				
			cur_ptr += blocks * sizeof(unsigned int);
			break;
		}	
		case SQUASHFS_LREG_TYPE: {
			struct squashfs_lreg_inode_header inode;
			int frag_bytes, blocks, i;
			long long start, file_bytes = 0;
			unsigned int *block_list;

			/*
			 * There should always be enough bytes to read an
			 * inode of the expected type
			 */
			if(NO_INODE_BYTES(squashfs_lreg_inode_header))
				/* corrupted filesystem */
				goto corrupted;

			SQUASHFS_SWAP_LREG_INODE_HEADER(cur_ptr, &inode);

			frag_bytes = inode.fragment == SQUASHFS_INVALID_FRAG ?
				0 : inode.file_size % sBlk->block_size;
			blocks = inode.fragment == SQUASHFS_INVALID_FRAG ?
				(inode.file_size + sBlk->block_size - 1) >>
				sBlk->block_log : inode.file_size >>
				sBlk->block_log;
			start = inode.start_block;

			TRACE("scan_inode_table: extended regular "
				"file, file_size %lld, blocks %d\n",
				inode.file_size, blocks);

			cur_ptr += sizeof(inode);

			if(NO_BYTES(blocks * sizeof(unsigned int)))
				/* corrupted filesystem */
				goto corrupted;

			block_list = malloc(blocks * sizeof(unsigned int));
			if(block_list == NULL)
				MEM_ERROR();

			SQUASHFS_SWAP_INTS(cur_ptr, block_list, blocks);

			*uncompressed_file += inode.file_size;
			(*file_count) ++;

			for(i = 0; i < blocks; i++)
				file_bytes +=
					SQUASHFS_COMPRESSED_SIZE_BLOCK
								(block_list[i]);

			if(inode.fragment != SQUASHFS_INVALID_FRAG &&
					inode.fragment >= sBlk->fragments) {
				free(block_list);
				goto corrupted;
			}

			add_file(start, inode.file_size, file_bytes,
				block_list, blocks, inode.fragment,
				inode.offset, frag_bytes);

			cur_ptr += blocks * sizeof(unsigned int);
			break;
		}	
		case SQUASHFS_SYMLINK_TYPE:
		case SQUASHFS_LSYMLINK_TYPE: {
			struct squashfs_symlink_inode_header inode;

			/*
			 * There should always be enough bytes to read an
			 * inode of the expected type
			 */
			if(NO_INODE_BYTES(squashfs_symlink_inode_header))
				/* corrupted filesystem */
				goto corrupted;

			SQUASHFS_SWAP_SYMLINK_INODE_HEADER(cur_ptr, &inode);

			(*sym_count) ++;

			cur_ptr += sizeof(inode);

			if (inode.inode_type == SQUASHFS_LSYMLINK_TYPE) {
				if(NO_BYTES(inode.symlink_size +
							sizeof(unsigned int)))
					/* corrupted filesystem */
					goto corrupted;
				cur_ptr += inode.symlink_size +
							sizeof(unsigned int);
			} else {
				if(NO_BYTES(inode.symlink_size))
					/* corrupted filesystem */
					goto corrupted;
				cur_ptr += inode.symlink_size;
			}
			break;
		}
		case SQUASHFS_DIR_TYPE: {
			struct squashfs_dir_inode_header dir_inode;

			/*
			 * There should always be enough bytes to read an
			 * inode of the expected type
			 */
			if(NO_INODE_BYTES(squashfs_dir_inode_header))
				/* corrupted filesystem */
				goto corrupted;
				
			SQUASHFS_SWAP_DIR_INODE_HEADER(cur_ptr, &dir_inode);

			if(dir_inode.start_block < directory_start_block)
				*uncompressed_directory += dir_inode.file_size;

			(*dir_count) ++;
			cur_ptr += sizeof(struct squashfs_dir_inode_header);
			break;
		}
		case SQUASHFS_LDIR_TYPE: {
			struct squashfs_ldir_inode_header dir_inode;
			int i;

			/*
			 * There should always be enough bytes to read an
			 * inode of the expected type
			 */
			if(NO_INODE_BYTES(squashfs_ldir_inode_header))
				/* corrupted filesystem */
				goto corrupted;

			SQUASHFS_SWAP_LDIR_INODE_HEADER(cur_ptr, &dir_inode);

			if(dir_inode.start_block < directory_start_block)
				*uncompressed_directory += dir_inode.file_size;

			(*dir_count) ++;
			cur_ptr += sizeof(struct squashfs_ldir_inode_header);

			/*
			 * Read and check the directory index for correctness
			 */
			for(i = 0; i < dir_inode.i_count; i++) {
				struct squashfs_dir_index index;

				if(NO_BYTES(sizeof(index)))
					/* corrupted filesystem */
					goto corrupted;
			
				SQUASHFS_SWAP_DIR_INDEX(cur_ptr, &index);

				cur_ptr += sizeof(index);

				if(NO_BYTES(index.size + 1))
					/* corrupted filesystem */
					goto corrupted;

				cur_ptr += index.size + 1;
			}
			break;
		}
	 	case SQUASHFS_BLKDEV_TYPE:
	 	case SQUASHFS_CHRDEV_TYPE:
			/*
			 * There should always be enough bytes to read an
			 * inode of the expected type
			 */
			if(NO_INODE_BYTES(squashfs_dev_inode_header))
				/* corrupted filesystem */
				goto corrupted;

			(*dev_count) ++;
			cur_ptr += sizeof(struct squashfs_dev_inode_header);
			break;
	 	case SQUASHFS_LBLKDEV_TYPE:
	 	case SQUASHFS_LCHRDEV_TYPE:
			/*
			 * There should always be enough bytes to read an
			 * inode of the expected type
			 */
			if(NO_INODE_BYTES(squashfs_ldev_inode_header))
				/* corrupted filesystem */
				goto corrupted;

			(*dev_count) ++;
			cur_ptr += sizeof(struct squashfs_ldev_inode_header);
			break;
		case SQUASHFS_FIFO_TYPE:
			/*
			 * There should always be enough bytes to read an
			 * inode of the expected type
			 */
			if(NO_INODE_BYTES(squashfs_ipc_inode_header))
				/* corrupted filesystem */
				goto corrupted;

			(*fifo_count) ++;
			cur_ptr += sizeof(struct squashfs_ipc_inode_header);
			break;
		case SQUASHFS_LFIFO_TYPE:
			/*
			 * There should always be enough bytes to read an
			 * inode of the expected type
			 */
			if(NO_INODE_BYTES(squashfs_lipc_inode_header))
				/* corrupted filesystem */
				goto corrupted;

			(*fifo_count) ++;
			cur_ptr += sizeof(struct squashfs_lipc_inode_header);
			break;
		case SQUASHFS_SOCKET_TYPE:
			/*
			 * There should always be enough bytes to read an
			 * inode of the expected type
			 */
			if(NO_INODE_BYTES(squashfs_ipc_inode_header))
				/* corrupted filesystem */
				goto corrupted;

			(*sock_count) ++;
			cur_ptr += sizeof(struct squashfs_ipc_inode_header);
			break;
		case SQUASHFS_LSOCKET_TYPE:
			/*
			 * There should always be enough bytes to read an
			 * inode of the expected type
			 */
			if(NO_INODE_BYTES(squashfs_lipc_inode_header))
				/* corrupted filesystem */
				goto corrupted;

			(*sock_count) ++;
			cur_ptr += sizeof(struct squashfs_lipc_inode_header);
			break;
	 	default:
			ERROR("Unknown inode type %d in scan_inode_table!\n",
					base.inode_type);
			goto corrupted;
		}
	}
	
	if(!quiet)
		printf("Read existing filesystem, %d inodes scanned\n", files);

	return inode_table;

corrupted:
	ERROR("scan_inode_table: filesystem corruption detected in "
		"scanning metadata\n");
corrupted2:
	free(inode_table);
	return NULL;
}


struct compressor *read_super(int fd, struct squashfs_super_block *sBlk, char *source)
{
	int res, bytes = 0;
	char buffer[SQUASHFS_METADATA_SIZE] __attribute__ ((aligned));

	res = read_fs_bytes(fd, SQUASHFS_START, sizeof(struct squashfs_super_block),
		sBlk);
	if(res == 0) {
		ERROR("Can't find a SQUASHFS superblock on %s\n",
				source);
		ERROR("Wrong filesystem or filesystem is corrupted!\n");
		goto failed_mount;
	}

	SQUASHFS_INSWAP_SUPER_BLOCK(sBlk);

	if(sBlk->s_magic != SQUASHFS_MAGIC) {
		if(sBlk->s_magic == SQUASHFS_MAGIC_SWAP)
			ERROR("Pre 4.0 big-endian filesystem on %s, appending"
				" to this is unsupported\n", source);
		else {
			ERROR("Can't find a SQUASHFS superblock on %s\n",
				source);
			ERROR("Wrong filesystem or filesystem is corrupted!\n");
		}
		goto failed_mount;
	}

	/* Check the MAJOR & MINOR versions */
	if(sBlk->s_major != SQUASHFS_MAJOR || sBlk->s_minor > SQUASHFS_MINOR) {
		if(sBlk->s_major < 4)
			ERROR("Filesystem on %s is a SQUASHFS %d.%d filesystem."
				"  Appending\nto SQUASHFS %d.%d filesystems is "
				"not supported.  Please convert it to a "
				"SQUASHFS 4 filesystem\n", source,
				sBlk->s_major,
				sBlk->s_minor, sBlk->s_major, sBlk->s_minor);
		else
			ERROR("Filesystem on %s is %d.%d, which is a later "
				"filesystem version than I support\n",
				source, sBlk->s_major, sBlk->s_minor);
		goto failed_mount;
	}

	/* Check the compression type */
	comp = lookup_compressor_id(sBlk->compression);
	if(!comp->supported) {
		ERROR("Filesystem on %s uses %s compression, this is "
			"unsupported by this version\n", source, comp->name);
		ERROR("Compressors available:\n");
		display_compressors(stderr, "", "");
		goto failed_mount;
	}

	/*
	 * Read extended superblock information from disk.
	 *
	 * Read compressor specific options from disk if present, and pass
	 * to compressor to set compressor options.
	 *
	 * Note, if there's no compressor options present, the compressor
	 * is still called to set the default options (the defaults may have
	 * been changed by the user specifying options on the command
	 * line which need to be over-ridden).
	 *
	 * Compressor_extract_options is also used to ensure that 
	 * we know how to decompress a filesystem compressed with these
	 * compression options.
	 */
	if(SQUASHFS_COMP_OPTS(sBlk->flags)) {
		bytes = read_block(fd, sizeof(*sBlk), NULL, 0, buffer);

		if(bytes == 0) {
			ERROR("Failed to read compressor options from append "
				"filesystem\n");
			ERROR("Filesystem corrupted?\n");
			goto failed_mount;
		}
	}

	res = compressor_extract_options(comp, sBlk->block_size, buffer, bytes);
	if(res == -1) {
		ERROR("Compressor failed to set compressor options\n");
		goto failed_mount;
	}

	if(quiet)
		return comp;

	printf("Found a valid %sSQUASHFS superblock on %s.\n",
		SQUASHFS_EXPORTABLE(sBlk->flags) ? "exportable " : "", source);
	printf("\tCompression used %s\n", comp->name);
	printf("\tInodes are %scompressed\n",
		SQUASHFS_UNCOMPRESSED_INODES(sBlk->flags) ? "un" : "");
	printf("\tData is %scompressed\n",
		SQUASHFS_UNCOMPRESSED_DATA(sBlk->flags) ? "un" : "");
	printf("\tFragments are %scompressed\n",
		SQUASHFS_UNCOMPRESSED_FRAGMENTS(sBlk->flags) ? "un" : "");
	printf("\tXattrs are %scompressed\n",
		SQUASHFS_UNCOMPRESSED_XATTRS(sBlk->flags) ? "un" : "");
	printf("\tFragments are %spresent in the filesystem\n",
		SQUASHFS_NO_FRAGMENTS(sBlk->flags) ? "not " : "");
	printf("\tAlways-use-fragments option is %sspecified\n",
		SQUASHFS_ALWAYS_FRAGMENTS(sBlk->flags) ? "" : "not ");
	printf("\tDuplicates are %sremoved\n",
		SQUASHFS_DUPLICATES(sBlk->flags) ? "" : "not ");
	printf("\tXattrs are %sstored\n",
		SQUASHFS_NO_XATTRS(sBlk->flags) ? "not " : "");
	printf("\tFilesystem size %.2f Kbytes (%.2f Mbytes)\n",
		sBlk->bytes_used / 1024.0, sBlk->bytes_used
		/ (1024.0 * 1024.0));
	printf("\tBlock size %d\n", sBlk->block_size);
	printf("\tNumber of fragments %u\n", sBlk->fragments);
	printf("\tNumber of inodes %d\n", sBlk->inodes);
	printf("\tNumber of ids %d\n", sBlk->no_ids);
	TRACE("sBlk->inode_table_start %llx\n", sBlk->inode_table_start);
	TRACE("sBlk->directory_table_start %llx\n",
		sBlk->directory_table_start);
	TRACE("sBlk->id_table_start %llx\n", sBlk->id_table_start);
	TRACE("sBlk->fragment_table_start %llx\n", sBlk->fragment_table_start);
	TRACE("sBlk->lookup_table_start %llx\n", sBlk->lookup_table_start);
	TRACE("sBlk->xattr_id_table_start %llx\n", sBlk->xattr_id_table_start);
	printf("\n");

	return comp;

failed_mount:
	return NULL;
}


static unsigned char *squashfs_readdir(int fd, int root_entries,
	unsigned int directory_start_block, int offset, unsigned int dir_size,
	unsigned int *last_directory_block, struct squashfs_super_block *sBlk,
	void (push_directory_entry)(char *, squashfs_inode, unsigned int, int))
{
	struct squashfs_dir_header dirh;
	char buffer[sizeof(struct squashfs_dir_entry) + SQUASHFS_NAME_LEN + 1]
		__attribute__ ((aligned));
	struct squashfs_dir_entry *dire = (struct squashfs_dir_entry *) buffer;
	unsigned char *directory_table = NULL;
	int byte, dir_count;
	long long start = sBlk->directory_table_start + directory_start_block;
	long long last_start_block = start, size = dir_size, bytes = 0;

	size += offset;
	directory_table = malloc((size + SQUASHFS_METADATA_SIZE * 2 - 1) &
		~(SQUASHFS_METADATA_SIZE - 1));
	if(directory_table == NULL)
		MEM_ERROR();

	while(bytes < size) {
		int expected = (size - bytes) >= SQUASHFS_METADATA_SIZE ?
			SQUASHFS_METADATA_SIZE : 0;

		TRACE("squashfs_readdir: reading block 0x%llx, bytes read so "
			"far %lld\n", start, bytes);

		last_start_block = start;
		byte = read_block(fd, start, &start, expected, directory_table + bytes);
		if(byte == 0) {
			ERROR("Failed to read directory\n");
			ERROR("Filesystem corrupted?\n");
			free(directory_table);
			return NULL;
		}
		bytes += byte;
	}

	if(!root_entries)
		goto all_done;

	bytes = offset;
 	while(bytes < size) {			
		SQUASHFS_SWAP_DIR_HEADER(directory_table + bytes, &dirh);

		dir_count = dirh.count + 1;

		/* dir_count should never be larger than SQUASHFS_DIR_COUNT */
		if(dir_count > SQUASHFS_DIR_COUNT) {
			ERROR("File system corrupted: too many entries in directory\n");
			free(directory_table);
			return NULL;
		}

		TRACE("squashfs_readdir: Read directory header @ byte position "
			"0x%llx, 0x%x directory entries\n", bytes, dir_count);
		bytes += sizeof(dirh);

		while(dir_count--) {
			SQUASHFS_SWAP_DIR_ENTRY(directory_table + bytes, dire);
			bytes += sizeof(*dire);

			/* size should never be SQUASHFS_NAME_LEN or larger */
			if(dire->size >= SQUASHFS_NAME_LEN) {
				ERROR("File system corrupted: filename too long\n");
				free(directory_table);
				return NULL;
			}

			memcpy(dire->name, directory_table + bytes,
				dire->size + 1);
			dire->name[dire->size + 1] = '\0';
			TRACE("squashfs_readdir: pushing directory entry %s, "
				"inode %x:%x, type 0x%x\n", dire->name,
				dirh.start_block, dire->offset, dire->type);
			push_directory_entry(dire->name,
				SQUASHFS_MKINODE(dirh.start_block,
				dire->offset), dirh.inode_number +
				dire->inode_number, dire->type);
			bytes += dire->size + 1;
		}
	}

all_done:
	*last_directory_block = (unsigned int) last_start_block -
		sBlk->directory_table_start;
	return directory_table;
}


static unsigned int *read_id_table(int fd, struct squashfs_super_block *sBlk)
{
	int indexes = SQUASHFS_ID_BLOCKS(sBlk->no_ids);
	long long index[indexes];
	int bytes = SQUASHFS_ID_BYTES(sBlk->no_ids);
	unsigned int *id_table;
	int res, i;

	id_table = malloc(bytes);
	if(id_table == NULL)
		MEM_ERROR();

	res = read_fs_bytes(fd, sBlk->id_table_start,
		SQUASHFS_ID_BLOCK_BYTES(sBlk->no_ids), index);
	if(res == 0) {
		ERROR("Failed to read id table index\n");
		ERROR("Filesystem corrupted?\n");
		free(id_table);
		return NULL;
	}

	SQUASHFS_INSWAP_ID_BLOCKS(index, indexes);

	for(i = 0; i < indexes; i++) {
		int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
					bytes & (SQUASHFS_METADATA_SIZE - 1);
		int length = read_block(fd, index[i], NULL, expected,
			((unsigned char *) id_table) +
			(i * SQUASHFS_METADATA_SIZE));
		TRACE("Read id table block %d, from 0x%llx, length %d\n", i,
			index[i], length);
		if(length == 0) {
			ERROR("Failed to read id table block %d, from 0x%llx, "
				"length %d\n", i, index[i], length);
			ERROR("Filesystem corrupted?\n");
			free(id_table);
			return NULL;
		}
	}

	SQUASHFS_INSWAP_INTS(id_table, sBlk->no_ids);

	for(i = 0; i < sBlk->no_ids; i++) {
		TRACE("Adding id %d to id tables\n", id_table[i]);
		create_id(id_table[i]);
	}

	return id_table;
}


static struct squashfs_fragment_entry *read_fragment_table(int fd, struct squashfs_super_block *sBlk)
{
	int res;
	unsigned int i;
	long long bytes = SQUASHFS_FRAGMENT_BYTES(sBlk->fragments);
	int indexes = SQUASHFS_FRAGMENT_INDEXES(sBlk->fragments);
	long long fragment_table_index[indexes];
	struct squashfs_fragment_entry *fragment_table;

	TRACE("read_fragment_table: %u fragments, reading %d fragment indexes "
		"from 0x%llx\n", sBlk->fragments, indexes,
		sBlk->fragment_table_start);

	fragment_table = malloc(bytes);
	if(fragment_table == NULL)
		MEM_ERROR();

	res = read_fs_bytes(fd, sBlk->fragment_table_start,
		SQUASHFS_FRAGMENT_INDEX_BYTES(sBlk->fragments),
		fragment_table_index);
	if(res == 0) {
		ERROR("Failed to read fragment table index\n");
		ERROR("Filesystem corrupted?\n");
		free(fragment_table);
		return NULL;
	}

	SQUASHFS_INSWAP_FRAGMENT_INDEXES(fragment_table_index, indexes);

	for(i = 0; i < indexes; i++) {
		int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
					bytes & (SQUASHFS_METADATA_SIZE - 1);
		int length = read_block(fd, fragment_table_index[i], NULL,
			expected, ((unsigned char *) fragment_table) +
			(i * SQUASHFS_METADATA_SIZE));
		TRACE("Read fragment table block %d, from 0x%llx, length %d\n",
			i, fragment_table_index[i], length);
		if(length == 0) {
			ERROR("Failed to read fragment table block %d, from "
				"0x%llx, length %d\n", i,
				fragment_table_index[i], length);
			ERROR("Filesystem corrupted?\n");
			free(fragment_table);
			return NULL;
		}
	}

	for(i = 0; i < sBlk->fragments; i++)
		SQUASHFS_INSWAP_FRAGMENT_ENTRY(&fragment_table[i]);

	return fragment_table;
}


static squashfs_inode *read_inode_lookup_table(int fd, struct squashfs_super_block *sBlk)
{
	int lookup_bytes = SQUASHFS_LOOKUP_BYTES(sBlk->inodes);
	int indexes = SQUASHFS_LOOKUP_BLOCKS(sBlk->inodes);
	long long index[indexes];
	int res, i;
	squashfs_inode *inode_lookup_table;

	inode_lookup_table = malloc(lookup_bytes);
	if(inode_lookup_table == NULL)
		MEM_ERROR();

	res = read_fs_bytes(fd, sBlk->lookup_table_start,
		SQUASHFS_LOOKUP_BLOCK_BYTES(sBlk->inodes), index);
	if(res == 0) {
		ERROR("Failed to read inode lookup table index\n");
		ERROR("Filesystem corrupted?\n");
		free(inode_lookup_table);
		return NULL;
	}

	SQUASHFS_INSWAP_LONG_LONGS(index, indexes);

	for(i = 0; i <  indexes; i++) {
		int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
				lookup_bytes & (SQUASHFS_METADATA_SIZE - 1);
		int length = read_block(fd, index[i], NULL, expected,
			((unsigned char *) inode_lookup_table) +
			(i * SQUASHFS_METADATA_SIZE));
		TRACE("Read inode lookup table block %d, from 0x%llx, length "
			"%d\n", i, index[i], length);
		if(length == 0) {
			ERROR("Failed to read inode lookup table block %d, "
				"from 0x%llx, length %d\n", i, index[i],
				length);
			ERROR("Filesystem corrupted?\n");
			free(inode_lookup_table);
			return NULL;
		}
	}

	SQUASHFS_INSWAP_LONG_LONGS(inode_lookup_table, sBlk->inodes);

	return inode_lookup_table;
}


long long read_filesystem(char *root_name, int fd, struct squashfs_super_block *sBlk,
	char **cinode_table, char **data_cache, char **cdirectory_table,
	char **directory_data_cache, unsigned int *last_directory_block,
	int *inode_dir_offset, unsigned int *inode_dir_file_size,
	unsigned int *root_inode_size, unsigned int *inode_dir_start_block,
	unsigned int *file_count, unsigned int *sym_count, unsigned int *dev_count,
	unsigned int *dir_count, unsigned int *fifo_count, unsigned int *sock_count,
	long long *uncompressed_file, long long *uncompressed_inode,
	long long *uncompressed_directory, unsigned int *inode_dir_inode_number,
	unsigned int *inode_dir_parent_inode,
	void (push_directory_entry)(char *, squashfs_inode, unsigned int, int),
	struct squashfs_fragment_entry **fragment_table,
	squashfs_inode **inode_lookup_table)
{
	unsigned char *inode_table = NULL, *directory_table = NULL;
	long long start = sBlk->inode_table_start;
	long long end = sBlk->directory_table_start;
	long long root_inode_start = start +
		SQUASHFS_INODE_BLK(sBlk->root_inode);
	unsigned int root_inode_offset =
		SQUASHFS_INODE_OFFSET(sBlk->root_inode);
	long long root_inode_block;
	union squashfs_inode_header inode;
	unsigned int *id_table = NULL;
	int res;

	if(!quiet)
		printf("Scanning existing filesystem...\n");

	if(get_xattrs(fd, sBlk) == 0)
		goto error;

	if(sBlk->fragments > 0) {
		*fragment_table = read_fragment_table(fd, sBlk);
		if(*fragment_table == NULL)
			goto error;
	}

	if(sBlk->lookup_table_start != SQUASHFS_INVALID_BLK) {
		*inode_lookup_table = read_inode_lookup_table(fd, sBlk);
		if(*inode_lookup_table == NULL)
			goto error;
	}

	id_table = read_id_table(fd, sBlk);
	if(id_table == NULL)
		goto error;

	inode_table = scan_inode_table(fd, start, end, root_inode_start,
		root_inode_offset, sBlk, &inode, &root_inode_block,
		root_inode_size, uncompressed_file, uncompressed_directory,
		file_count, sym_count, dev_count, dir_count, fifo_count,
		sock_count, id_table);
	if(inode_table == NULL)
		goto error;

	*uncompressed_inode = root_inode_block;

	if(inode.base.inode_type == SQUASHFS_DIR_TYPE ||
			inode.base.inode_type == SQUASHFS_LDIR_TYPE) {
		if(inode.base.inode_type == SQUASHFS_DIR_TYPE) {
			*inode_dir_start_block = inode.dir.start_block;
			*inode_dir_offset = inode.dir.offset;
			*inode_dir_file_size = inode.dir.file_size - 3;
			*inode_dir_inode_number = inode.dir.inode_number;
			*inode_dir_parent_inode = inode.dir.parent_inode;
		} else {
			*inode_dir_start_block = inode.ldir.start_block;
			*inode_dir_offset = inode.ldir.offset;
			*inode_dir_file_size = inode.ldir.file_size - 3;
			*inode_dir_inode_number = inode.ldir.inode_number;
			*inode_dir_parent_inode = inode.ldir.parent_inode;
		}

		directory_table = squashfs_readdir(fd, !root_name,
			*inode_dir_start_block, *inode_dir_offset,
			*inode_dir_file_size, last_directory_block, sBlk,
			push_directory_entry);
		if(directory_table == NULL) 
			goto error;

		root_inode_start -= start;
		*cinode_table = malloc(root_inode_start);
		if(*cinode_table == NULL)
			MEM_ERROR();

	       	res = read_fs_bytes(fd, start, root_inode_start, *cinode_table);
		if(res == 0) {
			ERROR("Failed to read inode table\n");
			ERROR("Filesystem corrupted?\n");
			goto error;
		}

		*cdirectory_table = malloc(*last_directory_block);
		if(*cdirectory_table == NULL)
			MEM_ERROR();

		res = read_fs_bytes(fd, sBlk->directory_table_start,
			*last_directory_block, *cdirectory_table);
		if(res == 0) {
			ERROR("Failed to read directory table\n");
			ERROR("Filesystem corrupted?\n");
			goto error;
		}

		*data_cache = malloc(root_inode_offset + *root_inode_size);
		if(*data_cache == NULL)
			MEM_ERROR();

		memcpy(*data_cache, inode_table + root_inode_block,
			root_inode_offset + *root_inode_size);

		*directory_data_cache = malloc(*inode_dir_offset +
			*inode_dir_file_size);
		if(*directory_data_cache == NULL)
			MEM_ERROR();

		memcpy(*directory_data_cache, directory_table,
			*inode_dir_offset + *inode_dir_file_size);

		free(id_table);
		free(inode_table);
		free(directory_table);
		return sBlk->inode_table_start;
	}

error:
	free(id_table);
	free(inode_table);
	free(directory_table);
	return 0;
}
