1/*
2 * Unsquash a squashfs filesystem.  This is a highly compressed read only
3 * filesystem.
4 *
5 * Copyright (c) 2009, 2010, 2011, 2012, 2013
6 * Phillip Lougher <phillip@squashfs.org.uk>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2,
11 * or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 *
22 * unsquash-3.c
23 */
24
25#include "unsquashfs.h"
26#include "squashfs_compat.h"
27
28static squashfs_fragment_entry_3 *fragment_table;
29
30int read_fragment_table_3(long long *directory_table_end)
31{
32	int res, i;
33	int bytes = SQUASHFS_FRAGMENT_BYTES_3(sBlk.s.fragments);
34	int indexes = SQUASHFS_FRAGMENT_INDEXES_3(sBlk.s.fragments);
35	long long fragment_table_index[indexes];
36
37	TRACE("read_fragment_table: %d fragments, reading %d fragment indexes "
38		"from 0x%llx\n", sBlk.s.fragments, indexes,
39		sBlk.s.fragment_table_start);
40
41	if(sBlk.s.fragments == 0) {
42		*directory_table_end = sBlk.s.fragment_table_start;
43		return TRUE;
44	}
45
46	fragment_table = malloc(bytes);
47	if(fragment_table == NULL)
48		EXIT_UNSQUASH("read_fragment_table: failed to allocate "
49			"fragment table\n");
50
51	if(swap) {
52		long long sfragment_table_index[indexes];
53
54		res = read_fs_bytes(fd, sBlk.s.fragment_table_start,
55			SQUASHFS_FRAGMENT_INDEX_BYTES_3(sBlk.s.fragments),
56			sfragment_table_index);
57		if(res == FALSE) {
58			ERROR("read_fragment_table: failed to read fragment "
59				"table index\n");
60			return FALSE;
61		}
62		SQUASHFS_SWAP_FRAGMENT_INDEXES_3(fragment_table_index,
63			sfragment_table_index, indexes);
64	} else {
65		res = read_fs_bytes(fd, sBlk.s.fragment_table_start,
66			SQUASHFS_FRAGMENT_INDEX_BYTES_3(sBlk.s.fragments),
67			fragment_table_index);
68		if(res == FALSE) {
69			ERROR("read_fragment_table: failed to read fragment "
70				"table index\n");
71			return FALSE;
72		}
73	}
74
75	for(i = 0; i < indexes; i++) {
76		int expected = (i + 1) != indexes ? SQUASHFS_METADATA_SIZE :
77					bytes & (SQUASHFS_METADATA_SIZE - 1);
78		int length = read_block(fd, fragment_table_index[i], NULL,
79			expected, ((char *) fragment_table) + (i *
80			SQUASHFS_METADATA_SIZE));
81		TRACE("Read fragment table block %d, from 0x%llx, length %d\n",
82			i, fragment_table_index[i], length);
83		if(length == FALSE) {
84			ERROR("read_fragment_table: failed to read fragment "
85				"table block\n");
86			return FALSE;
87		}
88	}
89
90	if(swap) {
91		squashfs_fragment_entry_3 sfragment;
92		for(i = 0; i < sBlk.s.fragments; i++) {
93			SQUASHFS_SWAP_FRAGMENT_ENTRY_3((&sfragment),
94				(&fragment_table[i]));
95			memcpy((char *) &fragment_table[i], (char *) &sfragment,
96				sizeof(squashfs_fragment_entry_3));
97		}
98	}
99
100	*directory_table_end = fragment_table_index[0];
101	return TRUE;
102}
103
104
105void read_fragment_3(unsigned int fragment, long long *start_block, int *size)
106{
107	TRACE("read_fragment: reading fragment %d\n", fragment);
108
109	squashfs_fragment_entry_3 *fragment_entry = &fragment_table[fragment];
110	*start_block = fragment_entry->start_block;
111	*size = fragment_entry->size;
112}
113
114
115struct inode *read_inode_3(unsigned int start_block, unsigned int offset)
116{
117	static union squashfs_inode_header_3 header;
118	long long start = sBlk.s.inode_table_start + start_block;
119	int bytes = lookup_entry(inode_table_hash, start);
120	char *block_ptr = inode_table + bytes + offset;
121	static struct inode i;
122
123	TRACE("read_inode: reading inode [%d:%d]\n", start_block,  offset);
124
125	if(bytes == -1)
126		EXIT_UNSQUASH("read_inode: inode table block %lld not found\n",
127			start);
128
129	if(swap) {
130		squashfs_base_inode_header_3 sinode;
131		memcpy(&sinode, block_ptr, sizeof(header.base));
132		SQUASHFS_SWAP_BASE_INODE_HEADER_3(&header.base, &sinode,
133			sizeof(squashfs_base_inode_header_3));
134	} else
135		memcpy(&header.base, block_ptr, sizeof(header.base));
136
137	i.xattr = SQUASHFS_INVALID_XATTR;
138	i.uid = (uid_t) uid_table[header.base.uid];
139	i.gid = header.base.guid == SQUASHFS_GUIDS ? i.uid :
140		(uid_t) guid_table[header.base.guid];
141	i.mode = lookup_type[header.base.inode_type] | header.base.mode;
142	i.type = header.base.inode_type;
143	i.time = header.base.mtime;
144	i.inode_number = header.base.inode_number;
145
146	switch(header.base.inode_type) {
147		case SQUASHFS_DIR_TYPE: {
148			squashfs_dir_inode_header_3 *inode = &header.dir;
149
150			if(swap) {
151				squashfs_dir_inode_header_3 sinode;
152				memcpy(&sinode, block_ptr, sizeof(header.dir));
153				SQUASHFS_SWAP_DIR_INODE_HEADER_3(&header.dir,
154					&sinode);
155			} else
156				memcpy(&header.dir, block_ptr,
157					sizeof(header.dir));
158
159			i.data = inode->file_size;
160			i.offset = inode->offset;
161			i.start = inode->start_block;
162			break;
163		}
164		case SQUASHFS_LDIR_TYPE: {
165			squashfs_ldir_inode_header_3 *inode = &header.ldir;
166
167			if(swap) {
168				squashfs_ldir_inode_header_3 sinode;
169				memcpy(&sinode, block_ptr, sizeof(header.ldir));
170				SQUASHFS_SWAP_LDIR_INODE_HEADER_3(&header.ldir,
171					&sinode);
172			} else
173				memcpy(&header.ldir, block_ptr,
174					sizeof(header.ldir));
175
176			i.data = inode->file_size;
177			i.offset = inode->offset;
178			i.start = inode->start_block;
179			break;
180		}
181		case SQUASHFS_FILE_TYPE: {
182			squashfs_reg_inode_header_3 *inode = &header.reg;
183
184			if(swap) {
185				squashfs_reg_inode_header_3 sinode;
186				memcpy(&sinode, block_ptr, sizeof(sinode));
187				SQUASHFS_SWAP_REG_INODE_HEADER_3(inode,
188					&sinode);
189			} else
190				memcpy(inode, block_ptr, sizeof(*inode));
191
192			i.data = inode->file_size;
193			i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
194				?  0 : inode->file_size % sBlk.s.block_size;
195			i.fragment = inode->fragment;
196			i.offset = inode->offset;
197			i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
198				(i.data + sBlk.s.block_size - 1) >>
199				sBlk.s.block_log :
200				i.data >> sBlk.s.block_log;
201			i.start = inode->start_block;
202			i.sparse = 1;
203			i.block_ptr = block_ptr + sizeof(*inode);
204			break;
205		}
206		case SQUASHFS_LREG_TYPE: {
207			squashfs_lreg_inode_header_3 *inode = &header.lreg;
208
209			if(swap) {
210				squashfs_lreg_inode_header_3 sinode;
211				memcpy(&sinode, block_ptr, sizeof(sinode));
212				SQUASHFS_SWAP_LREG_INODE_HEADER_3(inode,
213					&sinode);
214			} else
215				memcpy(inode, block_ptr, sizeof(*inode));
216
217			i.data = inode->file_size;
218			i.frag_bytes = inode->fragment == SQUASHFS_INVALID_FRAG
219				?  0 : inode->file_size % sBlk.s.block_size;
220			i.fragment = inode->fragment;
221			i.offset = inode->offset;
222			i.blocks = inode->fragment == SQUASHFS_INVALID_FRAG ?
223				(inode->file_size + sBlk.s.block_size - 1) >>
224				sBlk.s.block_log :
225				inode->file_size >> sBlk.s.block_log;
226			i.start = inode->start_block;
227			i.sparse = 1;
228			i.block_ptr = block_ptr + sizeof(*inode);
229			break;
230		}
231		case SQUASHFS_SYMLINK_TYPE: {
232			squashfs_symlink_inode_header_3 *inodep =
233				&header.symlink;
234
235			if(swap) {
236				squashfs_symlink_inode_header_3 sinodep;
237				memcpy(&sinodep, block_ptr, sizeof(sinodep));
238				SQUASHFS_SWAP_SYMLINK_INODE_HEADER_3(inodep,
239					&sinodep);
240			} else
241				memcpy(inodep, block_ptr, sizeof(*inodep));
242
243			i.symlink = malloc(inodep->symlink_size + 1);
244			if(i.symlink == NULL)
245				EXIT_UNSQUASH("read_inode: failed to malloc "
246					"symlink data\n");
247			strncpy(i.symlink, block_ptr +
248				sizeof(squashfs_symlink_inode_header_3),
249				inodep->symlink_size);
250			i.symlink[inodep->symlink_size] = '\0';
251			i.data = inodep->symlink_size;
252			break;
253		}
254 		case SQUASHFS_BLKDEV_TYPE:
255	 	case SQUASHFS_CHRDEV_TYPE: {
256			squashfs_dev_inode_header_3 *inodep = &header.dev;
257
258			if(swap) {
259				squashfs_dev_inode_header_3 sinodep;
260				memcpy(&sinodep, block_ptr, sizeof(sinodep));
261				SQUASHFS_SWAP_DEV_INODE_HEADER_3(inodep,
262					&sinodep);
263			} else
264				memcpy(inodep, block_ptr, sizeof(*inodep));
265
266			i.data = inodep->rdev;
267			break;
268			}
269		case SQUASHFS_FIFO_TYPE:
270		case SQUASHFS_SOCKET_TYPE:
271			i.data = 0;
272			break;
273		default:
274			EXIT_UNSQUASH("Unknown inode type %d in read_inode!\n",
275				header.base.inode_type);
276	}
277	return &i;
278}
279
280
281struct dir *squashfs_opendir_3(unsigned int block_start, unsigned int offset,
282	struct inode **i)
283{
284	squashfs_dir_header_3 dirh;
285	char buffer[sizeof(squashfs_dir_entry_3) + SQUASHFS_NAME_LEN + 1]
286		__attribute__((aligned));
287	squashfs_dir_entry_3 *dire = (squashfs_dir_entry_3 *) buffer;
288	long long start;
289	int bytes;
290	int dir_count, size;
291	struct dir_ent *new_dir;
292	struct dir *dir;
293
294	TRACE("squashfs_opendir: inode start block %d, offset %d\n",
295		block_start, offset);
296
297	*i = s_ops.read_inode(block_start, offset);
298
299	dir = malloc(sizeof(struct dir));
300	if(dir == NULL)
301		EXIT_UNSQUASH("squashfs_opendir: malloc failed!\n");
302
303	dir->dir_count = 0;
304	dir->cur_entry = 0;
305	dir->mode = (*i)->mode;
306	dir->uid = (*i)->uid;
307	dir->guid = (*i)->gid;
308	dir->mtime = (*i)->time;
309	dir->xattr = (*i)->xattr;
310	dir->dirs = NULL;
311
312	if ((*i)->data == 3)
313		/*
314		 * if the directory is empty, skip the unnecessary
315		 * lookup_entry, this fixes the corner case with
316		 * completely empty filesystems where lookup_entry correctly
317		 * returning -1 is incorrectly treated as an error
318		 */
319		return dir;
320
321	start = sBlk.s.directory_table_start + (*i)->start;
322	bytes = lookup_entry(directory_table_hash, start);
323
324	if(bytes == -1)
325		EXIT_UNSQUASH("squashfs_opendir: directory block %d not "
326			"found!\n", block_start);
327
328	bytes += (*i)->offset;
329	size = (*i)->data + bytes - 3;
330
331	while(bytes < size) {
332		if(swap) {
333			squashfs_dir_header_3 sdirh;
334			memcpy(&sdirh, directory_table + bytes, sizeof(sdirh));
335			SQUASHFS_SWAP_DIR_HEADER_3(&dirh, &sdirh);
336		} else
337			memcpy(&dirh, directory_table + bytes, sizeof(dirh));
338
339		dir_count = dirh.count + 1;
340		TRACE("squashfs_opendir: Read directory header @ byte position "
341			"%d, %d directory entries\n", bytes, dir_count);
342		bytes += sizeof(dirh);
343
344		/* dir_count should never be larger than 256 */
345		if(dir_count > 256)
346			goto corrupted;
347
348		while(dir_count--) {
349			if(swap) {
350				squashfs_dir_entry_3 sdire;
351				memcpy(&sdire, directory_table + bytes,
352					sizeof(sdire));
353				SQUASHFS_SWAP_DIR_ENTRY_3(dire, &sdire);
354			} else
355				memcpy(dire, directory_table + bytes,
356					sizeof(*dire));
357			bytes += sizeof(*dire);
358
359			/* size should never be larger than SQUASHFS_NAME_LEN */
360			if(dire->size > SQUASHFS_NAME_LEN)
361				goto corrupted;
362
363			memcpy(dire->name, directory_table + bytes,
364				dire->size + 1);
365			dire->name[dire->size + 1] = '\0';
366			TRACE("squashfs_opendir: directory entry %s, inode "
367				"%d:%d, type %d\n", dire->name,
368				dirh.start_block, dire->offset, dire->type);
369			if((dir->dir_count % DIR_ENT_SIZE) == 0) {
370				new_dir = realloc(dir->dirs, (dir->dir_count +
371					DIR_ENT_SIZE) * sizeof(struct dir_ent));
372				if(new_dir == NULL)
373					EXIT_UNSQUASH("squashfs_opendir: "
374						"realloc failed!\n");
375				dir->dirs = new_dir;
376			}
377			strcpy(dir->dirs[dir->dir_count].name, dire->name);
378			dir->dirs[dir->dir_count].start_block =
379				dirh.start_block;
380			dir->dirs[dir->dir_count].offset = dire->offset;
381			dir->dirs[dir->dir_count].type = dire->type;
382			dir->dir_count ++;
383			bytes += dire->size + 1;
384		}
385	}
386
387	return dir;
388
389corrupted:
390	free(dir->dirs);
391	free(dir);
392	return NULL;
393}
394