diff options
Diffstat (limited to 'squashfs-tools/read_fs.c')
-rw-r--r-- | squashfs-tools/read_fs.c | 1090 |
1 files changed, 1090 insertions, 0 deletions
diff --git a/squashfs-tools/read_fs.c b/squashfs-tools/read_fs.c new file mode 100644 index 0000000..f9976c0 --- /dev/null +++ b/squashfs-tools/read_fs.c @@ -0,0 +1,1090 @@ +/* + * 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; +} |